Skip to content

Commit

Permalink
deps/crypto: add AEAD, RSA OAEP and EC support
Browse files Browse the repository at this point in the history
This patch adds more crypto suites to the crypto crate

1. Add AES-256-GCM AEAD API.
2. Mark RSA PKCS#1 v1.5 Padding encryption scheme as "deprecated".
3. Add EC suites for key wrapping. Now supports P256 curve and
ECDH-ES-A256KW algorithm.

Signed-off-by: Xynnn007 <xynnn@linux.alibaba.com>
  • Loading branch information
Xynnn007 committed Dec 17, 2024
1 parent 64b2e7e commit 2ba4165
Show file tree
Hide file tree
Showing 14 changed files with 461 additions and 37 deletions.
25 changes: 23 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions attestation-agent/deps/crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ edition = "2021"

[dependencies]
aes-gcm = { workspace = true, optional = true }
aes-kw = { version = "0.2.1", optional = true }
anyhow.workspace = true
base64.workspace = true
concat-kdf = { version = "0.1.0", optional = true }
ctr = { workspace = true, optional = true }
kbs-types.workspace = true
openssl = { workspace = true, features = ["vendored"], optional = true}
openssl = { workspace = true, features = ["vendored"], optional = true }
p256 = { version = "0.13.1", features = ["ecdh"] }
rand.workspace = true
rsa = { workspace = true, optional = true }
serde.workspace = true
Expand All @@ -25,5 +28,5 @@ rstest.workspace = true

[features]
default = ["rust-crypto"]
rust-crypto = ["dep:aes-gcm", "ctr", "rsa"]
openssl = ["dep:openssl"]
rust-crypto = ["dep:aes-gcm", "ctr", "rsa/sha2", "aes-kw", "concat-kdf"]
openssl = ["dep:openssl"]
35 changes: 34 additions & 1 deletion attestation-agent/deps/crypto/src/asymmetric.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
// SPDX-License-Identifier: Apache-2.0
//

// This lint checker is for [`rsa::PaddingMode::PKCS1v15`]
// TODO: remove this when the deprecated attribute is removed
#[allow(deprecated)]
pub mod rsa {
#[cfg(feature = "openssl")]
pub use crate::native::rsa::*;
Expand All @@ -15,9 +18,12 @@ pub mod rsa {
/// more information.
#[derive(EnumString, AsRefStr)]
pub enum PaddingMode {
#[strum(serialize = "RSA-OAEP")]
/// RSAES OAEP using SHA-256 and MGF1 with SHA-256
#[strum(serialize = "RSA-OAEP-256")]
OAEP,

/// RSA PKCS#1 v1.5
#[deprecated(note = "This algorithm is no longer recommended.")]
#[strum(serialize = "RSA1_5")]
PKCS1v15,
}
Expand All @@ -26,3 +32,30 @@ pub mod rsa {

pub const RSA_KTY: &str = "RSA";
}

pub mod ec {
#[cfg(feature = "openssl")]
pub use crate::native::ec::*;

#[cfg(all(feature = "rust-crypto", not(feature = "openssl")))]
pub use crate::rust::ec::*;

/// The elliptic curve key type
pub const EC_KTY: &str = "EC";

/// Definations of different key wrapping algorithms. Refer to
/// <https://datatracker.ietf.org/doc/html/rfc7518> for
/// more information.
#[derive(EnumString, AsRefStr)]
pub enum KeyWrapAlgorithm {
/// ECDH-ES using Concat KDF and CEK wrapped with "A256KW"
#[strum(serialize = "ECDH-ES+A256KW")]
EcdhEsA256Kw,
}

#[derive(EnumString, AsRefStr)]
pub enum Curve {
#[strum(serialize = "P-256")]
P256,
}
}
8 changes: 4 additions & 4 deletions attestation-agent/deps/crypto/src/native/aes256ctr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
use anyhow::*;
use openssl::symm::Cipher;

pub fn decrypt(encrypted_data: &[u8], key: &[u8], iv: &[u8]) -> Result<Vec<u8>> {
pub fn decrypt(key: &[u8], encrypted_data: &[u8], iv: &[u8]) -> Result<Vec<u8>> {
let cipher = Cipher::aes_256_ctr();

openssl::symm::decrypt(cipher, key, Some(iv), encrypted_data)
.map_err(|e| anyhow!(e.to_string()))
}

pub fn encrypt(data: &[u8], key: &[u8], iv: &[u8]) -> Result<Vec<u8>> {
pub fn encrypt(key: &[u8], data: &[u8], iv: &[u8]) -> Result<Vec<u8>> {
let cipher = Cipher::aes_256_ctr();
let ciphertext =
openssl::symm::encrypt(cipher, key, Some(iv), data).map_err(|e| anyhow!(e.to_string()))?;
Expand All @@ -40,8 +40,8 @@ mod tests {
b"16bytes ivlength"
)]
fn en_decrypt(#[case] plaintext: &[u8], #[case] key: &[u8], #[case] iv: &[u8]) {
let ciphertext = encrypt(plaintext, key, iv).expect("encryption failed");
let plaintext_de = decrypt(&ciphertext, key, iv).expect("decryption failed");
let ciphertext = encrypt(key, plaintext, iv).expect("encryption failed");
let plaintext_de = decrypt(key, &ciphertext, iv).expect("decryption failed");
assert_eq!(plaintext, plaintext_de);
}
}
27 changes: 20 additions & 7 deletions attestation-agent/deps/crypto/src/native/aes256gcm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,20 @@ use openssl::symm::Cipher;

const TAG_LENGTH: usize = 16;

pub fn decrypt(encrypted_data: &[u8], key: &[u8], iv: &[u8]) -> Result<Vec<u8>> {
pub fn decrypt_with_aad(
key: &[u8],
encrypted_data: &[u8],
iv: &[u8],
aad: &[u8],
tag: &[u8],
) -> Result<Vec<u8>> {
let cipher = Cipher::aes_256_gcm();

openssl::symm::decrypt_aead(cipher, key, Some(iv), aad, encrypted_data, tag)
.map_err(|e| anyhow!("{e:?}"))
}

pub fn decrypt(key: &[u8], encrypted_data: &[u8], iv: &[u8]) -> Result<Vec<u8>> {
let cipher = Cipher::aes_256_gcm();
if encrypted_data.len() < TAG_LENGTH {
bail!("Illegal length of ciphertext");
Expand All @@ -21,7 +34,7 @@ pub fn decrypt(encrypted_data: &[u8], key: &[u8], iv: &[u8]) -> Result<Vec<u8>>
.map_err(|e| anyhow!(e.to_string()))
}

pub fn encrypt(data: &[u8], key: &[u8], iv: &[u8]) -> Result<Vec<u8>> {
pub fn encrypt(key: &[u8], data: &[u8], iv: &[u8]) -> Result<Vec<u8>> {
let cipher = Cipher::aes_256_gcm();
let mut tag = [0u8; TAG_LENGTH];
let mut ciphertext = openssl::symm::encrypt_aead(cipher, key, Some(iv), &[], data, &mut tag)
Expand All @@ -37,11 +50,11 @@ mod tests {
use super::{decrypt, encrypt};

#[rstest]
#[case(b"plaintext1", b"0123456789abcdefghijklmnopqrstuv", b"unique nonce")]
#[case(b"plaintext2", b"hijklmnopqrstuv0123456789abcdefg", b"unique2nonce")]
fn en_decrypt(#[case] plaintext: &[u8], #[case] key: &[u8], #[case] iv: &[u8]) {
let ciphertext = encrypt(plaintext, key, iv).expect("encryption failed");
let plaintext_de = decrypt(&ciphertext, key, iv).expect("decryption failed");
#[case(b"0123456789abcdefghijklmnopqrstuv", b"plaintext1", b"unique nonce")]
#[case(b"hijklmnopqrstuv0123456789abcdefg", b"plaintext2", b"unique2nonce")]
fn en_decrypt(#[case] key: &[u8], #[case] plaintext: &[u8], #[case] iv: &[u8]) {
let ciphertext = encrypt(key, plaintext, iv).expect("encryption failed");
let plaintext_de = decrypt(key, &ciphertext, iv).expect("decryption failed");
assert_eq!(plaintext, plaintext_de);
}
}
168 changes: 168 additions & 0 deletions attestation-agent/deps/crypto/src/native/ec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright (c) 2024 Alibaba Cloud
//
// SPDX-License-Identifier: Apache-2.0
//

use anyhow::{anyhow, Context, Result};
use openssl::{
aes::{self, AesKey},
bn::{BigNum, BigNumContext},
derive::Deriver,
ec::{EcGroup, EcKey},
hash::{Hasher, MessageDigest},
nid::Nid,
pkey::{PKey, Private},
};

use crate::{
ec::{Curve, KeyWrapAlgorithm},
AES_GCM_256_KEY_BITS,
};

#[derive(Clone, Debug)]
pub enum EcKeyPair {
P256(P256EcKeyPair),
}

impl Default for EcKeyPair {
fn default() -> Self {
Self::P256(P256EcKeyPair::new().expect("Create P256 key pair failed"))
}
}

impl EcKeyPair {
fn private_key(&self) -> &PKey<Private> {
match self {
Self::P256(p256) => p256.private_key(),
}
}

pub fn curve(&self) -> Curve {
match self {
Self::P256(_) => Curve::P256,
}
}

pub fn x(&self) -> Result<Vec<u8>> {
match self {
Self::P256(p256) => p256.x(),
}
}

pub fn y(&self) -> Result<Vec<u8>> {
match self {
Self::P256(p256) => p256.y(),
}
}

pub fn unwrap_key(
&self,
encrypted_key: Vec<u8>,
epk_x: Vec<u8>,
epk_y: Vec<u8>,
wrapping_algorithm: KeyWrapAlgorithm,
) -> Result<Vec<u8>> {
let group = match self.curve() {
Curve::P256 => EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?,
};
let point = group.generator();
let mut point = point.to_owned(&group)?;

let epk_x = BigNum::from_slice(&epk_x)?;
let epk_y = BigNum::from_slice(&epk_y)?;

let mut ctx = BigNumContext::new()?;
point.set_affine_coordinates_gfp(&group, &epk_x, &epk_y, &mut ctx)?;

let epk = EcKey::from_public_key(&group, &point)?;
let epk = PKey::from_ec_key(epk)?;
match wrapping_algorithm {
KeyWrapAlgorithm::EcdhEsA256Kw => {
let mut deriver = Deriver::new(self.private_key())?;
deriver.set_peer(&epk)?;
let z = deriver.derive_to_vec()?;
let shared_key = concat_kdf(
KeyWrapAlgorithm::EcdhEsA256Kw.as_ref(),
AES_GCM_256_KEY_BITS as usize / 8,
&z,
)?;
let mut key = vec![0; encrypted_key.len() - 8];
let unwrapping_key = AesKey::new_decrypt(&shared_key)
.map_err(|e| anyhow!("failed to create AES unwrapping key: {e:?}"))?;
aes::unwrap_key(&unwrapping_key, None, &mut key, &encrypted_key)
.map_err(|e| anyhow!("failed to unwrap key: {e:?}"))?;
Ok(key)
}
}
}
}

fn concat_kdf(alg: &str, target_length: usize, z: &[u8]) -> Result<Vec<u8>> {
let target_length_bytes = ((target_length * 8) as u32).to_be_bytes();
let alg_len_bytes = (alg.len() as u32).to_be_bytes();

let mut output = Vec::new();
let md = MessageDigest::sha256();
let count = target_length.div_ceil(md.size());
for i in 0..count {
let mut hasher = Hasher::new(md)?;
hasher.update(&((i + 1) as u32).to_be_bytes())?;
hasher.update(z)?;
hasher.update(&alg_len_bytes)?;
hasher.update(alg.as_bytes())?;
hasher.update(&0_u32.to_be_bytes())?;
hasher.update(&0_u32.to_be_bytes())?;
hasher.update(&target_length_bytes)?;

let digest = hasher.finish()?;
output.extend(digest.to_vec());
}

if output.len() > target_length {
output.truncate(target_length);
}

Ok(output)
}

#[derive(Clone, Debug)]
pub struct P256EcKeyPair {
private_key: PKey<Private>,
}

impl P256EcKeyPair {
fn private_key(&self) -> &PKey<Private> {
&self.private_key
}

pub fn new() -> Result<Self> {
let ec_group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?;
let ec_key = EcKey::generate(&ec_group)?;
let private_key = PKey::from_ec_key(ec_key)?;
Ok(Self { private_key })
}

pub fn x(&self) -> Result<Vec<u8>> {
let private_key = self.private_key.ec_key().context("must be a ec key")?;
let public_key = private_key.public_key();
let mut x = BigNum::new()?;
let mut _y = BigNum::new()?;
let mut ctx = BigNumContext::new()?;
public_key.affine_coordinates_gfp(private_key.group(), &mut x, &mut _y, &mut ctx)?;
let mut x = x.to_vec();
x.resize(32, b'0');
Ok(x)
}

pub fn y(&self) -> Result<Vec<u8>> {
let private_key = self.private_key.ec_key().context("must be a ec key")?;
let public_key = private_key.public_key();
let mut _x = BigNum::new()?;
let mut y = BigNum::new()?;
let mut ctx = BigNumContext::new()?;
public_key.affine_coordinates_gfp(private_key.group(), &mut _x, &mut y, &mut ctx)?;
let mut y = y.to_vec();
y.resize(32, b'0');
Ok(y)
}
}
1 change: 1 addition & 0 deletions attestation-agent/deps/crypto/src/native/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
pub mod aes256ctr;
pub mod aes256gcm;

pub mod ec;
pub mod rsa;
1 change: 1 addition & 0 deletions attestation-agent/deps/crypto/src/native/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ impl RSAKeyPair {
.private_key
.private_decrypt(&cipher_text, &mut plaintext, Padding::PKCS1_OAEP)
.map_err(|e| anyhow!("RSA key decrypt OAEP failed: {:?}", e))?,
#[allow(deprecated)]
PaddingMode::PKCS1v15 => self
.private_key
.private_decrypt(&cipher_text, &mut plaintext, Padding::PKCS1)
Expand Down
Loading

0 comments on commit 2ba4165

Please sign in to comment.