dcrypt_sign/ecdsa/p192/
mod.rs

1//! ECDSA implementation for NIST P-192 curve
2//!
3//! This implementation follows FIPS 186-4: Digital Signature Standard (DSS)
4//! and SP 800-56A Rev. 3: Recommendation for Pair-Wise Key-Establishment Schemes
5//! Using Discrete Logarithm Cryptography. SHA-256 is used as the hash function
6//! as recommended for P-192.
7
8use crate::ecdsa::common::SignatureComponents;
9use dcrypt_algorithms::ec::p192 as ec; // Use P-192 algorithms
10use dcrypt_algorithms::hash::sha2::Sha256; // Use Sha256
11use dcrypt_algorithms::hash::HashFunction;
12use dcrypt_algorithms::mac::hmac::Hmac;
13use dcrypt_api::{error::Error as ApiError, Result as ApiResult, Signature as SignatureTrait};
14use dcrypt_internal::constant_time::ct_eq;
15use rand::{CryptoRng, RngCore};
16use zeroize::Zeroize;
17
18/// ECDSA signature scheme using NIST P-192 curve (secp192r1)
19pub struct EcdsaP192;
20
21/// P-192 public key in uncompressed format (0x04 || X || Y)
22#[derive(Clone, Zeroize)]
23pub struct EcdsaP192PublicKey(pub [u8; ec::P192_POINT_UNCOMPRESSED_SIZE]);
24
25/// P-192 secret key
26#[derive(Clone)]
27pub struct EcdsaP192SecretKey {
28    raw: ec::Scalar,
29    bytes: [u8; ec::P192_SCALAR_SIZE],
30}
31
32impl Zeroize for EcdsaP192SecretKey {
33    fn zeroize(&mut self) {
34        self.bytes.zeroize();
35        // self.raw is an ec::Scalar, which implements ZeroizeOnDrop.
36    }
37}
38
39impl Drop for EcdsaP192SecretKey {
40    fn drop(&mut self) {
41        self.zeroize();
42    }
43}
44
45/// P-192 signature encoded in ASN.1 DER format
46#[derive(Clone)]
47pub struct EcdsaP192Signature(pub Vec<u8>);
48
49// AsRef/AsMut implementations
50impl AsRef<[u8]> for EcdsaP192PublicKey {
51    fn as_ref(&self) -> &[u8] {
52        &self.0
53    }
54}
55impl AsMut<[u8]> for EcdsaP192PublicKey {
56    fn as_mut(&mut self) -> &mut [u8] {
57        &mut self.0
58    }
59}
60impl AsRef<[u8]> for EcdsaP192SecretKey {
61    fn as_ref(&self) -> &[u8] {
62        &self.bytes
63    }
64}
65// REMOVED: AsMut<[u8]> for EcdsaP192SecretKey - Security: prevent direct mutation of secret key bytes
66impl AsRef<[u8]> for EcdsaP192Signature {
67    fn as_ref(&self) -> &[u8] {
68        &self.0
69    }
70}
71impl AsMut<[u8]> for EcdsaP192Signature {
72    fn as_mut(&mut self) -> &mut [u8] {
73        &mut self.0
74    }
75}
76
77impl SignatureTrait for EcdsaP192 {
78    type PublicKey = EcdsaP192PublicKey;
79    type SecretKey = EcdsaP192SecretKey;
80    type SignatureData = EcdsaP192Signature;
81    type KeyPair = (Self::PublicKey, Self::SecretKey);
82
83    fn name() -> &'static str {
84        "ECDSA-P192"
85    }
86
87    fn keypair<R: CryptoRng + RngCore>(rng: &mut R) -> ApiResult<Self::KeyPair> {
88        let (sk_scalar, pk_point) = ec::generate_keypair(rng).map_err(ApiError::from)?;
89
90        let sk_bytes: [u8; ec::P192_SCALAR_SIZE] = sk_scalar.serialize();
91
92        if sk_scalar.is_zero() {
93            return Err(ApiError::InvalidParameter {
94                context: "ECDSA-P192 keypair",
95                #[cfg(feature = "std")]
96                message: "Generated secret key is zero".to_string(),
97            });
98        }
99
100        let secret_key = EcdsaP192SecretKey {
101            raw: sk_scalar,
102            bytes: sk_bytes,
103        };
104        let public_key = EcdsaP192PublicKey(pk_point.serialize_uncompressed());
105        Ok((public_key, secret_key))
106    }
107
108    fn public_key(keypair: &Self::KeyPair) -> Self::PublicKey {
109        keypair.0.clone()
110    }
111    fn secret_key(keypair: &Self::KeyPair) -> Self::SecretKey {
112        keypair.1.clone()
113    }
114
115    fn sign(message: &[u8], secret_key: &Self::SecretKey) -> ApiResult<Self::SignatureData> {
116        let mut hasher = Sha256::new(); // Use SHA-256 for P-192
117        hasher.update(message).map_err(ApiError::from)?;
118        let hash_output = hasher.finalize().map_err(ApiError::from)?;
119
120        // Truncate or reduce hash output to P-192 scalar size (24 bytes)
121        let mut z_bytes_fixed_size = [0u8; ec::P192_SCALAR_SIZE];
122        z_bytes_fixed_size.copy_from_slice(&hash_output.as_ref()[..ec::P192_SCALAR_SIZE]);
123        let z = reduce_bytes_to_scalar_p192(&z_bytes_fixed_size)?;
124
125        let d = secret_key.raw.clone();
126        let mut rng = rand::thread_rng();
127
128        loop {
129            let k = deterministic_k_hedged_p192::<Sha256, _>(&d, &z, &mut rng); // Specify hash for k_hedged
130
131            let kg = ec::scalar_mult_base_g(&k).map_err(ApiError::from)?;
132
133            if kg.is_identity() {
134                continue;
135            }
136            let r_bytes = kg.x_coordinate_bytes();
137
138            let r = match reduce_bytes_to_scalar_p192(&r_bytes) {
139                Ok(scalar) if !scalar.is_zero() => scalar,
140                _ => continue,
141            };
142
143            let k_inv = k.inv_mod_n().map_err(ApiError::from)?;
144            let rd = r.mul_mod_n(&d).map_err(ApiError::from)?;
145            let z_plus_rd = z.add_mod_n(&rd).map_err(ApiError::from)?;
146            let s = k_inv.mul_mod_n(&z_plus_rd).map_err(ApiError::from)?;
147
148            if s.is_zero() {
149                continue;
150            }
151
152            let sig_comps = SignatureComponents {
153                r: r.serialize().to_vec(),
154                s: s.serialize().to_vec(),
155            };
156            return Ok(EcdsaP192Signature(sig_comps.to_der()));
157        }
158    }
159
160    fn verify(
161        message: &[u8],
162        signature: &Self::SignatureData,
163        public_key: &Self::PublicKey,
164    ) -> ApiResult<()> {
165        let sig_comps = SignatureComponents::from_der(&signature.0)?;
166
167        if sig_comps.r.len() > ec::P192_SCALAR_SIZE || sig_comps.s.len() > ec::P192_SCALAR_SIZE {
168            return Err(ApiError::InvalidSignature {
169                context: "ECDSA-P192 verify",
170                #[cfg(feature = "std")]
171                message: "Invalid signature component size".to_string(),
172            });
173        }
174
175        let mut r_bytes = [0u8; ec::P192_SCALAR_SIZE];
176        let mut s_bytes = [0u8; ec::P192_SCALAR_SIZE];
177        let r_offset = ec::P192_SCALAR_SIZE.saturating_sub(sig_comps.r.len());
178        let s_offset = ec::P192_SCALAR_SIZE.saturating_sub(sig_comps.s.len());
179        r_bytes[r_offset..].copy_from_slice(&sig_comps.r);
180        s_bytes[s_offset..].copy_from_slice(&sig_comps.s);
181
182        let r = ec::Scalar::new(r_bytes).map_err(|_| ApiError::InvalidSignature {
183            context: "ECDSA-P192 verify",
184            #[cfg(feature = "std")]
185            message: "Invalid r component".to_string(),
186        })?;
187        let s = ec::Scalar::new(s_bytes).map_err(|_| ApiError::InvalidSignature {
188            context: "ECDSA-P192 verify",
189            #[cfg(feature = "std")]
190            message: "Invalid s component".to_string(),
191        })?;
192
193        let mut hasher = Sha256::new(); // Use SHA-256
194        hasher.update(message).map_err(ApiError::from)?;
195        let hash_output = hasher.finalize().map_err(ApiError::from)?;
196
197        let mut z_bytes_fixed_size = [0u8; ec::P192_SCALAR_SIZE];
198        z_bytes_fixed_size.copy_from_slice(&hash_output.as_ref()[..ec::P192_SCALAR_SIZE]); // Truncate SHA-256 output
199        let z = reduce_bytes_to_scalar_p192(&z_bytes_fixed_size)?;
200
201        let s_inv = s.inv_mod_n().map_err(ApiError::from)?;
202        let u1 = z.mul_mod_n(&s_inv).map_err(ApiError::from)?;
203        let u2 = r.mul_mod_n(&s_inv).map_err(ApiError::from)?;
204
205        let q_point = ec::Point::deserialize_uncompressed(&public_key.0).map_err(ApiError::from)?;
206
207        if q_point.is_identity() {
208            return Err(ApiError::InvalidKey {
209                context: "ECDSA-P192 verify",
210                #[cfg(feature = "std")]
211                message: "Public key is the point at infinity".to_string(),
212            });
213        }
214
215        let u1g = ec::scalar_mult_base_g(&u1).map_err(ApiError::from)?;
216        let u2q = ec::scalar_mult(&u2, &q_point).map_err(ApiError::from)?;
217
218        let point = u1g.add(&u2q);
219
220        if point.is_identity() {
221            return Err(ApiError::InvalidSignature {
222                context: "ECDSA-P192 verify",
223                #[cfg(feature = "std")]
224                message: "Verification point is identity".to_string(),
225            });
226        }
227
228        let x1_bytes = point.x_coordinate_bytes();
229        let v = reduce_bytes_to_scalar_p192(&x1_bytes)?;
230
231        if !ct_eq(r.serialize(), v.serialize()) {
232            return Err(ApiError::InvalidSignature {
233                context: "ECDSA-P192 verify",
234                #[cfg(feature = "std")]
235                message: "Signature verification failed (r != v)".to_string(),
236            });
237        }
238        Ok(())
239    }
240}
241
242// Adapt deterministic_k_hedged for P-192 using a specified HashFunction (e.g., Sha256)
243fn deterministic_k_hedged_p192<H: HashFunction + Clone, R: RngCore + CryptoRng>(
244    d: &ec::Scalar,
245    z: &ec::Scalar,
246    rng: &mut R,
247) -> ec::Scalar {
248    use zeroize::Zeroize;
249    let hash_len = H::output_size(); // e.g., 32 for Sha256
250    let hmac_block_len = H::block_size();
251
252    let mut rbuf = [0u8; ec::P192_SCALAR_SIZE]; // 24 bytes for P-192
253    rng.fill_bytes(&mut rbuf);
254
255    let mut v_hmac_block = vec![0x01u8; hmac_block_len];
256    let mut k_hmac_block = vec![0x00u8; hmac_block_len];
257
258    // Step C
259    {
260        let mut mac = Hmac::<H>::new(&k_hmac_block).unwrap();
261        mac.update(&v_hmac_block).unwrap();
262        mac.update(&[0x00]).unwrap();
263        mac.update(&d.serialize()).unwrap();
264        mac.update(&z.serialize()).unwrap();
265        mac.update(&rbuf).unwrap();
266        let mac_res = mac.finalize().unwrap();
267        k_hmac_block[..hash_len].copy_from_slice(&mac_res);
268        k_hmac_block[hash_len..].fill(0);
269    }
270    // Step D
271    let v_new_res = Hmac::<H>::mac(&k_hmac_block, &v_hmac_block).unwrap();
272    v_hmac_block[..hash_len].copy_from_slice(&v_new_res);
273    v_hmac_block[hash_len..].fill(0);
274    // Step E
275    {
276        let mut mac = Hmac::<H>::new(&k_hmac_block).unwrap();
277        mac.update(&v_hmac_block).unwrap();
278        mac.update(&[0x01]).unwrap();
279        mac.update(&d.serialize()).unwrap();
280        mac.update(&z.serialize()).unwrap();
281        mac.update(&rbuf).unwrap();
282        let mac_res = mac.finalize().unwrap();
283        k_hmac_block[..hash_len].copy_from_slice(&mac_res);
284        k_hmac_block[hash_len..].fill(0);
285    }
286    // Step F
287    let v_new_res = Hmac::<H>::mac(&k_hmac_block, &v_hmac_block).unwrap();
288    v_hmac_block[..hash_len].copy_from_slice(&v_new_res);
289    v_hmac_block[hash_len..].fill(0);
290
291    // Step G/H
292    loop {
293        let v_new_res = Hmac::<H>::mac(&k_hmac_block, &v_hmac_block).unwrap();
294        v_hmac_block[..hash_len].copy_from_slice(&v_new_res);
295        v_hmac_block[hash_len..].fill(0);
296
297        let mut candidate_scalar_bytes = [0u8; ec::P192_SCALAR_SIZE];
298        candidate_scalar_bytes.copy_from_slice(&v_hmac_block[..ec::P192_SCALAR_SIZE]);
299
300        if let Ok(candidate) = ec::Scalar::new(candidate_scalar_bytes) {
301            rbuf.zeroize();
302            return candidate;
303        }
304
305        let mut mac = Hmac::<H>::new(&k_hmac_block).unwrap();
306        mac.update(&v_hmac_block).unwrap();
307        mac.update(&[0x00]).unwrap();
308        let mac_res = mac.finalize().unwrap();
309        k_hmac_block[..hash_len].copy_from_slice(&mac_res);
310        k_hmac_block[hash_len..].fill(0);
311
312        let v_new_res = Hmac::<H>::mac(&k_hmac_block, &v_hmac_block).unwrap();
313        v_hmac_block[..hash_len].copy_from_slice(&v_new_res);
314        v_hmac_block[hash_len..].fill(0);
315    }
316}
317
318fn reduce_bytes_to_scalar_p192(bytes: &[u8; ec::P192_SCALAR_SIZE]) -> ApiResult<ec::Scalar> {
319    ec::Scalar::new(*bytes).map_err(|algo_err| match algo_err {
320        dcrypt_algorithms::error::Error::Parameter {
321            ref name,
322            ref reason,
323        } if name.as_ref() == "P-192 Scalar"
324            && reason.as_ref().contains("Scalar cannot be zero") =>
325        {
326            ApiError::InvalidSignature {
327                context: "ECDSA-P192 scalar reduction",
328                #[cfg(feature = "std")]
329                message: "Computed scalar component is zero or invalid".to_string(),
330            }
331        }
332        _ => ApiError::from(algo_err),
333    })
334}
335
336#[cfg(test)]
337mod tests;