Skip to content

Commit

Permalink
RSA PKCS1 v1.5 Encryption Support (#492)
Browse files Browse the repository at this point in the history
  • Loading branch information
skmcgrail authored Aug 19, 2024
1 parent bc9f59a commit 5346709
Show file tree
Hide file tree
Showing 6 changed files with 447 additions and 28 deletions.
57 changes: 57 additions & 0 deletions aws-lc-rs-testing/tests/rsa_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

use aws_lc_rs::rsa::{
Pkcs1PrivateDecryptingKey, Pkcs1PublicEncryptingKey, PrivateDecryptingKey, PublicEncryptingKey,
};
use openssl::rsa::{Padding, Rsa};

#[test]
fn rsa2048_pkcs1_openssl_interop() {
const PKCS8_PRIVATE_KEY: &[u8] =
include_bytes!("../../aws-lc-rs/tests/data/rsa_test_private_key_2048.p8");
const RSA_PRIVATE_KEY: &[u8] =
include_bytes!("../../aws-lc-rs/tests/data/rsa_test_private_key_2048.der");
const PUBLIC_KEY: &[u8] =
include_bytes!("../../aws-lc-rs/tests/data/rsa_test_public_key_2048.x509");
const MESSAGE: &[u8] = b"OpenSSL KAT";

let aws_public_key = PublicEncryptingKey::from_der(PUBLIC_KEY).expect("public key");
let aws_public_key = Pkcs1PublicEncryptingKey::new(aws_public_key).expect("public key");

let mut ciphertext = vec![0u8; aws_public_key.ciphertext_size()];
let ciphertext: &[u8] = aws_public_key
.encrypt(MESSAGE, &mut ciphertext)
.expect("encrypted");

assert_ne!(MESSAGE, ciphertext);

let ossl_private_key = Rsa::private_key_from_der(RSA_PRIVATE_KEY).expect("private key");

let mut message = vec![0u8; ossl_private_key.size().try_into().expect("usize cast")];
let message_len = ossl_private_key
.private_decrypt(ciphertext, &mut message, Padding::PKCS1)
.expect("decrypted");
let message: &[u8] = &message[0..message_len];

assert_eq!(MESSAGE, message);

let aws_private_key = PrivateDecryptingKey::from_pkcs8(PKCS8_PRIVATE_KEY).expect("private key");
let aws_private_key = Pkcs1PrivateDecryptingKey::new(aws_private_key).expect("private key");
let ossl_public_key = Rsa::public_key_from_der(PUBLIC_KEY).expect("public key");

let mut ciphertext = vec![0u8; ossl_public_key.size().try_into().expect("usize cast")];
let ciphertext_len = ossl_public_key
.public_encrypt(MESSAGE, &mut ciphertext, Padding::PKCS1)
.expect("encrypted");
let ciphertext: &[u8] = &ciphertext[0..ciphertext_len];

assert_ne!(MESSAGE, ciphertext);

let mut plaintext = vec![0u8; aws_private_key.min_output_size()];
let plaintext: &[u8] = aws_private_key
.decrypt(ciphertext, &mut plaintext)
.expect("decrypted");

assert_eq!(MESSAGE, plaintext);
}
1 change: 1 addition & 0 deletions aws-lc-rs/src/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ pub use self::{
OaepAlgorithm, OaepPrivateDecryptingKey, OaepPublicEncryptingKey, OAEP_SHA1_MGF1SHA1,
OAEP_SHA256_MGF1SHA256, OAEP_SHA384_MGF1SHA384, OAEP_SHA512_MGF1SHA512,
},
pkcs1::{Pkcs1PrivateDecryptingKey, Pkcs1PublicEncryptingKey},
EncryptionAlgorithmId, PrivateDecryptingKey, PublicEncryptingKey,
},
key::{KeyPair, KeySize, PublicKey, PublicKeyComponents},
Expand Down
1 change: 1 addition & 0 deletions aws-lc-rs/src/rsa/encryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 OR ISC

pub(super) mod oaep;
pub(super) mod pkcs1;

use super::{
encoding,
Expand Down
187 changes: 187 additions & 0 deletions aws-lc-rs/src/rsa/encryption/pkcs1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#![allow(clippy::module_name_repetitions)]

use super::{PrivateDecryptingKey, PublicEncryptingKey};
use crate::{error::Unspecified, fips::indicator_check, ptr::LcPtr};
use aws_lc::{
EVP_PKEY_CTX_new, EVP_PKEY_CTX_set_rsa_padding, EVP_PKEY_decrypt, EVP_PKEY_decrypt_init,
EVP_PKEY_encrypt, EVP_PKEY_encrypt_init, EVP_PKEY_CTX, RSA_PKCS1_PADDING,
};
use core::{fmt::Debug, ptr::null_mut};

/// RSA PKCS1-v1.5 public key for encryption.
pub struct Pkcs1PublicEncryptingKey {
public_key: PublicEncryptingKey,
}

impl Pkcs1PublicEncryptingKey {
/// Constructs an `Pkcs1PublicEncryptingKey` from a `PublicEncryptingKey`.
/// # Errors
/// * `Unspecified`: Any error that occurs while attempting to construct an RSA-OAEP public key.
pub fn new(public_key: PublicEncryptingKey) -> Result<Self, Unspecified> {
Ok(Self { public_key })
}

/// Encrypts the contents in `plaintext` and writes the corresponding ciphertext to `ciphertext`.
/// Returns the subslice of `ciphertext` containing the ciphertext output.
///
/// # Max Plaintext Length
/// The provided length of `plaintext` must be at most [`Self::max_plaintext_size`].
///
/// # Sizing `output`
/// The length of `output` must be greater than or equal to [`Self::ciphertext_size`].
///
/// # Errors
/// * `Unspecified` for any error that occurs while encrypting `plaintext`.
pub fn encrypt<'ciphertext>(
&self,
plaintext: &[u8],
ciphertext: &'ciphertext mut [u8],
) -> Result<&'ciphertext mut [u8], Unspecified> {
let pkey_ctx = LcPtr::new(unsafe { EVP_PKEY_CTX_new(*self.public_key.0, null_mut()) })?;

if 1 != unsafe { EVP_PKEY_encrypt_init(*pkey_ctx) } {
return Err(Unspecified);
}

configure_pkcs1_crypto_operation(&pkey_ctx)?;

let mut out_len = ciphertext.len();

if 1 != indicator_check!(unsafe {
EVP_PKEY_encrypt(
*pkey_ctx,
ciphertext.as_mut_ptr(),
&mut out_len,
plaintext.as_ptr(),
plaintext.len(),
)
}) {
return Err(Unspecified);
};

Ok(&mut ciphertext[..out_len])
}

/// Returns the RSA key size in bytes.
#[must_use]
pub fn key_size_bytes(&self) -> usize {
self.public_key.key_size_bytes()
}

/// Returns the RSA key size in bits.
#[must_use]
pub fn key_size_bits(&self) -> usize {
self.public_key.key_size_bits()
}

/// Returns the max plaintext that could be encrypted using this key.
#[must_use]
pub fn max_plaintext_size(&self) -> usize {
const RSA_PKCS1_PADDING_SIZE: usize = 11; // crypto/fipsmodule/rsa/internal.h
self.key_size_bytes() - RSA_PKCS1_PADDING_SIZE
}

/// Returns the max ciphertext size that will be output by `Self::encrypt`.
#[must_use]
pub fn ciphertext_size(&self) -> usize {
self.key_size_bytes()
}
}

impl Debug for Pkcs1PublicEncryptingKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Pkcs1PublicEncryptingKey")
.finish_non_exhaustive()
}
}

/// RSA PKCS1-v1.5 private key for decryption.
pub struct Pkcs1PrivateDecryptingKey {
private_key: PrivateDecryptingKey,
}

impl Pkcs1PrivateDecryptingKey {
/// Constructs an `Pkcs1PrivateDecryptingKey` from a `PrivateDecryptingKey`.
/// # Errors
/// * `Unspecified`: Any error that occurs while attempting to construct an RSA-OAEP public key.
pub fn new(private_key: PrivateDecryptingKey) -> Result<Self, Unspecified> {
Ok(Self { private_key })
}

/// Decrypts the contents in `ciphertext` and writes the corresponding plaintext to `plaintext`.
/// Returns the subslice of `plaintext` containing the plaintext output.
///
/// # Max Ciphertext Length
/// The provided length of `ciphertext` must be [`Self::key_size_bytes`].
///
/// # Sizing `output`
/// The length of `output` must be greater than or equal to [`Self::min_output_size`].
///
/// # Errors
/// * `Unspecified` for any error that occurs while decrypting `ciphertext`.
pub fn decrypt<'plaintext>(
&self,
ciphertext: &[u8],
plaintext: &'plaintext mut [u8],
) -> Result<&'plaintext mut [u8], Unspecified> {
let pkey_ctx = LcPtr::new(unsafe { EVP_PKEY_CTX_new(*self.private_key.0, null_mut()) })?;

if 1 != unsafe { EVP_PKEY_decrypt_init(*pkey_ctx) } {
return Err(Unspecified);
}

configure_pkcs1_crypto_operation(&pkey_ctx)?;

let mut out_len = plaintext.len();

if 1 != indicator_check!(unsafe {
EVP_PKEY_decrypt(
*pkey_ctx,
plaintext.as_mut_ptr(),
&mut out_len,
ciphertext.as_ptr(),
ciphertext.len(),
)
}) {
return Err(Unspecified);
};

Ok(&mut plaintext[..out_len])
}

/// Returns the RSA key size in bytes.
#[must_use]
pub fn key_size_bytes(&self) -> usize {
self.private_key.key_size_bytes()
}

/// Returns the RSA key size in bits.
#[must_use]
pub fn key_size_bits(&self) -> usize {
self.private_key.key_size_bits()
}

/// Returns the minimum plaintext buffer size required for `Self::decrypt`.
#[must_use]
pub fn min_output_size(&self) -> usize {
self.key_size_bytes()
}
}

impl Debug for Pkcs1PrivateDecryptingKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Pkcs1PrivateDecryptingKey")
.finish_non_exhaustive()
}
}

fn configure_pkcs1_crypto_operation(evp_pkey_ctx: &LcPtr<EVP_PKEY_CTX>) -> Result<(), Unspecified> {
if 1 != unsafe { EVP_PKEY_CTX_set_rsa_padding(**evp_pkey_ctx, RSA_PKCS1_PADDING) } {
return Err(Unspecified);
};

Ok(())
}
Binary file not shown.
Loading

0 comments on commit 5346709

Please sign in to comment.