Skip to content

Commit

Permalink
Add support of Octet String and X509 bytes encoding (#255)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hanson Char committed Oct 27, 2023
1 parent 1bdaefe commit be38de5
Show file tree
Hide file tree
Showing 3 changed files with 324 additions and 39 deletions.
289 changes: 268 additions & 21 deletions aws-lc-rs/src/agreement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,23 @@
//! ```
use crate::ec::{
ec_group_from_nid, ec_point_from_bytes, evp_key_generate, evp_pkey_from_public_point,
evp_pkey_from_x509_pubkey, marshal_x509_public_key_to_buffer, PUBLIC_KEY_MAX_LEN,
};
use crate::error::Unspecified;
use crate::fips::indicator_check;
use crate::ptr::LcPtr;
use crate::rand::SecureRandom;
use crate::{ec, test};
use aws_lc::{
EVP_PKEY_CTX_new, EVP_PKEY_CTX_new_id, EVP_PKEY_derive, EVP_PKEY_derive_init,
EVP_PKEY_derive_set_peer, EVP_PKEY_get_raw_public_key, EVP_PKEY_keygen, EVP_PKEY_keygen_init,
EVP_PKEY_new_raw_public_key, NID_X9_62_prime256v1, NID_secp384r1, NID_secp521r1, EVP_PKEY,
EVP_PKEY_X25519, NID_X25519,
d2i_X509_PUBKEY, EVP_PKEY_CTX_new, EVP_PKEY_CTX_new_id, EVP_PKEY_derive, EVP_PKEY_derive_init,
EVP_PKEY_derive_set_peer, EVP_PKEY_get_raw_private_key, EVP_PKEY_get_raw_public_key,
EVP_PKEY_keygen, EVP_PKEY_keygen_init, EVP_PKEY_new_raw_public_key, NID_X9_62_prime256v1,
NID_secp384r1, NID_secp521r1, EVP_PKEY, EVP_PKEY_X25519, NID_X25519, X25519_PUBLIC_VALUE_LEN,
};

use core::fmt;
use std::fmt::{Debug, Formatter};
use std::os::raw::{c_long, c_uchar};
use std::ptr::null_mut;

#[allow(non_camel_case_types)]
Expand Down Expand Up @@ -138,6 +140,30 @@ pub static ECDH_P521: Algorithm = Algorithm {
id: AlgorithmID::ECDH_P521,
};

#[derive(Debug, PartialEq, Eq)]
enum EncodingID {
// A sequence of 8-bit bytes
OctetString,
// X509 DER encoding
X509,
}

/// Encoding of bytes
#[derive(Debug, PartialEq, Eq)]
pub struct Encoding {
id: EncodingID,
}

/// Octet String encoding
pub static OCTET_STRING: Encoding = Encoding {
id: EncodingID::OctetString,
};

/// X509 DER encoding
pub static X509: Encoding = Encoding {
id: EncodingID::X509,
};

/// X25519 (ECDH using Curve25519) as described in [RFC 7748].
///
/// Everything is as described in RFC 7748. Key agreement will fail if the
Expand All @@ -149,7 +175,6 @@ pub static ECDH_P521: Algorithm = Algorithm {
pub static X25519: Algorithm = Algorithm {
id: AlgorithmID::X25519,
};
#[cfg(test)]
const X25519_PRIVATE_KEY_LEN: usize = aws_lc::X25519_PRIVATE_KEY_LEN as usize;
#[cfg(test)]
const ECDH_P256_PRIVATE_KEY_LEN: usize = 32;
Expand Down Expand Up @@ -446,6 +471,7 @@ impl Clone for PublicKey {
pub struct UnparsedPublicKey<B: AsRef<[u8]>> {
alg: &'static Algorithm,
bytes: B,
encoding: &'static Encoding,
}

impl<B: Copy + AsRef<[u8]>> Copy for UnparsedPublicKey<B> {}
Expand All @@ -461,23 +487,42 @@ impl<B: Debug + AsRef<[u8]>> Debug for UnparsedPublicKey<B> {
}

impl<B: AsRef<[u8]>> UnparsedPublicKey<B> {
/// Constructs a new `UnparsedPublicKey`.
/// Constructs a new `UnparsedPublicKey` with [`OCTET_STRING`] encoding.
pub fn new(algorithm: &'static Algorithm, bytes: B) -> Self {
UnparsedPublicKey {
alg: algorithm,
bytes,
encoding: &OCTET_STRING,
}
}

/// The agreement algorithm associated with this public key
/// Constructs a new `UnparsedPublicKey` with the specified [Encoding].
pub fn new_with_encoding(
algorithm: &'static Algorithm,
bytes: B,
encoding: &'static Encoding,
) -> Self {
UnparsedPublicKey {
alg: algorithm,
bytes,
encoding,
}
}

/// The agreement algorithm associated with this public key.
pub fn algorithm(&self) -> &'static Algorithm {
self.alg
}

/// The bytes provided for this public key
/// The bytes provided for this public key.
pub fn bytes(&self) -> &B {
&self.bytes
}

/// The encoding of the bytes provided for this public key.
pub fn encoding(&self) -> &'static Encoding {
self.encoding
}
}

/// Performs a key agreement with an ephemeral private key and the given public
Expand Down Expand Up @@ -513,7 +558,7 @@ impl<B: AsRef<[u8]>> UnparsedPublicKey<B> {
#[inline]
#[allow(clippy::needless_pass_by_value)]
#[allow(clippy::missing_panics_doc)]
pub fn agree_ephemeral<B: AsRef<[u8]>, F, R, E>(
pub fn agree_ephemeral<B: AsRef<[u8]>, F, R, E: Copy>(
my_private_key: EphemeralPrivateKey,
peer_public_key: &UnparsedPublicKey<B>,
error_value: E,
Expand All @@ -530,25 +575,90 @@ where
return Err(error_value);
}
let peer_pub_bytes = peer_public_key.bytes.as_ref();
if peer_pub_bytes.len() != expected_pub_key_len {
return Err(error_value);
if peer_public_key.encoding == &OCTET_STRING {
let peer_pub_bytes_len = peer_pub_bytes.len();
if peer_pub_bytes_len != expected_pub_key_len {
return Err(error_value);
}
}

let mut buffer = [0u8; MAX_AGREEMENT_SECRET_LEN];

let secret: &[u8] = match &my_private_key.inner_key {
KeyInner::X25519(priv_key) => {
x25519_diffie_hellman(&mut buffer, priv_key, peer_pub_bytes).or(Err(error_value))?
}
KeyInner::ECDH_P256(priv_key)
| KeyInner::ECDH_P384(priv_key)
| KeyInner::ECDH_P521(priv_key) => {
ec_key_ecdh(&mut buffer, priv_key, peer_pub_bytes, expected_nid).or(Err(error_value))?
KeyInner::X25519(priv_key, ..) => match peer_public_key.encoding.id {
EncodingID::OctetString => {
x25519_dh_with_octstr_peer_pubkey(
peer_pub_bytes,
&mut buffer,
priv_key,
error_value,
)?;
&buffer[0..X25519_SHARED_KEY_LEN]
}
EncodingID::X509 => {
x25519_dh_with_x509_peer_pubkey(
peer_pub_bytes,
&mut buffer,
priv_key,
error_value,
)?;
&buffer[0..X25519_SHARED_KEY_LEN]
}
},
KeyInner::ECDH_P256(ec_key) | KeyInner::ECDH_P384(ec_key) | KeyInner::ECDH_P521(ec_key) => {
let pub_key_bytes = peer_public_key.bytes.as_ref();
ec_key_ecdh(
&mut buffer,
ec_key,
pub_key_bytes,
peer_public_key.encoding,
expected_nid,
)
.or(Err(error_value))?
}
};
kdf(secret)
}

#[inline]
fn x25519_dh_with_octstr_peer_pubkey<E>(
peer_pub_bytes: &[u8],
shared_secret: &mut [u8; MAX_AGREEMENT_SECRET_LEN],
priv_key: &LcPtr<EVP_PKEY>,
error_value: E,
) -> Result<(), E> {
let mut pub_key = [0u8; X25519_PUBLIC_VALUE_LEN as usize];
pub_key.copy_from_slice(peer_pub_bytes);
x25519_diffie_hellman(shared_secret, priv_key, &pub_key).map_err(|()| error_value)?;
Ok(())
}

#[inline]
fn x25519_dh_with_x509_peer_pubkey<E: Copy>(
peer_x509_pubkey: &[u8],
shared_secret: &mut [u8; MAX_AGREEMENT_SECRET_LEN],
priv_key: &LcPtr<EVP_PKEY>,
error_value: E,
) -> Result<(), E> {
let len = match c_long::try_from(peer_x509_pubkey.len()) {
Ok(len) => len,
Err(_) => return Err(error_value),
};
let x509_pubkey = LcPtr::new(unsafe {
d2i_X509_PUBKEY(
null_mut(),
&peer_x509_pubkey.as_ptr() as *const *const u8 as *mut *const c_uchar,
len,
)
})
.map_err(|()| error_value)?;
let mut peer_octstr_pubkey = [0u8; PUBLIC_KEY_MAX_LEN];
marshal_x509_public_key_to_buffer(&mut peer_octstr_pubkey, &x509_pubkey)
.map_err(|_| error_value)?;
x25519_diffie_hellman_x509(shared_secret, priv_key, &peer_octstr_pubkey)
.map_err(|()| error_value)
}

// Current max secret length is P-521's.
const MAX_AGREEMENT_SECRET_LEN: usize = ECDH_P521_PRIVATE_KEY_LEN;

Expand All @@ -558,11 +668,18 @@ fn ec_key_ecdh<'a>(
buffer: &'a mut [u8; MAX_AGREEMENT_SECRET_LEN],
priv_key: &LcPtr<EVP_PKEY>,
peer_pub_key_bytes: &[u8],
peer_pub_key_bytes_encoding: &Encoding,
nid: i32,
) -> Result<&'a [u8], ()> {
let ec_group = unsafe { ec_group_from_nid(nid)? };
let pub_key_point = unsafe { ec_point_from_bytes(&ec_group, peer_pub_key_bytes) }?;
let pub_key = unsafe { evp_pkey_from_public_point(&ec_group, &pub_key_point) }?;
// let peer_ec_key = match peer_pub_key_bytes_encoding.id {
let pub_key = match peer_pub_key_bytes_encoding.id {
EncodingID::OctetString => {
let ec_group = unsafe { ec_group_from_nid(nid)? };
let pub_key_point = unsafe { ec_point_from_bytes(&ec_group, peer_pub_key_bytes)? };
unsafe { evp_pkey_from_public_point(&ec_group, &pub_key_point)? }
}
EncodingID::X509 => evp_pkey_from_x509_pubkey(peer_pub_key_bytes)?,
};

let pkey_ctx = LcPtr::new(unsafe { EVP_PKEY_CTX_new(**priv_key, null_mut()) })?;

Expand Down Expand Up @@ -627,10 +744,42 @@ fn x25519_diffie_hellman<'a>(
Ok(&buffer[0..X25519_SHARED_KEY_LEN])
}

#[inline]
fn x25519_diffie_hellman_x509(
buffer: &mut [u8],
evp_priv_key: &LcPtr<EVP_PKEY>,
peer_pub_key: &[u8],
) -> Result<(), ()> {
let mut priv_key = [0u8; X25519_PRIVATE_KEY_LEN];
if 1 != unsafe {
EVP_PKEY_get_raw_private_key(
**evp_priv_key,
priv_key.as_mut_ptr(),
&priv_key.len() as *const _ as *mut usize,
)
} {
return Err(());
}
debug_assert!(buffer.len() >= X25519_SHARED_KEY_LEN);
if 1 != unsafe {
aws_lc::X25519(
buffer.as_mut_ptr(),
priv_key.as_ptr(),
peer_pub_key.as_ptr(),
)
} {
return Err(());
}
Ok(())
}

#[cfg(test)]
mod tests {
use crate::agreement::Algorithm;
use crate::error::Unspecified;
use crate::rand::SystemRandom;
use crate::{agreement, rand, test, test_file};
use aws_lc::EVP_DecodeBase64;

#[cfg(feature = "fips")]
mod fips;
Expand Down Expand Up @@ -1036,4 +1185,102 @@ mod tests {
Ok(Vec::from(agreed_value))
})
}

fn agreement_agree_ephemeral_x509_peer_pubkey_(
x509_peer_public_key: &str,
algorithm: &'static Algorithm,
rng: &SystemRandom,
) {
const SUCCESS: i32 = 1;
let in_ = x509_peer_public_key.as_bytes();
let max_out: usize = in_.len() * 3 / 4;
let mut out = vec![0u8; max_out];
let mut out_len: usize = 0;
// EVP_DecodeBase64 decodes in_len bytes from base64 and writes *out_len bytes to out.
// max_out is the size of the output buffer.
// If it is not enough for the maximum output size, the operation fails.
// It returns one on success or zero on error.
let ret = unsafe {
EVP_DecodeBase64(
out.as_mut_ptr(),
&mut out_len,
max_out,
in_.as_ptr(),
in_.len(),
)
};
assert_eq!(ret, SUCCESS);
assert!(out_len <= max_out);
out.truncate(out_len);
let my_private_key = agreement::EphemeralPrivateKey::generate(algorithm, rng)
.expect("Failed to generate key");
let peer_public_key = {
agreement::UnparsedPublicKey::new_with_encoding(
algorithm,
out.as_slice(),
&agreement::X509,
)
};
agreement::agree_ephemeral(
my_private_key,
&peer_public_key,
Unspecified,
|key_material| {
let expected_key_len = match algorithm.id {
agreement::AlgorithmID::ECDH_P256 | agreement::AlgorithmID::X25519 => 32,
agreement::AlgorithmID::ECDH_P384 => 48,
agreement::AlgorithmID::ECDH_P521 => 66,
};
assert_eq!(key_material.len(), expected_key_len);
Ok(())
},
)
.expect("Failed in agree_ephemeral");
}

#[test]
fn agreement_agree_ephemeral_x509_peer_pubkey() {
const ECDH_P256_X509_PEER_PUBLIC_KEY: &str = concat!(
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECKby9ft1JuAokk2umfeafRyOL1pN",
"Z3T+LVvb5Cx6f/ngOdL2vJM2hQy9S962OodCQq0ZnhXE4KjnvycuFvCvOg==",
);
const ECDH_P384_X509_PEER_PUBLIC_KEY: &str = concat!(
"MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEInBNE3e20bBl8ZM9zbxPWP0oF7/vRTou",
"BNayEEitfB2HUQ45TltlAvq2LbaF08687o5jQOAJfDA6T5mKn6/19MwX5zI7Wt7/",
"xCYH0kg7Bz26I1hi6XfhQ49Owhh0BMKH",
);
const ECDH_P521_X509_PEER_PUBLIC_KEY: &str = concat!(
"MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA/KHBKOQAB4kAyQ/ED7GdO+ICswP7s",
"stFf7nljB5Unbu5fTpVTwqR1GGWWBnnew58gSU1h5ECwSOpMY7vfGbIGHUAIZ0V9Y",
"j3Foo+FvAoE4dog1Gy+LpK+cXbacpZhQQrzBnWMeicZjs3R7esCymjjtULEZjVple",
"HDYiVLAHdHIzfYdo=",
);
const X25519_X509_PEER_PUBLIC_KEY: &str =
concat!("MCowBQYDK2VuAyEAXei13Z9eh6EzPZ+OvAFha+rIMANFwT6IN5w6tMm9A0Y=",);

let rng = rand::SystemRandom::new();
agreement_agree_ephemeral_x509_peer_pubkey_(
ECDH_P256_X509_PEER_PUBLIC_KEY,
&agreement::ECDH_P256,
&rng,
);

agreement_agree_ephemeral_x509_peer_pubkey_(
ECDH_P384_X509_PEER_PUBLIC_KEY,
&agreement::ECDH_P384,
&rng,
);

agreement_agree_ephemeral_x509_peer_pubkey_(
ECDH_P521_X509_PEER_PUBLIC_KEY,
&agreement::ECDH_P521,
&rng,
);

agreement_agree_ephemeral_x509_peer_pubkey_(
X25519_X509_PEER_PUBLIC_KEY,
&agreement::X25519,
&rng,
);
}
}
Loading

0 comments on commit be38de5

Please sign in to comment.