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])?;

View File

@@ -0,0 +1,76 @@
#!/usr/bin/env bash
set -euo pipefail
OUT_PREFIX=${1:-host}
SUBJ="/C=US/O=IBM/OU=Testing/CN=Qsafe HK"
if [ -f "${OUT_PREFIX}.hybrid.crt" ]; then
echo "[*] Nothing to do... ${OUT_PREFIX}.hybrid.crt already exists"
exit 0
fi
echo "[*] Generating ML-KEM-1024 keypair..."
if openssl list -kem-algorithms 2>/dev/null | grep -qi ML-KEM-1024; then
echo " -> OpenSSL OQS provider detected"
if ! [ -f "${OUT_PREFIX}.mlkem.key" ]; then
echo "[*] Generate ML-KEM key (target public key)"
openssl genpkey -algorithm MLKEM1024 -out "${OUT_PREFIX}.mlkem.key"
openssl pkey -in "${OUT_PREFIX}.mlkem.key" -pubout -out "${OUT_PREFIX}.mlkem.pub.pem"
fi
if ! [ -f "issuer.key" ] || ! [ -f "issuer.crt" ]; then
echo "[*] Generate RSA issuer (signing key)"
openssl req -x509 -newkey rsa:4096 -nodes \
-keyout issuer.key \
-out issuer.crt \
-days 365 \
-subj "${SUBJ}"
fi
if ! [ -f "${OUT_PREFIX}.mlkem.crt" ]; then
echo "[*] Create certificate with injected ML-KEM public key"
openssl req -new -newkey rsa:2048 -nodes \
-keyout tmp.key \
-subj "${SUBJ}" |
openssl x509 -req \
-CA issuer.crt \
-CAkey issuer.key \
-CAcreateserial \
-days 365 \
-set_serial 0x66A376DBB5502C74C6 \
-force_pubkey "${OUT_PREFIX}.mlkem.pub.pem" \
-extfile <(
cat <<EOF
basicConstraints=CA:FALSE
keyUsage=critical,keyAgreement
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid
crlDistributionPoints=URI:http://issuer.crl
EOF
) \
-out "${OUT_PREFIX}.mlkem.crt"
fi
else
echo " -> No PQ support, using placeholder"
exit 1
fi
echo "[*] Building hybrid PEM: ${OUT_PREFIX}.hybrid.pem"
{
echo "-----BEGIN CERTIFICATE-----"
sed '/-----/d' "${OUT_PREFIX}.pem.crt"
echo "-----END CERTIFICATE-----"
echo "-----BEGIN CERTIFICATE-----"
sed '/-----/d' "${OUT_PREFIX}.mlkem.crt"
echo "-----END CERTIFICATE-----"
} >"${OUT_PREFIX}.hybrid.crt"
echo "[✓] Done"
echo " -> ${OUT_PREFIX}.hybrid.crt"

View File

@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICXDCCAb6gAwIBAgIUcOg3S9/SHLdnpu6iaI7QI2cszsgwCgYIKoZIzj0EAwIw
QDELMAkGA1UEBhMCVVMxDDAKBgNVBAoMA0lCTTEQMA4GA1UECwwHVGVzdGluZzER
MA8GA1UEAwwIUXNhZmUgSEswHhcNMjYwNjI1MTM1MTE4WhcNMzYwNjIyMTM1MTE4
WjBAMQswCQYDVQQGEwJVUzEMMAoGA1UECgwDSUJNMRAwDgYDVQQLDAdUZXN0aW5n
MREwDwYDVQQDDAhRc2FmZSBISzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAfB0
HaSx/tEDOnA0tv/tSFc2v1t0YarC8L9QcV0bU+o4Gk6OBriQi0GbUna4CndAPWrr
BYvC/ZVd2nKqZhx5WGKNAAq/NObSdvH6uNfjZY27pS56fB2abxa5UzlH/KkYAU+J
m1sFjZhOqD1Bvb/mizhz1WqdHucGSQYO55WDL+KH06Goo1MwUTAdBgNVHQ4EFgQU
/n03CIsLupLkHh5OwcdJFfucD8IwHwYDVR0jBBgwFoAU/n03CIsLupLkHh5OwcdJ
FfucD8IwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgOBiwAwgYcCQTTEnxAy
8/inrBEMtAINb9yA+LAXk5mDQIa7WXOiKRiJKLqHyrjPXqMIAtNgWwSmLggHiIYE
R4MiacrfbVdEOda3AkIBLv0XRN8zEaMLDlis6EdhgF+/AXFtsYxsjafZpoqnfx2X
YfbIgFpLUTcYWwoPi5uopbiAwv3AQy3x5CEFwG3e5PY=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,8 @@
-----BEGIN PRIVATE KEY-----
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAKog0FWiwlXuAS1jK
dNsahw9KZ+r4zvZrp1LmDSOQNOfkauXHH1EmBEnUkYccsi7PibYkz80sxbtk+NVW
Kk6pGk2hgYkDgYYABAHwdB2ksf7RAzpwNLb/7UhXNr9bdGGqwvC/UHFdG1PqOBpO
jga4kItBm1J2uAp3QD1q6wWLwv2VXdpyqmYceVhijQAKvzTm0nbx+rjX42WNu6Uu
enwdmm8WuVM5R/ypGAFPiZtbBY2YTqg9Qb2/5os4c9VqnR7nBkkGDueVgy/ih9Oh
qA==
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,84 @@
-----BEGIN CERTIFICATE-----
MIIFFDCCAvygAwIBAgIUO+SNGBgRvkmR/lKiZhiN4l4zS70wDQYJKoZIhvcNAQEN
BQAwgcwxCzAJBgNVBAYTAlVTMTQwMgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2lu
ZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtJbnRlcm5hdGlvbmFs
IEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMREwDwYDVQQIDAhOZXcgWW9y
azEVMBMGA1UEBwwMUG91Z2hrZWVwc2llMScwJQYDVQQLDB5JQk0gWiBIb3N0IEtl
eSBTaWduaW5nIFNlcnZpY2UwIBcNMjMwMzI5MDkwNDQ4WhgPMjM4NzEyMzEwOTA0
NDhaMIG2MQswCQYDVQQGEwJVUzE0MDIGA1UECgwrSW50ZXJuYXRpb25hbCBCdXNp
bmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrSW50ZXJuYXRpb25h
bCBCdXNpbmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlvbjERMA8GA1UECAwITmV3IFlv
cmsxDzANBgNVBAcMBkFybW9uazEXMBUGA1UECwwOSUJNIFogSG9zdCBLZXkwgZsw
EAYHKoZIzj0CAQYFK4EEACMDgYYABAF2iBzYS4tt5xI8fpAO33jn97aEmZFGsSY7
qLhshEfwirbUacKxKO2eHDUBWAWs09MCM9ORIvfi+KocKxR7eIO0BgDDRVbCXPkv
Kc5mvUQRTWt7PHjhOj+QvbkAQPaHblJi93imGpN9GzSALrJX404Gct1fKjR63aoo
IDUJKnDDXC55c6OBhzCBhDAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vMTI3LjAu
MC4xOjEyMzQvY3JsL2libS5jcmwwDgYDVR0PAQH/BAQDAgMIMB8GA1UdIwQYMBaA
FN6M1/Do0NgBB3F9b9loWSA+sGd8MB0GA1UdDgQWBBR6ESQ+J+iXQe0a/KZ2+N+Z
8a9SZjANBgkqhkiG9w0BAQ0FAAOCAgEAVWJHoCxxkqh7b+y93PY4KPy4jJC4gN1S
dAPqttD/yteJ/4mbVel1/KNSoQBk5EpJmRqeBwHCgGJaT/TxYXNw/b8mRPxe/xbb
wieZMqlSmH028UjYDku1eM0IgHISgoCesIR95D5iAOWbMMVUwIHIHTfmhK7DmZVe
SPf7RIkctrpYxZh0Gw8KLZO6Mfy/9tq3dps0A7KS6jjdrF+M9LavPGwFvtfvRMTi
rdteByO2saGAKDvrjtievwlWCNBJlKV1arW9krN7eqJY5YO6eRbX6UjuhbPRgjte
eZ4jL121TBJaKZU7Q/lvYHIWfzstwQdiem2Ua1GyiiEvPZrQlmqQ3gDBtwJQGB4Q
2myP7MY7THiKObjaB8qRsVxKM78ktwAtAYSZv7gZlmSJ/uTMzDV5D2TQxqs7zwCj
sV+psUn4nvh58xP+DW+MYbF/Cpmzvul9FjMKBs270vE1q+gMot27rbQHRRJ4lVN5
khiG6Oi6blVkPKExIVIiaZ9diXK6NhtWp15PWljNiDxZO+zpkeuw7cKLn/idzmvP
Gcj6m7DqcdsSIHNKbR5iM2VuDhg/j8uBD3uF2Wlymp31TBQgdYWSihJpwZKHNqJD
uq9SmegwI5gYg64KwABZM9hGbl/krXt/0CeCR5HRc+fthanKx/tO2tbCuVT3FR5J
XpKNUy1D78o=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
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
jq+SvDvKMqeV
-----END CERTIFICATE-----

View File

@@ -0,0 +1,54 @@
-----BEGIN CERTIFICATE-----
MIIJlTCCB32gAwIBAgIJZqN227VQLHTGMA0GCSqGSIb3DQEBCwUAMEAxCzAJBgNV
BAYTAlVTMQwwCgYDVQQKDANJQk0xEDAOBgNVBAsMB1Rlc3RpbmcxETAPBgNVBAMM
CFFzYWZlIEhLMB4XDTI2MDcxMzA4NDg1MFoXDTI3MDcxMzA4NDg1MFowQDELMAkG
A1UEBhMCVVMxDDAKBgNVBAoMA0lCTTEQMA4GA1UECwwHVGVzdGluZzERMA8GA1UE
AwwIUXNhZmUgSEswggYyMAsGCWCGSAFlAwQEAwOCBiEAIAunnYUo3qtiqjiGykgq
tqGahVSQEMcZxZueg+UM6bKFRLYkxCkC+sIp/uIjDyp7QDRq75JIfwUXarNTd2ma
NfJRTktE3kY86rF8sCZ0h3hv13wxTVoWWNFiysenJqMsA0O/fEkGu5dQlqbDL7g1
7Wwy/OBgs3xViKB7KUxBH5TGzbq0YGEDbXY1gSx/cTApvfDPVLGSI/QqoxIBF4gf
hciO6CZcAJ1GfbKzwSLNWWtn1WqLM7m4omIkvjDIXtdk6SyX7owLbVdRbMsjCQot
6ccXWFjPhqisAtaLmrcZzRyk8TLNx+MtGKo5dHZ6lqiyotKrK6o/Y0yobuxAwJvN
cZWO+/UyHBuJpvGItWFkmSSMJ9iZv1dpManJe0GfSIe72veACYBhatPMdRmvCieg
r0yn1Vq5/kDIibw6/5LJpdYu8Tpg8oXIcEcN86fLBbqKALZtFpFCgTSXU4PDodCQ
QhLB9ki/YOR5yVqE50pa2DeyG8g8G8dnoVFRtaTNdIIf+QNZcXaa3/VstqAOgVIY
xomsb5GI9RqpSUUTxzhCPivJAHeGfLfG9Mk2W2bNlrqdw6u6GTPEm+GhQBtAl2pP
HBwrTvAeDAODECKgE5pJk3wXckwp4FpjIfaLFTlGaKkhlNS9jYmrVHYZkfFEdbUs
ApAzVyoQa0tWM+ilCbLLKGIjn1BIPsmkvCAxcTUUBDy5JgVjlJd0BXbLK6pEKTfA
BiO+0fRvZcQ/FxJnrndzoDQP90ie72OwK4dIn+Ku5dFoFkjN2dpS26qStahyZJg2
gqweLtdB+SgZBzJW9YFClHxK77AlI1otNJttDhaLE3keBgeyLgqgqutMEmOZYxcp
C7OSQNYFN2Ifw+hoHvwykPRpBLkZiTkccEsQEsJyP4A8A7e1cild1aNSc/On9MtV
UvIEuANLNVUCymi48kWTS7yXeyGLD2szhgM6ihYIvukqXYiuO7sR58SvDLdIIxZ5
HTJTQbKPsQPKkPRNLJV+DOPKVTN6zKA7DSfPNbg3hRApx+NTH9xqdplLZaV7j+FU
rPBYEuc3LVmqamlOH/wYOSSPyMbF0VJCldyIUeMOqBKGItUc9ito3eBsfSoYJ0xX
BitqLCnGCtSkZEaNRay6ujka7vg3U/FS59oWxap6Ttm6HWCExSxMZTh33GSTBEao
zHyWd3RtxzGapngsnixEFNV6ypsyqGJbHOJ7k2drUjRIqXJ5yjgp8UKy9vO2XoMi
5Yw2qmOil6QR5Ck7QgJBILAGVkOVktdimeVtlaoIe1gnX6Rk5EA1AZQyqeBw62MC
XJKJTOxnenJYQys6vSw1H0Sd7EPITKaSGAtd30y3ljdrP6YG4dwwjAmsDdmdNgR2
z0BvTnG8PQqRNIWfPXsnkHqcpycN0sieDnG0m1hP14JONACtuNuKkNunQKQgBMMV
g9w4Rcc3L2Um+Ean/UI4LJJLYAkPIXAwfPqZmBNXTBqWNHCeFNW5nakn9reBR2xz
rPFutTu3QGaDoCFkeqAKiADGwsi0JqqU2iyvT3WbQ4EvQDgJo8soVIfCFXpYhuSn
yAEUSXrLvSgP0ht80EDLiMd7Xom75+hXx+UbX1qrd0ubY8nJRShSznwmJnUnB0x5
z/zJ18o9mdWb+/M2IbuZ2CFeX1sJItlxFvsNUswuIDQoQmtWiUm2a6RElsOwAGg8
yTurJuKqfTcsQNWbr4g+cTFsTZRYwDFEAyUlfqJicppO6ohlAGaCYgcA8HiOB5SU
3pdf/PDG5GG52ZpnhrlqBSOjuUolKzGtwCCHAfohk9LD+BFJUSyuBuxZyMEh4tsT
84Cgy+tZUCipQeS0eMKSyKs/7pEndBhAhMBc0mZDY1MNaethKPRm83xVnZMUFTJr
CQi3WlAgXdsqWHkak/aXf2CdTOxBEaq2fhSP5cJLDABlJQivSUbJwLOBU0OewSE8
9vougwvD7DkmZxavwFQ8ggOmjUqp3oGbmdRlcLWuOOYUW6Nz/eC1XJEq7qmuQALF
taZIYXxj55ePw8ysPtOKRQqpRfs2w5I/wOJy4FxDHmJ4Wjg1VBA6QzxpFHJ7n4hJ
ydO9p/6dl6BHWK6klp/F7xGjgYEwfzAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwID
CDAdBgNVHQ4EFgQU+Ua9eLZDQEHqy6feSZpQkKTPEr4wHwYDVR0jBBgwFoAUaSni
RLnV8oxx0lUba0yC4VPe6egwIgYDVR0fBBswGTAXoBWgE4YRaHR0cDovL2lzc3Vl
ci5jcmwwDQYJKoZIhvcNAQELBQADggIBAEIGXfa9ORdcmtF9EO5YctN0ZoZOIWgK
HM6S0UNe8VghZOKpn/7RArS/04CXA1hb12I6CWxRrvU3qxak95OWHKl405tS0ysz
nipuguN+r8B59KlcIvnh2vbctWf2uW9SJF9uWu6cSL/T27TAA0V07412givkXOIZ
kfgPpW19fqQbNuhoY2yYOh0bl/vFWtY4dORbR86mo3RV9JUQ8KtsVIdJOuz0RcnH
3L+AavQLblLfCdmKJnBC1Zia1d/295wOdUj8n1d+Eq6ho66YBlLJN1zrAZHELPj2
l6dNal+AxCyPgTJ+h2owWRTOZOqMB8/Dun1m1EnwZKowQg+HTPT0NyMwZpfMfK7m
IHe7WZE/5tXPRnUl6KfDXSSrkxrXeAHJmkSnOEyLS4YheKEzBkMTaPU2YzdBPXy0
gfr9SIRmBkQSwE0pCJgVhcO0yvTlKhXiWrXPZ7uXMnCbXLJzhfiVWIJs+aqyKo1f
8mbJK23JOFv42LAZ1j1l7WWNPCAlGK4KgWCIHXeplY1XSyQ6jXECHzegRWPKm2aq
SFr+5QQcedN+//3Ss3zDnEhGxjmlz2TB1lrgh5fcXQbKlkQ5HuPv1CFhhc7oUg0x
DEGDCA08LfeuLeRhecF95ws9ICzHSLJL+RMujcQFEeQZq3uRG5PsKllgCiE2Rk9T
jq+SvDvKMqeV
-----END CERTIFICATE-----

View File

@@ -0,0 +1,71 @@
-----BEGIN PRIVATE KEY-----
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
+JA=
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,36 @@
-----BEGIN PUBLIC KEY-----
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
pJafxe8R
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFYTCCA0mgAwIBAgIUIDYNUFtHABjQvM11wfnJrL2T9v8wDQYJKoZIhvcNAQEL
BQAwQDELMAkGA1UEBhMCVVMxDDAKBgNVBAoMA0lCTTEQMA4GA1UECwwHVGVzdGlu
ZzERMA8GA1UEAwwIUXNhZmUgSEswHhcNMjYwNzEzMDg1MTU4WhcNMjcwNzEzMDg1
MTU4WjBAMQswCQYDVQQGEwJVUzEMMAoGA1UECgwDSUJNMRAwDgYDVQQLDAdUZXN0
aW5nMREwDwYDVQQDDAhRc2FmZSBISzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
AgoCggIBAJ9mCfJnjEYsXB6ORAxZtbbvIfAt5+EyG/bhSDhy2O2LH+VSwnrrPDCa
TSp0xlU6tXgJVlIPBH6vKE5cUkJErWGfxtE/nmuhy1j3DnSCN7cGjcAk62iV00bh
DwBpTqDlhqtAxpEFVE04yqLhxblh0ZaWcxhSNFCDFoD5VUnGwuSwxvWcMudd8LzO
XhJDLePUwXDpFa4mhnff0kUMe6VXLHd9QvPIvE7XUEUR5jncjTOD5inu/eyBcYpK
5kqJdBY97DduUt1dtS780p2rPS/HExuSo0qvfeVdt9lo/amugEMqVGFYXsAH6KRz
SpBFRDfd9MGmbX7xOe/cscNx87CjSdwQ8k8frH3xfhaHcqvq0E67WzRP6DGPsytj
26IXIVfpkCVrcfVNUTHPqrTOXjIKje2WbvTwmJv2t+xgwEHuREmrL133BiG1QPwX
GMDYxp1u/RsVvi6k/yX0NMVz8UxRDfhMIMKeU+tiyNL8t4/hikVapYTbvJGUussZ
zIRgbxlEqqPicQH4fdJcWMi+tB1wSyN4wLd93emcb09fVrc95G8LTnJ32UgjXnl2
V5MHCIgCYIzSaf/+u/uISfQQ5AT5F5eVoVWpN+t2X9cMgFyvL2+fcrkUAYmRbMGN
fm0k7+svquXmDEzPUxIR9BGWTYxwklb3uOdZENiTumqKsUSM7dqhAgMBAAGjUzBR
MB0GA1UdDgQWBBQLbGItsikIfnLb8EIztBn5jfRgQjAfBgNVHSMEGDAWgBQLbGIt
sikIfnLb8EIztBn5jfRgQjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA
A4ICAQCNPAKyNyD+m6tBmfCvXd1kFj1dKZUrgPEuPkpnQk01n6eR12pNXAnUcDDx
HAJM/IFC69zZrekDaFV9lQ5tA5IiZefG09srPHSmEmsISBcDGBJjUx9qZNlKI3R+
gVchEb812PstD5ErY9qFUByrOKMTWLSUVoUS7k8IMNGHMELbl2mQlvc/sIYD8wt5
3/W93OQKnemlqEtC1ljCaDPX3T/eCH5qI+VBiC9OXY7lhUujI0Cs0yJAZ/2G7vcM
NIoCIKmZ+C5KKH6r8aG9NE+ciG2yQCvi7J8OWaesJoCLVZxygEYgorIJNxRTcCYH
En1sDGnQR/NKJPcpDT8KNXMriMmzMDXzecSCuE/tE8tL0lbAnncxJ+AlHAWlt09G
x7rSD3v+Axq5oC0S396ctdwgPwETP5FgLvJSuGicXROsUS4VmPW42h0BovMPI/TF
AfjBnKbgQ7aqppeb9nK9JGd7FTLDv1V2iaYsKUP2A2qmAuQVPrSuckNHt+V+xFlC
nkJA0RNUh3XKTFNSNXJtOVpGsr6/kDttnrlKKwDyzk7LAfbMld4RkNV29SSdEY7z
hGmimwe1ogcRYYqVevG1OcVUrugDaj+Ujkvs918VMS7RUW+IOdrjItLgvNKdjjgm
ARZcI7R0xiEH5+CGCTTurP83RIjPfnyuA1A5GRrbVGyE84Wuhg==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
YFF7USKdWC1isfkk3OmsHvIwXzfRUA==
-----END PRIVATE KEY-----