dcrypt_kem/ecdh/p224/
mod.rs1use super::concat_kdf_ikm;
14use crate::error::Error as KemError;
15use alloc::vec::Vec;
16use dcrypt_algorithms::ec::p224 as ec; use dcrypt_algorithms::hash::sha2::Sha256;
18use dcrypt_algorithms::mac::hmac::Hmac;
19use dcrypt_api::{
20 error::Error as ApiError,
21 traits::serialize::{Serialize, SerializeSecret},
22 Kem, Key as ApiKey, Result as ApiResult, ZeroizingBytes,
23};
24use dcrypt_common::security::SecretBuffer;
25use dcrypt_internal::random::{CryptoRng, RngCore};
26use dcrypt_internal::zeroing::Zeroizing;
27
28const KDF_INFO: &[u8] = b"dcrypt-v3/ECDH-P224-KEM/shared-secret";
29const CONFIRMATION_LABEL: &[u8] = b"dcrypt-v3/ECDH-P224-KEM/confirmation";
30
31pub struct EcdhP224;
33
34#[derive(Clone)]
36pub struct EcdhP224PublicKey([u8; ec::P224_POINT_COMPRESSED_SIZE]);
37
38impl_zeroize_tuple!(EcdhP224PublicKey);
39
40#[derive(Clone)]
42pub struct EcdhP224SecretKey(SecretBuffer<{ ec::P224_SCALAR_SIZE }>);
43
44impl_zeroize_on_drop_tuple!(EcdhP224SecretKey);
45
46#[derive(Clone)]
48pub struct EcdhP224SharedSecret(ApiKey);
49
50impl_zeroize_on_drop_tuple!(EcdhP224SharedSecret);
51
52#[derive(Clone)]
54pub struct EcdhP224Ciphertext([u8; ec::P224_CIPHERTEXT_SIZE]);
55
56impl EcdhP224PublicKey {
58 pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
59 if bytes.len() != ec::P224_POINT_COMPRESSED_SIZE {
60 return Err(ApiError::InvalidLength {
61 context: "EcdhP224PublicKey::from_bytes",
62 expected: ec::P224_POINT_COMPRESSED_SIZE,
63 actual: bytes.len(),
64 });
65 }
66 let point = ec::Point::deserialize_compressed(bytes)
67 .map_err(|e| ApiError::from(KemError::from(e)))?;
68 if point.is_identity() {
69 return Err(ApiError::InvalidKey {
70 context: "EcdhP224PublicKey::from_bytes",
71 #[cfg(feature = "std")]
72 message: "Public key cannot be the identity point".to_string(),
73 });
74 }
75 let mut key_bytes = [0u8; ec::P224_POINT_COMPRESSED_SIZE];
76 key_bytes.copy_from_slice(bytes);
77 Ok(Self(key_bytes))
78 }
79
80 pub fn to_bytes(&self) -> Vec<u8> {
81 self.0.to_vec()
82 }
83
84 pub fn validate(&self) -> ApiResult<()> {
85 let point = ec::Point::deserialize_compressed(&self.0)
86 .map_err(|e| ApiError::from(KemError::from(e)))?;
87 if point.is_identity() {
88 return Err(ApiError::InvalidKey {
89 context: "validate_public_key",
90 #[cfg(feature = "std")]
91 message: "Public key is the identity point".to_string(),
92 });
93 }
94 Ok(())
95 }
96}
97
98impl Serialize for EcdhP224PublicKey {
99 fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
100 Self::from_bytes(bytes)
101 }
102 fn to_bytes(&self) -> Vec<u8> {
103 self.to_bytes()
104 }
105}
106
107impl EcdhP224SecretKey {
109 pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
110 if bytes.len() != ec::P224_SCALAR_SIZE {
111 return Err(ApiError::InvalidLength {
112 context: "EcdhP224SecretKey::from_bytes",
113 expected: ec::P224_SCALAR_SIZE,
114 actual: bytes.len(),
115 });
116 }
117 let mut buffer = SecretBuffer::zeroed();
118 buffer.as_mut().copy_from_slice(bytes);
119 let scalar = ec::Scalar::from_secret_buffer(buffer.clone())
120 .map_err(|e| ApiError::from(KemError::from(e)))?;
121 drop(scalar);
122 Ok(Self(buffer))
123 }
124 pub fn to_bytes(&self) -> ZeroizingBytes {
125 self.0.to_bytes_zeroizing_boxed()
126 }
127 pub fn validate(&self) -> ApiResult<()> {
128 let _ = ec::Scalar::from_secret_buffer(self.0.clone())
129 .map_err(|e| ApiError::from(KemError::from(e)))?;
130 Ok(())
131 }
132}
133
134impl SerializeSecret for EcdhP224SecretKey {
135 fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
136 Self::from_bytes(bytes)
137 }
138 fn to_bytes_zeroizing(&self) -> ZeroizingBytes {
139 self.to_bytes()
140 }
141}
142
143impl EcdhP224SharedSecret {
145 pub fn to_bytes(&self) -> ZeroizingBytes {
146 self.0.to_bytes_zeroizing_boxed()
147 }
148}
149
150impl SerializeSecret for EcdhP224SharedSecret {
151 fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
152 if bytes.len() != ec::P224_KEM_SHARED_SECRET_KDF_OUTPUT_SIZE {
153 return Err(ApiError::InvalidLength {
154 context: "EcdhP224SharedSecret::from_bytes",
155 expected: ec::P224_KEM_SHARED_SECRET_KDF_OUTPUT_SIZE,
156 actual: bytes.len(),
157 });
158 }
159 Ok(Self(ApiKey::new(bytes)))
160 }
161 fn to_bytes_zeroizing(&self) -> ZeroizingBytes {
162 self.to_bytes()
163 }
164}
165
166impl EcdhP224Ciphertext {
168 pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
169 if bytes.len() != ec::P224_CIPHERTEXT_SIZE {
170 return Err(ApiError::InvalidLength {
171 context: "EcdhP224Ciphertext::from_bytes",
172 expected: ec::P224_CIPHERTEXT_SIZE,
173 actual: bytes.len(),
174 });
175 }
176 let pk_bytes = &bytes[..ec::P224_POINT_COMPRESSED_SIZE];
177 let point = ec::Point::deserialize_compressed(pk_bytes)
178 .map_err(|e| ApiError::from(KemError::from(e)))?;
179 if point.is_identity() {
180 return Err(ApiError::InvalidCiphertext {
181 context: "EcdhP224Ciphertext::from_bytes",
182 #[cfg(feature = "std")]
183 message: "Ephemeral public key cannot be the identity point".to_string(),
184 });
185 }
186 let mut ct_bytes = [0u8; ec::P224_CIPHERTEXT_SIZE];
187 ct_bytes.copy_from_slice(bytes);
188 Ok(Self(ct_bytes))
189 }
190 pub fn to_bytes(&self) -> Vec<u8> {
191 self.0.to_vec()
192 }
193 pub fn validate(&self) -> ApiResult<()> {
194 let pk_bytes = &self.0[..ec::P224_POINT_COMPRESSED_SIZE];
195 let point = ec::Point::deserialize_compressed(pk_bytes)
196 .map_err(|e| ApiError::from(KemError::from(e)))?;
197 if point.is_identity() {
198 return Err(ApiError::InvalidCiphertext {
199 context: "validate_ciphertext",
200 #[cfg(feature = "std")]
201 message: "Ciphertext contains identity point".to_string(),
202 });
203 }
204 Ok(())
205 }
206}
207
208impl Serialize for EcdhP224Ciphertext {
209 fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
210 Self::from_bytes(bytes)
211 }
212 fn to_bytes(&self) -> Vec<u8> {
213 self.to_bytes()
214 }
215}
216
217fn calc_auth_tag(shared_secret: &[u8]) -> Result<[u8; ec::P224_TAG_SIZE], KemError> {
222 let mut hmac = Hmac::<Sha256>::new(shared_secret).map_err(KemError::from)?;
223 hmac.update(CONFIRMATION_LABEL).map_err(KemError::from)?;
224
225 let tag_vec = hmac.finalize().map_err(KemError::from)?;
227
228 let mut truncated = [0u8; ec::P224_TAG_SIZE];
230 truncated.copy_from_slice(&tag_vec[..ec::P224_TAG_SIZE]);
231 Ok(truncated)
232}
233
234impl Kem for EcdhP224 {
235 type PublicKey = EcdhP224PublicKey;
236 type SecretKey = EcdhP224SecretKey;
237 type SharedSecret = EcdhP224SharedSecret;
238 type Ciphertext = EcdhP224Ciphertext;
239 type KeyPair = (Self::PublicKey, Self::SecretKey);
240
241 fn name() -> &'static str {
242 "ECDH-P224"
243 }
244
245 fn keypair<R: CryptoRng + RngCore>(rng: &mut R) -> ApiResult<Self::KeyPair> {
246 let (sk_scalar, pk_point) =
247 ec::generate_keypair(rng).map_err(|e| ApiError::from(KemError::from(e)))?;
248 let public_key = EcdhP224PublicKey(pk_point.serialize_compressed());
249 let secret_key = EcdhP224SecretKey(sk_scalar.as_secret_buffer().clone());
250 Ok((public_key, secret_key))
251 }
252
253 fn public_key(keypair: &Self::KeyPair) -> Self::PublicKey {
254 keypair.0.clone()
255 }
256
257 fn secret_key(keypair: &Self::KeyPair) -> Self::SecretKey {
258 keypair.1.clone()
259 }
260
261 fn encapsulate<R: CryptoRng + RngCore>(
262 rng: &mut R,
263 public_key_recipient: &Self::PublicKey,
264 ) -> ApiResult<(Self::Ciphertext, Self::SharedSecret)> {
265 let pk_r_point = ec::Point::deserialize_compressed(&public_key_recipient.0)
266 .map_err(|e| ApiError::from(KemError::from(e)))?;
267 if pk_r_point.is_identity() {
268 return Err(ApiError::InvalidKey {
269 context: "ECDH-P224 encapsulate",
270 #[cfg(feature = "std")]
271 message: "Recipient public key is identity".to_string(),
272 });
273 }
274
275 let (ephemeral_scalar, ephemeral_point) =
276 ec::generate_keypair(rng).map_err(|e| ApiError::from(KemError::from(e)))?;
277 let ephemeral_pk_compressed = ephemeral_point.serialize_compressed();
278
279 let shared_point = ec::scalar_mult(&ephemeral_scalar, &pk_r_point)
280 .map_err(|e| ApiError::from(KemError::from(e)))?;
281 if shared_point.is_identity() {
282 return Err(ApiError::DecryptionFailed {
283 context: "ECDH-P224 encapsulate",
284 #[cfg(feature = "std")]
285 message: "Shared point is identity".to_string(),
286 });
287 }
288 let x_coord_bytes = Zeroizing::new(shared_point.x_coordinate_bytes());
289
290 let kdf_ikm = concat_kdf_ikm(
291 x_coord_bytes.as_ref(),
292 &ephemeral_pk_compressed,
293 &public_key_recipient.0,
294 );
295
296 let ss_bytes = ec::kdf_hkdf_sha256_for_ecdh_kem(&kdf_ikm, Some(KDF_INFO))
297 .map_err(|e| ApiError::from(KemError::from(e)))?;
298
299 let shared_secret = EcdhP224SharedSecret(ApiKey::new(&ss_bytes[..]));
300
301 let mut ct_bytes = [0u8; ec::P224_CIPHERTEXT_SIZE];
303 ct_bytes[..ec::P224_POINT_COMPRESSED_SIZE].copy_from_slice(&ephemeral_pk_compressed);
304 let tag = calc_auth_tag(&ss_bytes[..]).map_err(ApiError::from)?;
305 ct_bytes[ec::P224_POINT_COMPRESSED_SIZE..].copy_from_slice(&tag);
306 let ciphertext = EcdhP224Ciphertext(ct_bytes);
307
308 drop(ephemeral_scalar);
309 Ok((ciphertext, shared_secret))
310 }
311
312 fn decapsulate(
313 secret_key_recipient: &Self::SecretKey,
314 ciphertext_ephemeral_pk: &Self::Ciphertext,
315 ) -> ApiResult<Self::SharedSecret> {
316 let (pk_bytes, tag_bytes) = ciphertext_ephemeral_pk
318 .0
319 .split_at(ec::P224_POINT_COMPRESSED_SIZE);
320
321 let mut received_tag = [0u8; ec::P224_TAG_SIZE];
323 received_tag.copy_from_slice(tag_bytes);
324
325 let sk_r_scalar = ec::Scalar::from_secret_buffer(secret_key_recipient.0.clone())
326 .map_err(|e| ApiError::from(KemError::from(e)))?;
327 let q_e_point = ec::Point::deserialize_compressed(pk_bytes)
328 .map_err(|e| ApiError::from(KemError::from(e)))?;
329 if q_e_point.is_identity() {
330 return Err(ApiError::InvalidCiphertext {
331 context: "ECDH-P224 decapsulate",
332 #[cfg(feature = "std")]
333 message: "Ephemeral PK is identity".to_string(),
334 });
335 }
336
337 let shared_point = ec::scalar_mult(&sk_r_scalar, &q_e_point)
338 .map_err(|e| ApiError::from(KemError::from(e)))?;
339 if shared_point.is_identity() {
340 return Err(ApiError::DecryptionFailed {
341 context: "ECDH-P224 decapsulate",
342 #[cfg(feature = "std")]
343 message: "Shared point is identity".to_string(),
344 });
345 }
346 let x_coord_bytes = Zeroizing::new(shared_point.x_coordinate_bytes());
347 let q_r_point =
348 ec::scalar_mult_base_g(&sk_r_scalar).map_err(|e| ApiError::from(KemError::from(e)))?;
349
350 let kdf_ikm = concat_kdf_ikm(
351 x_coord_bytes.as_ref(),
352 pk_bytes,
353 &q_r_point.serialize_compressed(),
354 );
355
356 let ss_bytes = ec::kdf_hkdf_sha256_for_ecdh_kem(&kdf_ikm, Some(KDF_INFO))
357 .map_err(|e| ApiError::from(KemError::from(e)))?;
358
359 let expected_tag = calc_auth_tag(&ss_bytes[..]).map_err(ApiError::from)?;
361
362 use dcrypt_common::security::SecureCompare;
364 if !received_tag.secure_eq(&expected_tag) {
365 return Err(ApiError::DecryptionFailed {
366 context: "ECDH-P224 decapsulate",
367 #[cfg(feature = "std")]
368 message: "Authentication tag mismatch".to_string(),
369 });
370 }
371
372 Ok(EcdhP224SharedSecret(ApiKey::new(&ss_bytes[..])))
373 }
374}
375
376#[cfg(test)]
377mod tests;