-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
RSA PKCS1 v1.5 Encryption Support (#492)
- Loading branch information
Showing
6 changed files
with
447 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
Oops, something went wrong.