1use crate::{DogtagError, DogtagResult};
4use zeroize::Zeroizing;
5
6pub struct KemEncapsulation {
8 pub ciphertext: Vec<u8>,
10 pub shared_secret: Zeroizing<Vec<u8>>,
13}
14
15pub fn ml_kem_encapsulate(transport_pub_key_der: &[u8]) -> DogtagResult<KemEncapsulation> {
21 unsafe {
22 let mut ptr = transport_pub_key_der.as_ptr();
23 let evp_pkey = openssl_sys::d2i_PUBKEY(
24 std::ptr::null_mut(),
25 &mut ptr,
26 transport_pub_key_der.len() as std::os::raw::c_long,
27 );
28 if evp_pkey.is_null() {
29 return Err(DogtagError::KraError(
30 "d2i_PUBKEY failed for transport key".into(),
31 ));
32 }
33
34 let ctx = openssl_sys::EVP_PKEY_CTX_new(evp_pkey, std::ptr::null_mut());
35 if ctx.is_null() {
36 openssl_sys::EVP_PKEY_free(evp_pkey);
37 return Err(DogtagError::KraError("EVP_PKEY_CTX_new failed".into()));
38 }
39
40 let rc = openssl_sys::EVP_PKEY_encapsulate_init(ctx, std::ptr::null());
41 if rc <= 0 {
42 openssl_sys::EVP_PKEY_CTX_free(ctx);
43 openssl_sys::EVP_PKEY_free(evp_pkey);
44 return Err(DogtagError::KraError(
45 "EVP_PKEY_encapsulate_init failed".into(),
46 ));
47 }
48
49 let mut ct_len: usize = 0;
51 let mut ss_len: usize = 0;
52 let rc = openssl_sys::EVP_PKEY_encapsulate(
53 ctx,
54 std::ptr::null_mut(),
55 &mut ct_len,
56 std::ptr::null_mut(),
57 &mut ss_len,
58 );
59 if rc <= 0 {
60 openssl_sys::EVP_PKEY_CTX_free(ctx);
61 openssl_sys::EVP_PKEY_free(evp_pkey);
62 return Err(DogtagError::KraError(
63 "EVP_PKEY_encapsulate size query failed".into(),
64 ));
65 }
66
67 let mut ciphertext = vec![0u8; ct_len];
68 let mut shared_secret = vec![0u8; ss_len];
69
70 let rc = openssl_sys::EVP_PKEY_encapsulate(
71 ctx,
72 ciphertext.as_mut_ptr(),
73 &mut ct_len,
74 shared_secret.as_mut_ptr(),
75 &mut ss_len,
76 );
77 openssl_sys::EVP_PKEY_CTX_free(ctx);
78 openssl_sys::EVP_PKEY_free(evp_pkey);
79
80 if rc <= 0 {
81 return Err(DogtagError::KraError("EVP_PKEY_encapsulate failed".into()));
82 }
83
84 ciphertext.truncate(ct_len);
85 shared_secret.truncate(ss_len);
86
87 tracing::info!(
88 ct_len = ciphertext.len(),
89 ss_len = shared_secret.len(),
90 "ML-KEM encapsulation complete"
91 );
92
93 Ok(KemEncapsulation {
94 ciphertext,
95 shared_secret: Zeroizing::new(shared_secret),
96 })
97 }
98}
99
100pub fn aes_kwp_wrap(kek: &[u8], plaintext: &[u8]) -> DogtagResult<Vec<u8>> {
102 unsafe {
103 let cipher_name = match kek.len() {
104 16 => c"AES-128-WRAP-PAD",
105 24 => c"AES-192-WRAP-PAD",
106 32 => c"AES-256-WRAP-PAD",
107 _ => {
108 return Err(DogtagError::KraError(format!(
109 "Invalid KEK length {} (expected 16, 24, or 32)",
110 kek.len()
111 )));
112 }
113 };
114
115 let cipher = openssl_sys::EVP_CIPHER_fetch(
116 std::ptr::null_mut(),
117 cipher_name.as_ptr(),
118 std::ptr::null(),
119 );
120 if cipher.is_null() {
121 return Err(DogtagError::KraError("AES-KWP cipher not available".into()));
122 }
123
124 let ctx = openssl_sys::EVP_CIPHER_CTX_new();
125 if ctx.is_null() {
126 openssl_sys::EVP_CIPHER_free(cipher as *mut _);
127 return Err(DogtagError::KraError("EVP_CIPHER_CTX_new failed".into()));
128 }
129
130 openssl_sys::EVP_CIPHER_CTX_set_flags(ctx, openssl_sys::EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
131
132 let rc = openssl_sys::EVP_EncryptInit_ex(
133 ctx,
134 cipher,
135 std::ptr::null_mut(),
136 kek.as_ptr(),
137 std::ptr::null(),
138 );
139 openssl_sys::EVP_CIPHER_free(cipher as *mut _);
141 if rc != 1 {
142 openssl_sys::EVP_CIPHER_CTX_free(ctx);
143 return Err(DogtagError::KraError("EVP_EncryptInit_ex failed".into()));
144 }
145
146 let max_out = plaintext.len() + 32;
147 let mut output = vec![0u8; max_out];
148 let mut out_len: i32 = 0;
149
150 let rc = openssl_sys::EVP_EncryptUpdate(
151 ctx,
152 output.as_mut_ptr(),
153 &mut out_len,
154 plaintext.as_ptr(),
155 plaintext.len() as i32,
156 );
157 if rc != 1 {
158 openssl_sys::EVP_CIPHER_CTX_free(ctx);
159 return Err(DogtagError::KraError("EVP_EncryptUpdate failed".into()));
160 }
161
162 let mut final_len: i32 = 0;
163 let rc = openssl_sys::EVP_EncryptFinal_ex(
164 ctx,
165 output.as_mut_ptr().add(out_len as usize),
166 &mut final_len,
167 );
168 openssl_sys::EVP_CIPHER_CTX_free(ctx);
169
170 if rc != 1 {
171 return Err(DogtagError::KraError("EVP_EncryptFinal_ex failed".into()));
172 }
173
174 let total = (out_len + final_len) as usize;
175 output.truncate(total);
176
177 tracing::debug!(
178 kek_len = kek.len(),
179 plaintext_len = plaintext.len(),
180 wrapped_len = total,
181 "AES-KWP wrap complete"
182 );
183
184 Ok(output)
185 }
186}