dcrypt_kem/ecdh/p256/
mod.rs1use super::concat_kdf_ikm;
17use crate::error::Error as KemError;
18use alloc::vec::Vec;
19use dcrypt_algorithms::ec::p256 as ec_p256;
20use dcrypt_api::{
21 error::Error as ApiError,
22 traits::serialize::{Serialize, SerializeSecret},
23 Kem, Key as ApiKey, Result as ApiResult, ZeroizingBytes,
24};
25use dcrypt_common::security::SecretBuffer;
26use dcrypt_internal::random::{try_fill_bytes_zeroing_on_error, CryptoRng, RngCore};
27use dcrypt_internal::zeroing::Zeroizing;
28
29const KDF_INFO: &[u8] = b"dcrypt-v3/ECDH-P256-KEM/shared-secret";
30
31pub struct EcdhP256;
33
34#[derive(Clone)]
36pub struct EcdhP256PublicKey([u8; ec_p256::P256_POINT_COMPRESSED_SIZE]);
37
38impl_zeroize_tuple!(EcdhP256PublicKey);
39
40impl AsRef<[u8]> for EcdhP256PublicKey {
41 fn as_ref(&self) -> &[u8] {
42 &self.0
43 }
44}
45
46impl AsMut<[u8]> for EcdhP256PublicKey {
47 fn as_mut(&mut self) -> &mut [u8] {
48 &mut self.0
49 }
50}
51
52#[derive(Clone)]
54pub struct EcdhP256SecretKey(SecretBuffer<{ ec_p256::P256_SCALAR_SIZE }>);
55
56impl_zeroize_on_drop_tuple!(EcdhP256SecretKey);
57
58impl AsRef<[u8]> for EcdhP256SecretKey {
59 fn as_ref(&self) -> &[u8] {
60 self.0.as_ref()
61 }
62}
63
64#[derive(Clone)]
66pub struct EcdhP256SharedSecret(ApiKey);
67
68impl_zeroize_on_drop_tuple!(EcdhP256SharedSecret);
69
70impl AsRef<[u8]> for EcdhP256SharedSecret {
71 fn as_ref(&self) -> &[u8] {
72 self.0.as_ref()
73 }
74}
75
76#[derive(Clone)]
78pub struct EcdhP256Ciphertext([u8; ec_p256::P256_POINT_COMPRESSED_SIZE]);
79
80impl AsRef<[u8]> for EcdhP256Ciphertext {
81 fn as_ref(&self) -> &[u8] {
82 &self.0
83 }
84}
85
86impl AsMut<[u8]> for EcdhP256Ciphertext {
87 fn as_mut(&mut self) -> &mut [u8] {
88 &mut self.0
89 }
90}
91
92impl EcdhP256PublicKey {
94 pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
95 if bytes.len() != ec_p256::P256_POINT_COMPRESSED_SIZE {
96 return Err(ApiError::InvalidLength {
97 context: "EcdhP256PublicKey::from_bytes",
98 expected: ec_p256::P256_POINT_COMPRESSED_SIZE,
99 actual: bytes.len(),
100 });
101 }
102 let point = ec_p256::Point::deserialize_compressed(bytes)
103 .map_err(|e| ApiError::from(KemError::from(e)))?;
104 if point.is_identity() {
105 return Err(ApiError::InvalidKey {
106 context: "EcdhP256PublicKey::from_bytes",
107 #[cfg(feature = "std")]
108 message: "Public key cannot be the identity point".to_string(),
109 });
110 }
111 let mut key_bytes = [0u8; ec_p256::P256_POINT_COMPRESSED_SIZE];
112 key_bytes.copy_from_slice(bytes);
113 Ok(Self(key_bytes))
114 }
115 pub fn to_bytes(&self) -> Vec<u8> {
116 self.0.to_vec()
117 }
118}
119
120impl Serialize for EcdhP256PublicKey {
121 fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
122 Self::from_bytes(bytes)
123 }
124 fn to_bytes(&self) -> Vec<u8> {
125 self.to_bytes()
126 }
127}
128
129impl EcdhP256SecretKey {
131 pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
132 if bytes.len() != ec_p256::P256_SCALAR_SIZE {
133 return Err(ApiError::InvalidLength {
134 context: "EcdhP256SecretKey::from_bytes",
135 expected: ec_p256::P256_SCALAR_SIZE,
136 actual: bytes.len(),
137 });
138 }
139 let mut buffer = SecretBuffer::zeroed();
140 buffer.as_mut().copy_from_slice(bytes);
141 let scalar = ec_p256::Scalar::from_secret_buffer(buffer.clone())
142 .map_err(|e| ApiError::from(KemError::from(e)))?;
143 drop(scalar);
144 Ok(Self(buffer))
145 }
146 pub fn to_bytes(&self) -> ZeroizingBytes {
147 self.0.to_bytes_zeroizing_boxed()
148 }
149}
150
151impl SerializeSecret for EcdhP256SecretKey {
152 fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
153 Self::from_bytes(bytes)
154 }
155 fn to_bytes_zeroizing(&self) -> ZeroizingBytes {
156 self.to_bytes()
157 }
158}
159
160impl EcdhP256SharedSecret {
162 pub fn to_bytes(&self) -> ZeroizingBytes {
163 self.0.to_bytes_zeroizing_boxed()
164 }
165 pub fn to_zeroizing_bytes(&self) -> ZeroizingBytes {
166 self.to_bytes()
167 }
168}
169
170impl SerializeSecret for EcdhP256SharedSecret {
171 fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
172 if bytes.len() != ec_p256::P256_KEM_SHARED_SECRET_KDF_OUTPUT_SIZE {
173 return Err(ApiError::InvalidLength {
174 context: "EcdhP256SharedSecret::from_bytes",
175 expected: ec_p256::P256_KEM_SHARED_SECRET_KDF_OUTPUT_SIZE,
176 actual: bytes.len(),
177 });
178 }
179 Ok(Self(ApiKey::new(bytes)))
180 }
181 fn to_bytes_zeroizing(&self) -> ZeroizingBytes {
182 self.to_zeroizing_bytes()
183 }
184}
185
186impl EcdhP256Ciphertext {
188 pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
189 if bytes.len() != ec_p256::P256_POINT_COMPRESSED_SIZE {
190 return Err(ApiError::InvalidLength {
191 context: "EcdhP256Ciphertext::from_bytes",
192 expected: ec_p256::P256_POINT_COMPRESSED_SIZE,
193 actual: bytes.len(),
194 });
195 }
196 let point = ec_p256::Point::deserialize_compressed(bytes)
197 .map_err(|e| ApiError::from(KemError::from(e)))?;
198 if point.is_identity() {
199 return Err(ApiError::InvalidCiphertext {
200 context: "EcdhP256Ciphertext::from_bytes",
201 #[cfg(feature = "std")]
202 message: "Ephemeral public key cannot be the identity point".to_string(),
203 });
204 }
205 let mut ct_bytes = [0u8; ec_p256::P256_POINT_COMPRESSED_SIZE];
206 ct_bytes.copy_from_slice(bytes);
207 Ok(Self(ct_bytes))
208 }
209 pub fn to_bytes(&self) -> Vec<u8> {
210 self.0.to_vec()
211 }
212}
213
214impl Serialize for EcdhP256Ciphertext {
215 fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
216 Self::from_bytes(bytes)
217 }
218 fn to_bytes(&self) -> Vec<u8> {
219 self.to_bytes()
220 }
221}
222
223impl Kem for EcdhP256 {
224 type PublicKey = EcdhP256PublicKey;
225 type SecretKey = EcdhP256SecretKey;
226 type SharedSecret = EcdhP256SharedSecret;
227 type Ciphertext = EcdhP256Ciphertext;
228 type KeyPair = (Self::PublicKey, Self::SecretKey);
229
230 fn name() -> &'static str {
231 "ECDH-P256"
232 }
233
234 fn keypair<R: CryptoRng + RngCore>(rng: &mut R) -> ApiResult<Self::KeyPair> {
235 let (sk_scalar, pk_point) =
236 ec_p256::generate_keypair(rng).map_err(|e| ApiError::from(KemError::from(e)))?;
237 let public_key = EcdhP256PublicKey(pk_point.serialize_compressed());
238 let secret_key = EcdhP256SecretKey(sk_scalar.as_secret_buffer().clone());
239 Ok((public_key, secret_key))
240 }
241
242 fn public_key(keypair: &Self::KeyPair) -> Self::PublicKey {
243 keypair.0.clone()
244 }
245
246 fn secret_key(keypair: &Self::KeyPair) -> Self::SecretKey {
247 keypair.1.clone()
248 }
249
250 fn encapsulate<R: CryptoRng + RngCore>(
251 rng: &mut R,
252 public_key_recipient: &Self::PublicKey,
253 ) -> ApiResult<(Self::Ciphertext, Self::SharedSecret)> {
254 let pk_r_point = ec_p256::Point::deserialize_compressed(&public_key_recipient.0)
255 .map_err(|e| ApiError::from(KemError::from(e)))?;
256 if pk_r_point.is_identity() {
257 return Err(ApiError::InvalidKey {
258 context: "ECDH-P256 encapsulate",
259 #[cfg(feature = "std")]
260 message: "Recipient public key cannot be the identity point".to_string(),
261 });
262 }
263 let ephemeral_scalar = loop {
267 let mut ephemeral_bytes = Zeroizing::new([0u8; ec_p256::P256_SCALAR_SIZE]);
268 try_fill_bytes_zeroing_on_error(rng, ephemeral_bytes.as_mut()).map_err(|_| {
269 ApiError::RandomGenerationError {
270 context: "ECDH-P256 encapsulate",
271 #[cfg(feature = "std")]
272 message: "caller-provided randomness source failed".to_string(),
273 }
274 })?;
275 let ephemeral_buffer = SecretBuffer::new(*ephemeral_bytes);
276 if let Ok(scalar) = ec_p256::Scalar::from_secret_buffer(ephemeral_buffer) {
277 break scalar;
278 }
279 };
280 let ephemeral_point = ec_p256::scalar_mult_base_g(&ephemeral_scalar)
281 .map_err(|e| ApiError::from(KemError::from(e)))?;
282 let ciphertext = EcdhP256Ciphertext(ephemeral_point.serialize_compressed());
283 let shared_point = ec_p256::scalar_mult(&ephemeral_scalar, &pk_r_point)
284 .map_err(|e| ApiError::from(KemError::from(e)))?;
285 if shared_point.is_identity() {
286 return Err(ApiError::DecryptionFailed {
287 context: "ECDH-P256 encapsulate",
288 #[cfg(feature = "std")]
289 message: "Shared point is the identity".to_string(),
290 });
291 }
292 let x_coord_bytes = Zeroizing::new(shared_point.x_coordinate_bytes());
293 let kdf_ikm = concat_kdf_ikm(
294 x_coord_bytes.as_ref(),
295 &ephemeral_point.serialize_compressed(),
296 &public_key_recipient.0,
297 );
298 let ss_bytes = ec_p256::kdf_hkdf_sha256_for_ecdh_kem(&kdf_ikm, Some(KDF_INFO))
299 .map_err(|e| ApiError::from(KemError::from(e)))?;
300 let shared_secret = EcdhP256SharedSecret(ApiKey::new(&ss_bytes[..]));
301 drop(ephemeral_scalar);
302 Ok((ciphertext, shared_secret))
303 }
304
305 fn decapsulate(
306 secret_key_recipient: &Self::SecretKey,
307 ciphertext_ephemeral_pk: &Self::Ciphertext,
308 ) -> ApiResult<Self::SharedSecret> {
309 let scalar_result = ec_p256::Scalar::from_secret_buffer(secret_key_recipient.0.clone());
310 let sk_r_scalar = match scalar_result {
311 Ok(scalar) => scalar,
312 Err(e) => return Err(ApiError::from(KemError::from(e))),
313 };
314 let q_e_point = ec_p256::Point::deserialize_compressed(&ciphertext_ephemeral_pk.0)
315 .map_err(|e| ApiError::from(KemError::from(e)))?;
316 if q_e_point.is_identity() {
317 return Err(ApiError::InvalidCiphertext {
318 context: "ECDH-P256 decapsulate",
319 #[cfg(feature = "std")]
320 message: "Ephemeral public key cannot be the identity point".to_string(),
321 });
322 }
323 let shared_point = ec_p256::scalar_mult(&sk_r_scalar, &q_e_point)
324 .map_err(|e| ApiError::from(KemError::from(e)))?;
325 if shared_point.is_identity() {
326 return Err(ApiError::DecryptionFailed {
327 context: "ECDH-P256 decapsulate",
328 #[cfg(feature = "std")]
329 message: "Shared point is the identity".to_string(),
330 });
331 }
332 let x_coord_bytes = Zeroizing::new(shared_point.x_coordinate_bytes());
333 let q_r_point = ec_p256::scalar_mult_base_g(&sk_r_scalar)
334 .map_err(|e| ApiError::from(KemError::from(e)))?;
335 let kdf_ikm = concat_kdf_ikm(
336 x_coord_bytes.as_ref(),
337 &ciphertext_ephemeral_pk.0,
338 &q_r_point.serialize_compressed(),
339 );
340 let ss_bytes = ec_p256::kdf_hkdf_sha256_for_ecdh_kem(&kdf_ikm, Some(KDF_INFO))
341 .map_err(|e| ApiError::from(KemError::from(e)))?;
342 let shared_secret = EcdhP256SharedSecret(ApiKey::new(&ss_bytes[..]));
343 Ok(shared_secret)
344 }
345}
346
347#[cfg(test)]
348mod tests;