Skip to content
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

RSA PKCS1 v1.5 Encryption Support #492

Merged
merged 2 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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 then or equal to [`Self::ciphertext_size`].
skmcgrail marked this conversation as resolved.
Show resolved Hide resolved
///
/// # 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 and with the provided algorithm.
skmcgrail marked this conversation as resolved.
Show resolved Hide resolved
#[must_use]
pub fn max_plaintext_size(&self) -> usize {
const RSA_PKCS1_PADDING_SIZE: usize = 11; // crypto/fipsmodule/rsa/internal.h
justsmth marked this conversation as resolved.
Show resolved Hide resolved
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 then 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
Loading