From 2b6e1d218f4734bfe7a03ae227b2fdf259d0f7c5 Mon Sep 17 00:00:00 2001 From: Samuel Bailey Date: Thu, 2 Jul 2020 12:44:57 +0100 Subject: [PATCH] Added asymmetric encrypt and decrypt Signed-off-by: Samuel Bailey --- Cargo.toml | 2 +- src/core/basic_client.rs | 116 ++++++++++++++++++++++++++++++++- src/core/testing/core_tests.rs | 66 +++++++++++++++++++ 3 files changed, 181 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7f462d6..0d18a4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ edition = "2018" documentation = "https://docs.rs/crate/parsec-client" [dependencies] -parsec-interface = "0.17.1" +parsec-interface = "0.18.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 index 0ebd90d..fb5efaa 100644 --- a/src/core/basic_client.rs +++ b/src/core/basic_client.rs @@ -7,7 +7,9 @@ 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_algorithm::{AsymmetricEncryption, AsymmetricSignature}; +use parsec_interface::operations::psa_asymmetric_decrypt::Operation as PsaAsymDecrypt; +use parsec_interface::operations::psa_asymmetric_encrypt::Operation as PsaAsymEncrypt; 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; @@ -229,7 +231,6 @@ impl BasicClient { ProviderID::Core, &self.auth_data, )?; - if let NativeResult::ListProviders(res) = res { Ok(res.providers) } else { @@ -522,6 +523,117 @@ impl BasicClient { Ok(()) } + /// **[Cryptographic Operation]** Encrypt a short message. + /// + /// The key intended for encrypting **must** have its `encrypt` flag set + /// to `true` in its [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.Policy.html). + /// + /// The encryption will be performed with the algorithm defined in `alg`, + /// but only after checking that the key policy and type conform with it. + /// + /// `salt` can be provided if supported by the algorithm. If the algorithm does not support salt, pass + // an empty vector. If the algorithm supports optional salt, pass an empty vector to indicate no + // salt. For RSA PKCS#1 v1.5 encryption, no salt is supported. + /// + /// # Errors + /// + /// If the implicit client provider is `ProviderID::Core`, a client error + /// of `InvalidProvider` type is returned. + /// + /// If the implicit client provider has not been set, a client error of + /// `NoProvider` type is returned. + /// + /// See the operation-specific response codes returned by the service + /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_asymmetric_encrypt.html#specific-response-status-codes). + pub fn psa_asymmetric_encrypt( + &self, + key_name: String, + encrypt_alg: AsymmetricEncryption, + plaintext: &[u8], + salt: Option<&[u8]>, + ) -> Result> { + let salt = salt.map(|salt_ref| salt_ref.to_vec().into()); + let crypto_provider = self.can_provide_crypto()?; + + let op = PsaAsymEncrypt { + key_name, + alg: encrypt_alg, + plaintext: plaintext.to_vec().into(), + salt, + }; + + let encrypt_res = self.op_client.process_operation( + NativeOperation::PsaAsymmetricEncrypt(op), + crypto_provider, + &self.auth_data, + )?; + + if let NativeResult::PsaAsymmetricEncrypt(res) = encrypt_res { + Ok(res.ciphertext.to_vec()) + } 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]** Decrypt a short message. + /// + /// The key intended for decrypting **must** have its `decrypt` flag set + /// to `true` in its [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.Policy.html). + /// + /// The decryption will be performed with the algorithm defined in `alg`, + /// but only after checking that the key policy and type conform with it. + /// + /// `salt` can be provided if supported by the algorithm. If the algorithm does not support salt, pass + // an empty vector. If the algorithm supports optional salt, pass an empty vector to indicate no + // salt. For RSA PKCS#1 v1.5 encryption, no salt is supported. + /// + /// # Errors + /// + /// If the implicit client provider is `ProviderID::Core`, a client error + /// of `InvalidProvider` type is returned. + /// + /// If the implicit client provider has not been set, a client error of + /// `NoProvider` type is returned. + /// + /// See the operation-specific response codes returned by the service + /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_asymmetric_decrypt.html#specific-response-status-codes). + pub fn psa_asymmetric_decrypt( + &self, + key_name: String, + encrypt_alg: AsymmetricEncryption, + ciphertext: &[u8], + salt: Option<&[u8]>, + ) -> Result> { + let salt = match salt { + Some(salt) => Some(Zeroizing::new(salt.to_vec())), + None => None, + }; + let crypto_provider = self.can_provide_crypto()?; + + let op = PsaAsymDecrypt { + key_name, + alg: encrypt_alg, + ciphertext: Zeroizing::new(ciphertext.to_vec()), + salt, + }; + + let decrypt_res = self.op_client.process_operation( + NativeOperation::PsaAsymmetricDecrypt(op), + crypto_provider, + &self.auth_data, + )?; + + if let NativeResult::PsaAsymmetricDecrypt(res) = decrypt_res { + Ok(res.plaintext.to_vec()) + } 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)) + } + } + fn can_provide_crypto(&self) -> Result { match self.implicit_provider { None => Err(Error::Client(ClientErrorKind::NoProvider)), diff --git a/src/core/testing/core_tests.rs b/src/core/testing/core_tests.rs index da22922..4b486df 100644 --- a/src/core/testing/core_tests.rs +++ b/src/core/testing/core_tests.rs @@ -352,6 +352,72 @@ fn verify_hash_test() { // VerifyHash response is empty so no checking to be done } +#[test] +fn asymmetric_encrypt_test() { + let mut client: TestBasicClient = Default::default(); + let plaintext = vec![0x77_u8; 32]; + let key_name = String::from("key_name"); + let encrypt_algorithm = AsymmetricEncryption::RsaPkcs1v15Crypt; + let ciphertext = vec![0x33_u8; 128]; + client.set_mock_read(&get_response_bytes_from_result( + NativeResult::PsaAsymmetricEncrypt(operations::psa_asymmetric_encrypt::Result { + ciphertext: ciphertext.clone().into(), + }), + )); + + // Check response: + assert_eq!( + client + .psa_asymmetric_encrypt(key_name.clone(), encrypt_algorithm, &plaintext, None) + .expect("Failed to encrypt message"), + ciphertext + ); + + // Check request: + let op = get_operation_from_req_bytes(client.get_mock_write()); + if let NativeOperation::PsaAsymmetricEncrypt(op) = op { + assert_eq!(op.key_name, key_name); + assert_eq!(op.alg, encrypt_algorithm); + assert_eq!(*op.plaintext, plaintext); + assert_eq!(op.salt, None); + } else { + panic!("Got wrong operation type: {:?}", op); + } +} + +#[test] +fn asymmetric_decrypt_test() { + let mut client: TestBasicClient = Default::default(); + let plaintext = vec![0x77_u8; 32]; + let key_name = String::from("key_name"); + let encrypt_algorithm = AsymmetricEncryption::RsaPkcs1v15Crypt; + let ciphertext = vec![0x33_u8; 128]; + client.set_mock_read(&get_response_bytes_from_result( + NativeResult::PsaAsymmetricDecrypt(operations::psa_asymmetric_decrypt::Result { + plaintext: plaintext.clone().into(), + }), + )); + + // Check response + assert_eq!( + client + .psa_asymmetric_decrypt(key_name.clone(), encrypt_algorithm, &ciphertext, None) + .expect("Failed to encrypt message"), + plaintext + ); + + // Check request: + let op = get_operation_from_req_bytes(client.get_mock_write()); + if let NativeOperation::PsaAsymmetricDecrypt(op) = op { + assert_eq!(op.key_name, key_name); + assert_eq!(op.alg, encrypt_algorithm); + assert_eq!(*op.ciphertext, ciphertext); + assert_eq!(op.salt, None); + } else { + panic!("Got wrong operation type: {:?}", op); + } +} + #[test] fn different_response_type_test() { let mut client: TestBasicClient = Default::default();