diff --git a/Cargo.lock b/Cargo.lock index bfd8f68af5..5f9569651c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3628,6 +3628,7 @@ dependencies = [ "itp-sgx-crypto", "itp-sgx-externalities", "itp-sgx-io", + "itp-sgx-temp-dir", "itp-stf-interface", "itp-stf-state-observer", "itp-time-utils", diff --git a/core-primitives/settings/src/lib.rs b/core-primitives/settings/src/lib.rs index 1d569ac862..536949f983 100644 --- a/core-primitives/settings/src/lib.rs +++ b/core-primitives/settings/src/lib.rs @@ -42,7 +42,6 @@ pub mod files { pub static SIDECHAIN_PURGE_LIMIT: u64 = 100; // keep the last.. sidechainblocks when purging // used by enclave - pub const AES_KEY_FILE_AND_INIT_V: &str = "aes_key_sealed.bin"; pub const LIGHT_CLIENT_DB: &str = "light_client_db.bin"; pub const RA_DUMP_CERT_DER_FILE: &str = "ra_dump_cert.der"; diff --git a/core-primitives/sgx/crypto/src/aes.rs b/core-primitives/sgx/crypto/src/aes.rs index d63bed35a2..0c1414e84c 100644 --- a/core-primitives/sgx/crypto/src/aes.rs +++ b/core-primitives/sgx/crypto/src/aes.rs @@ -21,15 +21,20 @@ use crate::{ }; use aes::Aes128; use codec::{Decode, Encode}; -use derive_more::Display; use ofb::{ cipher::{NewStreamCipher, SyncStreamCipher}, Ofb, }; -use std::convert::{TryFrom, TryInto}; +use std::{ + convert::{TryFrom, TryInto}, + path::PathBuf, +}; type AesOfb = Ofb; +/// File name of the sealed AES key data. +pub const AES_KEY_FILE_AND_INIT_V: &str = "aes_key_and_iv_sealed_data.bin"; + #[derive(Debug, Default, Encode, Decode, Clone, Copy, PartialEq, Eq)] pub struct Aes { pub key: [u8; 16], @@ -42,8 +47,20 @@ impl Aes { } } -#[derive(Copy, Clone, Debug, Display)] -pub struct AesSeal; +#[derive(Clone, Debug)] +pub struct AesSeal { + base_path: PathBuf, +} + +impl AesSeal { + pub fn new(base_path: PathBuf) -> Self { + Self { base_path } + } + + pub fn path(&self) -> PathBuf { + self.base_path.join(AES_KEY_FILE_AND_INIT_V) + } +} impl StateCrypto for Aes { type Error = Error; @@ -70,29 +87,63 @@ pub fn de_or_encrypt(aes: &Aes, data: &mut [u8]) -> Result<()> { aes.try_into().map(|mut ofb: AesOfb| ofb.apply_keystream(data)) } +pub trait AesSealing { + fn unseal_key(&self) -> Result; + + fn exists(&self) -> bool; + + fn create_sealed_if_absent(&self) -> Result<()>; + + fn create_sealed(&self) -> Result<()>; +} + #[cfg(feature = "sgx")] pub use sgx::*; #[cfg(feature = "sgx")] pub mod sgx { - use super::*; - use itp_settings::files::AES_KEY_FILE_AND_INIT_V; - use itp_sgx_io::{seal, unseal, SealedIO, StaticSealedIO}; + use crate::key_repository::KeyRepository; + use itp_sgx_io::{seal, unseal, SealedIO}; use log::info; use sgx_rand::{Rng, StdRng}; use std::sgxfs::SgxFile; - impl StaticSealedIO for AesSeal { - type Error = Error; - type Unsealed = Aes; + /// Gets a repository for an AES key and initializes + /// a fresh key if it doesn't exist at `path`. + pub fn get_aes_repository(path: PathBuf) -> Result> { + let aes_seal = AesSeal::new(path); + aes_seal.create_sealed_if_absent()?; + let aes_key = aes_seal.unseal_key()?; + Ok(KeyRepository::new(aes_key, aes_seal.into())) + } + + impl AesSealing for AesSeal { + fn unseal_key(&self) -> Result { + self.unseal() + } - fn unseal_from_static_file() -> Result { - Ok(unseal(AES_KEY_FILE_AND_INIT_V).map(|b| Decode::decode(&mut b.as_slice()))??) + fn exists(&self) -> bool { + SgxFile::open(self.path()).is_ok() } - fn seal_to_static_file(unsealed: &Self::Unsealed) -> Result<()> { - Ok(unsealed.using_encoded(|bytes| seal(bytes, AES_KEY_FILE_AND_INIT_V))?) + fn create_sealed_if_absent(&self) -> Result<()> { + if !self.exists() { + info!("Keyfile not found, creating new! {}", self.path().display()); + return self.create_sealed() + } + Ok(()) + } + + fn create_sealed(&self) -> Result<()> { + let mut key = [0u8; 16]; + let mut iv = [0u8; 16]; + let mut rand = StdRng::new()?; + + rand.fill_bytes(&mut key); + rand.fill_bytes(&mut iv); + + Ok(self.seal(&Aes::new(key, iv))?) } } @@ -101,30 +152,52 @@ pub mod sgx { type Unsealed = Aes; fn unseal(&self) -> Result { - Self::unseal_from_static_file() + Ok(unseal(self.path()).map(|b| Decode::decode(&mut b.as_slice()))??) } fn seal(&self, unsealed: &Self::Unsealed) -> Result<()> { - Self::seal_to_static_file(&unsealed) + Ok(unsealed.using_encoded(|bytes| seal(bytes, self.path()))?) } } +} - pub fn create_sealed_if_absent() -> Result<()> { - if SgxFile::open(AES_KEY_FILE_AND_INIT_V).is_err() { - info!("[Enclave] Keyfile not found, creating new! {}", AES_KEY_FILE_AND_INIT_V); - return create_sealed() - } - Ok(()) +#[cfg(feature = "test")] +pub mod sgx_tests { + use super::sgx::*; + use crate::{key_repository::AccessKey, AesSeal, AesSealing}; + use itp_sgx_temp_dir::TempDir; + + pub fn using_get_aes_repository_twice_initializes_key_only_once() { + let temp_dir = + TempDir::with_prefix("using_get_aes_repository_twice_initializes_key_only_once") + .unwrap(); + let temp_path = temp_dir.path().to_path_buf(); + let key1 = get_aes_repository(temp_path.clone()).unwrap().retrieve_key().unwrap(); + let key2 = get_aes_repository(temp_path).unwrap().retrieve_key().unwrap(); + assert_eq!(key1, key2); } - pub fn create_sealed() -> Result<()> { - let mut key = [0u8; 16]; - let mut iv = [0u8; 16]; + pub fn aes_sealing_works() { + let temp_dir = TempDir::with_prefix("aes_sealing_works").unwrap(); + let seal = AesSeal::new(temp_dir.path().to_path_buf()); + + // Create new sealed keys and unseal them + assert!(!seal.exists()); + seal.create_sealed_if_absent().unwrap(); + let key = seal.unseal_key().unwrap(); + + assert!(seal.exists()); + + // Should not change anything because the key is already there. + seal.create_sealed_if_absent().unwrap(); + let key_same = seal.unseal_key().unwrap(); + + assert_eq!(key, key_same); - let mut rand = StdRng::new()?; + // Should overwrite previous keys. + seal.create_sealed().unwrap(); + let key_different = seal.unseal_key().unwrap(); - rand.fill_bytes(&mut key); - rand.fill_bytes(&mut iv); - AesSeal::seal_to_static_file(&Aes::new(key, iv)) + assert_ne!(key_different, key); } } diff --git a/core-primitives/sgx/crypto/src/lib.rs b/core-primitives/sgx/crypto/src/lib.rs index 2dcd271b31..b876b05203 100644 --- a/core-primitives/sgx/crypto/src/lib.rs +++ b/core-primitives/sgx/crypto/src/lib.rs @@ -57,4 +57,8 @@ pub mod tests { pub use super::rsa3072::sgx_tests::{ rsa3072_sealing_works, using_get_rsa3072_repository_twice_initializes_key_only_once, }; + + pub use super::aes::sgx_tests::{ + aes_sealing_works, using_get_aes_repository_twice_initializes_key_only_once, + }; } diff --git a/core-primitives/stf-state-handler/Cargo.toml b/core-primitives/stf-state-handler/Cargo.toml index 7b8d31b2b0..739473c96b 100644 --- a/core-primitives/stf-state-handler/Cargo.toml +++ b/core-primitives/stf-state-handler/Cargo.toml @@ -22,6 +22,9 @@ itp-stf-state-observer = { path = "../stf-state-observer", default-features = fa itp-time-utils = { path = "../../core-primitives/time-utils", default-features = false } itp-types = { path = "../types", default-features = false } +# for tests +itp-sgx-temp-dir = { version = "0.1", default-features = false, optional = true, path = "../../core-primitives/sgx/temp-dir" } + # sgx enabled external libraries rust-base58_sgx = { package = "rust-base58", rev = "sgx_1.1.3", git = "https://github.com/mesalock-linux/rust-base58-sgx", optional = true, default-features = false, features = ["mesalock_sgx"] } thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } @@ -71,4 +74,5 @@ sgx = [ test = [ "itp-sgx-crypto/mocks", "itp-stf-interface/mocks", + "itp-sgx-temp-dir", ] diff --git a/core-primitives/stf-state-handler/src/test/sgx_tests.rs b/core-primitives/stf-state-handler/src/test/sgx_tests.rs index 4ad8ff1947..da659e06f9 100644 --- a/core-primitives/stf-state-handler/src/test/sgx_tests.rs +++ b/core-primitives/stf-state-handler/src/test/sgx_tests.rs @@ -33,18 +33,23 @@ use crate::{ use codec::{Decode, Encode}; use ita_stf::{State as StfState, StateType as StfStateType}; use itp_hashing::Hash; -use itp_sgx_crypto::{mocks::KeyRepositoryMock, Aes, AesSeal, StateCrypto}; +use itp_sgx_crypto::{ + get_aes_repository, + key_repository::{AccessKey, KeyRepository}, + Aes, AesSeal, StateCrypto, +}; use itp_sgx_externalities::{SgxExternalities, SgxExternalitiesTrait}; -use itp_sgx_io::{write, StaticSealedIO}; +use itp_sgx_io::write; +use itp_sgx_temp_dir::TempDir; use itp_stf_state_observer::state_observer::StateObserver; use itp_types::{ShardIdentifier, H256}; use std::{sync::Arc, thread, vec::Vec}; const STATE_SNAPSHOTS_CACHE_SIZE: usize = 3; -type StateKeyRepositoryMock = KeyRepositoryMock; +type StateKeyRepository = KeyRepository; type TestStateInitializer = InitializeStateMock; -type TestStateFileIo = SgxStateFileIo; +type TestStateFileIo = SgxStateFileIo; type TestStateRepository = StateSnapshotRepository; type TestStateRepositoryLoader = StateSnapshotRepositoryLoader; @@ -70,6 +75,14 @@ impl Drop for ShardDirectoryHandle { } } +/// Gets a temporary key repository. +/// +/// We pass and ID such that it doesn't clash with other temp repositories. +fn temp_state_key_repository(id: &str) -> StateKeyRepository { + let temp_dir = TempDir::with_prefix(id).unwrap(); + get_aes_repository(temp_dir.path().to_path_buf()).unwrap() +} + // Fixme: Move this test to sgx-runtime: // // https://github.com/integritee-network/sgx-runtime/issues/23 @@ -88,7 +101,10 @@ pub fn test_sgx_state_decode_encode_works() { pub fn test_encrypt_decrypt_state_type_works() { // given let state = given_hello_world_state(); - let state_key = AesSeal::unseal_from_static_file().unwrap(); + + let state_key = temp_state_key_repository("test_encrypt_decrypt_state_type_works") + .retrieve_key() + .unwrap(); // when let mut state_buffer = state.state.encode(); @@ -104,7 +120,9 @@ pub fn test_encrypt_decrypt_state_type_works() { pub fn test_write_and_load_state_works() { // given let shard: ShardIdentifier = [94u8; 32].into(); - let (state_handler, shard_dir_handle) = initialize_state_handler_with_directory_handle(&shard); + let state_key_access = Arc::new(temp_state_key_repository("test_write_and_load_state_works")); + let (state_handler, shard_dir_handle) = + initialize_state_handler_with_directory_handle(&shard, state_key_access); let state = given_hello_world_state(); @@ -124,7 +142,10 @@ pub fn test_write_and_load_state_works() { pub fn test_ensure_subsequent_state_loads_have_same_hash() { // given let shard: ShardIdentifier = [49u8; 32].into(); - let (state_handler, shard_dir_handle) = initialize_state_handler_with_directory_handle(&shard); + let state_key_access = + Arc::new(temp_state_key_repository("test_ensure_subsequent_state_loads_have_same_hash")); + let (state_handler, shard_dir_handle) = + initialize_state_handler_with_directory_handle(&shard, state_key_access); let (lock, initial_state) = state_handler.load_for_mutation(&shard).unwrap(); state_handler.write_after_mutation(initial_state.clone(), lock, &shard).unwrap(); @@ -143,7 +164,10 @@ pub fn test_write_access_locks_read_until_finished() { // given let shard: ShardIdentifier = [47u8; 32].into(); - let (state_handler, shard_dir_handle) = initialize_state_handler_with_directory_handle(&shard); + let state_key_access = + Arc::new(temp_state_key_repository("test_write_access_locks_read_until_finished")); + let (state_handler, shard_dir_handle) = + initialize_state_handler_with_directory_handle(&shard, state_key_access); let new_state_key = "my_new_state".encode(); let (lock, mut state_to_mutate) = state_handler.load_for_mutation(&shard).unwrap(); @@ -173,7 +197,10 @@ pub fn test_write_access_locks_read_until_finished() { pub fn test_state_handler_file_backend_is_initialized() { let shard: ShardIdentifier = [11u8; 32].into(); - let (state_handler, shard_dir_handle) = initialize_state_handler_with_directory_handle(&shard); + let state_key_access = + Arc::new(temp_state_key_repository("test_state_handler_file_backend_is_initialized")); + let (state_handler, shard_dir_handle) = + initialize_state_handler_with_directory_handle(&shard, state_key_access); assert!(state_handler.shard_exists(&shard).unwrap()); assert!(1 <= state_handler.list_shards().unwrap().len()); // only greater equal, because there might be other (non-test) shards present @@ -189,7 +216,11 @@ pub fn test_state_handler_file_backend_is_initialized() { pub fn test_multiple_state_updates_create_snapshots_up_to_cache_size() { let shard: ShardIdentifier = [17u8; 32].into(); - let (state_handler, _shard_dir_handle) = initialize_state_handler_with_directory_handle(&shard); + let state_key_access = Arc::new(temp_state_key_repository( + "test_multiple_state_updates_create_snapshots_up_to_cache_size", + )); + let (state_handler, _shard_dir_handle) = + initialize_state_handler_with_directory_handle(&shard, state_key_access); assert_eq!(1, number_of_files_in_shard_dir(&shard).unwrap()); @@ -234,8 +265,7 @@ pub fn test_multiple_state_updates_create_snapshots_up_to_cache_size() { pub fn test_file_io_get_state_hash_works() { let shard: ShardIdentifier = [21u8; 32].into(); let _shard_dir_handle = ShardDirectoryHandle::new(shard).unwrap(); - let state_key_access = - Arc::new(StateKeyRepositoryMock::new(AesSeal::unseal_from_static_file().unwrap())); + let state_key_access = Arc::new(temp_state_key_repository("test_file_io_get_state_hash_works")); let file_io = TestStateFileIo::new(state_key_access); @@ -251,7 +281,10 @@ pub fn test_file_io_get_state_hash_works() { pub fn test_state_files_from_handler_can_be_loaded_again() { let shard: ShardIdentifier = [15u8; 32].into(); - let (state_handler, _shard_dir_handle) = initialize_state_handler_with_directory_handle(&shard); + let state_key_access = + Arc::new(temp_state_key_repository("test_state_files_from_handler_can_be_loaded_again")); + let (state_handler, _shard_dir_handle) = + initialize_state_handler_with_directory_handle(&shard, state_key_access.clone()); update_state(state_handler.as_ref(), &shard, ("test_key_1".encode(), "value1".encode())); update_state(state_handler.as_ref(), &shard, ("test_key_2".encode(), "value2".encode())); @@ -263,7 +296,7 @@ pub fn test_state_files_from_handler_can_be_loaded_again() { update_state(state_handler.as_ref(), &shard, ("test_key_3".encode(), "value3".encode())); // We initialize another state handler to load the state from the changes we just made. - let updated_state_handler = initialize_state_handler(); + let updated_state_handler = initialize_state_handler(state_key_access); assert_eq!(STATE_SNAPSHOTS_CACHE_SIZE, number_of_files_in_shard_dir(&shard).unwrap()); assert_eq!( @@ -281,8 +314,9 @@ pub fn test_state_files_from_handler_can_be_loaded_again() { pub fn test_list_state_ids_ignores_files_not_matching_the_pattern() { let shard: ShardIdentifier = [21u8; 32].into(); let _shard_dir_handle = ShardDirectoryHandle::new(shard).unwrap(); - let state_key_access = - Arc::new(StateKeyRepositoryMock::new(AesSeal::unseal_from_static_file().unwrap())); + let state_key_access = Arc::new(temp_state_key_repository( + "test_list_state_ids_ignores_files_not_matching_the_pattern", + )); let file_io = TestStateFileIo::new(state_key_access); @@ -315,14 +349,13 @@ pub fn test_in_memory_state_initializes_from_shard_directory() { fn initialize_state_handler_with_directory_handle( shard: &ShardIdentifier, + state_key_access: Arc, ) -> (Arc, ShardDirectoryHandle) { let shard_dir_handle = ShardDirectoryHandle::new(*shard).unwrap(); - (initialize_state_handler(), shard_dir_handle) + (initialize_state_handler(state_key_access), shard_dir_handle) } -fn initialize_state_handler() -> Arc { - let state_key_access = - Arc::new(StateKeyRepositoryMock::new(AesSeal::unseal_from_static_file().unwrap())); +fn initialize_state_handler(state_key_access: Arc) -> Arc { let file_io = Arc::new(TestStateFileIo::new(state_key_access)); let state_initializer = Arc::new(TestStateInitializer::new(StfState::new(Default::default()))); let state_repository_loader = @@ -368,6 +401,10 @@ fn given_initialized_shard(shard: &ShardIdentifier) -> Result<()> { fn number_of_files_in_shard_dir(shard: &ShardIdentifier) -> Result { let shard_dir_path = shard_path(shard); - let files_in_dir = std::fs::read_dir(shard_dir_path).map_err(|e| Error::Other(e.into()))?; + let files_in_dir = + std::fs::read_dir(shard_dir_path.clone()).map_err(|e| Error::Other(e.into()))?; + + log::info!("File in shard dir: {:?}", files_in_dir); + Ok(files_in_dir.count()) } diff --git a/enclave-runtime/Cargo.lock b/enclave-runtime/Cargo.lock index 1f786e41c6..57ec28e5cf 100644 --- a/enclave-runtime/Cargo.lock +++ b/enclave-runtime/Cargo.lock @@ -2091,6 +2091,7 @@ dependencies = [ "itp-sgx-crypto", "itp-sgx-externalities", "itp-sgx-io", + "itp-sgx-temp-dir", "itp-stf-interface", "itp-stf-state-observer", "itp-time-utils", diff --git a/enclave-runtime/src/initialization/mod.rs b/enclave-runtime/src/initialization/mod.rs index c2d7dc999f..d7d971fa4d 100644 --- a/enclave-runtime/src/initialization/mod.rs +++ b/enclave-runtime/src/initialization/mod.rs @@ -26,9 +26,9 @@ use crate::{ EnclaveSidechainApi, EnclaveSidechainBlockImportQueue, EnclaveSidechainBlockImportQueueWorker, EnclaveSidechainBlockImporter, EnclaveSidechainBlockSyncer, EnclaveStateFileIo, EnclaveStateHandler, - EnclaveStateInitializer, EnclaveStateKeyRepository, EnclaveStateObserver, - EnclaveStateSnapshotRepository, EnclaveStfEnclaveSigner, EnclaveTopPool, - EnclaveTopPoolAuthor, GLOBAL_ATTESTATION_HANDLER_COMPONENT, GLOBAL_OCALL_API_COMPONENT, + EnclaveStateInitializer, EnclaveStateObserver, EnclaveStateSnapshotRepository, + EnclaveStfEnclaveSigner, EnclaveTopPool, EnclaveTopPoolAuthor, + GLOBAL_ATTESTATION_HANDLER_COMPONENT, GLOBAL_OCALL_API_COMPONENT, GLOBAL_RPC_WS_HANDLER_COMPONENT, GLOBAL_SHIELDING_KEY_REPOSITORY_COMPONENT, GLOBAL_SIDECHAIN_BLOCK_COMPOSER_COMPONENT, GLOBAL_SIDECHAIN_BLOCK_SYNCER_COMPONENT, GLOBAL_SIDECHAIN_IMPORT_QUEUE_COMPONENT, GLOBAL_SIDECHAIN_IMPORT_QUEUE_WORKER_COMPONENT, @@ -61,9 +61,8 @@ use itp_component_container::{ComponentGetter, ComponentInitializer}; use itp_primitives_cache::GLOBAL_PRIMITIVES_CACHE; use itp_settings::files::STATE_SNAPSHOTS_CACHE_SIZE; use itp_sgx_crypto::{ - aes, get_ed25519_repository, get_rsa3072_repository, key_repository::AccessKey, AesSeal, + get_aes_repository, get_ed25519_repository, get_rsa3072_repository, key_repository::AccessKey, }; -use itp_sgx_io::StaticSealedIO; use itp_stf_state_handler::{ handle_state::HandleState, query_shard_state::QueryShardState, state_snapshot_repository::VersionedStateAccess, @@ -87,16 +86,12 @@ pub(crate) fn init_enclave( let signer = signing_key_repository.retrieve_key()?; info!("[Enclave initialized] Ed25519 prim raw : {:?}", signer.public().0); - let shielding_key_repository = Arc::new(get_rsa3072_repository(base_dir)?); + let shielding_key_repository = Arc::new(get_rsa3072_repository(base_dir.clone())?); GLOBAL_SHIELDING_KEY_REPOSITORY_COMPONENT.initialize(shielding_key_repository.clone()); // Create the aes key that is used for state encryption such that a key is always present in tests. // It will be overwritten anyway if mutual remote attestation is performed with the primary worker. - aes::create_sealed_if_absent().map_err(Error::Crypto)?; - - let state_key = AesSeal::unseal_from_static_file()?; - let state_key_repository = - Arc::new(EnclaveStateKeyRepository::new(state_key, Arc::new(AesSeal))); + let state_key_repository = Arc::new(get_aes_repository(base_dir)?); GLOBAL_STATE_KEY_REPOSITORY_COMPONENT.initialize(state_key_repository.clone()); let state_file_io = Arc::new(EnclaveStateFileIo::new(state_key_repository)); diff --git a/enclave-runtime/src/test/tests_main.rs b/enclave-runtime/src/test/tests_main.rs index b6d3874f27..777627c8b0 100644 --- a/enclave-runtime/src/test/tests_main.rs +++ b/enclave-runtime/src/test/tests_main.rs @@ -91,6 +91,8 @@ pub extern "C" fn test_main_entrance() -> size_t { itp_stf_state_handler::test::sgx_tests::test_file_io_get_state_hash_works, itp_stf_state_handler::test::sgx_tests::test_list_state_ids_ignores_files_not_matching_the_pattern, itp_stf_state_handler::test::sgx_tests::test_in_memory_state_initializes_from_shard_directory, + itp_sgx_crypto::tests::aes_sealing_works, + itp_sgx_crypto::tests::using_get_aes_repository_twice_initializes_key_only_once, itp_sgx_crypto::tests::ed25529_sealing_works, itp_sgx_crypto::tests::using_get_ed25519_repository_twice_initializes_key_only_once, itp_sgx_crypto::tests::rsa3072_sealing_works,