pv: Add hybrid key derivation support

Add derive_aes256_gcm_key_hybrid() function that derives an Aes256GcmKey
and ML-KEM1024 ciphertext from a private ECDH customer key, a public
ECDH target key, and a public ML-KEM target key. This enables hybrid
post-quantum cryptography support.

Assisted-by: IBM Bob:1.0.5
Co-developed-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Timo Keller <tkeller@linux.ibm.com>
Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
This commit is contained in:
Timo Keller
2026-06-26 15:37:07 +02:00
committed by Steffen Eiden
parent 1746d2bb6a
commit ebe56592c8
15 changed files with 921 additions and 5 deletions

View File

@@ -7,12 +7,14 @@ use std::fmt::Display;
use std::ops::Range;
use enum_dispatch::enum_dispatch;
use openssl::bn::BigNumContext;
use openssl::derive::Deriver;
use openssl::ec::{EcGroup, EcKey};
use openssl::ec::{EcGroup, EcKey, EcPoint};
use openssl::error::ErrorStack;
use openssl::hash::{DigestBytes, MessageDigest};
use openssl::md::MdRef;
use openssl::nid::Nid;
use openssl::pkey::{HasPublic, Id, PKey, PKeyRef, Private, Public};
use openssl::pkey::{HasPublic, Id, KeyType, PKey, PKeyRef, Private, Public};
use openssl::pkey_ctx::{HkdfMode, PkeyCtx};
use openssl::rand::rand_bytes;
use openssl::rsa::Padding;
@@ -23,8 +25,51 @@ use openssl::symm::{
use pv_core::request::Confidential;
use crate::error::Result;
use crate::openssl_extensions::PkeyEncapsulateContext;
use crate::req::get_pub_ecdh_points;
use crate::request::EcPubKeyCoord;
use crate::Error;
/// Compute ECDH shared secret from public and private keys
///
/// It is expected that the public and private key are with respect to EC-P521.
///
/// Note that the output is the concatenation of the 80-bytes-left-padded x and
/// 80-bytes-left-padded y coordinate.
fn ecdh_shared_secret(
pub_key: &PKeyRef<Public>,
priv_key: &PKeyRef<Private>,
) -> Result<[u8; 160], ErrorStack> {
let pub_key = pub_key.ec_key()?;
let priv_key = priv_key.ec_key()?;
// Verify both keys use the EC-P521 curve (SECP521R1)
assert_eq!(
pub_key.group().curve_name(),
Some(Nid::SECP521R1),
"Public key must use EC-P521 curve"
);
assert_eq!(
priv_key.group().curve_name(),
Some(Nid::SECP521R1),
"Private key must use EC-P521 curve"
);
pub_key.check_key()?;
priv_key.check_key()?;
let group = pub_key.group();
let mut bn_ctx = BigNumContext::new()?;
let mut point = EcPoint::new(group)?;
point.mul2(
group,
pub_key.public_key(),
priv_key.private_key(),
&mut bn_ctx,
)?;
let coord = get_pub_ecdh_points(&point, group)?;
Ok(coord)
}
/// An AES256-GCM key that will purge itself out of the memory when going out of scope
pub type Aes256GcmKey = Confidential<[u8; SymKeyType::AES_256_GCM_KEY_LEN]>;
/// An AES256-XTS key that will purge itself out of the memory when going out of scope
@@ -214,6 +259,220 @@ pub fn derive_aes256_gcm_key(k1: &PKeyRef<Private>, k2: &PKeyRef<Public>) -> Res
))
}
/// Determines the KeyType of a given PKey by testing against all known key types.
///
/// This function iterates through all known OpenSSL key types and uses the `is_a()`
/// method to identify which type the provided key matches. This is more reliable than
/// using `Id` to `KeyType` conversion, especially for newer key types like ML-KEM
/// that may not have a direct `Id` mapping.
///
/// # Parameters
///
/// * `key` - A reference to the PKey to identify
///
/// # Returns
///
/// * `Some(KeyType)` - If the key matches one of the known key types
/// * `None` - If the key type is not recognized or doesn't match any known types
fn pkey_to_keytype<T>(key: &PKeyRef<T>) -> Option<KeyType> {
const KNOWN_KEY_TYPES: &[KeyType] = &[
KeyType::RSA,
KeyType::RSA_PSS,
KeyType::DSA,
KeyType::DH,
KeyType::EC,
KeyType::HMAC,
KeyType::CMAC,
KeyType::X25519,
KeyType::ED25519,
KeyType::X448,
KeyType::ED448,
KeyType::ML_KEM_512,
KeyType::ML_KEM_768,
KeyType::ML_KEM_1024,
];
KNOWN_KEY_TYPES
.iter()
.find(|&&key_type| key.is_a(key_type))
.copied()
}
fn key_type_str(t: KeyType) -> &'static str {
if t == KeyType::RSA {
"RSA"
} else if t == KeyType::RSA_PSS {
"RSA-PSS"
} else if t == KeyType::DSA {
"DSA"
} else if t == KeyType::DH {
"DH"
} else if t == KeyType::EC {
"EC"
} else if t == KeyType::HMAC {
"HMAC"
} else if t == KeyType::CMAC {
"CMAC"
} else if t == KeyType::X25519 {
"X25519"
} else if t == KeyType::ED25519 {
"ED25519"
} else if t == KeyType::X448 {
"X448"
} else if t == KeyType::ED448 {
"ED448"
} else if t == KeyType::ML_KEM_512 {
"ML-KEM-512"
} else if t == KeyType::ML_KEM_768 {
"ML-KEM-768"
} else if t == KeyType::ML_KEM_1024 {
"ML-KEM-1024"
} else {
"unknown"
}
}
/// Validates that a key matches the expected key type.
///
/// # Errors
///
/// Returns an error if the key doesn't match the expected type.
pub(crate) fn validate_key_type<T: HasPublic>(
key: &PKeyRef<T>,
key_name: &str,
expected_type: KeyType,
) -> Result<()> {
if !key.is_a(expected_type) {
return Err(Error::RetrInvKey {
what: "key type",
kind: key_name.to_string(),
value: pkey_to_keytype(key)
.map(key_type_str)
.unwrap_or("unknown")
.to_string(),
exp: key_type_str(expected_type).to_string(),
});
}
Ok(())
}
/// Validates that a key is an EC key with the specified curve.
///
/// # Errors
///
/// Returns an error if the key is not an EC key or doesn't use the expected curve.
pub(crate) fn validate_ec_key<T: HasPublic>(
key: &PKeyRef<T>,
key_name: &str,
expected_curve: Nid,
) -> Result<()> {
if key.id() != Id::EC {
return Err(Error::RetrInvKey {
what: "key type",
kind: key_name.to_string(),
value: pkey_to_keytype(key)
.map(key_type_str)
.unwrap_or("unknown")
.to_string(),
exp: format!("EC ({})", expected_curve.long_name().unwrap_or("unknown")),
});
}
let ec_key = key.ec_key()?;
if ec_key.group().curve_name() != Some(expected_curve) {
return Err(Error::RetrInvKey {
what: "curve",
kind: key_name.to_string(),
value: ec_key
.group()
.curve_name()
.and_then(|nid| nid.long_name().ok())
.unwrap_or("unknown")
.to_string(),
exp: expected_curve.long_name().unwrap_or("unknown").to_string(),
});
}
Ok(())
}
/// Derive a symmetric AES 256 GCM key from a private target key, a public
/// customer key, and a public ML-KEM target key.
///
/// # Returns
///
/// The derived key and the ML-KEM ciphertext (KC).
///
/// # Errors
///
/// This function will return an error if something went bad in OpenSSL or the
/// wrong key types were used.
pub fn derive_aes256_gcm_key_hybrid(
priv_ecdh_cust_key: &PKeyRef<Private>,
pub_ecdh_target_key: &PKeyRef<Public>,
pub_mlkem_target_key: &PKeyRef<Public>,
) -> Result<(Aes256GcmKey, Vec<u8>)> {
let mut buffer: Vec<u8> = vec![0, 0, 0, 1];
validate_ec_key(priv_ecdh_cust_key, "ECDH customer key", Nid::SECP521R1)?;
validate_ec_key(pub_ecdh_target_key, "ECDH target key", Nid::SECP521R1)?;
validate_key_type(
pub_mlkem_target_key,
"ML-KEM target key",
KeyType::ML_KEM_1024,
)?;
// Derive the ECDH shared secret
let ecdh_derived_secret = ecdh_shared_secret(pub_ecdh_target_key, priv_ecdh_cust_key)?;
assert_eq!(ecdh_derived_secret.as_ref().len(), 160);
buffer.extend_from_slice(ecdh_derived_secret.as_ref());
// Derive the ML-KEM shared secret
let mut ctx = PkeyCtx::new(pub_mlkem_target_key)?;
ctx.encapsulate_init()?;
let (mut ciphertext, mut shared_secret) = (vec![], vec![]);
ctx.encapsulate_to_vec(&mut ciphertext, &mut shared_secret)?;
assert_eq!(ciphertext.len(), 1568);
assert_eq!(shared_secret.len(), 32);
buffer.extend_from_slice(&shared_secret);
// Append the private ECDH customer key
let pub_ecdh_cust_key = EcPubKeyCoord::try_from(priv_ecdh_cust_key)?;
assert_eq!(pub_ecdh_cust_key.as_ref().len(), 160);
buffer.extend_from_slice(pub_ecdh_cust_key.as_ref());
// Append the ciphertext
buffer.extend_from_slice(&ciphertext);
// Append the public ECDH target key
let pub_ecdh_target_key: EcPubKeyCoord = pub_ecdh_target_key.try_into()?;
assert_eq!(pub_ecdh_target_key.as_ref().len(), 160);
buffer.extend_from_slice(pub_ecdh_target_key.as_ref());
// Append the public ML-KEM target key
assert_eq!(pub_mlkem_target_key.raw_public_key()?.len(), 1568);
buffer.extend_from_slice(&pub_mlkem_target_key.raw_public_key()?);
// Append the magic string
const STRING: &str = "PQC Secure Execution with Format-2 Key Slots KS2";
assert_eq!(STRING.len(), 48);
buffer.extend_from_slice(STRING.as_bytes());
// Sanity check
assert_eq!(buffer.len(), 4 + 160 + 32 + 160 + 1568 + 160 + 1568 + 48);
let secr = Confidential::new(buffer);
// Panic: does not panic as SHA256 digest is 32 bytes long
Ok((
Aes256GcmKey::new(
hash(MessageDigest::sha256(), secr.value())?
.as_ref()
.try_into()
.unwrap(),
),
ciphertext,
))
}
/// Generate a random array.
///
/// # Errors
@@ -760,6 +1019,112 @@ mod tests {
assert_eq!(&calc_key, &exp_key);
}
#[test]
fn derive_aes256_gcm_key_hybrid() {
let (cust_key, host_key_1, host_key_2) = get_test_keys_hybrid();
let entropy = [0x5au8; 4096];
let nonce = [0xa5u8; 48];
let _rng = DeterministicTestRandGuard::install(&entropy, &nonce).unwrap();
let exp_key: Aes256GcmKey = [
197, 167, 157, 112, 186, 112, 72, 125, 192, 219, 168, 132, 178, 167, 249, 123, 149, 3,
151, 166, 162, 66, 120, 39, 41, 230, 143, 54, 172, 10, 200, 143,
]
.into();
let exp_kc = [
54, 117, 96, 77, 148, 147, 170, 100, 34, 177, 95, 7, 35, 243, 145, 115, 7, 87, 178, 9,
169, 99, 193, 99, 244, 195, 23, 78, 11, 153, 221, 196, 5, 192, 253, 192, 86, 49, 194,
236, 43, 69, 183, 125, 166, 87, 158, 188, 13, 152, 19, 6, 253, 29, 194, 0, 101, 236,
28, 171, 3, 236, 53, 186, 191, 109, 7, 83, 220, 93, 126, 29, 19, 203, 201, 39, 59, 7,
131, 51, 81, 73, 254, 69, 105, 185, 214, 179, 155, 194, 189, 122, 106, 130, 249, 48, 4,
33, 245, 170, 163, 4, 223, 208, 138, 224, 203, 119, 105, 59, 187, 153, 235, 90, 79,
127, 29, 136, 230, 142, 78, 83, 27, 131, 58, 126, 76, 53, 129, 20, 85, 108, 86, 64,
244, 90, 84, 177, 239, 105, 90, 41, 118, 189, 88, 174, 224, 216, 29, 10, 123, 81, 212,
203, 197, 120, 20, 190, 3, 45, 37, 194, 208, 249, 232, 221, 67, 10, 62, 121, 143, 169,
227, 165, 17, 30, 85, 223, 44, 141, 114, 142, 105, 119, 187, 41, 46, 8, 6, 17, 29, 165,
117, 254, 92, 174, 231, 25, 117, 69, 112, 216, 80, 73, 185, 54, 50, 119, 145, 220, 174,
26, 105, 81, 114, 210, 144, 148, 109, 218, 64, 78, 231, 196, 229, 88, 46, 128, 106,
125, 204, 58, 184, 127, 193, 207, 86, 163, 98, 164, 57, 242, 29, 59, 251, 227, 185, 60,
18, 68, 74, 47, 203, 61, 164, 78, 245, 100, 87, 148, 210, 97, 158, 252, 79, 78, 50,
143, 35, 231, 215, 211, 75, 133, 214, 227, 140, 27, 21, 46, 221, 84, 89, 165, 161, 227,
46, 117, 193, 254, 190, 237, 130, 28, 57, 52, 14, 235, 154, 115, 172, 185, 67, 116, 34,
242, 158, 209, 0, 126, 196, 93, 224, 29, 246, 145, 65, 73, 185, 196, 4, 107, 124, 241,
157, 230, 168, 244, 238, 84, 188, 173, 17, 238, 26, 161, 24, 176, 229, 226, 33, 244,
167, 41, 107, 156, 29, 226, 248, 64, 146, 191, 210, 234, 76, 144, 219, 92, 136, 173,
241, 98, 0, 71, 135, 214, 196, 116, 63, 243, 73, 71, 130, 171, 86, 204, 149, 69, 164,
20, 177, 122, 95, 226, 95, 126, 106, 160, 59, 97, 137, 8, 73, 113, 189, 172, 24, 114,
60, 62, 249, 193, 3, 99, 34, 153, 42, 238, 77, 181, 80, 185, 223, 39, 8, 44, 215, 119,
214, 30, 136, 19, 215, 35, 184, 69, 94, 10, 170, 179, 51, 183, 105, 237, 237, 48, 199,
122, 159, 87, 183, 71, 230, 87, 102, 77, 81, 116, 28, 126, 195, 72, 50, 157, 223, 243,
83, 36, 16, 168, 111, 209, 132, 12, 96, 56, 140, 57, 144, 75, 253, 119, 123, 168, 2,
79, 214, 121, 80, 154, 93, 235, 222, 130, 181, 166, 97, 51, 106, 21, 138, 224, 8, 144,
223, 162, 152, 183, 6, 80, 64, 144, 21, 155, 56, 255, 108, 248, 125, 196, 46, 99, 119,
94, 104, 63, 46, 15, 165, 30, 98, 75, 212, 193, 116, 151, 189, 65, 42, 83, 253, 183,
41, 195, 45, 206, 178, 66, 36, 215, 197, 105, 236, 79, 91, 135, 164, 71, 187, 199, 200,
150, 226, 182, 254, 6, 234, 109, 3, 17, 116, 249, 44, 211, 184, 61, 189, 44, 181, 249,
8, 58, 230, 236, 8, 188, 14, 178, 100, 120, 250, 29, 1, 204, 158, 46, 161, 39, 66, 76,
42, 114, 149, 160, 31, 87, 254, 181, 224, 17, 162, 163, 99, 11, 34, 149, 50, 203, 205,
224, 38, 18, 233, 161, 49, 7, 151, 63, 81, 68, 71, 174, 49, 22, 143, 93, 50, 0, 154,
152, 178, 134, 147, 152, 118, 196, 241, 233, 67, 102, 149, 179, 213, 176, 118, 64, 172,
143, 134, 196, 232, 154, 110, 129, 155, 159, 103, 117, 202, 11, 35, 75, 104, 5, 11,
160, 147, 174, 49, 248, 45, 247, 16, 7, 64, 209, 255, 170, 243, 242, 40, 158, 94, 239,
194, 225, 113, 24, 90, 243, 73, 137, 217, 175, 130, 50, 133, 139, 250, 145, 190, 76,
151, 183, 30, 86, 146, 59, 171, 214, 211, 135, 203, 192, 42, 189, 90, 47, 152, 132,
168, 252, 175, 71, 234, 118, 207, 161, 176, 254, 189, 54, 174, 160, 178, 158, 133, 122,
63, 75, 95, 201, 55, 139, 2, 208, 232, 110, 74, 201, 196, 135, 244, 156, 87, 208, 101,
203, 121, 187, 16, 106, 80, 120, 165, 44, 147, 182, 114, 173, 186, 185, 255, 99, 85,
88, 26, 27, 43, 203, 176, 207, 88, 20, 253, 169, 210, 168, 109, 75, 234, 239, 8, 243,
244, 65, 164, 193, 255, 240, 215, 54, 158, 188, 93, 93, 54, 46, 77, 152, 78, 174, 154,
67, 248, 24, 235, 172, 240, 83, 224, 17, 100, 217, 15, 172, 176, 46, 85, 107, 105, 127,
147, 158, 202, 255, 145, 237, 84, 223, 100, 214, 38, 133, 169, 112, 227, 138, 220, 125,
72, 197, 5, 227, 94, 245, 42, 70, 33, 209, 243, 70, 229, 37, 118, 214, 147, 43, 87, 39,
241, 107, 26, 169, 28, 72, 223, 133, 145, 44, 248, 213, 52, 127, 250, 99, 193, 115,
113, 147, 89, 112, 237, 199, 208, 36, 155, 106, 144, 73, 249, 8, 116, 198, 107, 120,
233, 145, 11, 155, 178, 7, 66, 157, 255, 206, 128, 155, 233, 111, 148, 194, 214, 238,
252, 230, 96, 119, 30, 37, 73, 133, 129, 87, 185, 149, 251, 156, 17, 8, 83, 106, 207,
98, 203, 100, 39, 199, 127, 253, 59, 37, 121, 161, 216, 146, 6, 178, 183, 243, 191, 91,
106, 243, 132, 111, 216, 163, 87, 210, 197, 173, 146, 65, 131, 194, 96, 70, 6, 7, 192,
45, 173, 71, 44, 134, 122, 60, 173, 208, 238, 22, 187, 208, 212, 51, 191, 185, 174, 3,
125, 28, 134, 216, 209, 4, 224, 199, 16, 15, 56, 70, 188, 216, 92, 24, 96, 57, 125,
138, 151, 73, 254, 245, 106, 53, 4, 150, 74, 43, 42, 4, 157, 238, 125, 168, 41, 224,
22, 249, 45, 117, 32, 180, 161, 41, 39, 180, 96, 24, 2, 102, 57, 116, 34, 75, 90, 72,
134, 176, 2, 196, 59, 143, 182, 201, 117, 178, 153, 81, 108, 167, 122, 139, 71, 197,
55, 114, 60, 161, 130, 14, 29, 79, 152, 55, 136, 62, 190, 228, 202, 53, 126, 4, 173,
99, 28, 190, 224, 255, 134, 123, 166, 162, 244, 55, 26, 81, 120, 207, 193, 10, 103,
153, 215, 220, 12, 71, 67, 217, 154, 212, 44, 200, 232, 0, 178, 39, 44, 22, 7, 14, 215,
183, 192, 104, 51, 46, 93, 102, 195, 65, 9, 191, 241, 237, 151, 5, 64, 103, 228, 162,
41, 123, 29, 5, 80, 203, 198, 234, 230, 107, 53, 60, 58, 253, 47, 152, 22, 77, 81, 86,
215, 132, 152, 135, 6, 218, 46, 92, 192, 218, 198, 234, 76, 178, 25, 203, 48, 61, 76,
215, 96, 6, 49, 195, 37, 225, 10, 175, 222, 186, 133, 63, 50, 236, 215, 247, 17, 199,
8, 134, 64, 246, 194, 167, 105, 15, 57, 62, 50, 51, 243, 192, 242, 122, 11, 46, 202,
47, 10, 71, 153, 212, 226, 38, 12, 90, 150, 154, 152, 233, 7, 172, 111, 185, 160, 246,
0, 166, 113, 90, 37, 203, 166, 43, 53, 255, 211, 127, 139, 73, 7, 10, 164, 1, 168, 223,
87, 127, 43, 47, 87, 68, 84, 247, 223, 108, 108, 113, 36, 17, 50, 98, 236, 48, 10, 219,
182, 107, 240, 198, 207, 20, 178, 9, 142, 14, 93, 163, 166, 147, 38, 176, 172, 156, 73,
174, 238, 175, 231, 130, 159, 51, 128, 76, 34, 37, 138, 19, 3, 59, 71, 78, 144, 238,
226, 214, 188, 27, 42, 142, 245, 238, 131, 190, 211, 240, 41, 122, 69, 124, 171, 75,
115, 45, 144, 133, 176, 19, 81, 125, 230, 149, 235, 159, 6, 155, 195, 119, 62, 140, 50,
52, 209, 124, 3, 93, 232, 20, 130, 138, 110, 60, 183, 177, 161, 52, 114, 91, 19, 211,
156, 185, 202, 200, 36, 103, 253, 113, 45, 245, 177, 238, 43, 144, 38, 221, 0, 102, 50,
255, 20, 154, 56, 156, 155, 92, 157, 57, 209, 77, 84, 88, 24, 116, 116, 54, 213, 222,
76, 212, 193, 168, 216, 247, 125, 135, 114, 226, 128, 140, 250, 103, 82, 215, 238, 32,
74, 252, 45, 224, 23, 95, 126, 124, 135, 124, 128, 53, 203, 40, 65, 222, 8, 83, 178,
211, 64, 141, 64, 98, 188, 134, 100, 65, 166, 52, 249, 1, 206, 58, 55, 195, 23, 218,
239, 41, 73, 88, 113, 148, 132, 209, 93, 37, 205, 58, 92, 14, 1, 133, 168, 162, 192,
147, 70, 167, 101, 170, 152, 159, 0, 212, 26, 97, 49, 43, 217, 173, 38, 215, 136, 26,
208, 244, 19, 83, 207, 38, 224, 254, 92, 169, 219, 236, 172, 49, 55, 98, 55, 15, 187,
173, 114, 99, 130, 211, 78, 168, 221, 209, 250, 88, 189, 17, 186, 172, 129, 56, 90,
238, 120, 23, 176, 87, 133, 81, 244, 29, 2, 215, 34, 88, 247, 231, 167, 56,
];
let (exc_key, kc) =
super::derive_aes256_gcm_key_hybrid(&cust_key, &host_key_1, &host_key_2).unwrap();
assert_eq!(exc_key, exp_key);
assert_eq!(kc, exp_kc);
}
#[test]
fn hkdf_rfc_5869() {
use openssl::md::Md;
@@ -933,4 +1298,94 @@ mod tests {
}))
));
}
#[test]
fn validate_ec_key_valid() {
let (cust_key, host_key) = get_test_keys();
// Both test keys are SECP521R1 EC keys
assert!(validate_ec_key(&cust_key, "customer key", Nid::SECP521R1).is_ok());
assert!(validate_ec_key(&host_key, "host key", Nid::SECP521R1).is_ok());
}
#[test]
fn validate_ec_key_wrong_curve() {
let (cust_key, _) = get_test_keys();
// Test key is SECP521R1, but we expect SECP384R1
let result = validate_ec_key(&cust_key, "customer key", Nid::SECP384R1);
assert!(result.is_err());
if let Err(Error::RetrInvKey {
what,
kind,
value,
exp,
}) = result
{
assert_eq!(what, "curve");
assert_eq!(kind, "customer key");
assert_eq!(value, "secp521r1");
assert_eq!(exp, "secp384r1");
} else {
panic!("Expected RetrInvKey error");
}
}
#[test]
fn validate_ec_key_not_ec() {
let keypair = crate::get_test_asset!("keys/rsa2048key.pem");
let keypair = PKey::private_key_from_pem(keypair).unwrap();
// RSA key is not an EC key
let result = validate_ec_key(&keypair, "EC key", Nid::SECP521R1);
assert!(result.is_err());
if let Err(Error::RetrInvKey {
what,
kind,
value,
exp,
}) = result
{
assert_eq!(what, "key type");
assert_eq!(kind, "EC key");
assert_eq!(value, "RSA");
assert_eq!(exp, "EC (secp521r1)");
} else {
panic!("Expected RetrInvKey error");
}
}
#[test]
fn validate_mlkem_key_valid() {
let (_, _, mlkem_key) = get_test_keys_hybrid();
// The third key from get_test_keys_hybrid is ML-KEM-1024
assert!(validate_key_type(&mlkem_key, "ML-KEM key", KeyType::ML_KEM_1024).is_ok());
}
#[test]
fn validate_mlkem_key_wrong_type() {
let (ec_key, _) = get_test_keys();
// EC key is not ML-KEM
let result = validate_key_type(&ec_key, "EC key", KeyType::ML_KEM_1024);
assert!(result.is_err());
if let Err(Error::RetrInvKey {
what,
kind,
value,
exp,
}) = result
{
assert_eq!(what, "key type");
assert_eq!(kind, "EC key");
assert_eq!(value, "EC");
assert_eq!(exp, "ML-KEM-1024");
} else {
panic!("Expected RetrInvKey error");
}
}
}

View File

@@ -14,5 +14,4 @@ mod stackable_crl;
pub use akid::*;
pub use bio::*;
pub use crl::*;
#[expect(unused_imports)]
pub use ml_kem::*;

View File

@@ -47,7 +47,10 @@ impl EcPubKeyCoord {
/// Get the pub ECDH coordinates in the format the Ultravisor expects it:
/// The two coordinates are padded to 80 bytes each.
fn get_pub_ecdh_points(pkey: &EcPointRef, grp: &EcGroupRef) -> Result<[u8; 160], ErrorStack> {
pub(crate) fn get_pub_ecdh_points(
pkey: &EcPointRef,
grp: &EcGroupRef,
) -> Result<[u8; 160], ErrorStack> {
let mut x = BigNum::new()?;
let mut y = BigNum::new()?;
let mut bn_ctx = BigNumContext::new()?;

View File

@@ -9,7 +9,8 @@ use openssl::pkey::{PKey, PKeyRef, Private, Public};
use super::ec_coord::EcPubKeyCoord;
use super::encrypt::Encrypt;
use crate::crypto::{derive_aes256_gcm_key, encrypt_aead, hash};
#[expect(unused)]
use crate::crypto::{derive_aes256_gcm_key, derive_aes256_gcm_key_hybrid, encrypt_aead, hash};
use crate::request::HostKey;
use crate::Result;

View File

@@ -17,6 +17,7 @@ mod request;
// Re-export public types
pub use context::ReqEncrCtx;
pub(crate) use ec_coord::get_pub_ecdh_points;
pub use ec_coord::EcPubKeyCoord;
pub use encrypt::{Aad, Encrypt};
pub use header::RequestHdr;

View File

@@ -89,6 +89,27 @@ pub fn get_test_key_and_cert() -> (PKey<Private>, X509) {
(cust_key, host_key)
}
pub fn get_test_key_and_cert_hybrid() -> (PKey<Private>, X509, X509) {
let pub_key = get_test_asset!("keys/public_cust.bin");
let priv_key = get_test_asset!("keys/private_cust.bin");
let host_key = get_test_asset!("keys/host.ec.crt");
let host_keys = get_test_asset!("keys/host.hybrid.crt");
assert_eq!(pub_key.len(), 160);
assert_eq!(priv_key.len(), 80);
let cust_key = get_keypair(pub_key, priv_key).unwrap();
let host_key1 = X509::from_pem(host_key).unwrap();
let host_keys = X509::stack_from_pem(host_keys).unwrap();
assert_eq!(host_keys.len(), 2);
println!("host_key1 = {host_key1:?}");
println!("host_keys[0] = {:?}", host_keys[0]);
println!("host_keys[1] = {:?}", host_keys[1]);
(cust_key, host_key1, host_keys[1].clone())
}
/// TEST ONLY! Get a fixed private/public pair and a fixed public key
///
/// Intended for TESTING only. All parts of the key including the private key are checked in git and
@@ -98,6 +119,15 @@ pub fn get_test_keys() -> (PKey<Private>, PKey<Public>) {
(cust_key, host.public_key().unwrap())
}
pub fn get_test_keys_hybrid() -> (PKey<Private>, PKey<Public>, PKey<Public>) {
let (cust_key, host_key_1, host_key_2) = get_test_key_and_cert_hybrid();
(
cust_key,
host_key_1.public_key().unwrap(),
host_key_2.public_key().unwrap(),
)
}
fn read_ecdh_pubkey(coords: &[u8]) -> Result<PKey<Public>, ErrorStack> {
assert!(coords.len() == 160);
let x = BigNum::from_slice(&coords[..80])?;