diff --git a/Cargo.toml b/Cargo.toml index 6ab7069..e818a44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ categories = ["development-tools"] edition = "2018" [dependencies] -parsec-interface = "0.11.0" +parsec-interface = "0.13.0" num = "0.2.1" rand = "0.7.3" log = "0.4.8" diff --git a/src/core/basic_client.rs b/src/core/basic_client.rs new file mode 100644 index 0000000..573f11f --- /dev/null +++ b/src/core/basic_client.rs @@ -0,0 +1,495 @@ +// Copyright 2020 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 +//! Basic client for Parsec integration +use super::operation_client::OperationClient; +use super::{Opcode, ProviderID}; +use crate::auth::AuthenticationData; +use crate::error::{ClientErrorKind, Error, Result}; +use parsec_interface::operations::list_opcodes::Operation as ListOpcodes; +use parsec_interface::operations::list_providers::{Operation as ListProviders, ProviderInfo}; +use parsec_interface::operations::ping::Operation as Ping; +use parsec_interface::operations::psa_algorithm::AsymmetricSignature; +use parsec_interface::operations::psa_destroy_key::Operation as PsaDestroyKey; +use parsec_interface::operations::psa_export_public_key::Operation as PsaExportPublicKey; +use parsec_interface::operations::psa_generate_key::Operation as PsaGenerateKey; +use parsec_interface::operations::psa_import_key::Operation as PsaImportKey; +use parsec_interface::operations::psa_key_attributes::KeyAttributes; +use parsec_interface::operations::psa_sign_hash::Operation as PsaSignHash; +use parsec_interface::operations::psa_verify_hash::Operation as PsaVerifyHash; +use parsec_interface::operations::{NativeOperation, NativeResult}; +use std::collections::HashSet; + +/// Core client for Parsec service +/// +/// The client exposes low-level functionality for using the Parsec service. +/// Below you can see code examples for a few of the operations supported. +/// +/// Creating a `BasicClient` instance: +///```no_run +///use parsec_client::auth::AuthenticationData; +///use parsec_client::BasicClient; +///use parsec_client::core::ProviderID; +/// +///let app_name = String::from("app-name"); +///let app_auth_data = AuthenticationData::AppIdentity(app_name); +///let desired_provider = ProviderID::Pkcs11; +///let client: BasicClient = BasicClient::new(app_auth_data, desired_provider); +///``` +/// +/// Performing a Ping operation helps to determine if the service is available +/// and what wire protocol it supports. Currently only a version 1.0 of the wire +/// protocol exists and new versions are expected to be extremely rare. +///```no_run +///# use parsec_client::auth::AuthenticationData; +///# use parsec_client::BasicClient; +///# use parsec_client::core::ProviderID; +///# let client: BasicClient = BasicClient::new(AuthenticationData::AppIdentity(String::from("app-name")), ProviderID::Pkcs11); +///let res = client.ping(); +/// +///if let Ok((wire_prot_v_maj, wire_prot_v_min)) = res { +/// println!( +/// "Success! Service wire protocol version is {}.{}", +/// wire_prot_v_maj, wire_prot_v_min +/// ); +///} else { +/// panic!("Ping failed. Error: {:?}", res); +///} +///``` +/// +/// Providers are abstracted representations of the secure elements that +/// PARSEC offers abstraction over. Providers are the ones to execute the +/// cryptographic operations requested by the user. +/// +/// Checking for available providers: +///```no_run +///# use parsec_client::auth::AuthenticationData; +///# use parsec_client::BasicClient; +///# use parsec_client::core::ProviderID; +///# let client: BasicClient = BasicClient::new(AuthenticationData::AppIdentity(String::from("app-name")), ProviderID::Pkcs11); +///use uuid::Uuid; +/// +///// Identify provider by its UUID (in this case, the PKCS11 provider) +///let desired_provider_uuid = Uuid::parse_str("30e39502-eba6-4d60-a4af-c518b7f5e38f").unwrap(); +///let available_providers = client.list_providers().expect("Failed to list providers"); +///if available_providers +/// .iter() +/// .filter(|provider| provider.uuid == desired_provider_uuid) +/// .count() +/// == 0 +///{ +/// panic!("Did not find desired provider!"); +///} +///``` +/// +/// Checking operations supported by the provider we're interested in is done +/// through the `list_provider_operations` method: +///```no_run +///# use parsec_client::auth::AuthenticationData; +///# use parsec_client::BasicClient; +///# use parsec_client::core::ProviderID; +///# let client: BasicClient = BasicClient::new(AuthenticationData::AppIdentity(String::from("app-name")), ProviderID::Pkcs11); +///use parsec_client::core::Opcode; +/// +///let desired_provider = ProviderID::Pkcs11; +///let provider_opcodes = client +/// .list_provider_operations(desired_provider) +/// .expect("Failed to list opcodes"); +///// Each operation is identified by a specific `Opcode` +///assert!(provider_opcodes.contains(&Opcode::PsaGenerateKey)); +///assert!(provider_opcodes.contains(&Opcode::PsaSignHash)); +///assert!(provider_opcodes.contains(&Opcode::PsaDestroyKey)); +///``` +/// +/// Creating a key-pair for signing SHA256 digests with RSA PKCS#1 v1.5: +///```no_run +///# use parsec_client::auth::AuthenticationData; +///# use parsec_client::BasicClient; +///# use parsec_client::core::ProviderID; +///# let client: BasicClient = BasicClient::new(AuthenticationData::AppIdentity(String::from("app-name")), ProviderID::Pkcs11); +///use parsec_client::core::psa_algorithm::{Algorithm, AsymmetricSignature, Hash}; +///use parsec_client::core::psa_key_attributes::{KeyAttributes, KeyPolicy, KeyType, UsageFlags}; +/// +///let key_name = String::from("rusty key 🔑"); +///// This algorithm identifier will be used within the key policy (i.e. what +///// algorithms are usable with the key) and for indicating the desired +///// algorithm for each operation involving the key. +///let asym_sign_algo = AsymmetricSignature::RsaPkcs1v15Sign { +/// hash_alg: Hash::Sha256, +///}; +/// +///// The key attributes define and limit the usage of the key material stored +///// by the underlying cryptographic provider. +///let key_attrs = KeyAttributes { +/// key_type: KeyType::RsaKeyPair, +/// key_bits: 2048, +/// key_policy: KeyPolicy { +/// key_usage_flags: UsageFlags { +/// export: true, +/// copy: true, +/// cache: true, +/// encrypt: false, +/// decrypt: false, +/// sign_message: true, +/// verify_message: false, +/// sign_hash: true, +/// verify_hash: false, +/// derive: false, +/// }, +/// key_algorithm: asym_sign_algo.into(), +/// }, +///}; +/// +///client +/// .psa_generate_key(key_name, key_attrs) +/// .expect("Failed to create key!"); +///``` +/// +/// It is recommended that before attempting to use cryptographic +/// operations users call [`list_providers`](#method.list_providers) +/// and [`list_provider_operations`](#method.list_provider_operations) +/// in order to figure out if their desired use case and provider are +/// available. +#[derive(Debug)] +pub struct BasicClient { + pub(crate) op_client: OperationClient, + pub(crate) auth_data: AuthenticationData, + pub(crate) implicit_provider: ProviderID, +} + +/// Main client functionality. +impl BasicClient { + /// Create a new Parsec client given the authentication data of the app and a provider ID. + /// + /// The `implicit_provider` will be used for all non-core operations. Thus, until a + /// different provider ID is set using [`set_implicit_provider`](#method.set_implicit_provider), + /// all cryptographic operations will be executed in the context of this provider (if it is + /// available within the service). + /// + /// In order to get a list of supported providers, call the [`list_providers`](#method.list_providers) + /// method. + pub fn new(auth_data: AuthenticationData, implicit_provider: ProviderID) -> Self { + BasicClient { + op_client: Default::default(), + auth_data, + implicit_provider, + } + } + + /// Update the authentication data of the client. + pub fn set_auth_data(&mut self, auth_data: AuthenticationData) { + self.auth_data = auth_data; + } + + /// Set the provider that the client will be implicitly working with. + pub fn set_implicit_provider(&mut self, provider: ProviderID) { + self.implicit_provider = provider; + } + + /// **[Core Operation]** List the opcodes supported by the specified provider. + pub fn list_provider_operations(&self, provider: ProviderID) -> Result> { + let res = self.op_client.process_operation( + NativeOperation::ListOpcodes(ListOpcodes { + provider_id: provider, + }), + ProviderID::Core, + &self.auth_data, + )?; + + if let NativeResult::ListOpcodes(res) = res { + Ok(res.opcodes) + } else { + // Should really not be reached given the checks we do, but it's not impossible if some + // changes happen in the interface + Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) + } + } + + /// **[Core Operation]** List the providers that are supported by the service. + pub fn list_providers(&self) -> Result> { + let res = self.op_client.process_operation( + NativeOperation::ListProviders(ListProviders {}), + ProviderID::Core, + &self.auth_data, + )?; + + if let NativeResult::ListProviders(res) = res { + Ok(res.providers) + } else { + // Should really not be reached given the checks we do, but it's not impossible if some + // changes happen in the interface + Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) + } + } + + /// **[Core Operation]** Send a ping request to the service. + /// + /// This operation is intended for testing connectivity to the + /// service and for retrieving the maximum wire protocol version + /// it supports. + pub fn ping(&self) -> Result<(u8, u8)> { + let res = self.op_client.process_operation( + NativeOperation::Ping(Ping {}), + ProviderID::Core, + &AuthenticationData::None, + )?; + + if let NativeResult::Ping(res) = res { + Ok((res.wire_protocol_version_maj, res.wire_protocol_version_min)) + } else { + // Should really not be reached given the checks we do, but it's not impossible if some + // changes happen in the interface + Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) + } + } + + /// **[Cryptographic Operation]** Generate a key. + /// + /// Creates a new key with the given name within the namespace of the + /// implicit client provider. Any UTF-8 string is considered a valid key name, + /// however names must be unique per provider. + /// + /// Persistence of keys is implemented at provider level, and currently all + /// providers persist all the keys users create. However, no methods exist + /// for discovering previously generated or imported keys, so users are + /// responsible for keeping track of keys they have created. + /// + /// # Errors + /// + /// If this method returns an error, no key will have been generated and + /// the name used will still be available for another key. + /// + /// If the implicit client provider is `ProviderID::Core`, a client error + /// of `InvalidProvider` type is returned. + /// + /// See the operation-specific response codes returned by the service + /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_generate_key.html#specific-response-status-codes). + pub fn psa_generate_key(&self, key_name: String, key_attributes: KeyAttributes) -> Result<()> { + self.can_provide_crypto()?; + + let op = PsaGenerateKey { + key_name, + attributes: key_attributes, + }; + + let _ = self.op_client.process_operation( + NativeOperation::PsaGenerateKey(op), + self.implicit_provider, + &self.auth_data, + )?; + + Ok(()) + } + + /// **[Cryptographic Operation]** Destroy a key. + /// + /// Given that keys are namespaced at a provider level, it is + /// important to call `psa_destroy_key` on the correct combination of + /// implicit client provider and `key_name`. + /// + /// # Errors + /// + /// If the implicit client provider is `ProviderID::Core`, a client error + /// of `InvalidProvider` type is returned. + /// + /// See the operation-specific response codes returned by the service + /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_destroy_key.html#specific-response-status-codes). + pub fn psa_destroy_key(&self, key_name: String) -> Result<()> { + self.can_provide_crypto()?; + + let op = PsaDestroyKey { key_name }; + + let _ = self.op_client.process_operation( + NativeOperation::PsaDestroyKey(op), + self.implicit_provider, + &self.auth_data, + )?; + + Ok(()) + } + + /// **[Cryptographic Operation]** Import a key. + /// + /// Creates a new key with the given name within the namespace of the + /// implicit client provider using the user-provided data. Any UTF-8 string is + /// considered a valid key name, however names must be unique per provider. + /// + /// The key material should follow the appropriate binary format expressed + /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_export_public_key.html). + /// Several crates (e.g. [`picky-asn1`](https://crates.io/crates/picky-asn1)) + /// can greatly help in dealing with binary encodings. + /// + /// Persistence of keys is implemented at provider level, and currently all + /// providers persist all the keys users create. However, no methods exist + /// for discovering previously generated or imported keys, so users are + /// responsible for keeping track of keys they have created. + /// + /// # Errors + /// + /// If this method returns an error, no key will have been imported and the + /// name used will still be available for another key. + /// + /// If the implicit client provider is `ProviderID::Core`, a client error + /// of `InvalidProvider` type is returned. + /// + /// See the operation-specific response codes returned by the service + /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_import_key.html#specific-response-status-codes). + pub fn psa_import_key( + &self, + key_name: String, + key_material: Vec, + key_attributes: KeyAttributes, + ) -> Result<()> { + self.can_provide_crypto()?; + + let op = PsaImportKey { + key_name, + attributes: key_attributes, + data: key_material, + }; + + let _ = self.op_client.process_operation( + NativeOperation::PsaImportKey(op), + self.implicit_provider, + &self.auth_data, + )?; + + Ok(()) + } + + /// **[Cryptographic Operation]** Export a public key or the public part of a key pair. + /// + /// The returned key material will follow the appropriate binary format expressed + /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_export_public_key.html). + /// Several crates (e.g. [`picky-asn1`](https://crates.io/crates/picky-asn1)) + /// can greatly help in dealing with binary encodings. + /// + /// In order to export a public key, the export flag found in the + /// [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.KeyPolicy.html) + /// **must** be `true`. + /// + /// # Errors + /// + /// If the implicit client provider is `ProviderID::Core`, a client error + /// of `InvalidProvider` type is returned. + /// + /// See the operation-specific response codes returned by the service + /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_export_public_key.html#specific-response-status-codes). + pub fn psa_export_public_key(&self, key_name: String) -> Result> { + self.can_provide_crypto()?; + + let op = PsaExportPublicKey { key_name }; + + let res = self.op_client.process_operation( + NativeOperation::PsaExportPublicKey(op), + self.implicit_provider, + &self.auth_data, + )?; + + if let NativeResult::PsaExportPublicKey(res) = res { + Ok(res.data) + } else { + // Should really not be reached given the checks we do, but it's not impossible if some + // changes happen in the interface + Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) + } + } + + /// **[Cryptographic Operation]** Create an asymmetric signature on a pre-computed message digest. + /// + /// The key intended for signing **must** have its `sign_hash` flag set + /// to `true` in its [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.KeyPolicy.html). + /// + /// The signature will be created with the algorithm defined in + /// `sign_algorithm`, but only after checking that the key policy + /// and type conform with it. + /// + /// `hash` must be a hash pre-computed over the message of interest + /// with the algorithm specified within `sign_algorithm`. + /// + /// # Errors + /// + /// If the implicit client provider is `ProviderID::Core`, a client error + /// of `InvalidProvider` type is returned. + /// + /// See the operation-specific response codes returned by the service + /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_sign_hash.html#specific-response-status-codes). + pub fn psa_sign_hash( + &self, + key_name: String, + hash: Vec, + sign_algorithm: AsymmetricSignature, + ) -> Result> { + self.can_provide_crypto()?; + + let op = PsaSignHash { + key_name, + alg: sign_algorithm, + hash, + }; + + let res = self.op_client.process_operation( + NativeOperation::PsaSignHash(op), + self.implicit_provider, + &self.auth_data, + )?; + + if let NativeResult::PsaSignHash(res) = res { + Ok(res.signature) + } else { + // Should really not be reached given the checks we do, but it's not impossible if some + // changes happen in the interface + Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) + } + } + + /// **[Cryptographic Operation]** Verify an existing asymmetric signature over a pre-computed message digest. + /// + /// The key intended for signing **must** have its `verify_hash` flag set + /// to `true` in its [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.KeyPolicy.html). + /// + /// The signature will be verifyied with the algorithm defined in + /// `sign_algorithm`, but only after checking that the key policy + /// and type conform with it. + /// + /// `hash` must be a hash pre-computed over the message of interest + /// with the algorithm specified within `sign_algorithm`. + /// + /// # Errors + /// + /// If the implicit client provider is `ProviderID::Core`, a client error + /// of `InvalidProvider` type is returned. + /// + /// See the operation-specific response codes returned by the service + /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_verify_hash.html#specific-response-status-codes). + pub fn psa_verify_hash( + &self, + key_name: String, + hash: Vec, + sign_algorithm: AsymmetricSignature, + signature: Vec, + ) -> Result<()> { + self.can_provide_crypto()?; + + let op = PsaVerifyHash { + key_name, + alg: sign_algorithm, + hash, + signature, + }; + + let _ = self.op_client.process_operation( + NativeOperation::PsaVerifyHash(op), + self.implicit_provider, + &self.auth_data, + )?; + + Ok(()) + } + + fn can_provide_crypto(&self) -> Result<()> { + match self.implicit_provider { + ProviderID::Core => Err(Error::Client(ClientErrorKind::InvalidProvider)), + _ => Ok(()), + } + } +} diff --git a/src/core/ipc_client/mod.rs b/src/core/ipc_handler/mod.rs similarity index 100% rename from src/core/ipc_client/mod.rs rename to src/core/ipc_handler/mod.rs diff --git a/src/core/ipc_client/unix_socket.rs b/src/core/ipc_handler/unix_socket.rs similarity index 87% rename from src/core/ipc_client/unix_socket.rs rename to src/core/ipc_handler/unix_socket.rs index 6558aa6..75ee98a 100644 --- a/src/core/ipc_client/unix_socket.rs +++ b/src/core/ipc_handler/unix_socket.rs @@ -10,16 +10,16 @@ use std::time::Duration; const DEFAULT_SOCKET_PATH: &str = "/tmp/security-daemon-socket"; const DEFAULT_TIMEOUT: Duration = Duration::from_secs(1); -/// IPC client for Unix domain sockets +/// IPC handler for Unix domain sockets #[derive(Debug, Clone)] -pub struct Client { +pub struct Handler { /// Path at which the socket can be found path: PathBuf, /// Timeout for reads and writes on the streams timeout: Option, } -impl Connect for Client { +impl Connect for Handler { fn connect(&self) -> Result> { let stream = UnixStream::connect(self.path.clone()).map_err(ClientErrorKind::Ipc)?; @@ -34,16 +34,16 @@ impl Connect for Client { } } -impl Client { +impl Handler { /// Create new client using given socket path and timeout duration pub fn new(path: PathBuf, timeout: Option) -> Self { - Client { path, timeout } + Handler { path, timeout } } } -impl Default for Client { +impl Default for Handler { fn default() -> Self { - Client { + Handler { path: DEFAULT_SOCKET_PATH.into(), timeout: Some(DEFAULT_TIMEOUT), } diff --git a/src/core/mod.rs b/src/core/mod.rs index 3b085c5..b775067 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,477 +1,12 @@ // Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 -//! Client library for integration with the Parsec service -pub mod ipc_client; -mod operation_handler; -mod request_handler; +//! Core helpers for integration with the Parsec service +pub mod basic_client; +pub mod ipc_handler; +pub mod operation_client; +pub mod request_client; mod testing; -use crate::auth::AuthenticationData; -use crate::error::{ClientErrorKind, Error, Result}; -use operation_handler::OperationHandler; -use parsec_interface::operations::list_opcodes::Operation as ListOpcodes; -use parsec_interface::operations::list_providers::{Operation as ListProviders, ProviderInfo}; -use parsec_interface::operations::ping::Operation as Ping; -use parsec_interface::operations::psa_algorithm::AsymmetricSignature; -use parsec_interface::operations::psa_destroy_key::Operation as PsaDestroyKey; -use parsec_interface::operations::psa_export_public_key::Operation as PsaExportPublicKey; -use parsec_interface::operations::psa_generate_key::Operation as PsaGenerateKey; -use parsec_interface::operations::psa_import_key::Operation as PsaImportKey; -use parsec_interface::operations::psa_key_attributes::KeyAttributes; -use parsec_interface::operations::psa_sign_hash::Operation as PsaSignHash; -use parsec_interface::operations::psa_verify_hash::Operation as PsaVerifyHash; -use parsec_interface::operations::{NativeOperation, NativeResult}; -use parsec_interface::requests::ProviderID; -use std::collections::HashSet; -use uuid::Uuid; - pub use parsec_interface::operations::{psa_algorithm, psa_key_attributes}; -pub use parsec_interface::requests::{BodyType, Opcode}; - -/// List of supported service providers. -#[derive(Debug, Copy, Clone)] -pub enum Provider { - /// Core provider, responsible for management operations. - Core, - /// Software-based provider rooted in Mbed Crypto - MbedCrypto, - /// Provider offering abstraction over the PKCS 11 interface - Pkcs11, - /// Provider offering abstraction over the TPM 2.0 interface - Tpm, -} - -impl Provider { - /// Get the ID associated with the provider. - pub fn id(self) -> ProviderID { - match self { - Provider::Core => ProviderID::Core, - Provider::MbedCrypto => ProviderID::MbedCrypto, - Provider::Pkcs11 => ProviderID::Pkcs11, - Provider::Tpm => ProviderID::Tpm, - } - } - - /// Get the v4 UUID associated with the provider. - pub fn uuid(self) -> Uuid { - // `.unwrap()` is safe below since the values are hardcoded and have been proven to work - match self { - Provider::Core => Uuid::parse_str("47049873-2a43-4845-9d72-831eab668784").unwrap(), - Provider::MbedCrypto => { - Uuid::parse_str("1c1139dc-ad7c-47dc-ad6b-db6fdb466552").unwrap() - } - Provider::Pkcs11 => Uuid::parse_str("30e39502-eba6-4d60-a4af-c518b7f5e38f").unwrap(), - Provider::Tpm => Uuid::parse_str("1e4954a4-ff21-46d3-ab0c-661eeb667e1d").unwrap(), - } - } -} - -/// Core client for Parsec service -/// -/// The client exposes low-level functionality for using the Parsec service. -/// Below you can see code examples for a few of the operations supported. -/// -/// Creating a `CoreClient` instance: -///```no_run -///use parsec_client::auth::AuthenticationData; -///use parsec_client::CoreClient; -/// -///let app_name = String::from("app-name"); -///let app_auth_data = AuthenticationData::AppIdentity(app_name); -///let client: CoreClient = CoreClient::new(app_auth_data); -///``` -/// -/// Performing a Ping operation to determine if the service is available -/// and what wire protocol it supports: -///```no_run -///# use parsec_client::auth::AuthenticationData; -///# use parsec_client::CoreClient; -///# let client: CoreClient = CoreClient::new(AuthenticationData::AppIdentity(String::from("app-name"))); -///let res = client.ping(); -/// -///if let Ok((wire_prot_v_maj, wire_prot_v_min)) = res { -/// println!( -/// "Success! Service wire protocol version is {}.{}", -/// wire_prot_v_maj, wire_prot_v_min -/// ); -///} else { -/// panic!("Ping failed. Error: {:?}", res); -///} -///``` -/// -/// Providers are abstracted representations of the secure elements that -/// PARSEC offers abstraction over. Providers are the ones to execute the -/// cryptographic operations requested by the user. -/// -/// Checking for available providers: -///```no_run -///# use parsec_client::auth::AuthenticationData; -///# use parsec_client::CoreClient; -///# let client: CoreClient = CoreClient::new(AuthenticationData::AppIdentity(String::from("app-name"))); -///use parsec_client::core::Provider; -/// -///let desired_provider = Provider::Tpm; -///let available_providers = client.list_providers().expect("Failed to list providers"); -///if available_providers -/// .iter() -/// .filter(|provider| provider.uuid == desired_provider.uuid()) -/// .count() -/// == 0 -///{ -/// panic!("Did not find desired provider!"); -///} -///``` -/// -/// Checking operations supported by the provider we're interested in: -///```no_run -///# use parsec_client::auth::AuthenticationData; -///# use parsec_client::CoreClient; -///# let client: CoreClient = CoreClient::new(AuthenticationData::AppIdentity(String::from("app-name"))); -///# use parsec_client::core::Provider; -///# let desired_provider = Provider::Tpm; -///use parsec_client::core::Opcode; -///let provider_opcodes = client -/// .list_provider_operations(desired_provider) -/// .expect("Failed to list opcodes"); -///// Each operation is identified by a specific `Opcode` -///assert!(provider_opcodes.contains(&Opcode::PsaGenerateKey)); -///assert!(provider_opcodes.contains(&Opcode::PsaSignHash)); -///assert!(provider_opcodes.contains(&Opcode::PsaDestroyKey)); -///``` -/// -/// Creating a key-pair for signing SHA256 digests with RSA PKCS#1 v1.5: -///```no_run -///# use parsec_client::auth::AuthenticationData; -///# use parsec_client::CoreClient; -///# let client: CoreClient = CoreClient::new(AuthenticationData::AppIdentity(String::from("app-name"))); -///# use parsec_client::core::Provider; -///# let desired_provider = Provider::Tpm; -///use parsec_client::core::psa_algorithm::{Algorithm, AsymmetricSignature, Hash}; -///use parsec_client::core::psa_key_attributes::{KeyAttributes, KeyPolicy, KeyType, UsageFlags}; -/// -///let key_name = String::from("rusty key 🔑"); -///// This algorithm identifier will be used within the key policy (i.e. what -///// algorithms are usable with the key) and for indicating the desired -///// algorithm for each operation involving the key. -///let asym_sign_algo = AsymmetricSignature::RsaPkcs1v15Sign { -/// hash_alg: Hash::Sha256, -///}; -/// -///// The key attributes define and limit the usage of the key material stored -///// by the underlying cryptographic provider. -///let key_attrs = KeyAttributes { -/// key_type: KeyType::RsaKeyPair, -/// key_bits: 2048, -/// key_policy: KeyPolicy { -/// key_usage_flags: UsageFlags { -/// export: true, -/// copy: true, -/// cache: true, -/// encrypt: false, -/// decrypt: false, -/// sign_message: true, -/// verify_message: false, -/// sign_hash: true, -/// verify_hash: false, -/// derive: false, -/// }, -/// key_algorithm: asym_sign_algo.into(), -/// }, -///}; -/// -///client -/// .psa_generate_key(desired_provider, key_name, key_attrs) -/// .expect("Failed to create key!"); -///``` -/// -/// It is recommended that before attempting to use cryptographic -/// operations users call [`list_providers`](#method.list_providers) -/// and [`list_provider_operations`](#method.list_provider_operations) -/// in order to figure out if their desired use case and provider are -/// available. -#[derive(Debug)] -pub struct CoreClient { - op_handler: OperationHandler, - auth_data: AuthenticationData, -} - -/// Main client functionality. -impl CoreClient { - /// Create a new Parsec client given the authentication data of the app. - pub fn new(auth_data: AuthenticationData) -> Self { - CoreClient { - op_handler: Default::default(), - auth_data, - } - } - - /// Update the authentication data of the client. - pub fn set_auth_data(&mut self, auth_data: AuthenticationData) { - self.auth_data = auth_data; - } - - /// List the opcodes supported by the specified provider. - pub fn list_provider_operations(&self, provider: Provider) -> Result> { - let res = self.op_handler.process_operation( - NativeOperation::ListOpcodes(ListOpcodes {}), - provider.id(), - &self.auth_data, - )?; - - if let NativeResult::ListOpcodes(res) = res { - Ok(res.opcodes) - } else { - // Should really not be reached given the checks we do, but it's not impossible if some - // changes happen in the interface - Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) - } - } - - /// List the providers that are supported by the service. - pub fn list_providers(&self) -> Result> { - let res = self.op_handler.process_operation( - NativeOperation::ListProviders(ListProviders {}), - ProviderID::Core, - &self.auth_data, - )?; - - if let NativeResult::ListProviders(res) = res { - Ok(res.providers) - } else { - // Should really not be reached given the checks we do, but it's not impossible if some - // changes happen in the interface - Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) - } - } - - /// Send a ping request to the service. - /// - /// This operation is intended for testing connectivity to the - /// service and for retrieving the maximum wire protocol version - /// it supports. - pub fn ping(&self) -> Result<(u8, u8)> { - let res = self.op_handler.process_operation( - NativeOperation::Ping(Ping {}), - Provider::Core.id(), - &AuthenticationData::None, - )?; - - if let NativeResult::Ping(res) = res { - Ok((res.wire_protocol_version_maj, res.wire_protocol_version_min)) - } else { - // Should really not be reached given the checks we do, but it's not impossible if some - // changes happen in the interface - Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) - } - } - - /// Generate a key. - /// - /// Creates a new key with the given name within the namespace of the - /// desired provider. Any UTF-8 string is considered a valid key name, - /// however names must be unique per provider. - /// - /// If this method returns an error, no key will have been generated and - /// the name used will still be available for another key. - /// - /// Persistence of keys is implemented at provider level, and currently all - /// providers persist all the keys users create. However, no methods exist - /// for discovering previously generated or imported keys, so users are - /// responsible for keeping track of keys they have created. - /// - /// See the operation-specific response codes returned by the service - /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_generate_key.html#specific-response-status-codes). - pub fn psa_generate_key( - &self, - provider: Provider, - key_name: String, - key_attributes: KeyAttributes, - ) -> Result<()> { - let op = PsaGenerateKey { - key_name, - attributes: key_attributes, - }; - - let _ = self.op_handler.process_operation( - NativeOperation::PsaGenerateKey(op), - provider.id(), - &self.auth_data, - )?; - - Ok(()) - } - - /// Destroy a key. - /// - /// Given that keys are namespaced at a provider level, it is - /// important to call `psa_destroy_key` on the correct combination of - /// `provider` and `key_name`. - /// - /// See the operation-specific response codes returned by the service - /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_destroy_key.html#specific-response-status-codes). - pub fn psa_destroy_key(&self, provider: Provider, key_name: String) -> Result<()> { - let op = PsaDestroyKey { key_name }; - - let _ = self.op_handler.process_operation( - NativeOperation::PsaDestroyKey(op), - provider.id(), - &self.auth_data, - )?; - - Ok(()) - } - - /// Import a key. - /// - /// Creates a new key with the given name within the namespace of the - /// desired provider using the user-provided data. Any UTF-8 string is - /// considered a valid key name, however names must be unique per provider. - /// - /// The key material should follow the appropriate binary format expressed - /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_export_public_key.html). - /// Several crates (e.g. [`picky-asn1`](https://crates.io/crates/picky-asn1)) - /// can greatly help in dealing with binary encodings. - /// - /// If this method returns an error, no key will have been imported and the - /// name used will still be available for another key. - /// - /// Persistence of keys is implemented at provider level, and currently all - /// providers persist all the keys users create. However, no methods exist - /// for discovering previously generated or imported keys, so users are - /// responsible for keeping track of keys they have created. - /// - /// See the operation-specific response codes returned by the service - /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_import_key.html#specific-response-status-codes). - pub fn psa_import_key( - &self, - provider: Provider, - key_name: String, - key_material: Vec, - key_attributes: KeyAttributes, - ) -> Result<()> { - let op = PsaImportKey { - key_name, - attributes: key_attributes, - data: key_material, - }; - - let _ = self.op_handler.process_operation( - NativeOperation::PsaImportKey(op), - provider.id(), - &self.auth_data, - )?; - - Ok(()) - } - - /// Export a public key or the public part of a key pair. - /// - /// The returned key material will follow the appropriate binary format expressed - /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_export_public_key.html). - /// Several crates (e.g. [`picky-asn1`](https://crates.io/crates/picky-asn1)) - /// can greatly help in dealing with binary encodings. - /// - /// In order to export a public key, the export flag found in the - /// [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.KeyPolicy.html) - /// **must** be `true`. - /// - /// See the operation-specific response codes returned by the service - /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_export_public_key.html#specific-response-status-codes). - pub fn psa_export_public_key(&self, provider: Provider, key_name: String) -> Result> { - let op = PsaExportPublicKey { key_name }; - - let res = self.op_handler.process_operation( - NativeOperation::PsaExportPublicKey(op), - provider.id(), - &self.auth_data, - )?; - - if let NativeResult::PsaExportPublicKey(res) = res { - Ok(res.data) - } else { - // Should really not be reached given the checks we do, but it's not impossible if some - // changes happen in the interface - Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) - } - } - - /// Create an asymmetric signature on a pre-computed message digest. - /// - /// The key intended for signing **must** have its `sign_hash` flag set - /// to `true` in its [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.KeyPolicy.html). - /// - /// The signature will be created with the algorithm defined in - /// `sign_algorithm`, but only after checking that the key policy - /// and type conform with it. - /// - /// `hash` must be a hash pre-computed over the message of interest - /// with the algorithm specified within `sign_algorithm`. - /// - /// See the operation-specific response codes returned by the service - /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_sign_hash.html#specific-response-status-codes). - pub fn psa_sign_hash( - &self, - provider: Provider, - key_name: String, - hash: Vec, - sign_algorithm: AsymmetricSignature, - ) -> Result> { - let op = PsaSignHash { - key_name, - alg: sign_algorithm, - hash, - }; - - let res = self.op_handler.process_operation( - NativeOperation::PsaSignHash(op), - provider.id(), - &self.auth_data, - )?; - - if let NativeResult::PsaSignHash(res) = res { - Ok(res.signature) - } else { - // Should really not be reached given the checks we do, but it's not impossible if some - // changes happen in the interface - Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) - } - } - - /// Verify an existing asymmetric signature over a pre-computed message digest. - /// - /// The key intended for signing **must** have its `verify_hash` flag set - /// to `true` in its [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.KeyPolicy.html). - /// - /// The signature will be verifyied with the algorithm defined in - /// `sign_algorithm`, but only after checking that the key policy - /// and type conform with it. - /// - /// `hash` must be a hash pre-computed over the message of interest - /// with the algorithm specified within `sign_algorithm`. - /// - /// See the operation-specific response codes returned by the service - /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_verify_hash.html#specific-response-status-codes). - pub fn psa_verify_hash( - &self, - provider: Provider, - key_name: String, - hash: Vec, - sign_algorithm: AsymmetricSignature, - signature: Vec, - ) -> Result<()> { - let op = PsaVerifyHash { - key_name, - alg: sign_algorithm, - hash, - signature, - }; - - let _ = self.op_handler.process_operation( - NativeOperation::PsaVerifyHash(op), - provider.id(), - &self.auth_data, - )?; - - Ok(()) - } -} +pub use parsec_interface::operations_protobuf::ProtobufConverter; +pub use parsec_interface::requests::{Opcode, ProviderID}; diff --git a/src/core/operation_handler.rs b/src/core/operation_client.rs similarity index 55% rename from src/core/operation_handler.rs rename to src/core/operation_client.rs index 03d2cce..8b4007b 100644 --- a/src/core/operation_handler.rs +++ b/src/core/operation_client.rs @@ -1,37 +1,45 @@ // Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 +//! Operation-level client #![allow(dead_code)] -use super::request_handler::RequestHandler; +use super::request_client::RequestClient; use crate::auth::AuthenticationData; use crate::error::{ClientErrorKind, Error, Result}; use derivative::Derivative; use parsec_interface::operations::{Convert, NativeOperation, NativeResult}; use parsec_interface::operations_protobuf::ProtobufConverter; use parsec_interface::requests::{ - request::RequestHeader, BodyType, Opcode, ProviderID, Request, Response, ResponseStatus, + request::RequestHeader, Opcode, ProviderID, Request, Response, ResponseStatus, }; -/// OperationHandler structure to send a `NativeOperation` and get a `NativeResult`. +/// Low-level client optimised for communicating with the Parsec service at an operation level. +/// +/// Usage is recommended when fine control over how operations are wrapped and processed is needed. #[derive(Derivative)] #[derivative(Debug)] -pub struct OperationHandler { +pub struct OperationClient { + /// Converter that manages request body conversions + /// + /// Defaults to a Protobuf converter + #[derivative(Debug = "ignore")] + pub content_converter: Box, + /// Converter that manages response body conversions + /// + /// Defaults to a Protobuf converter #[derivative(Debug = "ignore")] - pub converter: Box, - pub wire_protocol_version_maj: u8, - pub wire_protocol_version_min: u8, - pub content_type: BodyType, - pub accept_type: BodyType, - #[cfg_attr(test, derivative(Debug = "ignore"))] - pub request_handler: RequestHandler, + pub accept_converter: Box, + /// Client for request and response objects + pub request_client: RequestClient, } #[allow(clippy::new_without_default)] -impl OperationHandler { - /// Creates an OperationHandler instance. The request handler uses a timeout of 5 seconds on reads - /// and writes on the socket. It uses the version 1.0 to form request, the direct - /// authentication method and protobuf format as content type. - pub fn new() -> OperationHandler { +impl OperationClient { + /// Creates an OperationClient instance. The request client uses a timeout of 5 + /// seconds on reads and writes on the socket. It uses the version 1.0 wire protocol + /// to form requests, the direct authentication method and protobuf format as + /// content type. + pub fn new() -> OperationClient { Default::default() } @@ -43,16 +51,14 @@ impl OperationHandler { ) -> Result { let opcode = operation.opcode(); let body = self - .converter + .content_converter .operation_to_body(operation) .map_err(ClientErrorKind::Interface)?; let header = RequestHeader { - version_maj: self.wire_protocol_version_maj, - version_min: self.wire_protocol_version_min, provider, session: 0, // no provisioning of sessions yet - content_type: self.content_type, - accept_type: self.accept_type, + content_type: self.content_converter.body_type(), + accept_type: self.accept_converter.body_type(), auth_type: auth.auth_type(), opcode, }; @@ -78,7 +84,7 @@ impl OperationHandler { return Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)); } Ok(self - .converter + .accept_converter .body_to_result(response.body, opcode) .map_err(ClientErrorKind::Interface)?) } @@ -99,42 +105,34 @@ impl OperationHandler { let req_opcode = operation.opcode(); let request = self.operation_to_request(operation, provider, auth)?; - let response = self.request_handler.process_request(request)?; + let response = self.request_client.process_request(request)?; self.response_to_result(response, req_opcode) } } -impl Default for OperationHandler { +impl Default for OperationClient { fn default() -> Self { - OperationHandler { - converter: Box::from(ProtobufConverter {}), - wire_protocol_version_maj: 1, - wire_protocol_version_min: 0, - content_type: BodyType::Protobuf, - accept_type: BodyType::Protobuf, - request_handler: Default::default(), + OperationClient { + content_converter: Box::from(ProtobufConverter {}), + accept_converter: Box::from(ProtobufConverter {}), + request_client: Default::default(), } } } /// Configuration methods for controlling communication with the service. -impl crate::CoreClient { - /// Set the content type for requests and responses handled by this client. +impl crate::BasicClient { + /// Set the converter used for request bodies handled by this client. /// /// By default Protobuf will be used for this. - pub fn set_request_content_type(&mut self, content_type: BodyType) { - self.op_handler.content_type = content_type; - self.op_handler.accept_type = content_type; - match content_type { - BodyType::Protobuf => self.op_handler.converter = Box::from(ProtobufConverter {}), - } + pub fn set_request_body_converter(&mut self, content_converter: Box) { + self.op_client.content_converter = content_converter; } - /// Set the wire protocol version numbers to be used by the client. + /// Set the converter used for response bodies handled by this client. /// - /// Default version number is 1.0. - pub fn set_wire_protocol_version(&mut self, version_maj: u8, version_min: u8) { - self.op_handler.wire_protocol_version_maj = version_maj; - self.op_handler.wire_protocol_version_min = version_min; + /// By default Protobuf will be used for this. + pub fn set_response_body_converter(&mut self, accept_converter: Box) { + self.op_client.accept_converter = accept_converter; } } diff --git a/src/core/request_handler.rs b/src/core/request_client.rs similarity index 52% rename from src/core/request_handler.rs rename to src/core/request_client.rs index 2c0d3ab..3f0bf77 100644 --- a/src/core/request_handler.rs +++ b/src/core/request_client.rs @@ -1,26 +1,37 @@ // Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 -use super::ipc_client::{unix_socket, Connect}; +//! Request-level client +use super::ipc_handler::{unix_socket, Connect}; use crate::error::{ClientErrorKind, Result}; use derivative::Derivative; use parsec_interface::requests::{Request, Response}; const DEFAULT_MAX_BODY_SIZE: usize = usize::max_value(); -/// Low level client structure to send a `Request` and get a `Response`. +/// Low level client structure optimised for communicating with the service +/// at a request level of abstraction. +/// +/// Usage is recommended when fine control over the request header and IPC handler +/// is needed. #[derive(Derivative)] #[derivative(Debug)] -pub struct RequestHandler { +pub struct RequestClient { + /// Max size for response bodies + /// + /// Defaults to the max value of `usize` on the current platform pub max_body_size: usize, + /// Handler for IPC-related functionality + /// + /// Defaults to using Unix domain sockets #[derivative(Debug = "ignore")] - pub ipc_client: Box, + pub ipc_handler: Box, } -impl RequestHandler { +impl RequestClient { /// Send a request and get a response. pub fn process_request(&self, request: Request) -> Result { // Try to connect once, wait for a timeout until trying again. - let mut stream = self.ipc_client.connect()?; + let mut stream = self.ipc_handler.connect()?; request .write_to_stream(&mut stream) @@ -30,28 +41,28 @@ impl RequestHandler { } } -impl Default for RequestHandler { +impl Default for RequestClient { fn default() -> Self { - RequestHandler { + RequestClient { max_body_size: DEFAULT_MAX_BODY_SIZE, - ipc_client: Box::from(unix_socket::Client::default()), + ipc_handler: Box::from(unix_socket::Handler::default()), } } } /// Configuration methods for controlling IPC-level options. -impl crate::CoreClient { +impl crate::BasicClient { /// Set the maximum body size allowed for requests. /// /// Defaults to the maximum value of `usize`. pub fn set_max_body_size(&mut self, max_body_size: usize) { - self.op_handler.request_handler.max_body_size = max_body_size; + self.op_client.request_client.max_body_size = max_body_size; } /// Set the IPC handler used for communication with the service. /// - /// By default the [Unix domain socket client](../ipc_client/unix_socket/struct.Client.html) is used. - pub fn set_ipc_client(&mut self, ipc_client: Box) { - self.op_handler.request_handler.ipc_client = ipc_client; + /// By default the [Unix domain socket client](../ipc_handler/unix_socket/struct.Client.html) is used. + pub fn set_ipc_handler(&mut self, ipc_handler: Box) { + self.op_client.request_client.ipc_handler = ipc_handler; } } diff --git a/src/core/testing/core_tests.rs b/src/core/testing/core_tests.rs index c2833fe..7da3b00 100644 --- a/src/core/testing/core_tests.rs +++ b/src/core/testing/core_tests.rs @@ -1,7 +1,7 @@ // Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 -use super::{FailingMockIpc, TestCoreClient, DEFAULT_APP_NAME}; -use crate::core::Provider; +use super::{FailingMockIpc, TestBasicClient, DEFAULT_APP_NAME}; +use crate::core::ProviderID; use crate::error::{ClientErrorKind, Error}; use mockstream::{FailingMockStream, MockStream}; use parsec_interface::operations; @@ -14,14 +14,12 @@ use parsec_interface::operations_protobuf::ProtobufConverter; use parsec_interface::requests::Response; use parsec_interface::requests::ResponseStatus; use parsec_interface::requests::{request::RequestHeader, Request}; -use parsec_interface::requests::{AuthType, BodyType, Opcode, ProviderID}; +use parsec_interface::requests::{AuthType, BodyType, Opcode}; use std::collections::HashSet; use std::io::ErrorKind; const PROTOBUF_CONVERTER: ProtobufConverter = ProtobufConverter {}; const REQ_HEADER: RequestHeader = RequestHeader { - version_maj: 1, - version_min: 0, provider: ProviderID::Core, session: 0, content_type: BodyType::Protobuf, @@ -55,7 +53,7 @@ fn get_operation_from_req_bytes(bytes: Vec) -> NativeOperation { #[test] fn ping_test() { - let mut client: TestCoreClient = Default::default(); + let mut client: TestBasicClient = Default::default(); client.set_mock_read(&get_response_bytes_from_result(NativeResult::Ping( operations::ping::Result { wire_protocol_version_maj: 1, @@ -71,7 +69,7 @@ fn ping_test() { #[test] fn list_provider_test() { - let mut client: TestCoreClient = Default::default(); + let mut client: TestBasicClient = Default::default(); let mut provider_info = Vec::new(); provider_info.push(ProviderInfo { uuid: uuid::Uuid::nil(), @@ -98,7 +96,7 @@ fn list_provider_test() { #[test] fn list_provider_operations_test() { - let mut client: TestCoreClient = Default::default(); + let mut client: TestBasicClient = Default::default(); let mut opcodes = HashSet::new(); let _ = opcodes.insert(Opcode::PsaDestroyKey); let _ = opcodes.insert(Opcode::PsaGenerateKey); @@ -106,7 +104,7 @@ fn list_provider_operations_test() { operations::list_opcodes::Result { opcodes }, ))); let opcodes = client - .list_provider_operations(Provider::MbedCrypto) + .list_provider_operations(ProviderID::MbedCrypto) .expect("Failed to retrieve opcodes"); // Check request: // ListOpcodes request is empty so no checking to be done @@ -118,7 +116,7 @@ fn list_provider_operations_test() { #[test] fn psa_generate_key_test() { - let mut client: TestCoreClient = Default::default(); + let mut client: TestBasicClient = Default::default(); client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaGenerateKey(operations::psa_generate_key::Result {}), )); @@ -144,7 +142,7 @@ fn psa_generate_key_test() { }; client - .psa_generate_key(Provider::Tpm, key_name.clone(), key_attrs.clone()) + .psa_generate_key(key_name.clone(), key_attrs.clone()) .expect("failed to generate key"); // Check request: @@ -162,13 +160,13 @@ fn psa_generate_key_test() { #[test] fn psa_destroy_key_test() { - let mut client: TestCoreClient = Default::default(); + let mut client: TestBasicClient = Default::default(); client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaDestroyKey(operations::psa_destroy_key::Result {}), )); let key_name = String::from("key-name"); client - .psa_destroy_key(Provider::Pkcs11, key_name.clone()) + .psa_destroy_key(key_name.clone()) .expect("Failed to call destroy key"); // Check request: @@ -185,7 +183,7 @@ fn psa_destroy_key_test() { #[test] fn psa_import_key_test() { - let mut client: TestCoreClient = Default::default(); + let mut client: TestBasicClient = Default::default(); client.set_mock_read(&get_response_bytes_from_result(NativeResult::PsaImportKey( operations::psa_import_key::Result {}, ))); @@ -211,12 +209,7 @@ fn psa_import_key_test() { }; let key_data = vec![0xff_u8; 128]; client - .psa_import_key( - Provider::Pkcs11, - key_name.clone(), - key_data.clone(), - key_attrs.clone(), - ) + .psa_import_key(key_name.clone(), key_data.clone(), key_attrs.clone()) .unwrap(); // Check request: @@ -235,7 +228,7 @@ fn psa_import_key_test() { #[test] fn psa_export_public_key_test() { - let mut client: TestCoreClient = Default::default(); + let mut client: TestBasicClient = Default::default(); let key_data = vec![0xa5; 128]; client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaExportPublicKey(operations::psa_export_public_key::Result { @@ -247,7 +240,7 @@ fn psa_export_public_key_test() { // Check response: assert_eq!( client - .psa_export_public_key(Provider::MbedCrypto, key_name.clone()) + .psa_export_public_key(key_name.clone()) .expect("Failed to export public key"), key_data ); @@ -263,7 +256,7 @@ fn psa_export_public_key_test() { #[test] fn psa_sign_hash_test() { - let mut client: TestCoreClient = Default::default(); + let mut client: TestBasicClient = Default::default(); let hash = vec![0x77_u8; 32]; let key_name = String::from("key_name"); let sign_algorithm = AsymmetricSignature::Ecdsa { @@ -279,12 +272,7 @@ fn psa_sign_hash_test() { // Check response: assert_eq!( client - .psa_sign_hash( - Provider::MbedCrypto, - key_name.clone(), - hash.clone(), - sign_algorithm.clone() - ) + .psa_sign_hash(key_name.clone(), hash.clone(), sign_algorithm.clone()) .expect("Failed to sign hash"), signature ); @@ -302,7 +290,7 @@ fn psa_sign_hash_test() { #[test] fn verify_hash_test() { - let mut client: TestCoreClient = Default::default(); + let mut client: TestBasicClient = Default::default(); let hash = vec![0x77_u8; 32]; let key_name = String::from("key_name"); let sign_algorithm = AsymmetricSignature::Ecdsa { @@ -315,7 +303,6 @@ fn verify_hash_test() { client .psa_verify_hash( - Provider::MbedCrypto, key_name.clone(), hash.clone(), sign_algorithm.clone(), @@ -340,13 +327,13 @@ fn verify_hash_test() { #[test] fn different_response_type_test() { - let mut client: TestCoreClient = Default::default(); + let mut client: TestBasicClient = Default::default(); client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaVerifyHash(operations::psa_verify_hash::Result {}), )); let key_name = String::from("key-name"); let err = client - .psa_destroy_key(Provider::Pkcs11, key_name) + .psa_destroy_key(key_name) .expect_err("Error was expected"); assert_eq!( @@ -357,7 +344,7 @@ fn different_response_type_test() { #[test] fn response_status_test() { - let mut client: TestCoreClient = Default::default(); + let mut client: TestBasicClient = Default::default(); let mut stream = MockStream::new(); let status = ResponseStatus::PsaErrorDataCorrupt; let mut resp = Response::from_request_header(REQ_HEADER, ResponseStatus::Success); @@ -371,7 +358,7 @@ fn response_status_test() { #[test] fn malformed_response_test() { - let mut client: TestCoreClient = Default::default(); + let mut client: TestBasicClient = Default::default(); client.set_mock_read(&[0xcb_u8; 130]); let err = client.ping().expect_err("Error was expected"); @@ -383,7 +370,7 @@ fn malformed_response_test() { #[test] fn request_fields_test() { - let mut client: TestCoreClient = Default::default(); + let mut client: TestBasicClient = Default::default(); client.set_mock_read(&get_response_bytes_from_result(NativeResult::Ping( operations::ping::Result { wire_protocol_version_maj: 1, @@ -398,13 +385,13 @@ fn request_fields_test() { #[test] fn auth_value_test() { - let mut client: TestCoreClient = Default::default(); + let mut client: TestBasicClient = Default::default(); client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaDestroyKey(operations::psa_destroy_key::Result {}), )); let key_name = String::from("key-name"); client - .psa_destroy_key(Provider::Pkcs11, key_name) + .psa_destroy_key(key_name) .expect("Failed to call destroy key"); let req = get_req_from_bytes(client.get_mock_write()); @@ -416,8 +403,8 @@ fn auth_value_test() { #[test] fn failing_ipc_test() { - let mut client: TestCoreClient = Default::default(); - client.set_ipc_client(Box::from(FailingMockIpc(FailingMockStream::new( + let mut client: TestBasicClient = Default::default(); + client.set_ipc_handler(Box::from(FailingMockIpc(FailingMockStream::new( ErrorKind::ConnectionRefused, "connection was refused, so rude", 1, @@ -429,11 +416,3 @@ fn failing_ipc_test() { Error::Client(ClientErrorKind::Interface(ResponseStatus::ConnectionError)) ); } - -#[test] -fn provider_unwrap() { - let _ = Provider::Core.uuid(); - let _ = Provider::Tpm.uuid(); - let _ = Provider::Pkcs11.uuid(); - let _ = Provider::MbedCrypto.uuid(); -} diff --git a/src/core/testing/mod.rs b/src/core/testing/mod.rs index 7ab2d7b..4e6620a 100644 --- a/src/core/testing/mod.rs +++ b/src/core/testing/mod.rs @@ -1,11 +1,12 @@ // Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 #![cfg(test)] -use super::ipc_client::{Connect, ReadWrite}; -use super::CoreClient; +use super::basic_client::BasicClient; +use super::ipc_handler::{Connect, ReadWrite}; use crate::auth::AuthenticationData; use crate::error::Result; use mockstream::{FailingMockStream, SyncMockStream}; +use parsec_interface::requests::ProviderID; use std::ops::{Deref, DerefMut}; mod core_tests; @@ -28,12 +29,12 @@ impl Connect for FailingMockIpc { } } -struct TestCoreClient { - core_client: CoreClient, +struct TestBasicClient { + core_client: BasicClient, mock_stream: SyncMockStream, } -impl TestCoreClient { +impl TestBasicClient { pub fn set_mock_read(&mut self, bytes: &[u8]) { self.mock_stream.push_bytes_to_read(bytes); } @@ -43,32 +44,33 @@ impl TestCoreClient { } } -impl Deref for TestCoreClient { - type Target = CoreClient; +impl Deref for TestBasicClient { + type Target = BasicClient; fn deref(&self) -> &Self::Target { &self.core_client } } -impl DerefMut for TestCoreClient { +impl DerefMut for TestBasicClient { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.core_client } } -impl Default for TestCoreClient { +impl Default for TestBasicClient { fn default() -> Self { - let mut client = TestCoreClient { - core_client: CoreClient::new(AuthenticationData::AppIdentity(String::from( - DEFAULT_APP_NAME, - ))), + let mut client = TestBasicClient { + core_client: BasicClient::new( + AuthenticationData::AppIdentity(String::from(DEFAULT_APP_NAME)), + ProviderID::Pkcs11, + ), mock_stream: SyncMockStream::new(), }; client .core_client - .set_ipc_client(Box::from(MockIpc(client.mock_stream.clone()))); + .set_ipc_handler(Box::from(MockIpc(client.mock_stream.clone()))); client } } diff --git a/src/error.rs b/src/error.rs index 0589c80..00802e3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,6 +21,8 @@ pub enum ClientErrorKind { Ipc(::std::io::Error), /// The opcode of the response does not match the opcode of the request InvalidServiceResponseType, + /// The operation is not supported by the selected provider + InvalidProvider, } impl From for Error { @@ -53,6 +55,13 @@ impl PartialEq for ClientErrorKind { false } } + ClientErrorKind::InvalidProvider => { + if let ClientErrorKind::InvalidProvider = other { + true + } else { + false + } + } } } } diff --git a/src/lib.rs b/src/lib.rs index 6bc9c4a..e0ab8ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,12 +33,12 @@ #![allow(clippy::multiple_crate_versions)] //! Currently this crate allows interaction with the PARSEC service through -//! [`CoreClient`](core/struct.CoreClient.html), a low-level client that allows all supported operations to -//! be performed, requiring all operation parameters to be provided explicitly. +//! [`BasicClient`](core/basic_client/struct.BasicClient.html), a low-level client that allows +//! all supported operations to be performed, requiring all operation parameters +//! to be provided explicitly. pub mod auth; pub mod core; pub mod error; -pub use crate::core::ipc_client; -pub use crate::core::CoreClient; +pub use crate::core::basic_client::BasicClient;