Skip to content

Commit

Permalink
Added asymmetric encrypt and decrypt
Browse files Browse the repository at this point in the history
Signed-off-by: Samuel Bailey <samuel.bailey@arm.com>
  • Loading branch information
sbailey-arm committed Jul 7, 2020
1 parent 3ec787e commit bc47296
Show file tree
Hide file tree
Showing 10 changed files with 808 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion parsec-operations
14 changes: 14 additions & 0 deletions src/operations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 {
Expand All @@ -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,
}
}
}
Expand All @@ -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 {
Expand All @@ -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,
}
}
}
Expand Down
168 changes: 168 additions & 0 deletions src/operations/psa_asymmetric_decrypt.rs
Original file line number Diff line number Diff line change
@@ -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<Vec<u8>>,
/// Salt to use during decryption, if supported by the algorithm.
#[derivative(Debug = "ignore")]
pub salt: Option<zeroize::Zeroizing<Vec<u8>>>,
}

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<Vec<u8>>,
}

#[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
);
}
}
163 changes: 163 additions & 0 deletions src/operations/psa_asymmetric_encrypt.rs
Original file line number Diff line number Diff line change
@@ -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<Vec<u8>>,
/// Salt to use during encryption, if supported by the algorithm.
#[derivative(Debug = "ignore")]
pub salt: Option<zeroize::Zeroizing<Vec<u8>>>,
}

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<Vec<u8>>,
}

#[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
);
}
}
Loading

0 comments on commit bc47296

Please sign in to comment.