mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
d06d197522
The test cases uses the ml_kem functions, therefore disable the directive for tests. $ cargo test ... --> pv/src/openssl_extensions/ml_kem.rs:5:11 | 5 | #![expect(unused)] | ^^^^^^ | = note: `#[warn(unfulfilled_lint_expectations)]` on by default Reviewed-by: Steffen Eiden <seiden@linux.ibm.com> Signed-off-by: Marc Hartmayer <marc@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
596 lines
19 KiB
Rust
596 lines
19 KiB
Rust
// SPDX-License-Identifier: MIT
|
|
//
|
|
// Copyright IBM Corp.
|
|
|
|
#![cfg_attr(not(test), expect(unused))]
|
|
|
|
use std::ffi::CStr;
|
|
use std::ptr::NonNull;
|
|
|
|
use foreign_types::ForeignType;
|
|
use openssl::error::ErrorStack;
|
|
use openssl::pkey::{KeyType, PKey, Private};
|
|
use openssl::pkey_ctx::PkeyCtx;
|
|
|
|
// automatically generated by rust-bindgen 0.69.5
|
|
|
|
mod ffi {
|
|
#[repr(C)]
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub struct evp_pkey_ctx_st {
|
|
_unused: [u8; 0],
|
|
}
|
|
pub type EvpPkeyCtx = evp_pkey_ctx_st;
|
|
#[repr(C)]
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub struct ossl_param_st {
|
|
pub key: *const ::std::os::raw::c_char,
|
|
pub data_type: ::std::os::raw::c_uint,
|
|
pub data: *mut ::std::os::raw::c_void,
|
|
pub data_size: usize,
|
|
pub return_size: usize,
|
|
}
|
|
pub type OsslParam = ossl_param_st;
|
|
|
|
extern "C" {
|
|
pub fn EVP_PKEY_CTX_new_from_name(
|
|
libctx: *mut ::std::os::raw::c_void,
|
|
name: *const ::std::os::raw::c_char,
|
|
propquery: *const ::std::os::raw::c_char,
|
|
) -> *mut EvpPkeyCtx;
|
|
}
|
|
extern "C" {
|
|
pub fn EVP_PKEY_keygen_init(ctx: *mut EvpPkeyCtx) -> ::std::os::raw::c_int;
|
|
}
|
|
extern "C" {
|
|
pub fn EVP_PKEY_generate(
|
|
ctx: *mut EvpPkeyCtx,
|
|
ppkey: *mut *mut ::std::os::raw::c_void,
|
|
) -> ::std::os::raw::c_int;
|
|
}
|
|
extern "C" {
|
|
pub fn EVP_PKEY_CTX_free(ctx: *mut EvpPkeyCtx);
|
|
}
|
|
extern "C" {
|
|
pub fn EVP_PKEY_encapsulate_init(
|
|
ctx: *mut EvpPkeyCtx,
|
|
params: *const OsslParam,
|
|
) -> ::std::os::raw::c_int;
|
|
}
|
|
extern "C" {
|
|
pub fn EVP_PKEY_encapsulate(
|
|
ctx: *mut EvpPkeyCtx,
|
|
wrappedkey: *mut ::std::os::raw::c_uchar,
|
|
wrappedkeylen: *mut usize,
|
|
genkey: *mut ::std::os::raw::c_uchar,
|
|
genkeylen: *mut usize,
|
|
) -> ::std::os::raw::c_int;
|
|
}
|
|
extern "C" {
|
|
pub fn EVP_PKEY_decapsulate_init(
|
|
ctx: *mut EvpPkeyCtx,
|
|
params: *const OsslParam,
|
|
) -> ::std::os::raw::c_int;
|
|
}
|
|
extern "C" {
|
|
pub fn EVP_PKEY_decapsulate(
|
|
ctx: *mut EvpPkeyCtx,
|
|
unwrapped: *mut ::std::os::raw::c_uchar,
|
|
unwrappedlen: *mut usize,
|
|
wrapped: *const ::std::os::raw::c_uchar,
|
|
wrappedlen: usize,
|
|
) -> ::std::os::raw::c_int;
|
|
}
|
|
}
|
|
|
|
const ML_KEM_512_NAME: &CStr = c"ML-KEM-512";
|
|
const ML_KEM_768_NAME: &CStr = c"ML-KEM-768";
|
|
const ML_KEM_1024_NAME: &CStr = c"ML-KEM-1024";
|
|
|
|
fn ml_kem_name(key_type: KeyType) -> Result<&'static CStr, ErrorStack> {
|
|
if key_type == KeyType::ML_KEM_512 {
|
|
Ok(ML_KEM_512_NAME)
|
|
} else if key_type == KeyType::ML_KEM_768 {
|
|
Ok(ML_KEM_768_NAME)
|
|
} else if key_type == KeyType::ML_KEM_1024 {
|
|
Ok(ML_KEM_1024_NAME)
|
|
} else {
|
|
Err(ErrorStack::get())
|
|
}
|
|
}
|
|
|
|
/// Encapsulate a key
|
|
pub trait PkeyEncapsulateContext {
|
|
/// Initialize the encapsulation operation.
|
|
///
|
|
/// # Errors
|
|
/// Returns an error if the OpenSSL operation fails.
|
|
fn encapsulate_init(&mut self) -> Result<(), ErrorStack>;
|
|
|
|
/// Perform the encapsulation operation.
|
|
///
|
|
/// # Parameters
|
|
/// - `wrappedkey`: Optional buffer to receive the wrapped key.
|
|
/// - `genkey`: Optional buffer to receive the generated key.
|
|
///
|
|
/// # Returns
|
|
/// A tuple of `(wrappedkey_len, genkey_len)` on success.
|
|
///
|
|
/// # Errors
|
|
/// Returns an error if the OpenSSL operation fails.
|
|
fn encapsulate(
|
|
&mut self,
|
|
wrappedkey: Option<&mut [u8]>,
|
|
genkey: Option<&mut [u8]>,
|
|
) -> Result<(usize, usize), ErrorStack>;
|
|
|
|
/// Convenience method to encapsulate into vectors.
|
|
///
|
|
/// # Parameters
|
|
/// - `wrappedkey`: Buffer to receive the wrapped key (ciphertext).
|
|
/// - `genkey`: Buffer to receive the generated key (shared secret).
|
|
///
|
|
/// # Returns
|
|
/// A tuple of `(wrappedkey_len, genkey_len)` on success.
|
|
///
|
|
/// # Errors
|
|
/// Returns an error if the OpenSSL operation fails.
|
|
fn encapsulate_to_vec(
|
|
&mut self,
|
|
wrappedkey: &mut Vec<u8>,
|
|
genkey: &mut Vec<u8>,
|
|
) -> Result<(usize, usize), ErrorStack> {
|
|
let wrappedkey_base = wrappedkey.len();
|
|
let genkey_base = genkey.len();
|
|
|
|
// Query the required output buffer sizes.
|
|
let (wrappedkey_len, genkey_len) = self.encapsulate(None, None)?;
|
|
|
|
wrappedkey.resize(wrappedkey_base + wrappedkey_len, 0);
|
|
genkey.resize(genkey_base + genkey_len, 0);
|
|
|
|
let (wrappedkey_len, genkey_len) = self.encapsulate(
|
|
Some(&mut wrappedkey[wrappedkey_base..]),
|
|
Some(&mut genkey[genkey_base..]),
|
|
)?;
|
|
|
|
wrappedkey.truncate(wrappedkey_base + wrappedkey_len);
|
|
genkey.truncate(genkey_base + genkey_len);
|
|
|
|
Ok((wrappedkey_len, genkey_len))
|
|
}
|
|
}
|
|
|
|
impl<T> PkeyEncapsulateContext for PkeyCtx<T> {
|
|
#[inline]
|
|
fn encapsulate_init(&mut self) -> Result<(), ErrorStack> {
|
|
// SAFETY: self.as_ptr() returns a valid EVP_PKEY_CTX pointer. Parameter-based
|
|
// configuration is intentionally unsupported here, so a null params pointer is passed.
|
|
let ret = unsafe {
|
|
ffi::EVP_PKEY_encapsulate_init(self.as_ptr() as *mut ffi::EvpPkeyCtx, std::ptr::null())
|
|
};
|
|
|
|
if ret == 1 {
|
|
Ok(())
|
|
} else {
|
|
Err(ErrorStack::get())
|
|
}
|
|
}
|
|
|
|
fn encapsulate(
|
|
&mut self,
|
|
wrappedkey: Option<&mut [u8]>,
|
|
genkey: Option<&mut [u8]>,
|
|
) -> Result<(usize, usize), ErrorStack> {
|
|
let mut wrappedkey_len = wrappedkey.as_ref().map_or(0, |buf| buf.len());
|
|
let mut genkey_len = genkey.as_ref().map_or(0, |buf| buf.len());
|
|
|
|
let wrappedkey_ptr = wrappedkey
|
|
.map(|buf| buf.as_mut_ptr())
|
|
.unwrap_or(std::ptr::null_mut());
|
|
let genkey_ptr = genkey
|
|
.map(|buf| buf.as_mut_ptr())
|
|
.unwrap_or(std::ptr::null_mut());
|
|
|
|
// SAFETY: All pointers are either valid mutable buffers or null.
|
|
let ret = unsafe {
|
|
ffi::EVP_PKEY_encapsulate(
|
|
self.as_ptr() as *mut ffi::EvpPkeyCtx,
|
|
wrappedkey_ptr,
|
|
&mut wrappedkey_len,
|
|
genkey_ptr,
|
|
&mut genkey_len,
|
|
)
|
|
};
|
|
|
|
if ret == 1 {
|
|
Ok((wrappedkey_len, genkey_len))
|
|
} else {
|
|
Err(ErrorStack::get())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Decapsulate a key
|
|
pub trait PkeyDecapsulateContext {
|
|
/// Initialize the decapsulation operation.
|
|
///
|
|
/// # Errors
|
|
/// Returns an error if the OpenSSL operation fails.
|
|
fn decapsulate_init(&mut self) -> Result<(), ErrorStack>;
|
|
|
|
/// Perform the decapsulation operation.
|
|
///
|
|
/// # Parameters
|
|
/// - `wrapped`: The wrapped key (ciphertext) to decapsulate.
|
|
/// - `unwrapped`: Optional buffer to receive the unwrapped shared secret.
|
|
///
|
|
/// # Returns
|
|
/// The length of the unwrapped shared secret on success.
|
|
///
|
|
/// # Errors
|
|
/// Returns an error if the OpenSSL operation fails.
|
|
fn decapsulate(
|
|
&mut self,
|
|
wrapped: &[u8],
|
|
unwrapped: Option<&mut [u8]>,
|
|
) -> Result<usize, ErrorStack>;
|
|
|
|
/// Convenience method to decapsulate into a vector.
|
|
///
|
|
/// # Parameters
|
|
/// - `wrapped`: The wrapped key (ciphertext) to decapsulate.
|
|
/// - `unwrapped`: Buffer to receive the unwrapped shared secret.
|
|
///
|
|
/// # Returns
|
|
/// The length of the unwrapped shared secret on success.
|
|
///
|
|
/// # Errors
|
|
/// Returns an error if the OpenSSL operation fails.
|
|
fn decapsulate_to_vec(
|
|
&mut self,
|
|
wrapped: &[u8],
|
|
unwrapped: &mut Vec<u8>,
|
|
) -> Result<usize, ErrorStack> {
|
|
let unwrapped_base = unwrapped.len();
|
|
|
|
// Query the required output buffer size.
|
|
let unwrapped_len = self.decapsulate(wrapped, None)?;
|
|
|
|
unwrapped.resize(unwrapped_base + unwrapped_len, 0);
|
|
|
|
let unwrapped_len = self.decapsulate(wrapped, Some(&mut unwrapped[unwrapped_base..]))?;
|
|
|
|
unwrapped.truncate(unwrapped_base + unwrapped_len);
|
|
|
|
Ok(unwrapped_len)
|
|
}
|
|
}
|
|
|
|
impl<T> PkeyDecapsulateContext for PkeyCtx<T> {
|
|
#[inline]
|
|
fn decapsulate_init(&mut self) -> Result<(), ErrorStack> {
|
|
// SAFETY: self.as_ptr() returns a valid EVP_PKEY_CTX pointer. Parameter-based
|
|
// configuration is intentionally unsupported here, so a null params pointer is passed.
|
|
let ret = unsafe {
|
|
ffi::EVP_PKEY_decapsulate_init(self.as_ptr() as *mut ffi::EvpPkeyCtx, std::ptr::null())
|
|
};
|
|
|
|
if ret == 1 {
|
|
Ok(())
|
|
} else {
|
|
Err(ErrorStack::get())
|
|
}
|
|
}
|
|
|
|
fn decapsulate(
|
|
&mut self,
|
|
wrapped: &[u8],
|
|
unwrapped: Option<&mut [u8]>,
|
|
) -> Result<usize, ErrorStack> {
|
|
let mut unwrapped_len = unwrapped.as_ref().map_or(0, |buf| buf.len());
|
|
|
|
let unwrapped_ptr = unwrapped
|
|
.map(|buf| buf.as_mut_ptr())
|
|
.unwrap_or(std::ptr::null_mut());
|
|
|
|
// SAFETY: All pointers are either valid mutable buffers or null, and wrapped is a valid
|
|
// slice.
|
|
let ret = unsafe {
|
|
ffi::EVP_PKEY_decapsulate(
|
|
self.as_ptr() as *mut ffi::EvpPkeyCtx,
|
|
unwrapped_ptr,
|
|
&mut unwrapped_len,
|
|
wrapped.as_ptr(),
|
|
wrapped.len(),
|
|
)
|
|
};
|
|
|
|
if ret == 1 {
|
|
Ok(unwrapped_len)
|
|
} else {
|
|
Err(ErrorStack::get())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// ML-KEM key generation context.
|
|
///
|
|
/// The inner pointer is guaranteed to be non-null for the lifetime of the context.
|
|
pub struct MlKemKeyCtx {
|
|
ctx: NonNull<ffi::EvpPkeyCtx>,
|
|
}
|
|
|
|
impl MlKemKeyCtx {
|
|
/// Create a new ML-KEM key generation context.
|
|
///
|
|
/// # Parameters
|
|
/// - `key_type`: The ML-KEM key type (`KeyType::ML_KEM_512`, `KeyType::ML_KEM_768`, or
|
|
/// `KeyType::ML_KEM_1024`).
|
|
///
|
|
/// # Errors
|
|
/// Returns an error if the context cannot be created or the key type is unsupported.
|
|
pub fn new(key_type: KeyType) -> Result<Self, ErrorStack> {
|
|
let name = ml_kem_name(key_type)?;
|
|
|
|
// SAFETY: name is a valid null-terminated C string.
|
|
let ctx = unsafe {
|
|
ffi::EVP_PKEY_CTX_new_from_name(std::ptr::null_mut(), name.as_ptr(), std::ptr::null())
|
|
};
|
|
let Some(ctx) = NonNull::new(ctx) else {
|
|
return Err(ErrorStack::get());
|
|
};
|
|
|
|
// SAFETY: ctx is a valid non-null pointer.
|
|
let ret = unsafe { ffi::EVP_PKEY_keygen_init(ctx.as_ptr()) };
|
|
if ret != 1 {
|
|
// SAFETY: ctx is a valid non-null pointer.
|
|
unsafe { ffi::EVP_PKEY_CTX_free(ctx.as_ptr()) };
|
|
return Err(ErrorStack::get());
|
|
}
|
|
|
|
Ok(Self { ctx })
|
|
}
|
|
|
|
/// Generate an ML-KEM keypair.
|
|
///
|
|
/// # Returns
|
|
/// A `PKey` containing both the private and public key.
|
|
///
|
|
/// # Errors
|
|
/// Returns an error if key generation fails.
|
|
pub fn generate(&mut self) -> Result<PKey<Private>, ErrorStack> {
|
|
let mut pkey: *mut std::os::raw::c_void = std::ptr::null_mut();
|
|
|
|
// SAFETY: self.ctx is valid, and pkey is a valid mutable pointer.
|
|
let ret = unsafe { ffi::EVP_PKEY_generate(self.ctx.as_ptr(), &mut pkey) };
|
|
|
|
if ret != 1 || pkey.is_null() {
|
|
return Err(ErrorStack::get());
|
|
}
|
|
|
|
// SAFETY: pkey is a valid EVP_PKEY pointer created by OpenSSL.
|
|
unsafe { Ok(PKey::from_ptr(pkey as *mut openssl_sys::EVP_PKEY)) }
|
|
}
|
|
}
|
|
|
|
impl Drop for MlKemKeyCtx {
|
|
fn drop(&mut self) {
|
|
// SAFETY: self.ctx is guaranteed to be non-null and owned by this context.
|
|
unsafe {
|
|
ffi::EVP_PKEY_CTX_free(self.ctx.as_ptr());
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Generate an ML-KEM keypair.
|
|
///
|
|
/// # Parameters
|
|
/// - `key_type`: The ML-KEM key type (`KeyType::ML_KEM_512`, `KeyType::ML_KEM_768`, or
|
|
/// `KeyType::ML_KEM_1024`).
|
|
///
|
|
/// # Returns
|
|
/// A `PKey` containing both the private and public key.
|
|
///
|
|
/// # Errors
|
|
/// Returns an error if key generation fails.
|
|
pub fn generate_ml_kem(key_type: KeyType) -> Result<PKey<Private>, ErrorStack> {
|
|
let mut ctx = MlKemKeyCtx::new(key_type)?;
|
|
ctx.generate()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn roundtrip_ml_kem(key_type: KeyType, expected_ciphertext_len: usize) {
|
|
let keypair = generate_ml_kem(key_type).expect("Failed to generate ML-KEM keypair");
|
|
|
|
// Extract public key for encapsulation
|
|
let public_key_der = keypair
|
|
.public_key_to_der()
|
|
.expect("Failed to export public key");
|
|
let public_key =
|
|
PKey::public_key_from_der(&public_key_der).expect("Failed to import public key");
|
|
|
|
// Encapsulate: generate ciphertext and shared secret using public key
|
|
let mut ctx_enc =
|
|
PkeyCtx::new(&public_key).expect("Failed to create encapsulation context");
|
|
ctx_enc
|
|
.encapsulate_init()
|
|
.expect("Failed to initialize encapsulation");
|
|
|
|
let mut ciphertext = Vec::new();
|
|
let mut shared_secret_enc = Vec::new();
|
|
ctx_enc
|
|
.encapsulate_to_vec(&mut ciphertext, &mut shared_secret_enc)
|
|
.expect("Failed to encapsulate");
|
|
|
|
assert!(!ciphertext.is_empty(), "Ciphertext should not be empty");
|
|
assert!(
|
|
!shared_secret_enc.is_empty(),
|
|
"Shared secret should not be empty"
|
|
);
|
|
|
|
// Decapsulate: recover shared secret from ciphertext using private key
|
|
let mut ctx_dec = PkeyCtx::new(&keypair).expect("Failed to create decapsulation context");
|
|
ctx_dec
|
|
.decapsulate_init()
|
|
.expect("Failed to initialize decapsulation");
|
|
|
|
let mut shared_secret_dec = Vec::new();
|
|
ctx_dec
|
|
.decapsulate_to_vec(&ciphertext, &mut shared_secret_dec)
|
|
.expect("Failed to decapsulate");
|
|
|
|
// Verify that the shared secrets match
|
|
assert_eq!(
|
|
shared_secret_enc, shared_secret_dec,
|
|
"Shared secrets from encapsulation and decapsulation should match"
|
|
);
|
|
assert_eq!(ciphertext.len(), expected_ciphertext_len);
|
|
assert_eq!(shared_secret_enc.len(), 32);
|
|
}
|
|
|
|
#[test]
|
|
fn test_encapsulate_decapsulate_roundtrip_ml_kem_512() {
|
|
roundtrip_ml_kem(KeyType::ML_KEM_512, 768);
|
|
}
|
|
|
|
#[test]
|
|
fn test_encapsulate_decapsulate_roundtrip_ml_kem_768() {
|
|
roundtrip_ml_kem(KeyType::ML_KEM_768, 1088);
|
|
}
|
|
|
|
#[test]
|
|
fn test_encapsulate_decapsulate_roundtrip_ml_kem_1024() {
|
|
roundtrip_ml_kem(KeyType::ML_KEM_1024, 1568);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ml_kem_key_ctx_new_accepts_supported_key_types() {
|
|
MlKemKeyCtx::new(KeyType::ML_KEM_512).expect("ML-KEM-512 context creation must succeed");
|
|
MlKemKeyCtx::new(KeyType::ML_KEM_768).expect("ML-KEM-768 context creation must succeed");
|
|
MlKemKeyCtx::new(KeyType::ML_KEM_1024).expect("ML-KEM-1024 context creation must succeed");
|
|
}
|
|
|
|
#[test]
|
|
fn test_ml_kem_key_ctx_new_rejects_unsupported_key_type() {
|
|
assert!(
|
|
MlKemKeyCtx::new(KeyType::RSA).is_err(),
|
|
"Unsupported key type must fail"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ml_kem_key_ctx_generate_produces_ml_kem_keypair() {
|
|
let mut ctx = MlKemKeyCtx::new(KeyType::ML_KEM_768)
|
|
.expect("ML-KEM-768 context creation must succeed");
|
|
let keypair = ctx
|
|
.generate()
|
|
.expect("ML-KEM-768 key generation must succeed");
|
|
|
|
let public_key_der = keypair
|
|
.public_key_to_der()
|
|
.expect("Generated keypair must export a public key");
|
|
let public_key =
|
|
PKey::public_key_from_der(&public_key_der).expect("Generated public key must reimport");
|
|
|
|
let mut ctx_enc =
|
|
PkeyCtx::new(&public_key).expect("Failed to create encapsulation context");
|
|
ctx_enc
|
|
.encapsulate_init()
|
|
.expect("Failed to initialize encapsulation");
|
|
|
|
let mut ciphertext = Vec::new();
|
|
let mut shared_secret = Vec::new();
|
|
ctx_enc
|
|
.encapsulate_to_vec(&mut ciphertext, &mut shared_secret)
|
|
.expect("Generated keypair must support encapsulation");
|
|
|
|
assert_eq!(ciphertext.len(), 1088);
|
|
assert_eq!(shared_secret.len(), 32);
|
|
}
|
|
|
|
#[test]
|
|
fn test_generate_ml_kem_rejects_unsupported_key_type() {
|
|
generate_ml_kem(KeyType::RSA).expect_err("Unsupported key type must fail");
|
|
}
|
|
|
|
#[test]
|
|
fn test_encapsulate_requires_init() {
|
|
let keypair =
|
|
generate_ml_kem(KeyType::ML_KEM_512).expect("Failed to generate ML-KEM-512 keypair");
|
|
let public_key_der = keypair
|
|
.public_key_to_der()
|
|
.expect("Failed to export public key");
|
|
let public_key =
|
|
PKey::public_key_from_der(&public_key_der).expect("Failed to import public key");
|
|
|
|
let mut ctx_enc =
|
|
PkeyCtx::new(&public_key).expect("Failed to create encapsulation context");
|
|
let err = ctx_enc
|
|
.encapsulate_to_vec(&mut Vec::new(), &mut Vec::new())
|
|
.expect_err("Encapsulation without init must fail");
|
|
|
|
assert!(
|
|
!err.errors().is_empty(),
|
|
"OpenSSL should report an error when encapsulate_init was not called"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_decapsulate_requires_init() {
|
|
let keypair =
|
|
generate_ml_kem(KeyType::ML_KEM_512).expect("Failed to generate ML-KEM-512 keypair");
|
|
let mut ctx_dec = PkeyCtx::new(&keypair).expect("Failed to create decapsulation context");
|
|
let err = ctx_dec
|
|
.decapsulate_to_vec(&[0_u8; 768], &mut Vec::new())
|
|
.expect_err("Decapsulation without init must fail");
|
|
|
|
assert!(
|
|
!err.errors().is_empty(),
|
|
"OpenSSL should report an error when decapsulate_init was not called"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_decapsulate_rejects_undersized_output_buffer() {
|
|
let keypair =
|
|
generate_ml_kem(KeyType::ML_KEM_768).expect("Failed to generate ML-KEM-768 keypair");
|
|
|
|
let public_key_der = keypair
|
|
.public_key_to_der()
|
|
.expect("Failed to export public key");
|
|
let public_key =
|
|
PKey::public_key_from_der(&public_key_der).expect("Failed to import public key");
|
|
|
|
let mut ctx_enc =
|
|
PkeyCtx::new(&public_key).expect("Failed to create encapsulation context");
|
|
ctx_enc
|
|
.encapsulate_init()
|
|
.expect("Failed to initialize encapsulation");
|
|
|
|
let mut ciphertext = Vec::new();
|
|
let mut shared_secret = Vec::new();
|
|
ctx_enc
|
|
.encapsulate_to_vec(&mut ciphertext, &mut shared_secret)
|
|
.expect("Failed to encapsulate");
|
|
|
|
let mut ctx_dec = PkeyCtx::new(&keypair).expect("Failed to create decapsulation context");
|
|
ctx_dec
|
|
.decapsulate_init()
|
|
.expect("Failed to initialize decapsulation");
|
|
|
|
let mut undersized = [0_u8; 31];
|
|
let err = ctx_dec
|
|
.decapsulate(&ciphertext, Some(&mut undersized))
|
|
.expect_err("Undersized output buffer must fail");
|
|
|
|
assert!(
|
|
!err.errors().is_empty(),
|
|
"OpenSSL should report an error for an undersized output buffer"
|
|
);
|
|
}
|
|
}
|