From 546ee0054a7d73255a3e2ac3af10e4f7ca4be063 Mon Sep 17 00:00:00 2001 From: Pi Delport Date: Tue, 6 Jul 2021 15:02:04 +0200 Subject: [PATCH 1/6] feat(rtc_tenclave::kv_store): add KvStore::mutate helper --- rtc_tenclave/src/kv_store/mod.rs | 10 ++++++++++ rtc_tenclave/src/kv_store/tests.rs | 15 ++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/rtc_tenclave/src/kv_store/mod.rs b/rtc_tenclave/src/kv_store/mod.rs index 2edd5901..11582f96 100644 --- a/rtc_tenclave/src/kv_store/mod.rs +++ b/rtc_tenclave/src/kv_store/mod.rs @@ -42,6 +42,16 @@ pub trait KvStore { }; Ok(altered) } + + /// Mutate the value of `key`. + /// + /// This is like [`Self::mutate`], but only operates on existing values. + fn mutate(&mut self, key: &str, mutate_fn: F) -> Result, Self::Error> + where + F: FnOnce(V) -> V, + { + self.alter(key, |opt_v| opt_v.map(mutate_fn)) + } } #[cfg(test)] diff --git a/rtc_tenclave/src/kv_store/tests.rs b/rtc_tenclave/src/kv_store/tests.rs index 1198f783..0fb9309a 100644 --- a/rtc_tenclave/src/kv_store/tests.rs +++ b/rtc_tenclave/src/kv_store/tests.rs @@ -10,10 +10,23 @@ use tempfile::TempDir; use super::fs::std_filer::StdFiler; use super::fs::FsStore; -use super::in_memory::{InMemoryJsonStore, InMemoryStore}; +use super::in_memory::{InMemoryJsonStore, InMemoryStore, Never}; use super::inspect::InspectStore; use super::KvStore; +#[test] +fn test_mutate() -> Result<(), Never> { + let mut store = InMemoryStore::default(); + + assert_eq!(store.mutate("missing", |n| n + 1)?, None); + + store.save("existing", &2)?; + assert_eq!(store.mutate("existing", |n| n + 1)?, Some(3)); + assert_eq!(store.load("existing")?, Some(3)); + + Ok(()) +} + /// Verify that executing a sequence of store operations matches a simple model. #[test] fn prop_store_ops_match_model() { From 7ea2f96b54fb0ead22dbc259a956371fa1508d3c Mon Sep 17 00:00:00 2001 From: Pi Delport Date: Wed, 7 Jul 2021 20:52:34 +0200 Subject: [PATCH 2/6] feat(rtc_tenclave::kv_store): add KvStore::try_insert helper --- rtc_tenclave/src/kv_store/mod.rs | 11 +++++++++++ rtc_tenclave/src/kv_store/tests.rs | 13 +++++++++++++ 2 files changed, 24 insertions(+) diff --git a/rtc_tenclave/src/kv_store/mod.rs b/rtc_tenclave/src/kv_store/mod.rs index 11582f96..302df0c6 100644 --- a/rtc_tenclave/src/kv_store/mod.rs +++ b/rtc_tenclave/src/kv_store/mod.rs @@ -52,6 +52,17 @@ pub trait KvStore { { self.alter(key, |opt_v| opt_v.map(mutate_fn)) } + + /// Insert a value for `key`, if absent. If `key` already has a value, do nothing. + /// + /// Return the key's prior value (`None` if `value` was inserted) + fn try_insert(&mut self, key: &str, value: &V) -> Result, Self::Error> { + let loaded = self.load(key)?; + if loaded.is_none() { + self.save(key, value)?; + } + Ok(loaded) + } } #[cfg(test)] diff --git a/rtc_tenclave/src/kv_store/tests.rs b/rtc_tenclave/src/kv_store/tests.rs index 0fb9309a..130750a9 100644 --- a/rtc_tenclave/src/kv_store/tests.rs +++ b/rtc_tenclave/src/kv_store/tests.rs @@ -26,6 +26,19 @@ fn test_mutate() -> Result<(), Never> { Ok(()) } +#[test] +fn test_try_insert() -> Result<(), Never> { + let mut store = InMemoryStore::default(); + + assert_eq!(store.try_insert("missing", &42)?, None); + assert_eq!(store.load("missing")?, Some(42)); + + store.save("existing", &5)?; + assert_eq!(store.try_insert("existing", &42)?, Some(5)); + assert_eq!(store.load("existing")?, Some(5)); + + Ok(()) +} /// Verify that executing a sequence of store operations matches a simple model. #[test] From 474c53b3085382bd36917cabb76f6efa1dc9728f Mon Sep 17 00:00:00 2001 From: Pi Delport Date: Wed, 7 Jul 2021 19:12:08 +0200 Subject: [PATCH 3/6] test(rtc_data_service): factor out helper: issue_execution_token::make_request --- .../tests/ecalls/issue_execution_token.rs | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/rtc_data_service/tests/ecalls/issue_execution_token.rs b/rtc_data_service/tests/ecalls/issue_execution_token.rs index 4e015ec6..d3633c85 100644 --- a/rtc_data_service/tests/ecalls/issue_execution_token.rs +++ b/rtc_data_service/tests/ecalls/issue_execution_token.rs @@ -2,6 +2,7 @@ use std::convert::TryInto; use std::str::FromStr; use rtc_types::ExecReqMetadata; +use rtc_uenclave::{EnclaveConfig, RtcAuthEnclave}; use serde::{Deserialize, Serialize}; use sgx_types::sgx_target_info_t; @@ -15,10 +16,9 @@ pub struct ExecReqData { number_of_uses: u32, } -#[test] -fn test_issue_execution_token_success() { - let enclave = helpers::init_auth_enclave(); - +fn make_request( + enclave: &RtcAuthEnclave, +) -> ([u8; 32], [u8; 32], Vec, ExecReqMetadata) { let enclave_pubkey = enclave .create_report(&sgx_target_info_t::default()) .unwrap() @@ -53,15 +53,23 @@ fn test_issue_execution_token_success() { ) .unwrap(); - let result = enclave - .issue_execution_token( - &ciphertext[CRYPTO_BOX_BOXZEROBYTES..], - ExecReqMetadata { - uploader_pub_key: pubkey, - nonce, - }, - ) - .unwrap(); + let payload = ciphertext[CRYPTO_BOX_BOXZEROBYTES..].to_vec(); + + let metadata = ExecReqMetadata { + uploader_pub_key: pubkey, + nonce, + }; + + (enclave_pubkey, privkey, payload, metadata) +} + +#[test] +fn test_issue_execution_token_success() { + let enclave = helpers::init_auth_enclave(); + + let (enclave_pubkey, privkey, payload, metadata) = make_request(&enclave); + + let result = enclave.issue_execution_token(&payload, metadata).unwrap(); let mut m = vec![0_u8; result.ciphertext.len() + CRYPTO_BOX_BOXZEROBYTES]; From 3a165ebbe393c2f513bee78c9868619e605af249 Mon Sep 17 00:00:00 2001 From: Pi Delport Date: Wed, 7 Jul 2021 20:20:43 +0200 Subject: [PATCH 4/6] feat(rtc_auth_enclave::token_store): add ExecutionTokenSet container for issued tokens save_token now fails for invalid dataset_uuid lookups. --- rtc_auth_enclave/src/token_store.rs | 55 +++++++++++++++---- .../tests/ecalls/issue_execution_token.rs | 19 ++++++- 2 files changed, 62 insertions(+), 12 deletions(-) diff --git a/rtc_auth_enclave/src/token_store.rs b/rtc_auth_enclave/src/token_store.rs index 6c5e83ed..923354ca 100644 --- a/rtc_auth_enclave/src/token_store.rs +++ b/rtc_auth_enclave/src/token_store.rs @@ -14,16 +14,38 @@ use uuid::Uuid; use crate::{jwt, uuid_to_string}; +/// The set of execution tokens issued for a dataset. #[derive(Serialize, Deserialize)] -struct ExecutionTokenRecord { +struct ExecutionTokenSet { + dataset_uuid: Uuid, // XXX(Pi): This may be redundant? Remove, or keep for self-integrity checking? + + /// The dataset's unsealed size in bytes. + dataset_size: u64, + + /// Usage state of the issued execution tokens, by JWT ID (`jti`). + issued_tokens: HashMap, +} + +impl ExecutionTokenSet { + #[allow(unused)] + fn new(dataset_uuid: Uuid, dataset_size: u64) -> ExecutionTokenSet { + ExecutionTokenSet { + dataset_uuid, + dataset_size, + issued_tokens: HashMap::new(), + } + } +} + +/// Usage state of a single execution token. +#[derive(Serialize, Deserialize)] +struct ExecutionTokenState { exec_module_hash: [u8; 32], - dataset_uuid: Uuid, allowed_uses: u32, current_uses: u32, } -fn kv_store<'a>( -) -> MutexGuard<'a, impl KvStore, Error = io::Error>> { +fn kv_store<'a>() -> MutexGuard<'a, impl KvStore> { static TOKEN_FS_STORE: OnceCell>> = OnceCell::new(); let store = TOKEN_FS_STORE.get_or_init(|| { // TODO: Evaluate if this make sense, and what the possible attack vectors can be from relying on the @@ -58,6 +80,13 @@ pub(crate) fn issue_token( Ok(token) } +/// Save a newly-issued execution token's state to the store. +/// +/// Fail with error for invalid `dataset_uuid`. +/// +/// # Panics +/// +/// If `token_uuid` was already issued. fn save_token( dataset_uuid: Uuid, token_uuid: Uuid, @@ -66,17 +95,21 @@ fn save_token( ) -> Result<(), io::Error> { let mut store = kv_store(); let dataset_uuid_string = uuid_to_string(dataset_uuid); - let new_record = ExecutionTokenRecord { - dataset_uuid, + let new_token_state = ExecutionTokenState { exec_module_hash, allowed_uses: number_of_allowed_uses, current_uses: 0u32, }; - store.alter(&dataset_uuid_string, |records| { - let mut records = records.unwrap_or_else(HashMap::new); - records.insert(token_uuid, new_record); - Some(records) + let mutated = store.mutate(&dataset_uuid_string, |mut token_set| { + token_set.issued_tokens.insert(token_uuid, new_token_state); + token_set })?; - Ok(()) + + // Handle lookup failure + match mutated { + // TODO(Pi): Use something better than the io NotFound here? + None => Err(io::ErrorKind::NotFound.into()), + Some(_) => Ok(()), + } } diff --git a/rtc_data_service/tests/ecalls/issue_execution_token.rs b/rtc_data_service/tests/ecalls/issue_execution_token.rs index d3633c85..8ca2887f 100644 --- a/rtc_data_service/tests/ecalls/issue_execution_token.rs +++ b/rtc_data_service/tests/ecalls/issue_execution_token.rs @@ -63,7 +63,10 @@ fn make_request( (enclave_pubkey, privkey, payload, metadata) } -#[test] +// FIXME: The success case currently fails because there's no actual data / access key. +// Complete the test once that's available. +#[allow(dead_code)] +// #[test] fn test_issue_execution_token_success() { let enclave = helpers::init_auth_enclave(); @@ -97,3 +100,17 @@ fn test_issue_execution_token_success() { // TODO: Assert that decrypted value is a valid JWT } + +// Lookup failure: Invalid dataset UUID and access key. +#[test] +fn test_lookup_failure() { + let enclave = helpers::init_auth_enclave(); + + let (_enclave_pubkey, _privkey, payload, metadata) = make_request(&enclave); + + let err = enclave + .issue_execution_token(&payload, metadata) + .unwrap_err(); + + assert_eq!(format!("{:?}", err), "RtcEnclave(IO)") +} From f2611402bedfa1d78d6744867bfa4b30db3e76b8 Mon Sep 17 00:00:00 2001 From: Pi Delport Date: Thu, 8 Jul 2021 02:36:03 +0200 Subject: [PATCH 5/6] feat(rtc_auth_enclave::token_store): handle access keys --- rtc_auth_enclave/src/lib.rs | 1 + rtc_auth_enclave/src/token_store.rs | 58 +++++++++++++++++------------ 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/rtc_auth_enclave/src/lib.rs b/rtc_auth_enclave/src/lib.rs index a036e0e2..082446f4 100644 --- a/rtc_auth_enclave/src/lib.rs +++ b/rtc_auth_enclave/src/lib.rs @@ -93,6 +93,7 @@ fn issue_execution_token_impl( { let token = token_store::issue_token( Uuid::from_bytes(message.dataset_uuid), + message.dataset_access_key, message.exec_module_hash, message.number_of_uses, dataset_size, diff --git a/rtc_auth_enclave/src/token_store.rs b/rtc_auth_enclave/src/token_store.rs index 923354ca..74c65c8f 100644 --- a/rtc_auth_enclave/src/token_store.rs +++ b/rtc_auth_enclave/src/token_store.rs @@ -1,3 +1,4 @@ +use std::collections::hash_map::Entry; use std::collections::HashMap; use std::io; use std::path::Path; @@ -19,6 +20,9 @@ use crate::{jwt, uuid_to_string}; struct ExecutionTokenSet { dataset_uuid: Uuid, // XXX(Pi): This may be redundant? Remove, or keep for self-integrity checking? + /// The dataset's access key. + access_key: [u8; 24], + /// The dataset's unsealed size in bytes. dataset_size: u64, @@ -28,9 +32,10 @@ struct ExecutionTokenSet { impl ExecutionTokenSet { #[allow(unused)] - fn new(dataset_uuid: Uuid, dataset_size: u64) -> ExecutionTokenSet { + fn new(dataset_uuid: Uuid, access_key: [u8; 24], dataset_size: u64) -> ExecutionTokenSet { ExecutionTokenSet { dataset_uuid, + access_key, dataset_size, issued_tokens: HashMap::new(), } @@ -63,6 +68,7 @@ fn kv_store<'a>() -> MutexGuard<'a, impl KvStore Result<(), io::Error> { let mut store = kv_store(); let dataset_uuid_string = uuid_to_string(dataset_uuid); - let new_token_state = ExecutionTokenState { - exec_module_hash, - allowed_uses: number_of_allowed_uses, - current_uses: 0u32, - }; - - let mutated = store.mutate(&dataset_uuid_string, |mut token_set| { - token_set.issued_tokens.insert(token_uuid, new_token_state); - token_set - })?; - // Handle lookup failure - match mutated { + let mut token_set = store + .load(&dataset_uuid_string)? // TODO(Pi): Use something better than the io NotFound here? - None => Err(io::ErrorKind::NotFound.into()), - Some(_) => Ok(()), + .ok_or_else(|| io::ErrorKind::NotFound)?; + + // Update if the access key matches. + if token_set.access_key == access_key { + // TODO: Use [`HashMap::try_insert`] once stable. + // Unstable tracking issue: + match token_set.issued_tokens.entry(token_id) { + Entry::Occupied(_entry) => panic!( + "token_store::save_token: token_uuid={:?} already issued (this should not happen)", + token_id, + ), + Entry::Vacant(entry) => entry.insert(token_state), + }; + store.save(&dataset_uuid_string, &token_set)?; + Ok(()) + } else { + Err(io::ErrorKind::NotFound.into()) } } From 6ef2cf57e8951a91425103107304d3493160af64 Mon Sep 17 00:00:00 2001 From: Pi Delport Date: Wed, 23 Jun 2021 20:35:04 +0200 Subject: [PATCH 6/6] feat(rtc_auth_enclave::token_store): add save_access_key --- rtc_auth_enclave/src/token_store.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/rtc_auth_enclave/src/token_store.rs b/rtc_auth_enclave/src/token_store.rs index 74c65c8f..2923ddf0 100644 --- a/rtc_auth_enclave/src/token_store.rs +++ b/rtc_auth_enclave/src/token_store.rs @@ -31,7 +31,6 @@ struct ExecutionTokenSet { } impl ExecutionTokenSet { - #[allow(unused)] fn new(dataset_uuid: Uuid, access_key: [u8; 24], dataset_size: u64) -> ExecutionTokenSet { ExecutionTokenSet { dataset_uuid, @@ -65,6 +64,32 @@ fn kv_store<'a>() -> MutexGuard<'a, impl KvStore Result<(), io::Error> { + let mut store = kv_store(); + let dataset_uuid_string = uuid_to_string(dataset_uuid); + let empty_token_set = ExecutionTokenSet::new(dataset_uuid, access_key, dataset_size); + + match store.try_insert(&dataset_uuid_string, &empty_token_set)? { + None => Ok(()), + Some(_existing) => panic!( + "token_store::save_access_key: access key for dateset_uuid={:?} already saved (this should not happen)", + dataset_uuid, + ) + } +} + // Returns exec token hash pub(crate) fn issue_token( dataset_uuid: Uuid,