cmail_rpgp/crypto/
ecdsa.rs

1use ecdsa::SigningKey;
2use elliptic_curve::sec1::ToEncodedPoint;
3use p521::NistP521;
4use rand::{CryptoRng, Rng};
5use signature::hazmat::{PrehashSigner, PrehashVerifier};
6use zeroize::ZeroizeOnDrop;
7
8use crate::crypto::ecc_curve::ECCCurve;
9use crate::crypto::hash::HashAlgorithm;
10use crate::crypto::Signer;
11use crate::errors::Result;
12use crate::types::EcdsaPublicParams;
13use crate::types::{Mpi, PlainSecretParams, PublicParams};
14
15#[derive(Clone, PartialEq, Eq, ZeroizeOnDrop, derive_more::Debug)]
16pub enum SecretKey {
17    P256(#[debug("..")] p256::SecretKey),
18    P384(#[debug("..")] p384::SecretKey),
19    P521(#[debug("..")] p521::SecretKey),
20    Secp256k1(#[debug("..")] k256::SecretKey),
21    Unsupported {
22        /// The secret point.
23        #[debug("..")]
24        x: Mpi,
25        #[zeroize(skip)]
26        curve: ECCCurve,
27    },
28}
29
30impl Signer for SecretKey {
31    fn sign(
32        &self,
33        hash: HashAlgorithm,
34        digest: &[u8],
35        pub_params: &PublicParams,
36    ) -> Result<Vec<Vec<u8>>> {
37        ensure!(
38            matches!(pub_params, PublicParams::ECDSA(..)),
39            "invalid public params"
40        );
41
42        if let Some(field_size) = self.secret_key_length() {
43            // We require that the signing key length is matched by the hash digest length,
44            // see https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.2-5
45
46            let field_size = match field_size {
47                66 => 64, // nist p521 is treated as though it were 512 bit-sized
48                s => s,
49            };
50
51            ensure!(
52                digest.len() >= field_size,
53                "Hash digest size ({:?}) must at least match key size ({:?})",
54                hash,
55                self
56            );
57        }
58
59        let (r, s) = match self {
60            Self::P256(secret_key) => {
61                let secret = p256::ecdsa::SigningKey::from(secret_key);
62                let signature: p256::ecdsa::Signature = secret.sign_prehash(digest)?;
63                let (r, s) = signature.split_bytes();
64                (r.to_vec(), s.to_vec())
65            }
66            Self::P384(secret_key) => {
67                let secret = p384::ecdsa::SigningKey::from(secret_key);
68                let signature: p384::ecdsa::Signature = secret.sign_prehash(digest)?;
69                let (r, s) = signature.split_bytes();
70                (r.to_vec(), s.to_vec())
71            }
72            Self::P521(secret_key) => {
73                let secret: SigningKey<NistP521> = secret_key.into();
74                let signing_key = p521::ecdsa::SigningKey::from(secret);
75                let signature: p521::ecdsa::Signature = signing_key.sign_prehash(digest)?;
76                let (r, s) = signature.split_bytes();
77                (r.to_vec(), s.to_vec())
78            }
79            Self::Secp256k1(secret_key) => {
80                let secret = k256::ecdsa::SigningKey::from(secret_key);
81                let signature: k256::ecdsa::Signature = secret.sign_prehash(digest)?;
82                let (r, s) = signature.split_bytes();
83                (r.to_vec(), s.to_vec())
84            }
85            Self::Unsupported { curve, .. } => {
86                unsupported_err!("curve {:?} for ECDSA", curve)
87            }
88        };
89
90        Ok(vec![r, s])
91    }
92}
93
94impl SecretKey {
95    pub(crate) fn secret_key_length(&self) -> Option<usize> {
96        match self {
97            Self::P256 { .. } => Some(32),
98            Self::P384 { .. } => Some(48),
99            Self::P521 { .. } => Some(66),
100            Self::Secp256k1 { .. } => Some(32),
101            Self::Unsupported { .. } => None,
102        }
103    }
104}
105/// Generate an ECDSA KeyPair.
106pub fn generate_key<R: Rng + CryptoRng>(
107    mut rng: R,
108    curve: &ECCCurve,
109) -> Result<(PublicParams, PlainSecretParams)> {
110    match curve {
111        ECCCurve::P256 => {
112            let secret = p256::SecretKey::random(&mut rng);
113            let public = secret.public_key();
114            let secret = Mpi::from_slice(secret.to_bytes().as_slice());
115
116            Ok((
117                PublicParams::ECDSA(EcdsaPublicParams::P256 {
118                    key: public,
119                    p: Mpi::from_slice(public.to_encoded_point(false).as_bytes()),
120                }),
121                PlainSecretParams::ECDSA(secret),
122            ))
123        }
124
125        ECCCurve::P384 => {
126            let secret = p384::SecretKey::random(&mut rng);
127            let public = secret.public_key();
128            let secret = Mpi::from_slice(secret.to_bytes().as_slice());
129
130            Ok((
131                PublicParams::ECDSA(EcdsaPublicParams::P384 {
132                    key: public,
133                    p: Mpi::from_slice(public.to_encoded_point(false).as_bytes()),
134                }),
135                PlainSecretParams::ECDSA(secret),
136            ))
137        }
138
139        ECCCurve::P521 => {
140            let secret = p521::SecretKey::random(&mut rng);
141            let public = secret.public_key();
142            let secret = Mpi::from_slice(secret.to_bytes().as_slice());
143
144            Ok((
145                PublicParams::ECDSA(EcdsaPublicParams::P521 {
146                    key: public,
147                    p: Mpi::from_slice(public.to_encoded_point(false).as_bytes()),
148                }),
149                PlainSecretParams::ECDSA(secret),
150            ))
151        }
152
153        ECCCurve::Secp256k1 => {
154            let secret = k256::SecretKey::random(&mut rng);
155            let public = secret.public_key();
156            let secret = Mpi::from_slice(secret.to_bytes().as_slice());
157
158            Ok((
159                PublicParams::ECDSA(EcdsaPublicParams::Secp256k1 {
160                    key: public,
161                    p: Mpi::from_slice(public.to_encoded_point(false).as_bytes()),
162                }),
163                PlainSecretParams::ECDSA(secret),
164            ))
165        }
166
167        _ => unsupported_err!("curve {:?} for ECDSA", curve),
168    }
169}
170
171/// Verify an ECDSA signature.
172pub fn verify(
173    p: &EcdsaPublicParams,
174    hash: HashAlgorithm,
175    hashed: &[u8],
176    sig: &[Mpi],
177) -> Result<()> {
178    // NOTE: the `None` case will run into an `unsupported_err`, below, so it's ok not to consider it here
179    if let Some(field_size) = p.secret_key_length() {
180        // Error out for size mismatches that would get rejected in ecdsa::hazmat::bits2field
181        ensure!(
182            hashed.len() >= field_size / 2,
183            "Hash algorithm {:?} cannot be combined with key {:?}",
184            hash,
185            p
186        );
187
188        // RFC 9580:
189        // An ECDSA signature MUST use a hash algorithm with a digest size of at least the curve's "fsize" value [..]"
190        // (See https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.2-5)
191        let min_hash_len = match field_size {
192            // "[..] except in the case of NIST P-521, for which at least a 512-bit hash algorithm MUST be used"
193            521 => 512,
194
195            f => f,
196        };
197        let Some(digest_size) = hash.digest_size() else {
198            bail!("ECDSA signature: invalid hash algorithm: {:?}", hash);
199        };
200        ensure!(
201            digest_size * 8 >= min_hash_len,
202            "ECDSA signature: hash algorithm {:?} is too weak for key {:?}",
203            hash,
204            p
205        );
206    }
207
208    match p {
209        EcdsaPublicParams::P256 { key, .. } => {
210            const FLEN: usize = 32;
211            ensure_eq!(sig.len(), 2);
212            let r = sig[0].as_bytes();
213            let s = sig[1].as_bytes();
214            ensure!(r.len() <= FLEN, "invalid R (len)");
215            ensure!(s.len() <= FLEN, "invalid S (len)");
216            let mut sig_bytes = [0u8; 2 * FLEN];
217
218            // add padding if the values were encoded short
219            sig_bytes[(FLEN - r.len())..FLEN].copy_from_slice(r);
220            sig_bytes[FLEN + (FLEN - s.len())..].copy_from_slice(s);
221
222            let sig = p256::ecdsa::Signature::try_from(&sig_bytes[..])?;
223            let pk = p256::ecdsa::VerifyingKey::from_affine(key.as_affine().to_owned())?;
224
225            pk.verify_prehash(hashed, &sig)?;
226
227            Ok(())
228        }
229        EcdsaPublicParams::P384 { key, .. } => {
230            const FLEN: usize = 48;
231            ensure_eq!(sig.len(), 2);
232
233            let r = sig[0].as_bytes();
234            let s = sig[1].as_bytes();
235
236            ensure!(r.len() <= FLEN, "invalid R (len)");
237            ensure!(s.len() <= FLEN, "invalid S (len)");
238
239            let mut sig_bytes = [0u8; 2 * FLEN];
240
241            // add padding if the values were encoded short
242            sig_bytes[(FLEN - r.len())..FLEN].copy_from_slice(r);
243            sig_bytes[FLEN + (FLEN - s.len())..].copy_from_slice(s);
244
245            let pk = p384::ecdsa::VerifyingKey::from_affine(key.as_affine().to_owned())?;
246            let sig = p384::ecdsa::Signature::try_from(&sig_bytes[..])?;
247
248            pk.verify_prehash(hashed, &sig)?;
249
250            Ok(())
251        }
252        EcdsaPublicParams::P521 { key, .. } => {
253            const FLEN: usize = 66;
254            ensure_eq!(sig.len(), 2);
255
256            let r = sig[0].as_bytes();
257            let s = sig[1].as_bytes();
258
259            ensure!(r.len() <= FLEN, "invalid R (len)");
260            ensure!(s.len() <= FLEN, "invalid S (len)");
261
262            let mut sig_bytes = [0u8; 2 * FLEN];
263
264            // add padding if the values were encoded short
265            sig_bytes[(FLEN - r.len())..FLEN].copy_from_slice(r);
266            sig_bytes[FLEN + (FLEN - s.len())..].copy_from_slice(s);
267
268            let pk = p521::ecdsa::VerifyingKey::from_affine(key.as_affine().to_owned())?;
269            let sig = p521::ecdsa::Signature::try_from(&sig_bytes[..])?;
270
271            pk.verify_prehash(hashed, &sig)?;
272
273            Ok(())
274        }
275        EcdsaPublicParams::Secp256k1 { key, .. } => {
276            const FLEN: usize = 32;
277            ensure_eq!(sig.len(), 2);
278            let r = sig[0].as_bytes();
279            let s = sig[1].as_bytes();
280            ensure!(r.len() <= FLEN, "invalid R (len)");
281            ensure!(s.len() <= FLEN, "invalid S (len)");
282            let mut sig_bytes = [0u8; 2 * FLEN];
283
284            // add padding if the values were encoded short
285            sig_bytes[(FLEN - r.len())..FLEN].copy_from_slice(r);
286            sig_bytes[FLEN + (FLEN - s.len())..].copy_from_slice(s);
287
288            let pk = k256::ecdsa::VerifyingKey::from_affine(key.as_affine().to_owned())?;
289            let sig = k256::ecdsa::Signature::try_from(&sig_bytes[..])?;
290
291            pk.verify_prehash(hashed, &sig)?;
292
293            Ok(())
294        }
295        EcdsaPublicParams::Unsupported { curve, .. } => {
296            unsupported_err!("curve {:?} for ECDSA", curve.to_string())
297        }
298    }
299}