Skip to content

Added asymmetric encrypt and decrypt #63

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an idea: would it be wrong to include the salt inside the AsymmetricEncryption structure (in the RsaOaep structure)? That way this condition would be checked statically. But that seems to be a big deviation from the PSA API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's a good idea, even if it deviates from the PSA API a bit. It sounds more 'Rusty' to me 😉.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do that, we need to have separate implementations of RsaOaep for policy and for operations - it doesn't make any sense to have a salt for a policy. Then, if we do have an operation-specific enum for algorithms, I don't see why we couldn't include algorithm-specific fields inside those structures/variants.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, would be good but probably not as part of this PR.

/// * 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