diff --git a/Cargo.lock b/Cargo.lock index 97ee1df8..1dfcffe5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -274,6 +274,17 @@ dependencies = [ "syn 0.15.44", ] +[[package]] +name = "derivative" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb582b60359da160a9477ee80f15c8d784c477e69c217ef2cdd4169c24ea380f" +dependencies = [ + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", +] + [[package]] name = "derive_arbitrary" version = "0.4.0" @@ -621,22 +632,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] -name = "parsec-client-test" -version = "0.3.0" -source = "git+https://github.com/parallaxsecond/parsec-client-test?tag=0.3.0#e69044af0d9a4b6f3ce998447a6651ffdaa41644" +name = "parsec-client" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3832239b87248521954be6117ea958212bfc93f9cbe9abac66696928699e08cc" dependencies = [ - "derivative", + "derivative 2.1.1", "log", "num", "parsec-interface", "rand", + "uuid", ] [[package]] name = "parsec-interface" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d14be8b6d151c8054e2a1fe3b03c83748d138ebb0145637a57afbb576b211d7" +checksum = "5e87877b00d360536d09d4e8a12a637b6458fec56f5d4fe6c375b43fc4e25245" dependencies = [ "arbitrary", "bincode", @@ -659,11 +672,11 @@ dependencies = [ "bincode", "bindgen 0.50.1", "cargo_toml", - "derivative", + "derivative 1.0.4", "env_logger 0.7.1", "log", "num_cpus", - "parsec-client-test", + "parsec-client", "parsec-interface", "picky-asn1", "picky-asn1-der", diff --git a/Cargo.toml b/Cargo.toml index e97f47c3..c3b35d12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ name = "parsec" path = "src/bin/main.rs" [dependencies] -parsec-interface = "0.12.0" +parsec-interface = "0.13.0" rand = "0.7.2" base64 = "0.10.1" uuid = "0.7.4" @@ -40,12 +40,13 @@ derivative = "1.0.3" version = "3.0.0" [dev-dependencies] -parsec-client-test = { git = "https://github.com/parallaxsecond/parsec-client-test", tag = "0.3.0" } num_cpus = "1.10.1" picky-asn1-der = "0.2.2" picky-asn1 = "0.2.1" serde = { version = "1.0", features = ["derive"] } sha2 = "0.8.1" +parsec-client = "0.1.0" +parsec-interface = { version = "0.13.0", features = ["testing"] } [build-dependencies] bindgen = "0.50.0" diff --git a/README.md b/README.md index ac27cd0c..c706fd79 100644 --- a/README.md +++ b/README.md @@ -73,25 +73,7 @@ Contributions from the developer community are welcome. Please refer to the cont # Example -Launch the Parsec service with a single software-based provider (using the default configuration): -```bash -$ git clone https://github.com/parallaxsecond/parsec.git -$ cd parsec -$ RUST_LOG=info cargo run -``` - -Parsec Client Libraries can now communicate with the service. For example using the Rust Test client, -RSA signatures can be done as follows: -```rust -use parsec_client_test::TestClient; - -let mut client = TestClient::new(); -let key_name = String::from("🔑 What shall I sign? 🔑"); -client.generate_rsa_sign_key(key_name.clone()).unwrap(); -let signature = client.sign(key_name, - String::from("Platform AbstRaction for SECurity").into_bytes()) - .unwrap(); -``` +For examples of how to access PARSEC as a client application, check [this Rust client documentation](https://docs.rs/parsec-client/*/parsec_client/core/basic_client/struct.BasicClient.html). Check the [**user**](https://parallaxsecond.github.io/parsec-book/parsec_users.html), [**client developer**](https://parallaxsecond.github.io/parsec-book/parsec_client/index.html) and [**service developer**](https://parallaxsecond.github.io/parsec-book/parsec_service/index.html) guides for more information on building, installing, testing and using Parsec! diff --git a/tests/all_providers/mod.rs b/tests/all_providers/mod.rs index 1321938f..114f6c66 100644 --- a/tests/all_providers/mod.rs +++ b/tests/all_providers/mod.rs @@ -12,9 +12,8 @@ // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use parsec_client_test::TestClient; -use parsec_interface::requests::Result; -use parsec_interface::requests::{Opcode, ProviderID}; +use crate::test_clients::TestClient; +use parsec_interface::requests::{Opcode, ProviderID, Result}; use std::collections::HashSet; use uuid::Uuid; diff --git a/tests/mod.rs b/tests/mod.rs index 03c4ef98..4cc2e725 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -44,3 +44,4 @@ mod all_providers; mod per_provider; +mod test_clients; diff --git a/tests/per_provider/normal_tests/asym_sign_verify.rs b/tests/per_provider/normal_tests/asym_sign_verify.rs index 80cb6a73..658327b6 100644 --- a/tests/per_provider/normal_tests/asym_sign_verify.rs +++ b/tests/per_provider/normal_tests/asym_sign_verify.rs @@ -12,10 +12,11 @@ // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use parsec_client_test::TestClient; +use crate::test_clients::TestClient; use parsec_interface::operations::psa_algorithm::*; use parsec_interface::operations::psa_key_attributes::*; -use parsec_interface::requests::{ResponseStatus, Result}; +use parsec_interface::requests::ResponseStatus; +use parsec_interface::requests::Result; use sha2::{Digest, Sha256}; const HASH: [u8; 32] = [ diff --git a/tests/per_provider/normal_tests/auth.rs b/tests/per_provider/normal_tests/auth.rs index 377c9244..479af586 100644 --- a/tests/per_provider/normal_tests/auth.rs +++ b/tests/per_provider/normal_tests/auth.rs @@ -12,15 +12,16 @@ // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use parsec_client_test::TestClient; -use parsec_interface::requests::{ResponseStatus, Result}; +use crate::test_clients::TestClient; +use parsec_interface::requests::ResponseStatus; +use parsec_interface::requests::Result; #[test] fn two_auths_same_key_name() -> Result<()> { let key_name = String::from("two_auths_same_key_name"); let mut client = TestClient::new(); - let auth1 = String::from("first_client").into_bytes(); - let auth2 = String::from("second_client").into_bytes(); + let auth1 = String::from("first_client"); + let auth2 = String::from("second_client"); client.set_auth(auth1); client.generate_rsa_sign_key(key_name.clone())?; @@ -33,8 +34,8 @@ fn two_auths_same_key_name() -> Result<()> { fn delete_wrong_key() -> Result<()> { let key_name = String::from("delete_wrong_key"); let mut client = TestClient::new(); - let auth1 = String::from("first_client").into_bytes(); - let auth2 = String::from("second_client").into_bytes(); + let auth1 = String::from("first_client"); + let auth2 = String::from("second_client"); client.set_auth(auth1); client.generate_rsa_sign_key(key_name.clone())?; diff --git a/tests/per_provider/normal_tests/basic.rs b/tests/per_provider/normal_tests/basic.rs index c015c394..6ca922ac 100644 --- a/tests/per_provider/normal_tests/basic.rs +++ b/tests/per_provider/normal_tests/basic.rs @@ -12,38 +12,17 @@ // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use parsec_client_test::RequestTestClient; -use parsec_client_test::TestClient; +use crate::test_clients::RawRequestClient; use parsec_interface::requests::request::RawHeader; use parsec_interface::requests::{Opcode, ProviderID, ResponseStatus}; -#[test] -fn invalid_version() { - let mut client = RequestTestClient::new(); - let mut req_hdr = RawHeader::new(); - - req_hdr.provider = ProviderID::Core as u8; - req_hdr.opcode = Opcode::Ping as u16; - req_hdr.version_maj = 0xff; - - let resp = client - .send_raw_request(req_hdr, Vec::new()) - .expect("Failed to read Response"); - assert_eq!( - resp.header.status, - ResponseStatus::WireProtocolVersionNotSupported - ); - assert_eq!(resp.header.opcode, Opcode::Ping); -} - #[test] fn invalid_provider() { - let mut client = RequestTestClient::new(); + let mut client = RawRequestClient {}; let mut req_hdr = RawHeader::new(); req_hdr.provider = 0xff; req_hdr.opcode = Opcode::Ping as u16; - req_hdr.version_maj = 0x01; let resp = client .send_raw_request(req_hdr, Vec::new()) @@ -54,12 +33,11 @@ fn invalid_provider() { #[test] fn invalid_content_type() { - let mut client = RequestTestClient::new(); + let mut client = RawRequestClient {}; let mut req_hdr = RawHeader::new(); req_hdr.provider = ProviderID::Core as u8; req_hdr.opcode = Opcode::Ping as u16; - req_hdr.version_maj = 1; req_hdr.content_type = 0xff; let resp = client @@ -71,12 +49,11 @@ fn invalid_content_type() { #[test] fn invalid_accept_type() { - let mut client = RequestTestClient::new(); + let mut client = RawRequestClient {}; let mut req_hdr = RawHeader::new(); req_hdr.provider = ProviderID::Core as u8; req_hdr.opcode = Opcode::Ping as u16; - req_hdr.version_maj = 1; req_hdr.accept_type = 0xff; @@ -89,12 +66,11 @@ fn invalid_accept_type() { #[test] fn invalid_body_len() { - let mut client = RequestTestClient::new(); + let mut client = RawRequestClient {}; let mut req_hdr = RawHeader::new(); req_hdr.provider = ProviderID::Core as u8; req_hdr.opcode = Opcode::Ping as u16; - req_hdr.version_maj = 1; req_hdr.body_len = 0xff_ff; @@ -106,12 +82,11 @@ fn invalid_body_len() { #[test] fn invalid_auth_len() { - let mut client = RequestTestClient::new(); + let mut client = RawRequestClient {}; let mut req_hdr = RawHeader::new(); req_hdr.provider = ProviderID::Core as u8; req_hdr.opcode = Opcode::Ping as u16; - req_hdr.version_maj = 1; req_hdr.auth_len = 0xff_ff; @@ -123,26 +98,14 @@ fn invalid_auth_len() { #[test] fn invalid_opcode() { - let mut client = RequestTestClient::new(); + let mut client = RawRequestClient {}; let mut req_hdr = RawHeader::new(); req_hdr.provider = ProviderID::Core as u8; req_hdr.opcode = 0xff_ff; - req_hdr.version_maj = 1; let resp = client .send_raw_request(req_hdr, Vec::new()) .expect("Failed to read Response"); assert_eq!(resp.header.status, ResponseStatus::OpcodeDoesNotExist); } - -#[test] -fn wrong_provider_core() { - let mut client = TestClient::new(); - client.set_provider(Some(ProviderID::Core)); - - let response_status = client - .destroy_key(String::new()) - .expect_err("Core Provider should not support DestroyKey operation!"); - assert_eq!(response_status, ResponseStatus::PsaErrorNotSupported); -} diff --git a/tests/per_provider/normal_tests/create_destroy_key.rs b/tests/per_provider/normal_tests/create_destroy_key.rs index 6ef11dae..31345e78 100644 --- a/tests/per_provider/normal_tests/create_destroy_key.rs +++ b/tests/per_provider/normal_tests/create_destroy_key.rs @@ -12,12 +12,13 @@ // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use parsec_client_test::TestClient; +use crate::test_clients::TestClient; use parsec_interface::operations::psa_algorithm::{Algorithm, AsymmetricSignature, Hash}; use parsec_interface::operations::psa_key_attributes::{ KeyAttributes, KeyPolicy, KeyType, UsageFlags, }; -use parsec_interface::requests::{ResponseStatus, Result}; +use parsec_interface::requests::ResponseStatus; +use parsec_interface::requests::Result; use picky_asn1::wrapper::IntegerAsn1; use serde::{Deserialize, Serialize}; diff --git a/tests/per_provider/normal_tests/export_public_key.rs b/tests/per_provider/normal_tests/export_public_key.rs index cd98a30e..efa18f99 100644 --- a/tests/per_provider/normal_tests/export_public_key.rs +++ b/tests/per_provider/normal_tests/export_public_key.rs @@ -12,10 +12,11 @@ // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use parsec_client_test::TestClient; +use crate::test_clients::TestClient; use parsec_interface::operations::psa_algorithm::*; use parsec_interface::operations::psa_key_attributes::*; -use parsec_interface::requests::{ResponseStatus, Result}; +use parsec_interface::requests::ResponseStatus; +use parsec_interface::requests::Result; use picky_asn1::wrapper::IntegerAsn1; use serde::{Deserialize, Serialize}; diff --git a/tests/per_provider/normal_tests/import_key.rs b/tests/per_provider/normal_tests/import_key.rs index 624b7156..b4169bf8 100644 --- a/tests/per_provider/normal_tests/import_key.rs +++ b/tests/per_provider/normal_tests/import_key.rs @@ -12,10 +12,11 @@ // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use parsec_client_test::TestClient; +use crate::test_clients::TestClient; use parsec_interface::operations::psa_algorithm::*; use parsec_interface::operations::psa_key_attributes::*; -use parsec_interface::requests::{ResponseStatus, Result}; +use parsec_interface::requests::ResponseStatus; +use parsec_interface::requests::Result; use picky_asn1::wrapper::IntegerAsn1; use serde::{Deserialize, Serialize}; @@ -57,8 +58,6 @@ fn import_key() -> Result<()> { let mut client = TestClient::new(); let key_name = String::from("import_key"); - println!("{:?}", KEY_DATA.to_vec()); - client.import_rsa_public_key(key_name, KEY_DATA.to_vec()) } diff --git a/tests/per_provider/normal_tests/key_attributes.rs b/tests/per_provider/normal_tests/key_attributes.rs index 362e8414..63140a9c 100644 --- a/tests/per_provider/normal_tests/key_attributes.rs +++ b/tests/per_provider/normal_tests/key_attributes.rs @@ -1,11 +1,11 @@ // Copyright (c) 2020, Arm Limited, All Rights Reserved // SPDX-License-Identifier: Apache-2.0 -use parsec_client_test::TestClient; +use crate::test_clients::TestClient; use parsec_interface::operations::psa_algorithm::{Algorithm, AsymmetricSignature, Cipher, Hash}; use parsec_interface::operations::psa_key_attributes::{ KeyAttributes, KeyPolicy, KeyType, UsageFlags, }; -use parsec_interface::requests::{Opcode, ProviderID, ResponseStatus}; +use parsec_interface::requests::{ProviderID, ResponseStatus}; // Ignored as only RSA key types are supported for now. #[ignore] @@ -121,7 +121,7 @@ fn wrong_permitted_algorithm() { // The Mbed Crypto provider currently does not support other algorithms than the RSA PKCS 1v15 // signing algorithm with hash when checking policies only. - if client.get_cached_provider(Opcode::PsaSignHash) == ProviderID::MbedCrypto { + if client.provider().unwrap() == ProviderID::MbedCrypto { return; } diff --git a/tests/per_provider/normal_tests/ping.rs b/tests/per_provider/normal_tests/ping.rs index 42bbb808..4c22671b 100644 --- a/tests/per_provider/normal_tests/ping.rs +++ b/tests/per_provider/normal_tests/ping.rs @@ -12,17 +12,18 @@ // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use parsec_client_test::RequestTestClient; -use parsec_client_test::TestClient; +use crate::test_clients::RequestClient; +use crate::test_clients::TestClient; use parsec_interface::requests::request::{Request, RequestAuth, RequestBody}; use parsec_interface::requests::Opcode; use parsec_interface::requests::ProviderID; -use parsec_interface::requests::{ResponseStatus, Result}; +use parsec_interface::requests::ResponseStatus; +use parsec_interface::requests::Result; #[test] fn test_ping() -> Result<()> { let mut client = TestClient::new(); - let version = client.ping(ProviderID::Core)?; + let version = client.ping()?; assert_eq!(version.0, 1); assert_eq!(version.1, 0); @@ -31,15 +32,16 @@ fn test_ping() -> Result<()> { #[test] fn mangled_ping() { - let mut client = RequestTestClient::new(); + let client = RequestClient::default(); let mut req = Request::new(); - req.header.version_maj = 1; req.header.provider = ProviderID::Core; req.header.opcode = Opcode::Ping; req.auth = RequestAuth::from_bytes(Vec::from("root")); req.body = RequestBody::_from_bytes(vec![0x11, 0x22, 0x33, 0x44, 0x55]); - let resp = client.send_request(req).expect("Failed to read Response"); + let resp = client + .process_request(req) + .expect("Failed to read Response"); assert_eq!(resp.header.status, ResponseStatus::DeserializingBodyFailed); } diff --git a/tests/per_provider/persistent_after.rs b/tests/per_provider/persistent_after.rs index f49bdffd..c0062a4f 100644 --- a/tests/per_provider/persistent_after.rs +++ b/tests/per_provider/persistent_after.rs @@ -15,8 +15,9 @@ // These functions test for the service persistency to shutdown. They will be executed after the // service is shutdown, after the persistent_before tests are executed. -use parsec_client_test::TestClient; -use parsec_interface::requests::{Opcode, ProviderID, ResponseStatus, Result}; +use crate::test_clients::TestClient; +use parsec_interface::requests::Result; +use parsec_interface::requests::{ProviderID, ResponseStatus}; const HASH: [u8; 32] = [ 0x69, 0x3E, 0xDB, 0x1B, 0x22, 0x79, 0x03, 0xF4, 0xC0, 0xBF, 0xD6, 0x91, 0x76, 0x37, 0x84, 0xA2, @@ -39,7 +40,7 @@ fn reuse_to_sign() -> Result<()> { fn should_have_been_deleted() { let mut client = TestClient::new(); - if client.get_cached_provider(Opcode::PsaDestroyKey) == ProviderID::Tpm { + if client.provider().unwrap() == ProviderID::Tpm { // This test does not make sense for the TPM Provider. return; } diff --git a/tests/per_provider/persistent_before.rs b/tests/per_provider/persistent_before.rs index 182803d1..06be1cb8 100644 --- a/tests/per_provider/persistent_before.rs +++ b/tests/per_provider/persistent_before.rs @@ -15,7 +15,7 @@ // These functions test for the service persistency to shutdown. They will be executed before the // service is shutdown and before the persistent_after tests are executed. -use parsec_client_test::TestClient; +use crate::test_clients::TestClient; use parsec_interface::requests::Result; const HASH: [u8; 32] = [ diff --git a/tests/per_provider/stress_test.rs b/tests/per_provider/stress_test.rs index 6635cedc..b5dd2225 100644 --- a/tests/per_provider/stress_test.rs +++ b/tests/per_provider/stress_test.rs @@ -12,7 +12,7 @@ // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use parsec_client_test::{StressTestClient, StressTestConfig}; +use crate::test_clients::stress::{StressClient, StressTestConfig}; use std::time::Duration; #[test] @@ -27,5 +27,5 @@ fn stress_test() { check_interval: Some(Duration::from_millis(500)), }; - StressTestClient::execute(config); + StressClient::execute(config); } diff --git a/tests/test_clients/mod.rs b/tests/test_clients/mod.rs new file mode 100644 index 00000000..46b3e947 --- /dev/null +++ b/tests/test_clients/mod.rs @@ -0,0 +1,318 @@ +// Copyright 2020 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 +pub mod raw_request; +pub mod stress; + +pub use raw_request::RawRequestClient; + +pub use parsec_client::core::request_client::RequestClient; +pub use parsec_client::error; + +use log::error; +use parsec_client::auth::AuthenticationData; +use parsec_client::core::basic_client::BasicClient; +use parsec_client::error::Error; +use parsec_interface::operations::list_providers::ProviderInfo; +use parsec_interface::operations::psa_algorithm::{Algorithm, AsymmetricSignature, Hash}; +use parsec_interface::operations::psa_key_attributes::*; +use parsec_interface::operations::psa_key_attributes::{KeyAttributes, KeyPolicy, UsageFlags}; +use parsec_interface::requests::{Opcode, ProviderID, ResponseStatus, Result}; +use std::collections::HashSet; +use std::time::Duration; + +/// Client structure automatically choosing a provider and high-level operation functions. +#[derive(Debug)] +pub struct TestClient { + basic_client: BasicClient, + created_keys: Option>, +} + +fn convert_error(err: Error) -> ResponseStatus { + if let Error::Service(resp_status) = err { + resp_status + } else { + panic!( + "Expected to obtain a service error, but got a client error instead: {:?}", + err + ); + } +} + +impl TestClient { + /// Creates a TestClient instance. + /// + /// The implicit provider chosen for servicing cryptographic operations is decided through + /// a call to `list_providers`, followed by choosing the first non-Core provider. + pub fn new() -> TestClient { + let mut client = TestClient { + basic_client: BasicClient::new(AuthenticationData::AppIdentity(String::from("root"))), + created_keys: Some(HashSet::new()), + }; + + let crypto_provider = client.find_crypto_provider(); + client.set_provider(crypto_provider); + client + .basic_client + .set_timeout(Some(Duration::from_secs(10))); + + client + } + + fn find_crypto_provider(&self) -> ProviderID { + let providers = self + .basic_client + .list_providers() + .expect("List providers failed"); + for provider in providers { + if provider.id != ProviderID::Core { + return provider.id; + } + } + + ProviderID::Core + } + + /// Manually set the provider to execute the requests. + pub fn set_provider(&mut self, provider: ProviderID) { + self.basic_client.set_implicit_provider(provider); + } + + /// Get client provider + pub fn provider(&self) -> Option { + self.basic_client.implicit_provider() + } + + /// Set the client authentication string. + pub fn set_auth(&mut self, auth: String) { + self.basic_client + .set_auth_data(AuthenticationData::AppIdentity(auth)); + } + + /// Get client authentication string. + pub fn auth(&self) -> String { + if let AuthenticationData::AppIdentity(app_name) = self.basic_client.auth_data() { + app_name + } else { + panic!("Client should always be using AppIdentity-based authentication"); + } + } + + /// By default the `TestClient` instance will destroy the keys it created when it is dropped, + /// unless this function is called. + pub fn do_not_destroy_keys(&mut self) { + let _ = self.created_keys.take(); + } + + /// Creates a key with specific attributes. + pub fn generate_key(&mut self, key_name: String, attributes: KeyAttributes) -> Result<()> { + self.basic_client + .psa_generate_key(key_name.clone(), attributes) + .map_err(convert_error)?; + + let provider = self.provider().unwrap(); + let auth = self.auth(); + + if let Some(ref mut created_keys) = self.created_keys { + let _ = created_keys.insert((key_name, auth, provider)); + } + + Ok(()) + } + + /// Generate a 1024 bits RSA key pair. + /// The key can only be used for signing/verifying with the RSA PKCS 1v15 signing algorithm with SHA-256 and exporting its public part. + pub fn generate_rsa_sign_key(&mut self, key_name: String) -> Result<()> { + self.generate_key( + key_name, + KeyAttributes { + key_type: KeyType::RsaKeyPair, + key_bits: 1024, + key_policy: KeyPolicy { + key_usage_flags: UsageFlags { + sign_hash: true, + verify_hash: true, + sign_message: true, + verify_message: true, + export: true, + encrypt: false, + decrypt: false, + cache: false, + copy: false, + derive: false, + }, + key_algorithm: Algorithm::AsymmetricSignature( + AsymmetricSignature::RsaPkcs1v15Sign { + hash_alg: Hash::Sha256, + }, + ), + }, + }, + ) + } + + /// Imports and creates a key with specific attributes. + pub fn import_key( + &mut self, + key_name: String, + attributes: KeyAttributes, + data: Vec, + ) -> Result<()> { + self.basic_client + .psa_import_key(key_name.clone(), data, attributes) + .map_err(convert_error)?; + + let provider = self.provider().unwrap(); + let auth = self.auth(); + + if let Some(ref mut created_keys) = self.created_keys { + let _ = created_keys.insert((key_name, auth, provider)); + } + + Ok(()) + } + + /// Import a 1024 bits RSA public key. + /// The key can only be used for verifying with the RSA PKCS 1v15 signing algorithm with SHA-256. + pub fn import_rsa_public_key(&mut self, key_name: String, data: Vec) -> Result<()> { + self.import_key( + key_name, + KeyAttributes { + key_type: KeyType::RsaPublicKey, + key_bits: 1024, + key_policy: KeyPolicy { + key_usage_flags: UsageFlags { + sign_hash: false, + verify_hash: true, + sign_message: false, + verify_message: true, + export: false, + encrypt: false, + decrypt: false, + cache: false, + copy: false, + derive: false, + }, + key_algorithm: Algorithm::AsymmetricSignature( + AsymmetricSignature::RsaPkcs1v15Sign { + hash_alg: Hash::Sha256, + }, + ), + }, + }, + data, + ) + } + + /// Exports a public key. + pub fn export_public_key(&mut self, key_name: String) -> Result> { + self.basic_client + .psa_export_public_key(key_name) + .map_err(convert_error) + } + + /// Destroys a key. + pub fn destroy_key(&mut self, key_name: String) -> Result<()> { + self.basic_client + .psa_destroy_key(key_name.clone()) + .map_err(convert_error)?; + + let provider = self.provider().unwrap(); + let auth = self.auth(); + + if let Some(ref mut created_keys) = self.created_keys { + let _ = created_keys.remove(&(key_name, auth, provider)); + } + + Ok(()) + } + + /// Signs a short digest with a key. + pub fn sign( + &mut self, + key_name: String, + alg: AsymmetricSignature, + hash: Vec, + ) -> Result> { + self.basic_client + .psa_sign_hash(key_name, hash, alg) + .map_err(convert_error) + } + + /// Signs a short digest with an RSA key. + pub fn sign_with_rsa_sha256(&mut self, key_name: String, hash: Vec) -> Result> { + self.sign( + key_name, + AsymmetricSignature::RsaPkcs1v15Sign { + hash_alg: Hash::Sha256, + }, + hash, + ) + } + + /// Verifies a signature. + pub fn verify( + &mut self, + key_name: String, + alg: AsymmetricSignature, + hash: Vec, + signature: Vec, + ) -> Result<()> { + self.basic_client + .psa_verify_hash(key_name, hash, alg, signature) + .map_err(convert_error) + } + + /// Verifies a signature made with an RSA key. + pub fn verify_with_rsa_sha256( + &mut self, + key_name: String, + hash: Vec, + signature: Vec, + ) -> Result<()> { + self.verify( + key_name, + AsymmetricSignature::RsaPkcs1v15Sign { + hash_alg: Hash::Sha256, + }, + hash, + signature, + ) + } + + /// Lists the provider available for the Parsec service. + pub fn list_providers(&mut self) -> Result> { + self.basic_client.list_providers().map_err(convert_error) + } + + /// Lists the opcodes available for one provider to execute. + pub fn list_opcodes(&mut self, provider_id: ProviderID) -> Result> { + self.basic_client + .list_opcodes(provider_id) + .map_err(convert_error) + } + + /// Executes a ping operation. + pub fn ping(&mut self) -> Result<(u8, u8)> { + self.basic_client.ping().map_err(convert_error) + } +} + +impl Default for TestClient { + fn default() -> Self { + TestClient::new() + } +} + +impl Drop for TestClient { + fn drop(&mut self) { + if let Some(ref mut created_keys) = self.created_keys { + for (key_name, auth, provider) in created_keys.clone().iter() { + self.set_provider(*provider); + self.set_auth(auth.clone()); + if self.destroy_key(key_name.clone()).is_err() { + error!("Failed to destroy key '{}'", key_name); + } + } + } + } +} diff --git a/tests/test_clients/raw_request.rs b/tests/test_clients/raw_request.rs new file mode 100644 index 00000000..40ccdd0a --- /dev/null +++ b/tests/test_clients/raw_request.rs @@ -0,0 +1,49 @@ +// Copyright 2020 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 +use parsec_interface::requests::request::RawHeader; +use parsec_interface::requests::{Response, Result}; +use std::io::Write; +use std::os::unix::net::UnixStream; +use std::thread; +use std::time::Duration; + +const MAX_BODY_SIZE: usize = 1 << 31; + +/// Low level client structure to send a `Request` and get a `Response`. +#[derive(Copy, Clone, Debug)] +pub struct RawRequestClient; + +static SOCKET_PATH: &str = "/tmp/security-daemon-socket"; +const TIMEOUT: Duration = Duration::from_secs(5); + +#[allow(clippy::new_without_default)] +impl RawRequestClient { + /// Send a raw request. + /// + /// Send a raw request header and a collection of bytes. + pub fn send_raw_request(&mut self, request_hdr: RawHeader, bytes: Vec) -> Result { + // Try to connect once, wait for a timeout until trying again. + let mut stream = UnixStream::connect(SOCKET_PATH); + if stream.is_err() { + thread::sleep(TIMEOUT); + stream = UnixStream::connect(SOCKET_PATH); + } + let mut stream = stream.expect("Failed to connect to Unix socket"); + + stream + .set_read_timeout(Some(TIMEOUT)) + .expect("Failed to set read timeout for stream"); + stream + .set_write_timeout(Some(TIMEOUT)) + .expect("Failed to set write timeout for stream"); + + request_hdr + .write_to_stream(&mut stream) + .expect("Failed to write raw header to socket"); + stream + .write_all(&bytes) + .expect("Failed to write bytes to stream"); + + Response::read_from_stream(&mut stream, MAX_BODY_SIZE) + } +} diff --git a/tests/test_clients/stress.rs b/tests/test_clients/stress.rs new file mode 100644 index 00000000..ec485efa --- /dev/null +++ b/tests/test_clients/stress.rs @@ -0,0 +1,260 @@ +// Copyright 2020 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 +use crate::test_clients::TestClient; +use log::info; +use parsec_interface::requests::ResponseStatus; +use rand::Rng; +use rand::{ + distributions::{Alphanumeric, Distribution, Standard}, + thread_rng, +}; +use std::convert::TryInto; +use std::iter; +use std::sync::mpsc::{channel, Receiver}; +use std::thread; +use std::time::Duration; + +const HASH: [u8; 32] = [ + 0x69, 0x3E, 0xDB, 0x1B, 0x22, 0x79, 0x03, 0xF4, 0xC0, 0xBF, 0xD6, 0x91, 0x76, 0x37, 0x84, 0xA2, + 0x94, 0x8E, 0x92, 0x50, 0x35, 0xC2, 0x8C, 0x5C, 0x3C, 0xCA, 0xFE, 0x18, 0xE8, 0x81, 0x37, 0x78, +]; + +const KEY_DATA: [u8; 140] = [ + 48, 129, 137, 2, 129, 129, 0, 153, 165, 220, 135, 89, 101, 254, 229, 28, 33, 138, 247, 20, 102, + 253, 217, 247, 246, 142, 107, 51, 40, 179, 149, 45, 117, 254, 236, 161, 109, 16, 81, 135, 72, + 112, 132, 150, 175, 128, 173, 182, 122, 227, 214, 196, 130, 54, 239, 93, 5, 203, 185, 233, 61, + 159, 156, 7, 161, 87, 48, 234, 105, 161, 108, 215, 211, 150, 168, 156, 212, 6, 63, 81, 24, 101, + 72, 160, 97, 243, 142, 86, 10, 160, 122, 8, 228, 178, 252, 35, 209, 222, 228, 16, 143, 99, 143, + 146, 241, 186, 187, 22, 209, 86, 141, 24, 159, 12, 146, 44, 111, 254, 183, 54, 229, 109, 28, + 39, 22, 141, 173, 85, 26, 58, 9, 128, 27, 57, 131, 2, 3, 1, 0, 1, +]; + +#[derive(Copy, Clone, Debug)] +pub struct StressTestConfig { + pub no_threads: usize, + pub req_per_thread: usize, + pub req_interval: Option, + pub req_interval_deviation_millis: Option, + pub check_interval: Option, +} + +fn generate_string(size: usize) -> String { + let mut rng = thread_rng(); + iter::repeat(()) + .map(|()| rng.sample(Alphanumeric)) + .take(size) + .collect() +} + +#[derive(Copy, Clone, Debug)] +enum Operation { + CreateDestroyKey, + Sign, + Verify, + DestroyKey, + ImportDestroyKey, + ExportPublicKey, +} + +#[derive(Copy, Clone, Debug)] +pub struct StressClient; + +impl StressClient { + pub fn execute(config: StressTestConfig) { + info!("Starting stress test"); + + let mut threads = Vec::new(); + for _ in 0..config.no_threads { + threads.push(thread::spawn(move || { + StressTestWorker::new(config).run_test(); + })); + } + + let (send, recv) = channel(); + + let checker = thread::spawn(move || { + ServiceChecker::run_check(&config, recv); + }); + + for thread in threads { + thread.join().expect("Test thread panicked"); + } + + if config.check_interval.is_some() { + send.send(true.to_owned()).unwrap(); + } + + checker.join().expect("Check thread panicked"); + } +} + +struct StressTestWorker { + config: StressTestConfig, + test_key_name: String, + client: TestClient, +} + +impl StressTestWorker { + pub fn new(config: StressTestConfig) -> Self { + let mut client = TestClient::new(); + + // Create unique client auth + let auth = generate_string(10); + info!("Worker with auth `{}` starting.", auth); + client.set_auth(auth); + + // Create sign/verify key + let test_key_name = generate_string(10); + client + .generate_rsa_sign_key(test_key_name.clone()) + .expect("Failed to create key"); + + StressTestWorker { + config, + test_key_name, + client, + } + } + + pub fn run_test(mut self) { + for _ in 0..self.config.req_per_thread { + self.execute_request(); + + if let Some(mut interval) = self.config.req_interval { + if let Some(deviation) = self.config.req_interval_deviation_millis { + let dev = thread_rng().gen_range(0, 2 * deviation); + interval += Duration::from_millis(dev.try_into().unwrap()); + interval -= Duration::from_millis(deviation.try_into().unwrap()); + } + thread::sleep(interval); + } + } + } + + fn execute_request(&mut self) { + let op: Operation = rand::random(); + info!("Executing operation: {:?}", op); + + match op { + Operation::CreateDestroyKey => { + let key_name = generate_string(10); + info!("Creating key with name: {}", key_name); + self.client + .generate_rsa_sign_key(key_name.clone()) + .expect("Failed to create key"); + self.client + .destroy_key(key_name) + .expect("Failed to destroy key"); + } + Operation::Sign => { + info!("Signing with key: {}", self.test_key_name.clone()); + let _ = self + .client + .sign_with_rsa_sha256(self.test_key_name.clone(), HASH.to_vec()) + .expect("Failed to sign"); + } + Operation::Verify => { + info!("Verifying with key: {}", self.test_key_name.clone()); + let _ = self + .client + .verify_with_rsa_sha256( + self.test_key_name.clone(), + HASH.to_vec(), + vec![0xff; 128], + ) + .expect_err("Verification should faild."); + let status = self + .client + .verify_with_rsa_sha256( + self.test_key_name.clone(), + HASH.to_vec(), + vec![0xff; 128], + ) + .expect_err("Verification should faild."); + if !(status == ResponseStatus::PsaErrorInvalidSignature + || status == ResponseStatus::PsaErrorCorruptionDetected) + { + panic!("An invalid signature or a tampering detection should be the only reasons of the verification failing. Status returned: {:?}.", status); + } + } + Operation::DestroyKey => { + let key_name = generate_string(10); + info!("Destroying key with name: {}", key_name); + let _ = self + .client + .destroy_key(key_name) + .expect_err("Failed to destroy key"); + } + Operation::ImportDestroyKey => { + let key_name = generate_string(10); + info!("Importing key with name: {}", key_name); + self.client + .import_rsa_public_key(key_name.clone(), KEY_DATA.to_vec()) + .expect("Failed to import key"); + self.client + .destroy_key(key_name) + .expect("Failed to destroy key"); + } + Operation::ExportPublicKey => { + info!( + "Exporting public key with name: {}", + self.test_key_name.clone() + ); + let _ = self + .client + .export_public_key(self.test_key_name.clone()) + .expect("Failed to export key"); + } + } + } +} + +struct ServiceChecker; + +impl ServiceChecker { + pub fn run_check(config: &StressTestConfig, recv: Receiver) { + if config.check_interval.is_none() { + return; + } + + let mut client = TestClient::new(); + let key_name = String::from("checking_key"); + + loop { + info!("Verifying that the service is still operating correctly"); + client + .generate_rsa_sign_key(key_name.clone()) + .expect("Failed to create signing key"); + + let signature = client + .sign_with_rsa_sha256(key_name.clone(), HASH.to_vec()) + .expect("Failed to sign"); + + client + .verify_with_rsa_sha256(key_name.clone(), HASH.to_vec(), signature) + .expect("Verification failed"); + + client + .destroy_key(key_name.clone()) + .expect("Failed to destroy key"); + + thread::sleep(config.check_interval.unwrap()); + if recv.try_recv().is_ok() { + return; + } + } + } +} + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> Operation { + match rng.gen_range(0, 6) { + 0 => Operation::CreateDestroyKey, + 1 => Operation::Sign, + 2 => Operation::Verify, + 3 => Operation::DestroyKey, + 4 => Operation::ImportDestroyKey, + _ => Operation::ExportPublicKey, + } + } +}