// This file is part of Substrate. // Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //! Local keystore implementation use async_trait::async_trait; use parking_lot::RwLock; use sp_application_crypto::{ecdsa, ed25519, sr25519, AppKey, AppPair, IsWrappedBy}; use sp_core::{ crypto::{CryptoTypePublicPair, ExposeSecret, KeyTypeId, Pair as PairT, Public, SecretString}, sr25519::{Pair as Sr25519Pair, Public as Sr25519Public}, Encode, }; use sp_keystore::{ vrf::{make_transcript, VRFSignature, VRFTranscriptData}, CryptoStore, Error as TraitError, SyncCryptoStore, SyncCryptoStorePtr, }; use std::{ collections::{HashMap, HashSet}, fs::{self, File}, io::Write, path::PathBuf, sync::Arc, }; use crate::{Error, Result}; /// A local based keystore that is either memory-based or filesystem-based. pub struct LocalKeystore(RwLock); impl LocalKeystore { /// Create a local keystore from filesystem. pub fn open>(path: T, password: Option) -> Result { let inner = KeystoreInner::open(path, password)?; Ok(Self(RwLock::new(inner))) } /// Create a local keystore in memory. pub fn in_memory() -> Self { let inner = KeystoreInner::new_in_memory(); Self(RwLock::new(inner)) } /// Get a key pair for the given public key. /// /// Returns `Ok(None)` if the key doesn't exist, `Ok(Some(_))` if the key exists and /// `Err(_)` when something failed. pub fn key_pair( &self, public: &::Public, ) -> Result> { self.0.read().key_pair::(public) } } #[async_trait] impl CryptoStore for LocalKeystore { async fn keys( &self, id: KeyTypeId, ) -> std::result::Result, TraitError> { SyncCryptoStore::keys(self, id) } async fn sr25519_public_keys(&self, id: KeyTypeId) -> Vec { SyncCryptoStore::sr25519_public_keys(self, id) } async fn sr25519_generate_new( &self, id: KeyTypeId, seed: Option<&str>, ) -> std::result::Result { SyncCryptoStore::sr25519_generate_new(self, id, seed) } async fn ed25519_public_keys(&self, id: KeyTypeId) -> Vec { SyncCryptoStore::ed25519_public_keys(self, id) } async fn ed25519_generate_new( &self, id: KeyTypeId, seed: Option<&str>, ) -> std::result::Result { SyncCryptoStore::ed25519_generate_new(self, id, seed) } async fn ecdsa_public_keys(&self, id: KeyTypeId) -> Vec { SyncCryptoStore::ecdsa_public_keys(self, id) } async fn ecdsa_generate_new( &self, id: KeyTypeId, seed: Option<&str>, ) -> std::result::Result { SyncCryptoStore::ecdsa_generate_new(self, id, seed) } async fn insert_unknown( &self, id: KeyTypeId, suri: &str, public: &[u8], ) -> std::result::Result<(), ()> { SyncCryptoStore::insert_unknown(self, id, suri, public) } async fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool { SyncCryptoStore::has_keys(self, public_keys) } async fn supported_keys( &self, id: KeyTypeId, keys: Vec, ) -> std::result::Result, TraitError> { SyncCryptoStore::supported_keys(self, id, keys) } async fn sign_with( &self, id: KeyTypeId, key: &CryptoTypePublicPair, msg: &[u8], ) -> std::result::Result>, TraitError> { SyncCryptoStore::sign_with(self, id, key, msg) } async fn sr25519_vrf_sign( &self, key_type: KeyTypeId, public: &sr25519::Public, transcript_data: VRFTranscriptData, ) -> std::result::Result, TraitError> { SyncCryptoStore::sr25519_vrf_sign(self, key_type, public, transcript_data) } async fn ecdsa_sign_prehashed( &self, id: KeyTypeId, public: &ecdsa::Public, msg: &[u8; 32], ) -> std::result::Result, TraitError> { SyncCryptoStore::ecdsa_sign_prehashed(self, id, public, msg) } } impl SyncCryptoStore for LocalKeystore { fn keys(&self, id: KeyTypeId) -> std::result::Result, TraitError> { let raw_keys = self.0.read().raw_public_keys(id)?; Ok(raw_keys.into_iter().fold(Vec::new(), |mut v, k| { v.push(CryptoTypePublicPair(sr25519::CRYPTO_ID, k.clone())); v.push(CryptoTypePublicPair(ed25519::CRYPTO_ID, k.clone())); v.push(CryptoTypePublicPair(ecdsa::CRYPTO_ID, k)); v })) } fn supported_keys( &self, id: KeyTypeId, keys: Vec, ) -> std::result::Result, TraitError> { let all_keys = SyncCryptoStore::keys(self, id)?.into_iter().collect::>(); Ok(keys.into_iter().filter(|key| all_keys.contains(key)).collect::>()) } fn sign_with( &self, id: KeyTypeId, key: &CryptoTypePublicPair, msg: &[u8], ) -> std::result::Result>, TraitError> { match key.0 { ed25519::CRYPTO_ID => { let pub_key = ed25519::Public::from_slice(key.1.as_slice()); let key_pair = self .0 .read() .key_pair_by_type::(&pub_key, id) .map_err(|e| TraitError::from(e))?; key_pair.map(|k| k.sign(msg).encode()).map(Ok).transpose() }, sr25519::CRYPTO_ID => { let pub_key = sr25519::Public::from_slice(key.1.as_slice()); let key_pair = self .0 .read() .key_pair_by_type::(&pub_key, id) .map_err(|e| TraitError::from(e))?; key_pair.map(|k| k.sign(msg).encode()).map(Ok).transpose() }, ecdsa::CRYPTO_ID => { let pub_key = ecdsa::Public::from_slice(key.1.as_slice()); let key_pair = self .0 .read() .key_pair_by_type::(&pub_key, id) .map_err(|e| TraitError::from(e))?; key_pair.map(|k| k.sign(msg).encode()).map(Ok).transpose() }, _ => Err(TraitError::KeyNotSupported(id)), } } fn sr25519_public_keys(&self, key_type: KeyTypeId) -> Vec { self.0 .read() .raw_public_keys(key_type) .map(|v| v.into_iter().map(|k| sr25519::Public::from_slice(k.as_slice())).collect()) .unwrap_or_default() } fn sr25519_generate_new( &self, id: KeyTypeId, seed: Option<&str>, ) -> std::result::Result { let pair = match seed { Some(seed) => self.0.write().insert_ephemeral_from_seed_by_type::(seed, id), None => self.0.write().generate_by_type::(id), } .map_err(|e| -> TraitError { e.into() })?; Ok(pair.public()) } fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec { self.0 .read() .raw_public_keys(key_type) .map(|v| v.into_iter().map(|k| ed25519::Public::from_slice(k.as_slice())).collect()) .unwrap_or_default() } fn ed25519_generate_new( &self, id: KeyTypeId, seed: Option<&str>, ) -> std::result::Result { let pair = match seed { Some(seed) => self.0.write().insert_ephemeral_from_seed_by_type::(seed, id), None => self.0.write().generate_by_type::(id), } .map_err(|e| -> TraitError { e.into() })?; Ok(pair.public()) } fn ecdsa_public_keys(&self, key_type: KeyTypeId) -> Vec { self.0 .read() .raw_public_keys(key_type) .map(|v| v.into_iter().map(|k| ecdsa::Public::from_slice(k.as_slice())).collect()) .unwrap_or_default() } fn ecdsa_generate_new( &self, id: KeyTypeId, seed: Option<&str>, ) -> std::result::Result { let pair = match seed { Some(seed) => self.0.write().insert_ephemeral_from_seed_by_type::(seed, id), None => self.0.write().generate_by_type::(id), } .map_err(|e| -> TraitError { e.into() })?; Ok(pair.public()) } fn insert_unknown( &self, key_type: KeyTypeId, suri: &str, public: &[u8], ) -> std::result::Result<(), ()> { self.0.write().insert_unknown(key_type, suri, public).map_err(|_| ()) } fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool { public_keys .iter() .all(|(p, t)| self.0.read().key_phrase_by_type(&p, *t).ok().flatten().is_some()) } fn sr25519_vrf_sign( &self, key_type: KeyTypeId, public: &Sr25519Public, transcript_data: VRFTranscriptData, ) -> std::result::Result, TraitError> { let transcript = make_transcript(transcript_data); let pair = self.0.read().key_pair_by_type::(public, key_type)?; if let Some(pair) = pair { let (inout, proof, _) = pair.as_ref().vrf_sign(transcript); Ok(Some(VRFSignature { output: inout.to_output(), proof })) } else { Ok(None) } } fn ecdsa_sign_prehashed( &self, id: KeyTypeId, public: &ecdsa::Public, msg: &[u8; 32], ) -> std::result::Result, TraitError> { let pair = self.0.read().key_pair_by_type::(public, id)?; pair.map(|k| k.sign_prehashed(msg)).map(Ok).transpose() } } impl Into for LocalKeystore { fn into(self) -> SyncCryptoStorePtr { Arc::new(self) } } impl Into> for LocalKeystore { fn into(self) -> Arc { Arc::new(self) } } /// A local key store. /// /// Stores key pairs in a file system store + short lived key pairs in memory. /// /// Every pair that is being generated by a `seed`, will be placed in memory. struct KeystoreInner { path: Option, /// Map over `(KeyTypeId, Raw public key)` -> `Key phrase/seed` additional: HashMap<(KeyTypeId, Vec), String>, password: Option, } impl KeystoreInner { /// Open the store at the given path. /// /// Optionally takes a password that will be used to encrypt/decrypt the keys. fn open>(path: T, password: Option) -> Result { let path = path.into(); fs::create_dir_all(&path)?; Ok(Self { path: Some(path), additional: HashMap::new(), password }) } /// Get the password for this store. fn password(&self) -> Option<&str> { self.password.as_ref().map(|p| p.expose_secret()).map(|p| p.as_str()) } /// Create a new in-memory store. fn new_in_memory() -> Self { Self { path: None, additional: HashMap::new(), password: None } } /// Get the key phrase for the given public key and key type from the in-memory store. fn get_additional_pair(&self, public: &[u8], key_type: KeyTypeId) -> Option<&String> { let key = (key_type, public.to_vec()); self.additional.get(&key) } /// Insert the given public/private key pair with the given key type. /// /// Does not place it into the file system store. fn insert_ephemeral_pair(&mut self, pair: &Pair, seed: &str, key_type: KeyTypeId) { let key = (key_type, pair.public().to_raw_vec()); self.additional.insert(key, seed.into()); } /// Insert a new key with anonymous crypto. /// /// Places it into the file system store, if a path is configured. fn insert_unknown(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<()> { if let Some(path) = self.key_file_path(public, key_type) { Self::write_to_file(path, suri)?; } Ok(()) } /// Generate a new key. /// /// Places it into the file system store, if a path is configured. Otherwise insert /// it into the memory cache only. fn generate_by_type(&mut self, key_type: KeyTypeId) -> Result { let (pair, phrase, _) = Pair::generate_with_phrase(self.password()); if let Some(path) = self.key_file_path(pair.public().as_slice(), key_type) { Self::write_to_file(path, &phrase)?; } else { self.insert_ephemeral_pair(&pair, &phrase, key_type); } Ok(pair) } /// Write the given `data` to `file`. fn write_to_file(file: PathBuf, data: &str) -> Result<()> { let mut file = File::create(file)?; serde_json::to_writer(&file, data)?; file.flush()?; #[cfg(target_family = "unix")] { use std::os::unix::fs::PermissionsExt; file.set_permissions(fs::Permissions::from_mode(0o600))?; } Ok(()) } /// Create a new key from seed. /// /// Does not place it into the file system store. fn insert_ephemeral_from_seed_by_type( &mut self, seed: &str, key_type: KeyTypeId, ) -> Result { let pair = Pair::from_string(seed, None).map_err(|_| Error::InvalidSeed)?; self.insert_ephemeral_pair(&pair, seed, key_type); Ok(pair) } /// Get the key phrase for a given public key and key type. fn key_phrase_by_type(&self, public: &[u8], key_type: KeyTypeId) -> Result> { if let Some(phrase) = self.get_additional_pair(public, key_type) { return Ok(Some(phrase.clone())) } let path = if let Some(path) = self.key_file_path(public, key_type) { path } else { return Ok(None) }; if path.exists() { let file = File::open(path)?; serde_json::from_reader(&file).map_err(Into::into).map(Some) } else { Ok(None) } } /// Get a key pair for the given public key and key type. fn key_pair_by_type( &self, public: &Pair::Public, key_type: KeyTypeId, ) -> Result> { let phrase = if let Some(p) = self.key_phrase_by_type(public.as_slice(), key_type)? { p } else { return Ok(None) }; let pair = Pair::from_string(&phrase, self.password()).map_err(|_| Error::InvalidPhrase)?; if &pair.public() == public { Ok(Some(pair)) } else { Err(Error::InvalidPassword) } } /// Get the file path for the given public key and key type. /// /// Returns `None` if the keystore only exists in-memory and there isn't any path to provide. fn key_file_path(&self, public: &[u8], key_type: KeyTypeId) -> Option { let mut buf = self.path.as_ref()?.clone(); let key_type = hex::encode(key_type.0); let key = hex::encode(public); buf.push(key_type + key.as_str()); Some(buf) } /// Returns a list of raw public keys filtered by `KeyTypeId` fn raw_public_keys(&self, id: KeyTypeId) -> Result>> { let mut public_keys: Vec> = self .additional .keys() .into_iter() .filter_map(|k| if k.0 == id { Some(k.1.clone()) } else { None }) .collect(); if let Some(path) = &self.path { for entry in fs::read_dir(&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()) { match hex::decode(name) { Ok(ref hex) if hex.len() > 4 => { if &hex[0..4] != &id.0 { continue } let public = hex[4..].to_vec(); public_keys.push(public); }, _ => continue, } } } } Ok(public_keys) } /// Get a key pair for the given public key. /// /// Returns `Ok(None)` if the key doesn't exist, `Ok(Some(_))` if the key exists or `Err(_)` /// when something failed. pub fn key_pair( &self, public: &::Public, ) -> Result> { self.key_pair_by_type::(IsWrappedBy::from_ref(public), Pair::ID) .map(|v| v.map(Into::into)) } } #[cfg(test)] mod tests { use super::*; use sp_application_crypto::{ed25519, sr25519, AppPublic}; use sp_core::{crypto::Ss58Codec, testing::SR25519, Pair}; use std::{fs, str::FromStr}; use tempfile::TempDir; const TEST_KEY_TYPE: KeyTypeId = KeyTypeId(*b"test"); impl KeystoreInner { fn insert_ephemeral_from_seed(&mut self, seed: &str) -> Result { self.insert_ephemeral_from_seed_by_type::(seed, Pair::ID) .map(Into::into) } fn public_keys(&self) -> Result> { self.raw_public_keys(Public::ID) .map(|v| v.into_iter().map(|k| Public::from_slice(k.as_slice())).collect()) } fn generate(&mut self) -> Result { self.generate_by_type::(Pair::ID).map(Into::into) } } #[test] fn basic_store() { let temp_dir = TempDir::new().unwrap(); let mut store = KeystoreInner::open(temp_dir.path(), None).unwrap(); assert!(store.public_keys::().unwrap().is_empty()); let key: ed25519::AppPair = store.generate().unwrap(); let key2: ed25519::AppPair = store.key_pair(&key.public()).unwrap().unwrap(); assert_eq!(key.public(), key2.public()); assert_eq!(store.public_keys::().unwrap()[0], key.public()); } #[test] fn has_keys_works() { let temp_dir = TempDir::new().unwrap(); let store = LocalKeystore::open(temp_dir.path(), None).unwrap(); let key: ed25519::AppPair = store.0.write().generate().unwrap(); let key2 = ed25519::Pair::generate().0; assert!(!SyncCryptoStore::has_keys( &store, &[(key2.public().to_vec(), ed25519::AppPublic::ID)] )); assert!(!SyncCryptoStore::has_keys( &store, &[ (key2.public().to_vec(), ed25519::AppPublic::ID), (key.public().to_raw_vec(), ed25519::AppPublic::ID), ], )); assert!(SyncCryptoStore::has_keys( &store, &[(key.public().to_raw_vec(), ed25519::AppPublic::ID)] )); } #[test] fn test_insert_ephemeral_from_seed() { let temp_dir = TempDir::new().unwrap(); let mut store = KeystoreInner::open(temp_dir.path(), None).unwrap(); let pair: ed25519::AppPair = store .insert_ephemeral_from_seed( "0x3d97c819d68f9bafa7d6e79cb991eebcd77d966c5334c0b94d9e1fa7ad0869dc", ) .unwrap(); assert_eq!( "5DKUrgFqCPV8iAXx9sjy1nyBygQCeiUYRFWurZGhnrn3HJCA", pair.public().to_ss58check() ); drop(store); let store = KeystoreInner::open(temp_dir.path(), None).unwrap(); // Keys generated from seed should not be persisted! assert!(store.key_pair::(&pair.public()).unwrap().is_none()); } #[test] fn password_being_used() { let password = String::from("password"); let temp_dir = TempDir::new().unwrap(); let mut store = KeystoreInner::open( temp_dir.path(), Some(FromStr::from_str(password.as_str()).unwrap()), ) .unwrap(); let pair: ed25519::AppPair = store.generate().unwrap(); assert_eq!( pair.public(), store.key_pair::(&pair.public()).unwrap().unwrap().public(), ); // Without the password the key should not be retrievable let store = KeystoreInner::open(temp_dir.path(), None).unwrap(); assert!(store.key_pair::(&pair.public()).is_err()); let store = KeystoreInner::open( temp_dir.path(), Some(FromStr::from_str(password.as_str()).unwrap()), ) .unwrap(); assert_eq!( pair.public(), store.key_pair::(&pair.public()).unwrap().unwrap().public(), ); } #[test] fn public_keys_are_returned() { let temp_dir = TempDir::new().unwrap(); let mut store = KeystoreInner::open(temp_dir.path(), None).unwrap(); let mut keys = Vec::new(); for i in 0..10 { keys.push(store.generate::().unwrap().public()); keys.push( store .insert_ephemeral_from_seed::(&format!( "0x3d97c819d68f9bafa7d6e79cb991eebcd7{}d966c5334c0b94d9e1fa7ad0869dc", i )) .unwrap() .public(), ); } // Generate a key of a different type store.generate::().unwrap(); keys.sort(); let mut store_pubs = store.public_keys::().unwrap(); store_pubs.sort(); assert_eq!(keys, store_pubs); } #[test] fn store_unknown_and_extract_it() { let temp_dir = TempDir::new().unwrap(); let store = KeystoreInner::open(temp_dir.path(), None).unwrap(); let secret_uri = "//Alice"; let key_pair = sr25519::AppPair::from_string(secret_uri, None).expect("Generates key pair"); store .insert_unknown(SR25519, secret_uri, key_pair.public().as_ref()) .expect("Inserts unknown key"); let store_key_pair = store .key_pair_by_type::(&key_pair.public(), SR25519) .expect("Gets key pair from keystore") .unwrap(); assert_eq!(key_pair.public(), store_key_pair.public()); } #[test] fn store_ignores_files_with_invalid_name() { let temp_dir = TempDir::new().unwrap(); let store = LocalKeystore::open(temp_dir.path(), None).unwrap(); let file_name = temp_dir.path().join(hex::encode(&SR25519.0[..2])); fs::write(file_name, "test").expect("Invalid file is written"); assert!(SyncCryptoStore::sr25519_public_keys(&store, SR25519).is_empty()); } #[test] fn generate_with_seed_is_not_stored() { let temp_dir = TempDir::new().unwrap(); let store = LocalKeystore::open(temp_dir.path(), None).unwrap(); let _alice_tmp_key = SyncCryptoStore::sr25519_generate_new(&store, TEST_KEY_TYPE, Some("//Alice")).unwrap(); assert_eq!(SyncCryptoStore::sr25519_public_keys(&store, TEST_KEY_TYPE).len(), 1); drop(store); let store = LocalKeystore::open(temp_dir.path(), None).unwrap(); assert_eq!(SyncCryptoStore::sr25519_public_keys(&store, TEST_KEY_TYPE).len(), 0); } #[test] fn generate_can_be_fetched_in_memory() { let store = LocalKeystore::in_memory(); SyncCryptoStore::sr25519_generate_new(&store, TEST_KEY_TYPE, Some("//Alice")).unwrap(); assert_eq!(SyncCryptoStore::sr25519_public_keys(&store, TEST_KEY_TYPE).len(), 1); SyncCryptoStore::sr25519_generate_new(&store, TEST_KEY_TYPE, None).unwrap(); assert_eq!(SyncCryptoStore::sr25519_public_keys(&store, TEST_KEY_TYPE).len(), 2); } #[test] #[cfg(target_family = "unix")] fn uses_correct_file_permissions_on_unix() { use std::os::unix::fs::PermissionsExt; let temp_dir = TempDir::new().unwrap(); let store = LocalKeystore::open(temp_dir.path(), None).unwrap(); let public = SyncCryptoStore::sr25519_generate_new(&store, TEST_KEY_TYPE, None).unwrap(); let path = store.0.read().key_file_path(public.as_ref(), TEST_KEY_TYPE).unwrap(); let permissions = File::open(path).unwrap().metadata().unwrap().permissions(); assert_eq!(0o100600, permissions.mode()); } }