Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 4 additions & 1 deletion src/cryptography/hazmat/backends/openssl/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,10 @@ def x448_supported(self) -> bool:
)

def mlkem_supported(self) -> bool:
return rust_openssl.CRYPTOGRAPHY_IS_AWSLC
return (
rust_openssl.CRYPTOGRAPHY_IS_AWSLC
or rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
)

def mldsa_supported(self) -> bool:
return (
Expand Down
13 changes: 7 additions & 6 deletions src/rust/cryptography-key-parsing/src/pkcs8.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub struct PrivateKeyInfo<'a> {
}

// RFC 9935 Section 6
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
pub enum MlKemPrivateKey {
#[implicit(0)]
Expand All @@ -43,7 +43,8 @@ pub enum MlDsaPrivateKey {
/// AWS-LC's `raw_private_key()` returns the 2400-byte expanded key, not the seed.
/// Since AWS-LC 1.72.0, `private_key_to_pkcs8()` produces RFC 9935 seed-format
/// PKCS#8 when the key was created from a seed, so we round-trip through that.
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
/// BoringSSL's private key serialization also emits RFC 9935 seed-format PKCS#8.
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
pub fn mlkem_seed_from_pkey(
pkey: &openssl::pkey::PKeyRef<openssl::pkey::Private>,
) -> Result<MlKemPrivateKey, openssl::error::ErrorStack> {
Expand Down Expand Up @@ -154,7 +155,7 @@ pub fn parse_private_key(data: &[u8]) -> KeyParsingResult<ParsedPrivateKey> {
Ok(ParsedPrivateKey::Pkey(pkey))
}

#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
AlgorithmParameters::MlKem768 => {
let MlKemPrivateKey::Seed(seed) = asn1::parse_single::<MlKemPrivateKey>(k.private_key)?;
let pkey = cryptography_openssl::mlkem::new_raw_private_key(
Expand All @@ -164,7 +165,7 @@ pub fn parse_private_key(data: &[u8]) -> KeyParsingResult<ParsedPrivateKey> {
Ok(ParsedPrivateKey::Pkey(pkey))
}

#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
AlgorithmParameters::MlKem1024 => {
let MlKemPrivateKey::Seed(seed) = asn1::parse_single::<MlKemPrivateKey>(k.private_key)?;
let pkey = cryptography_openssl::mlkem::new_raw_private_key(
Expand Down Expand Up @@ -541,8 +542,8 @@ pub fn serialize_private_key(key: &ParsedPrivateKey) -> crate::KeySerializationR

(params, private_key_der)
}
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
cryptography_openssl::mlkem::PKEY_ID => {
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
id if cryptography_openssl::mlkem::is_mlkem_pkey_type(id) => {
let private_key_der = asn1::write_single(&mlkem_seed_from_pkey(pkey)?)?;
let params = match cryptography_openssl::mlkem::MlKemVariant::from_pkey(pkey) {
cryptography_openssl::mlkem::MlKemVariant::MlKem768 => {
Expand Down
8 changes: 4 additions & 4 deletions src/rust/cryptography-key-parsing/src/spki.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,15 @@ pub fn parse_public_key(data: &[u8]) -> KeyParsingResult<ParsedPublicKey> {

Ok(ParsedPublicKey::Pkey(openssl::pkey::PKey::from_dh(dh)?))
}
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
AlgorithmParameters::MlKem768 => Ok(ParsedPublicKey::Pkey(
cryptography_openssl::mlkem::new_raw_public_key(
cryptography_openssl::mlkem::MlKemVariant::MlKem768,
k.subject_public_key.as_bytes(),
)
.map_err(|_| KeyParsingError::InvalidKey)?,
)),
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
AlgorithmParameters::MlKem1024 => Ok(ParsedPublicKey::Pkey(
cryptography_openssl::mlkem::new_raw_public_key(
cryptography_openssl::mlkem::MlKemVariant::MlKem1024,
Expand Down Expand Up @@ -264,8 +264,8 @@ pub fn serialize_public_key(

(params, pub_key_der)
}
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
cryptography_openssl::mlkem::PKEY_ID => {
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
id if cryptography_openssl::mlkem::is_mlkem_pkey_type(id) => {
let raw_bytes = pkey.raw_public_key()?;
let params = match cryptography_openssl::mlkem::MlKemVariant::from_pkey(pkey) {
cryptography_openssl::mlkem::MlKemVariant::MlKem768 => {
Expand Down
2 changes: 1 addition & 1 deletion src/rust/cryptography-openssl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub mod fips;
pub mod hmac;
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
pub mod mldsa;
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
pub mod mlkem;
#[cfg(any(
CRYPTOGRAPHY_IS_BORINGSSL,
Expand Down
171 changes: 122 additions & 49 deletions src/rust/cryptography-openssl/src/mlkem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,33 @@

use foreign_types_shared::ForeignType;
use openssl_sys as ffi;
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
use std::os::raw::c_int;

use crate::{cvt, cvt_p, OpenSSLResult};

#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
pub const PKEY_ID: openssl::pkey::Id = openssl::pkey::Id::from_raw(ffi::NID_kem);

pub fn is_mlkem_pkey_type(id: openssl::pkey::Id) -> bool {
cfg_if::cfg_if! {
if #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] {
let raw = id.as_raw();
raw == ffi::NID_ML_KEM_768 || raw == ffi::NID_ML_KEM_1024
} else if #[cfg(CRYPTOGRAPHY_IS_AWSLC)] {
id == PKEY_ID
}
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MlKemVariant {
MlKem768,
MlKem1024,
}

impl MlKemVariant {
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
pub fn nid(self) -> c_int {
match self {
MlKemVariant::MlKem768 => ffi::NID_MLKEM768,
Expand All @@ -27,20 +41,44 @@ impl MlKemVariant {
pub fn from_pkey<T: openssl::pkey::HasPublic>(
pkey: &openssl::pkey::PKeyRef<T>,
) -> MlKemVariant {
// AWS-LC is missing the equivalent `EVP_PKEY_pqdsa_get_type`, so we
// are using the key size as a discriminator to find the variant.
let len = pkey
.raw_public_key()
.expect("valid ML-KEM public key")
.len();
match len {
1184 => MlKemVariant::MlKem768,
1568 => MlKemVariant::MlKem1024,
_ => panic!("Unsupported ML-KEM variant"),
cfg_if::cfg_if! {
if #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] {
match pkey.id().as_raw() {
ffi::NID_ML_KEM_768 => MlKemVariant::MlKem768,
ffi::NID_ML_KEM_1024 => MlKemVariant::MlKem1024,
_ => panic!("Unsupported ML-KEM variant"),
}
} else if #[cfg(CRYPTOGRAPHY_IS_AWSLC)] {
// AWS-LC is missing the equivalent `EVP_PKEY_pqdsa_get_type`,
// so we are using the key size as a discriminator to find the
// variant.
let len = pkey
.raw_public_key()
.expect("valid ML-KEM public key")
.len();
match len {
1184 => MlKemVariant::MlKem768,
1568 => MlKemVariant::MlKem1024,
_ => panic!("Unsupported ML-KEM variant"),
}
}
}
}
}

#[cfg(CRYPTOGRAPHY_IS_BORINGSSL)]
fn evp_pkey_alg(variant: MlKemVariant) -> *const ffi::EVP_PKEY_ALG {
// SAFETY: These functions return static, non-null pointers to the
// EVP_PKEY_ALG for each ML-KEM variant.
unsafe {
match variant {
MlKemVariant::MlKem768 => ffi::EVP_pkey_ml_kem_768(),
MlKemVariant::MlKem1024 => ffi::EVP_pkey_ml_kem_1024(),
}
}
}

#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
extern "C" {
// Manually declared because this function is in an experimental header
// in AWS-LC (April 2026).
Expand All @@ -57,49 +95,74 @@ pub fn new_raw_private_key(
variant: MlKemVariant,
seed: &[u8],
) -> OpenSSLResult<openssl::pkey::PKey<openssl::pkey::Private>> {
let ctx = openssl::pkey_ctx::PkeyCtx::new_id(PKEY_ID)?;
// SAFETY: ctx is a valid EVP_PKEY_CTX for KEM.
unsafe {
cvt(ffi::EVP_PKEY_CTX_kem_set_params(
ctx.as_ptr(),
variant.nid(),
))?
};
// SAFETY: ctx is a valid EVP_PKEY_CTX with KEM params set.
unsafe { cvt(ffi::EVP_PKEY_keygen_init(ctx.as_ptr()))? };

let mut pkey: *mut ffi::EVP_PKEY = std::ptr::null_mut();
let mut seed_len = seed.len();
// SAFETY: ctx is initialized for keygen, seed points to valid memory.
unsafe {
cvt(EVP_PKEY_keygen_deterministic(
ctx.as_ptr(),
&mut pkey,
seed.as_ptr(),
&mut seed_len,
))?;
cfg_if::cfg_if! {
if #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] {
// SAFETY: EVP_PKEY_from_private_seed creates a new EVP_PKEY from
// the seed. evp_pkey_alg returns a valid algorithm pointer.
unsafe {
let pkey = cvt_p(ffi::EVP_PKEY_from_private_seed(
evp_pkey_alg(variant),
seed.as_ptr(),
seed.len(),
))?;
Ok(openssl::pkey::PKey::from_ptr(pkey))
}
} else if #[cfg(CRYPTOGRAPHY_IS_AWSLC)] {
let ctx = openssl::pkey_ctx::PkeyCtx::new_id(PKEY_ID)?;
// SAFETY: ctx is a valid EVP_PKEY_CTX for KEM.
unsafe {
cvt(ffi::EVP_PKEY_CTX_kem_set_params(
ctx.as_ptr(),
variant.nid(),
))?
};
// SAFETY: ctx is a valid EVP_PKEY_CTX with KEM params set.
unsafe { cvt(ffi::EVP_PKEY_keygen_init(ctx.as_ptr()))? };

let mut pkey: *mut ffi::EVP_PKEY = std::ptr::null_mut();
let mut seed_len = seed.len();
// SAFETY: ctx is initialized for keygen, seed points to valid memory.
unsafe {
cvt(EVP_PKEY_keygen_deterministic(
ctx.as_ptr(),
&mut pkey,
seed.as_ptr(),
&mut seed_len,
))?;
}
assert_eq!(seed_len, 64);
// SAFETY: EVP_PKEY_keygen_deterministic succeeded, pkey is valid.
let pkey = unsafe { openssl::pkey::PKey::from_ptr(pkey) };
Ok(pkey)
}
}
let expected_seed_len = match variant {
MlKemVariant::MlKem768 | MlKemVariant::MlKem1024 => 64,
};
assert_eq!(seed_len, expected_seed_len);
// SAFETY: EVP_PKEY_keygen_deterministic succeeded, pkey is valid.
let pkey = unsafe { openssl::pkey::PKey::from_ptr(pkey) };
Ok(pkey)
}

pub fn new_raw_public_key(
variant: MlKemVariant,
data: &[u8],
) -> OpenSSLResult<openssl::pkey::PKey<openssl::pkey::Public>> {
// SAFETY: data points to valid memory of the given length.
unsafe {
let pkey = cvt_p(ffi::EVP_PKEY_kem_new_raw_public_key(
variant.nid(),
data.as_ptr(),
data.len(),
))?;
Ok(openssl::pkey::PKey::from_ptr(pkey))
cfg_if::cfg_if! {
if #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] {
let nid = match variant {
MlKemVariant::MlKem768 => ffi::NID_ML_KEM_768,
MlKemVariant::MlKem1024 => ffi::NID_ML_KEM_1024,
};
openssl::pkey::PKey::public_key_from_raw_bytes(
data,
openssl::pkey::Id::from_raw(nid),
)
} else if #[cfg(CRYPTOGRAPHY_IS_AWSLC)] {
// SAFETY: data points to valid memory of the given length.
unsafe {
let pkey = cvt_p(ffi::EVP_PKEY_kem_new_raw_public_key(
variant.nid(),
data.as_ptr(),
data.len(),
))?;
Ok(openssl::pkey::PKey::from_ptr(pkey))
}
}
}
}

Expand All @@ -111,6 +174,12 @@ pub fn encapsulate(
MlKemVariant::MlKem1024 => (1568, 32),
};
let ctx = openssl::pkey_ctx::PkeyCtx::new(pkey)?;
#[cfg(CRYPTOGRAPHY_IS_BORINGSSL)]
{
// SAFETY: ctx is a valid EVP_PKEY_CTX for the KEM operation.
let res = unsafe { ffi::EVP_PKEY_encapsulate_init(ctx.as_ptr(), std::ptr::null()) };
cvt(res)?;
}

let mut ciphertext = vec![0u8; ct_bytes];
let mut shared_secret = vec![0u8; ss_bytes];
Expand All @@ -136,10 +205,14 @@ pub fn decapsulate(
ciphertext: &[u8],
) -> OpenSSLResult<Vec<u8>> {
let ctx = openssl::pkey_ctx::PkeyCtx::new(pkey)?;
#[cfg(CRYPTOGRAPHY_IS_BORINGSSL)]
{
// SAFETY: ctx is a valid EVP_PKEY_CTX for the KEM operation.
let res = unsafe { ffi::EVP_PKEY_decapsulate_init(ctx.as_ptr(), std::ptr::null()) };
cvt(res)?;
}

let ss_bytes: usize = match MlKemVariant::from_pkey(pkey) {
MlKemVariant::MlKem768 | MlKemVariant::MlKem1024 => 32,
};
let ss_bytes: usize = 32;
let mut shared_secret = vec![0u8; ss_bytes];
let mut ss_len = ss_bytes;
// SAFETY: ctx is a valid EVP_PKEY_CTX, buffers are correctly sized.
Expand Down
8 changes: 4 additions & 4 deletions src/rust/src/backend/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ fn private_key_from_pkey<'p>(
openssl::pkey::Id::DHX => Ok(crate::backend::dh::private_key_from_pkey(pkey)
.into_pyobject(py)?
.into_any()),
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
cryptography_openssl::mlkem::PKEY_ID => {
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
id if cryptography_openssl::mlkem::is_mlkem_pkey_type(id) => {
match cryptography_openssl::mlkem::MlKemVariant::from_pkey(pkey) {
cryptography_openssl::mlkem::MlKemVariant::MlKem768 => {
Ok(crate::backend::mlkem::mlkem768_private_key_from_pkey(pkey)
Expand Down Expand Up @@ -370,8 +370,8 @@ fn public_key_from_pkey<'p>(
openssl::pkey::Id::DHX => Ok(crate::backend::dh::public_key_from_pkey(pkey)
.into_pyobject(py)?
.into_any()),
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
cryptography_openssl::mlkem::PKEY_ID => {
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
id if cryptography_openssl::mlkem::is_mlkem_pkey_type(id) => {
match cryptography_openssl::mlkem::MlKemVariant::from_pkey(pkey) {
cryptography_openssl::mlkem::MlKemVariant::MlKem768 => {
Ok(crate::backend::mlkem::mlkem768_public_key_from_pkey(pkey)
Expand Down
2 changes: 1 addition & 1 deletion src/rust/src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub(crate) mod kdf;
pub(crate) mod keys;
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
pub(crate) mod mldsa;
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
pub(crate) mod mlkem;
pub(crate) mod poly1305;
pub(crate) mod rand;
Expand Down
2 changes: 1 addition & 1 deletion src/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ mod _rust {
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
#[pymodule_export]
use crate::backend::mldsa::mldsa;
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
#[pymodule_export]
use crate::backend::mlkem::mlkem;
#[pymodule_export]
Expand Down