From bc47296acd5affe9735d9aa72952acfd6b471fc3 Mon Sep 17 00:00:00 2001 From: Samuel Bailey Date: Mon, 29 Jun 2020 17:48:14 +0100 Subject: [PATCH] Added asymmetric encrypt and decrypt Signed-off-by: Samuel Bailey --- Cargo.toml | 2 +- parsec-operations | 2 +- src/operations/mod.rs | 14 ++ src/operations/psa_asymmetric_decrypt.rs | 168 ++++++++++++++ src/operations/psa_asymmetric_encrypt.rs | 163 ++++++++++++++ .../convert_psa_asymmetric_decrypt.rs | 206 ++++++++++++++++++ .../convert_psa_asymmetric_encrypt.rs | 205 +++++++++++++++++ src/operations_protobuf/generated_ops.rs | 18 ++ src/operations_protobuf/mod.rs | 28 +++ src/requests/mod.rs | 4 + 10 files changed, 808 insertions(+), 2 deletions(-) create mode 100644 src/operations/psa_asymmetric_decrypt.rs create mode 100644 src/operations/psa_asymmetric_encrypt.rs create mode 100644 src/operations_protobuf/convert_psa_asymmetric_decrypt.rs create mode 100644 src/operations_protobuf/convert_psa_asymmetric_encrypt.rs diff --git a/Cargo.toml b/Cargo.toml index fc41b8e..72e7397 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ prost = "0.6.1" arbitrary = { version = "0.4.4", features = ["derive"] } uuid = "0.7.4" log = "0.4.8" -psa-crypto = { version = "0.2.0", default-features = false } +psa-crypto = { version = "0.2.2", default-features = false } zeroize = { version = "1.1.0", features = ["zeroize_derive"] } secrecy = { version = "0.6.0", features = ["serde"] } derivative = "2.1.1" diff --git a/parsec-operations b/parsec-operations index edf6aef..d89b451 160000 --- a/parsec-operations +++ b/parsec-operations @@ -1 +1 @@ -Subproject commit edf6aef1783ed578a15aefab3923d1262b0093d8 +Subproject commit d89b451b279115173db904bf2c1d4d3fd6ea1917 diff --git a/src/operations/mod.rs b/src/operations/mod.rs index 52288b8..43d405a 100644 --- a/src/operations/mod.rs +++ b/src/operations/mod.rs @@ -16,6 +16,8 @@ pub mod psa_export_public_key; pub mod psa_destroy_key; pub mod psa_sign_hash; pub mod psa_verify_hash; +pub mod psa_asymmetric_encrypt; +pub mod psa_asymmetric_decrypt; pub mod list_opcodes; pub mod list_providers; @@ -46,6 +48,10 @@ pub enum NativeOperation { PsaSignHash(psa_sign_hash::Operation), /// PsaVerifyHash operation PsaVerifyHash(psa_verify_hash::Operation), + /// PsaAsymmetricEncrypt operation + PsaAsymmetricEncrypt(psa_asymmetric_encrypt::Operation), + /// PsaAsymmetricDecrypt operation + PsaAsymmetricDecrypt(psa_asymmetric_decrypt::Operation), } impl NativeOperation { @@ -61,6 +67,8 @@ impl NativeOperation { NativeOperation::PsaExportPublicKey(_) => Opcode::PsaExportPublicKey, NativeOperation::ListOpcodes(_) => Opcode::ListOpcodes, NativeOperation::ListProviders(_) => Opcode::ListProviders, + NativeOperation::PsaAsymmetricEncrypt(_) => Opcode::PsaAsymmetricEncrypt, + NativeOperation::PsaAsymmetricDecrypt(_) => Opcode::PsaAsymmetricDecrypt, } } } @@ -87,6 +95,10 @@ pub enum NativeResult { PsaSignHash(psa_sign_hash::Result), /// PsaVerifyHash result PsaVerifyHash(psa_verify_hash::Result), + /// PsaAsymmetricEncrypt result + PsaAsymmetricEncrypt(psa_asymmetric_encrypt::Result), + /// PsaAsymmetricDecrypt result + PsaAsymmetricDecrypt(psa_asymmetric_decrypt::Result), } impl NativeResult { @@ -102,6 +114,8 @@ impl NativeResult { NativeResult::PsaExportPublicKey(_) => Opcode::PsaExportPublicKey, NativeResult::ListOpcodes(_) => Opcode::ListOpcodes, NativeResult::ListProviders(_) => Opcode::ListProviders, + NativeResult::PsaAsymmetricEncrypt(_) => Opcode::PsaAsymmetricEncrypt, + NativeResult::PsaAsymmetricDecrypt(_) => Opcode::PsaAsymmetricDecrypt, } } } diff --git a/src/operations/psa_asymmetric_decrypt.rs b/src/operations/psa_asymmetric_decrypt.rs new file mode 100644 index 0000000..8a570cf --- /dev/null +++ b/src/operations/psa_asymmetric_decrypt.rs @@ -0,0 +1,168 @@ +// Copyright 2020 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 +//! # PsaAsymmetricDecrypt operation +//! +//! Decrypt a short message with a public key. + +use super::psa_key_attributes::Attributes; +use crate::operations::psa_algorithm::AsymmetricEncryption; +use crate::requests::ResponseStatus; +#[cfg(feature = "fuzz")] +use arbitrary::Arbitrary; +use derivative::Derivative; + +/// Native object for asymmetric decryption operations. +#[derive(Derivative)] +#[derivative(Debug)] +pub struct Operation { + /// Defines which key should be used for the signing operation. + pub key_name: String, + /// An asymmetric encryption algorithm to be used for decryption, that is compatible with the type of key. + pub alg: AsymmetricEncryption, + /// The short encrypted message to be decrypted. + #[derivative(Debug = "ignore")] + pub ciphertext: zeroize::Zeroizing>, + /// Salt to use during decryption, if supported by the algorithm. + #[derivative(Debug = "ignore")] + pub salt: Option>>, +} + +impl Operation { + /// Validate the contents of the operation against the attributes of the key it targets + /// + /// This method checks that: + /// * the key policy allows decrypting messages + /// * the key policy allows the decryption algorithm requested in the operation + /// * the key type is compatible with the requested algorithm + /// * if the algorithm is RsaPkcs1v15Crypt, it has no salt (it is not compatible with salt) + /// * the message to decrypt is valid (not length 0) + pub fn validate(&self, key_attributes: Attributes) -> crate::requests::Result<()> { + key_attributes.can_decrypt_message()?; + key_attributes.permits_alg(self.alg.into())?; + key_attributes.compatible_with_alg(self.alg.into())?; + if (self.alg == AsymmetricEncryption::RsaPkcs1v15Crypt && self.salt != None) + || self.ciphertext.is_empty() + { + return Err(ResponseStatus::PsaErrorInvalidArgument); + } + Ok(()) + } +} + +/// Native object for asymmetric decrypt result. +// Debug derived as NativeResult enum requires it, even though nothing inside this Result is debuggable +// as `plaintext` is sensitive. +#[derive(Derivative)] +#[derivative(Debug)] +pub struct Result { + /// Decrypted message + #[derivative(Debug = "ignore")] + pub plaintext: zeroize::Zeroizing>, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::operations::psa_algorithm::{AsymmetricEncryption, Hash}; + use crate::operations::psa_key_attributes::{Lifetime, Policy, Type, UsageFlags}; + use zeroize::Zeroizing; + + fn get_attrs() -> Attributes { + Attributes { + lifetime: Lifetime::Persistent, + key_type: Type::RsaKeyPair, + bits: 256, + policy: Policy { + usage_flags: UsageFlags { + export: false, + copy: false, + cache: false, + encrypt: false, + decrypt: true, + sign_message: false, + verify_message: false, + sign_hash: false, + verify_hash: false, + derive: false, + }, + permitted_algorithms: AsymmetricEncryption::RsaPkcs1v15Crypt.into(), + }, + } + } + + #[test] + fn validate_success() { + (Operation { + key_name: String::from("some key"), + alg: AsymmetricEncryption::RsaPkcs1v15Crypt, + ciphertext: Zeroizing::new(vec![0xff, 32]), + salt: None, + }) + .validate(get_attrs()) + .unwrap(); + } + + #[test] + fn cannot_decrypt() { + let mut attrs = get_attrs(); + attrs.policy.usage_flags.decrypt = false; + assert_eq!( + (Operation { + key_name: String::from("some key"), + alg: AsymmetricEncryption::RsaPkcs1v15Crypt, + ciphertext: Zeroizing::new(vec![0xff, 32]), + salt: None, + }) + .validate(attrs) + .unwrap_err(), + ResponseStatus::PsaErrorNotPermitted + ); + } + + #[test] + fn wrong_algorithm() { + assert_eq!( + (Operation { + key_name: String::from("some key"), + alg: AsymmetricEncryption::RsaOaep { + hash_alg: Hash::Sha256, + }, + ciphertext: Zeroizing::new(vec![0xff, 32]), + salt: None, + }) + .validate(get_attrs()) + .unwrap_err(), + ResponseStatus::PsaErrorNotPermitted + ); + } + + #[test] + fn invalid_ciphertext() { + assert_eq!( + (Operation { + key_name: String::from("some key"), + alg: AsymmetricEncryption::RsaPkcs1v15Crypt, + ciphertext: Zeroizing::new(vec![]), + salt: None, + }) + .validate(get_attrs()) + .unwrap_err(), + ResponseStatus::PsaErrorInvalidArgument + ); + } + + #[test] + fn salt_with_rsapkcs1v15crypt() { + assert_eq!( + (Operation { + key_name: String::from("some key"), + alg: AsymmetricEncryption::RsaPkcs1v15Crypt, + ciphertext: Zeroizing::new(vec![0xff, 32]), + salt: Some(zeroize::Zeroizing::new(vec![0xff, 32])), + }) + .validate(get_attrs()) + .unwrap_err(), + ResponseStatus::PsaErrorInvalidArgument + ); + } +} diff --git a/src/operations/psa_asymmetric_encrypt.rs b/src/operations/psa_asymmetric_encrypt.rs new file mode 100644 index 0000000..5da7206 --- /dev/null +++ b/src/operations/psa_asymmetric_encrypt.rs @@ -0,0 +1,163 @@ +// Copyright 2020 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 +//! # PsaAsymmetricEncrypt operation +//! +//! Encrypt a short message with a public key. + +use super::psa_key_attributes::Attributes; +use crate::operations::psa_algorithm::AsymmetricEncryption; +use crate::requests::ResponseStatus; +use derivative::Derivative; + +/// Native object for asymmetric encryption operations. +#[derive(Derivative)] +#[derivative(Debug)] +pub struct Operation { + /// Defines which key should be used for the encryption operation. + pub key_name: String, + /// An asymmetric encryption algorithm that is compatible with the key type + pub alg: AsymmetricEncryption, + /// The short message to be encrypted. + #[derivative(Debug = "ignore")] + pub plaintext: zeroize::Zeroizing>, + /// Salt to use during encryption, if supported by the algorithm. + #[derivative(Debug = "ignore")] + pub salt: Option>>, +} + +impl Operation { + /// Validate the contents of the operation against the attributes of the key it targets + /// + /// This method checks that: + /// * the key policy allows encrypting messages + /// * the key policy allows the encryption algorithm requested in the operation + /// * the key type is compatible with the requested algorithm + /// * if the algorithm is RsaPkcs1v15Crypt, it has no salt (it is not compatible with salt) + /// * the message to encrypt is valid (not length 0) + pub fn validate(&self, key_attributes: Attributes) -> crate::requests::Result<()> { + key_attributes.can_encrypt_message()?; + key_attributes.permits_alg(self.alg.into())?; + key_attributes.compatible_with_alg(self.alg.into())?; + if (self.alg == AsymmetricEncryption::RsaPkcs1v15Crypt && self.salt != None) + || self.plaintext.is_empty() + { + return Err(ResponseStatus::PsaErrorInvalidArgument); + } + Ok(()) + } +} + +/// Native object for asymmetric encrypt result. +#[derive(Derivative)] +#[derivative(Debug)] +pub struct Result { + /// The `ciphertext` field contains the encrypted short message. + #[derivative(Debug = "ignore")] + pub ciphertext: zeroize::Zeroizing>, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::operations::psa_algorithm::{AsymmetricEncryption, Hash}; + use crate::operations::psa_key_attributes::{Lifetime, Policy, Type, UsageFlags}; + + fn get_attrs() -> Attributes { + Attributes { + lifetime: Lifetime::Persistent, + key_type: Type::RsaKeyPair, + bits: 256, + policy: Policy { + usage_flags: UsageFlags { + export: false, + copy: false, + cache: false, + encrypt: true, + decrypt: false, + sign_message: false, + verify_message: false, + sign_hash: false, + verify_hash: false, + derive: false, + }, + permitted_algorithms: AsymmetricEncryption::RsaPkcs1v15Crypt.into(), + }, + } + } + + #[test] + fn validate_success() { + (Operation { + key_name: String::from("some key"), + alg: AsymmetricEncryption::RsaPkcs1v15Crypt, + plaintext: vec![0xff, 32].into(), + salt: None, + }) + .validate(get_attrs()) + .unwrap(); + } + + #[test] + fn cannot_encrypt() { + let mut attrs = get_attrs(); + attrs.policy.usage_flags.encrypt = false; + assert_eq!( + (Operation { + key_name: String::from("some key"), + alg: AsymmetricEncryption::RsaPkcs1v15Crypt, + plaintext: vec![0xff, 32].into(), + salt: None, + }) + .validate(attrs) + .unwrap_err(), + ResponseStatus::PsaErrorNotPermitted + ); + } + + #[test] + fn wrong_algorithm() { + assert_eq!( + (Operation { + key_name: String::from("some key"), + alg: AsymmetricEncryption::RsaOaep { + hash_alg: Hash::Sha256, + }, + plaintext: vec![0xff, 32].into(), + salt: None, + }) + .validate(get_attrs()) + .unwrap_err(), + ResponseStatus::PsaErrorNotPermitted + ); + } + + #[test] + fn invalid_plaintext() { + assert_eq!( + (Operation { + key_name: String::from("some key"), + alg: AsymmetricEncryption::RsaPkcs1v15Crypt, + plaintext: vec![].into(), + salt: None, + }) + .validate(get_attrs()) + .unwrap_err(), + ResponseStatus::PsaErrorInvalidArgument + ); + } + + #[test] + fn salt_with_rsapkcs1v15crypt() { + assert_eq!( + (Operation { + key_name: String::from("some key"), + alg: AsymmetricEncryption::RsaPkcs1v15Crypt, + plaintext: vec![0xff, 32].into(), + salt: Some(zeroize::Zeroizing::new(vec![0xff, 32])), + }) + .validate(get_attrs()) + .unwrap_err(), + ResponseStatus::PsaErrorInvalidArgument + ); + } +} diff --git a/src/operations_protobuf/convert_psa_asymmetric_decrypt.rs b/src/operations_protobuf/convert_psa_asymmetric_decrypt.rs new file mode 100644 index 0000000..e2bba9a --- /dev/null +++ b/src/operations_protobuf/convert_psa_asymmetric_decrypt.rs @@ -0,0 +1,206 @@ +// Copyright 2020 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 +use super::generated_ops::psa_asymmetric_decrypt::{ + Operation as OperationProto, Result as ResultProto, +}; +use crate::operations::psa_asymmetric_decrypt::{Operation, Result}; +use crate::requests::ResponseStatus; +use log::error; +use std::convert::{TryFrom, TryInto}; +use zeroize::Zeroizing; + +impl TryFrom for Operation { + type Error = ResponseStatus; + + fn try_from(proto_op: OperationProto) -> std::result::Result { + let salt = match proto_op.salt.len() { + 0 => None, + _ => Some(Zeroizing::new(proto_op.salt)), + }; + + Ok(Operation { + key_name: proto_op.key_name, + alg: proto_op + .alg + .ok_or_else(|| { + error!("alg field of PsaAsymmetricDecrypt::Operation message is empty."); + ResponseStatus::InvalidEncoding + })? + .try_into()?, + ciphertext: proto_op.ciphertext.into(), + salt, + }) + } +} + +impl TryFrom for OperationProto { + type Error = ResponseStatus; + + fn try_from(op: Operation) -> std::result::Result { + let alg = Some(op.alg.try_into()?); + let salt = match op.salt { + Some(salt) => salt.to_vec(), + None => vec![], + }; + Ok(OperationProto { + key_name: op.key_name, + alg, + ciphertext: op.ciphertext.to_vec(), + salt, + }) + } +} + +impl TryFrom for Result { + type Error = ResponseStatus; + + fn try_from(proto_result: ResultProto) -> std::result::Result { + Ok(Result { + plaintext: proto_result.plaintext.into(), + }) + } +} + +impl TryFrom for ResultProto { + type Error = ResponseStatus; + + fn try_from(result: Result) -> std::result::Result { + Ok(ResultProto { + plaintext: result.plaintext.to_vec(), + }) + } +} + +#[cfg(test)] +mod test { + use super::super::generated_ops::psa_algorithm as algorithm_proto; + use super::super::generated_ops::psa_asymmetric_decrypt::{ + Operation as OperationProto, Result as ResultProto, + }; + use super::super::{Convert, ProtobufConverter}; + use crate::operations::psa_algorithm::AsymmetricEncryption; + use crate::operations::psa_asymmetric_decrypt::{Operation, Result}; + use std::convert::TryInto; + static CONVERTER: ProtobufConverter = ProtobufConverter {}; + use crate::operations::{NativeOperation, NativeResult}; + use crate::requests::{request::RequestBody, response::ResponseBody, Opcode}; + use zeroize::Zeroizing; + + #[test] + fn asym_proto_to_op() { + let mut proto: OperationProto = Default::default(); + let message = vec![0x11, 0x22, 0x33]; + let key_name = "test name".to_string(); + let salt: Vec = vec![]; + proto.ciphertext = message.clone(); + proto.alg = Some(algorithm_proto::algorithm::AsymmetricEncryption { + variant: Some( + algorithm_proto::algorithm::asymmetric_encryption::Variant::RsaPkcs1v15Crypt( + algorithm_proto::algorithm::asymmetric_encryption::RsaPkcs1v15Crypt {}, + ), + ), + }); + proto.key_name = key_name.clone(); + proto.salt = salt; + + let op: Operation = proto.try_into().expect("Failed to convert"); + + assert_eq!(op.ciphertext.to_vec(), message); + assert_eq!(op.key_name, key_name); + assert_eq!(op.salt, None); + } + + #[test] + fn asym_op_to_proto() { + let message = vec![0x11, 0x22, 0x33]; + let key_name = "test name".to_string(); + + let op = Operation { + ciphertext: Zeroizing::new(message.clone()), + alg: AsymmetricEncryption::RsaPkcs1v15Crypt, + key_name: key_name.clone(), + salt: None, + }; + + let proto: OperationProto = op.try_into().expect("Failed to convert"); + + assert_eq!(proto.ciphertext, message); + assert_eq!(proto.key_name, key_name); + assert_eq!(proto.salt, vec![]); + } + + #[test] + fn asym_proto_to_resp() { + let mut proto: ResultProto = Default::default(); + let plaintext: Vec = vec![0x11, 0x22, 0x33]; + proto.plaintext = plaintext.clone(); + + let result: Result = proto.try_into().expect("Failed to convert"); + + assert_eq!(*result.plaintext, plaintext); + } + + #[test] + fn asym_resp_to_proto() { + let plaintext = vec![0x11, 0x22, 0x33]; + let result = Result { + plaintext: plaintext.clone().into(), + }; + + let proto: ResultProto = result.try_into().expect("Failed to convert"); + + assert_eq!(proto.plaintext, plaintext); + } + + #[test] + fn psa_decrypt_message_op_e2e() { + let name = "test name".to_string(); + let op = Operation { + key_name: name, + alg: AsymmetricEncryption::RsaPkcs1v15Crypt, + ciphertext: Zeroizing::new(vec![0x11, 0x22, 0x33]), + salt: None, + }; + + let body = CONVERTER + .operation_to_body(NativeOperation::PsaAsymmetricDecrypt(op)) + .expect("Failed to convert to body"); + + let _ = CONVERTER + .body_to_operation(body, Opcode::PsaAsymmetricDecrypt) + .expect("Failed to convert to operation"); + } + + #[test] + fn resp_asym_decrypt_e2e() { + let result = Result { + plaintext: vec![0x11, 0x22, 0x33].into(), + }; + let body = CONVERTER + .result_to_body(NativeResult::PsaAsymmetricDecrypt(result)) + .expect("Failed to convert request"); + + assert!(CONVERTER + .body_to_result(body, Opcode::PsaAsymmetricDecrypt) + .is_ok()); + } + + #[test] + fn result_from_mangled_resp_body() { + let resp_body = + ResponseBody::from_bytes(vec![0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]); + assert!(CONVERTER + .body_to_result(resp_body, Opcode::PsaAsymmetricDecrypt) + .is_err()); + } + + #[test] + fn op_from_mangled_req_body() { + let req_body = + RequestBody::from_bytes(vec![0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]); + + assert!(CONVERTER + .body_to_operation(req_body, Opcode::PsaAsymmetricDecrypt) + .is_err()); + } +} diff --git a/src/operations_protobuf/convert_psa_asymmetric_encrypt.rs b/src/operations_protobuf/convert_psa_asymmetric_encrypt.rs new file mode 100644 index 0000000..7781b65 --- /dev/null +++ b/src/operations_protobuf/convert_psa_asymmetric_encrypt.rs @@ -0,0 +1,205 @@ +// Copyright 2020 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 +use super::generated_ops::psa_asymmetric_encrypt::{ + Operation as OperationProto, Result as ResultProto, +}; +use crate::operations::psa_asymmetric_encrypt::{Operation, Result}; +use crate::requests::ResponseStatus; +use log::error; +use std::convert::{TryFrom, TryInto}; +use zeroize::Zeroizing; + +impl TryFrom for Operation { + type Error = ResponseStatus; + + fn try_from(proto_op: OperationProto) -> std::result::Result { + let salt = match proto_op.salt.len() { + 0 => None, + _ => Some(Zeroizing::new(proto_op.salt)), + }; + + Ok(Operation { + key_name: proto_op.key_name, + alg: proto_op + .alg + .ok_or_else(|| { + error!("alg field of PsaAsymmetricEncrypt::Operation message is empty."); + ResponseStatus::InvalidEncoding + })? + .try_into()?, + plaintext: proto_op.plaintext.into(), + salt, + }) + } +} + +impl TryFrom for OperationProto { + type Error = ResponseStatus; + + fn try_from(op: Operation) -> std::result::Result { + let alg = Some(op.alg.try_into()?); + let salt = match op.salt { + Some(salt) => salt.to_vec(), + None => vec![], + }; + Ok(OperationProto { + key_name: op.key_name, + alg, + plaintext: op.plaintext.to_vec(), + salt, + }) + } +} + +impl TryFrom for Result { + type Error = ResponseStatus; + + fn try_from(proto_result: ResultProto) -> std::result::Result { + Ok(Result { + ciphertext: proto_result.ciphertext.into(), + }) + } +} + +impl TryFrom for ResultProto { + type Error = ResponseStatus; + + fn try_from(result: Result) -> std::result::Result { + Ok(ResultProto { + ciphertext: result.ciphertext.to_vec(), + }) + } +} + +#[cfg(test)] +mod test { + use super::super::generated_ops::psa_algorithm as algorithm_proto; + use super::super::generated_ops::psa_asymmetric_encrypt::{ + Operation as OperationProto, Result as ResultProto, + }; + use super::super::{Convert, ProtobufConverter}; + use crate::operations::psa_algorithm::AsymmetricEncryption; + use crate::operations::psa_asymmetric_encrypt::{Operation, Result}; + use std::convert::TryInto; + static CONVERTER: ProtobufConverter = ProtobufConverter {}; + use crate::operations::{NativeOperation, NativeResult}; + use crate::requests::{request::RequestBody, response::ResponseBody, Opcode}; + + #[test] + fn asym_proto_to_op() { + let mut proto: OperationProto = Default::default(); + let message = vec![0x11, 0x22, 0x33]; + let key_name = "test name".to_string(); + let salt: Vec = vec![]; + proto.plaintext = message.clone(); + proto.alg = Some(algorithm_proto::algorithm::AsymmetricEncryption { + variant: Some( + algorithm_proto::algorithm::asymmetric_encryption::Variant::RsaPkcs1v15Crypt( + algorithm_proto::algorithm::asymmetric_encryption::RsaPkcs1v15Crypt {}, + ), + ), + }); + proto.key_name = key_name.clone(); + proto.salt = salt; + + let op: Operation = proto.try_into().expect("Failed to convert"); + + assert_eq!(*op.plaintext, message); + assert_eq!(op.key_name, key_name); + assert_eq!(op.salt, None); + } + + #[test] + fn asym_op_to_proto() { + let message = vec![0x11, 0x22, 0x33]; + let key_name = "test name".to_string(); + + let op = Operation { + plaintext: message.clone().into(), + alg: AsymmetricEncryption::RsaPkcs1v15Crypt, + key_name: key_name.clone(), + salt: None, + }; + + let proto: OperationProto = op.try_into().expect("Failed to convert"); + + assert_eq!(proto.plaintext, message); + assert_eq!(proto.key_name, key_name); + assert_eq!(proto.salt, vec![]); + } + + #[test] + fn asym_proto_to_resp() { + let mut proto: ResultProto = Default::default(); + let ciphertext = vec![0x11, 0x22, 0x33]; + proto.ciphertext = ciphertext.clone(); + + let result: Result = proto.try_into().expect("Failed to convert"); + + assert_eq!(*result.ciphertext, ciphertext); + } + + #[test] + fn asym_resp_to_proto() { + let ciphertext = vec![0x11, 0x22, 0x33]; + let result = Result { + ciphertext: ciphertext.clone().into(), + }; + + let proto: ResultProto = result.try_into().expect("Failed to convert"); + + assert_eq!(proto.ciphertext, ciphertext); + } + + #[test] + fn psa_encrypt_message_op_e2e() { + let name = "test name".to_string(); + let op = Operation { + key_name: name, + alg: AsymmetricEncryption::RsaPkcs1v15Crypt, + plaintext: vec![0x11, 0x22, 0x33].into(), + salt: None, + }; + + let body = CONVERTER + .operation_to_body(NativeOperation::PsaAsymmetricEncrypt(op)) + .expect("Failed to convert to body"); + + let _ = CONVERTER + .body_to_operation(body, Opcode::PsaAsymmetricEncrypt) + .expect("Failed to convert to operation"); + } + + #[test] + fn resp_asym_encrypt_e2e() { + let result = Result { + ciphertext: vec![0x11, 0x22, 0x33].into(), + }; + let body = CONVERTER + .result_to_body(NativeResult::PsaAsymmetricEncrypt(result)) + .expect("Failed to convert request"); + + assert!(CONVERTER + .body_to_result(body, Opcode::PsaAsymmetricEncrypt) + .is_ok()); + } + + #[test] + fn result_from_mangled_resp_body() { + let resp_body = + ResponseBody::from_bytes(vec![0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]); + assert!(CONVERTER + .body_to_result(resp_body, Opcode::PsaAsymmetricEncrypt) + .is_err()); + } + + #[test] + fn op_from_mangled_req_body() { + let req_body = + RequestBody::from_bytes(vec![0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]); + + assert!(CONVERTER + .body_to_operation(req_body, Opcode::PsaAsymmetricEncrypt) + .is_err()); + } +} diff --git a/src/operations_protobuf/generated_ops.rs b/src/operations_protobuf/generated_ops.rs index 84d341a..4c1a6dd 100644 --- a/src/operations_protobuf/generated_ops.rs +++ b/src/operations_protobuf/generated_ops.rs @@ -17,6 +17,8 @@ macro_rules! include_protobuf_as_module { include_protobuf_as_module!(psa_sign_hash); include_protobuf_as_module!(psa_verify_hash); +include_protobuf_as_module!(psa_asymmetric_encrypt); +include_protobuf_as_module!(psa_asymmetric_decrypt); include_protobuf_as_module!(psa_generate_key); include_protobuf_as_module!(psa_destroy_key); include_protobuf_as_module!(psa_export_public_key); @@ -137,6 +139,7 @@ empty_clear_message!(psa_generate_key::Result); empty_clear_message!(psa_export_public_key::Operation); empty_clear_message!(psa_import_key::Result); empty_clear_message!(psa_verify_hash::Result); +empty_clear_message!(psa_asymmetric_encrypt::Result); impl ClearProtoMessage for psa_sign_hash::Operation { fn clear_message(&mut self) { @@ -168,3 +171,18 @@ impl ClearProtoMessage for psa_export_public_key::Result { self.data.zeroize(); } } + +impl ClearProtoMessage for psa_asymmetric_encrypt::Operation { + fn clear_message(&mut self) { + self.plaintext.zeroize(); + self.salt.zeroize(); + } +} + +impl ClearProtoMessage for psa_asymmetric_decrypt::Operation { + fn clear_message(&mut self) { self.salt.zeroize(); } +} + +impl ClearProtoMessage for psa_asymmetric_decrypt::Result { + fn clear_message(&mut self) { self.plaintext.zeroize(); } +} \ No newline at end of file diff --git a/src/operations_protobuf/mod.rs b/src/operations_protobuf/mod.rs index b1f76ee..2402663 100644 --- a/src/operations_protobuf/mod.rs +++ b/src/operations_protobuf/mod.rs @@ -14,6 +14,8 @@ mod convert_psa_sign_hash; mod convert_psa_verify_hash; mod convert_list_providers; mod convert_list_opcodes; +mod convert_psa_asymmetric_encrypt; +mod convert_psa_asymmetric_decrypt; #[rustfmt::skip] #[allow(unused_qualifications, missing_copy_implementations, clippy::pedantic, clippy::module_inception)] @@ -26,6 +28,8 @@ use crate::requests::{ use generated_ops::list_opcodes as list_opcodes_proto; use generated_ops::list_providers as list_providers_proto; use generated_ops::ping as ping_proto; +use generated_ops::psa_asymmetric_decrypt as psa_asymmetric_decrypt_proto; +use generated_ops::psa_asymmetric_encrypt as psa_asymmetric_encrypt_proto; use generated_ops::psa_destroy_key as psa_destroy_key_proto; use generated_ops::psa_export_public_key as psa_export_public_key_proto; use generated_ops::psa_generate_key as psa_generate_key_proto; @@ -107,6 +111,12 @@ impl Convert for ProtobufConverter { body.bytes(), psa_verify_hash_proto::Operation ))), + Opcode::PsaAsymmetricEncrypt => Ok(NativeOperation::PsaAsymmetricEncrypt( + wire_to_native!(body.bytes(), psa_asymmetric_encrypt_proto::Operation), + )), + Opcode::PsaAsymmetricDecrypt => Ok(NativeOperation::PsaAsymmetricDecrypt( + wire_to_native!(body.bytes(), psa_asymmetric_decrypt_proto::Operation), + )), } } @@ -140,6 +150,12 @@ impl Convert for ProtobufConverter { NativeOperation::PsaVerifyHash(operation) => Ok(RequestBody::from_bytes( native_to_wire!(operation, psa_verify_hash_proto::Operation), )), + NativeOperation::PsaAsymmetricEncrypt(operation) => Ok(RequestBody::from_bytes( + native_to_wire!(operation, psa_asymmetric_encrypt_proto::Operation), + )), + NativeOperation::PsaAsymmetricDecrypt(operation) => Ok(RequestBody::from_bytes( + native_to_wire!(operation, psa_asymmetric_decrypt_proto::Operation), + )), } } @@ -181,6 +197,12 @@ impl Convert for ProtobufConverter { body.bytes(), psa_verify_hash_proto::Result ))), + Opcode::PsaAsymmetricEncrypt => Ok(NativeResult::PsaAsymmetricEncrypt( + wire_to_native!(body.bytes(), psa_asymmetric_encrypt_proto::Result), + )), + Opcode::PsaAsymmetricDecrypt => Ok(NativeResult::PsaAsymmetricDecrypt( + wire_to_native!(body.bytes(), psa_asymmetric_decrypt_proto::Result), + )), } } @@ -221,6 +243,12 @@ impl Convert for ProtobufConverter { result, psa_verify_hash_proto::Result ))), + NativeResult::PsaAsymmetricEncrypt(result) => Ok(ResponseBody::from_bytes( + native_to_wire!(result, psa_asymmetric_encrypt_proto::Result), + )), + NativeResult::PsaAsymmetricDecrypt(result) => Ok(ResponseBody::from_bytes( + native_to_wire!(result, psa_asymmetric_decrypt_proto::Result), + )), } } } diff --git a/src/requests/mod.rs b/src/requests/mod.rs index f6bf4ee..80a3df0 100644 --- a/src/requests/mod.rs +++ b/src/requests/mod.rs @@ -91,6 +91,10 @@ pub enum Opcode { ListProviders = 8, /// ListOpcodes operation ListOpcodes = 9, + /// PsaAsymmetricEncrypt operation + PsaAsymmetricEncrypt = 10, + /// PsaAsymmetricDecrypt operation + PsaAsymmetricDecrypt = 11, } /// Listing of available authentication methods.