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