cmail_rpgp/crypto/
eddsa.rs

1//! EdDSA for OpenPGP.
2//!
3//! OpenPGP RFC 9580 specifies use of Ed25519 and Ed448.
4//!
5//! Use of Ed25519 is defined with two different framings (using different key types) in RFC 9580:
6//! - The new key format is called `Ed25519`. It can be used both with v4 and v6 keys.
7//! - The old key format has been renamed `EdDSALegacy`. It may only be used with v4 keys.
8//!
9//! Note: The two variants `Ed25519` and `EdDSALegacy` use the same cryptographic mechanism,
10//! and are interchangeable in terms of the low-level cryptographic primitives.
11//! However, at the OpenPGP layer their representation in the key material differs.
12//! This implicitly yields differing OpenPGP fingerprints, so the two OpenPGP key variants cannot
13//! be used interchangeably.
14
15use rand::{CryptoRng, Rng};
16use signature::{Signer as _, Verifier};
17use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
18
19use crate::crypto::ecc_curve::ECCCurve;
20use crate::crypto::hash::HashAlgorithm;
21use crate::crypto::Signer;
22use crate::errors::Result;
23use crate::types::{Mpi, PlainSecretParams, PublicParams};
24
25/// Specifies which OpenPGP framing (e.g. `Ed25519` vs. `EdDSALegacy`) is used, and also chooses
26/// between curve Ed25519 and Ed448 (TODO: not yet implemented)
27pub enum Mode {
28    /// EdDSALegacy (with curve Ed25519). May only be used with v4 keys.
29    ///
30    /// Ref <https://www.rfc-editor.org/rfc/rfc9580.html#key-eddsa-legacy>
31    EdDSALegacy,
32
33    /// Ed25519 as defined in RFC 9580
34    ///
35    /// Ref <https://www.rfc-editor.org/rfc/rfc9580.html#name-algorithm-specific-part-for-ed2>
36    Ed25519,
37}
38
39/// Secret key for EdDSA with Curve25519, the only combination we currently support.
40#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop, derive_more::Debug)]
41pub struct SecretKey {
42    /// The secret point.
43    #[debug("..")]
44    pub secret: [u8; 32],
45    #[debug("{}", hex::encode(oid))]
46    pub oid: Vec<u8>,
47}
48
49impl Signer for SecretKey {
50    fn sign(
51        &self,
52        _hash: HashAlgorithm,
53        digest: &[u8],
54        pub_params: &PublicParams,
55    ) -> Result<Vec<Vec<u8>>> {
56        let curve = match pub_params {
57            PublicParams::EdDSALegacy { curve, q } => {
58                ensure_eq!(q.len(), 33, "invalid Q (len)");
59                ensure_eq!(q[0], 0x40, "invalid Q (prefix)");
60
61                curve
62            }
63            PublicParams::Ed25519 { .. } => &ECCCurve::Ed25519,
64            _ => bail!("invalid public params"),
65        };
66
67        if curve != &ECCCurve::Ed25519 {
68            unsupported_err!("curve {:?} for EdDSA", curve.to_string());
69        }
70
71        let key = ed25519_dalek::SigningKey::from_bytes(&self.secret);
72
73        let signature = key.sign(digest);
74        let bytes = signature.to_bytes();
75
76        let r = bytes[..32].to_vec();
77        let s = bytes[32..].to_vec();
78
79        Ok(vec![r, s])
80    }
81}
82
83/// Generate an EdDSA KeyPair.
84///
85/// `mode` picks between supported EdDSA key formats and curves
86pub fn generate_key<R: Rng + CryptoRng>(
87    mut rng: R,
88    mode: Mode,
89) -> (PublicParams, PlainSecretParams) {
90    let mut bytes = Zeroizing::new([0u8; ed25519_dalek::SECRET_KEY_LENGTH]);
91    rng.fill_bytes(&mut *bytes);
92
93    let secret = ed25519_dalek::SigningKey::from_bytes(&bytes);
94    drop(bytes); // we're done with this slice, zeroize it
95
96    let public = ed25519_dalek::VerifyingKey::from(&secret);
97
98    match mode {
99        Mode::EdDSALegacy => {
100            // public key
101            let mut q = Vec::with_capacity(33);
102            q.push(0x40);
103            q.extend_from_slice(&public.to_bytes());
104
105            // secret key
106            let p = Mpi::from_slice(&secret.to_bytes());
107
108            (
109                PublicParams::EdDSALegacy {
110                    curve: ECCCurve::Ed25519,
111                    q: Mpi::from_raw(q),
112                },
113                PlainSecretParams::EdDSALegacy(p),
114            )
115        }
116        Mode::Ed25519 => (
117            PublicParams::Ed25519 {
118                public: public.to_bytes(),
119            },
120            PlainSecretParams::Ed25519(secret.to_bytes()),
121        ),
122    }
123}
124
125/// Verify an EdDSA signature.
126pub fn verify(
127    curve: &ECCCurve,
128    public: &[u8],
129    hash: HashAlgorithm,
130    hashed: &[u8],
131    sig_bytes: &[u8],
132) -> Result<()> {
133    match *curve {
134        ECCCurve::Ed25519 => {
135            let Some(digest_size) = hash.digest_size() else {
136                bail!("EdDSA signature: invalid hash algorithm: {:?}", hash);
137            };
138            ensure!(
139                digest_size * 8 >= 256,
140                "EdDSA signature: hash algorithm {:?} is too weak for Ed25519",
141                hash,
142            );
143
144            let pk: ed25519_dalek::VerifyingKey = public.try_into()?;
145            let sig = sig_bytes.try_into()?;
146
147            Ok(pk.verify(hashed, &sig)?)
148        }
149        _ => unsupported_err!("curve {:?} for EdDSA", curve.to_string()),
150    }
151}