askar_crypto/alg/
ed25519.rs

1//! Ed25519 signature and verification key support
2
3use core::{
4    convert::{TryFrom, TryInto},
5    fmt::{self, Debug, Formatter},
6};
7
8use curve25519_dalek::{edwards::CompressedEdwardsY, scalar::clamp_integer};
9use ed25519_dalek::{
10    SecretKey, Signature, Signer, SigningKey, VerifyingKey, KEYPAIR_LENGTH, PUBLIC_KEY_LENGTH,
11    SECRET_KEY_LENGTH, SIGNATURE_LENGTH as EDDSA_SIGNATURE_LENGTH,
12};
13use sha2::Digest;
14use subtle::ConstantTimeEq;
15use x25519_dalek::{PublicKey as XPublicKey, StaticSecret as XSecretKey};
16use zeroize::{Zeroize, ZeroizeOnDrop};
17
18use super::{x25519::X25519KeyPair, HasKeyAlg, HasKeyBackend, KeyAlg};
19use crate::{
20    buffer::{ArrayKey, WriteBuffer},
21    error::Error,
22    generic_array::typenum::{U32, U64},
23    jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk},
24    random::KeyMaterial,
25    repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairBytes, KeypairMeta},
26    sign::{KeySigVerify, KeySign, SignatureType},
27};
28
29/// The 'kty' value of an Ed25519 JWK
30pub const JWK_KEY_TYPE: &str = "OKP";
31/// The 'crv' value of an Ed25519 JWK
32pub const JWK_CURVE: &str = "Ed25519";
33
34/// An Ed25519 public key or keypair
35#[derive(Clone)]
36pub struct Ed25519KeyPair {
37    secret: Option<[u8; SECRET_KEY_LENGTH]>,
38    public: [u8; PUBLIC_KEY_LENGTH],
39}
40
41impl Ed25519KeyPair {
42    #[inline]
43    pub(crate) fn from_secret_key(secret: &SecretKey) -> Self {
44        let sk = SigningKey::from(secret);
45        let vk = VerifyingKey::from(&sk);
46        Self {
47            secret: Some(*secret),
48            public: vk.to_bytes(),
49        }
50    }
51
52    pub(crate) fn check_public_bytes(&self, pk: &[u8]) -> Result<(), Error> {
53        if self.public.ct_eq(pk).into() {
54            Ok(())
55        } else {
56            Err(err_msg!(InvalidKeyData, "invalid ed25519 keypair"))
57        }
58    }
59
60    /// Create a signing key from the secret key
61    pub fn to_signing_key(&self) -> Option<Ed25519SigningKey> {
62        self.secret
63            .as_ref()
64            .map(|sk| Ed25519SigningKey(SigningKey::from(sk)))
65    }
66
67    /// Convert this keypair to an X25519 keypair
68    pub fn to_x25519_keypair(&self) -> X25519KeyPair {
69        if let Some(secret) = self.secret.as_ref() {
70            let hash = sha2::Sha512::digest(secret);
71            // clamp result: we manually clamp the secret key for consistency with older versions,
72            // although it is not strictly necessary.
73            let secret = XSecretKey::from(clamp_integer(hash[..32].try_into().unwrap()));
74            let public = XPublicKey::from(&secret);
75            X25519KeyPair::new(Some(secret), public)
76        } else {
77            let public = XPublicKey::from(
78                CompressedEdwardsY(self.public)
79                    .decompress()
80                    .unwrap()
81                    .to_montgomery()
82                    .to_bytes(),
83            );
84            X25519KeyPair::new(None, public)
85        }
86    }
87
88    /// Sign a message with the secret key
89    pub fn sign(&self, message: &[u8]) -> Option<[u8; EDDSA_SIGNATURE_LENGTH]> {
90        self.to_signing_key().map(|sk| sk.sign(message))
91    }
92
93    /// Verify a signature against the public key
94    pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> bool {
95        if let Ok(sig) = Signature::try_from(signature) {
96            let vk = VerifyingKey::from_bytes(&self.public).unwrap();
97            vk.verify_strict(message, &sig).is_ok()
98        } else {
99            false
100        }
101    }
102}
103
104impl Debug for Ed25519KeyPair {
105    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
106        f.debug_struct("Ed25519KeyPair")
107            .field(
108                "secret",
109                if self.secret.is_some() {
110                    &"<secret>"
111                } else {
112                    &"None"
113                },
114            )
115            .field("public", &self.public)
116            .finish()
117    }
118}
119
120impl KeyGen for Ed25519KeyPair {
121    fn generate(rng: impl KeyMaterial) -> Result<Self, Error> {
122        let sk = ArrayKey::<U32>::generate(rng);
123        Ok(Self::from_secret_key((&sk).try_into().unwrap()))
124    }
125}
126
127impl HasKeyBackend for Ed25519KeyPair {}
128
129impl HasKeyAlg for Ed25519KeyPair {
130    fn algorithm(&self) -> KeyAlg {
131        KeyAlg::Ed25519
132    }
133}
134
135impl KeyMeta for Ed25519KeyPair {
136    type KeySize = U32;
137}
138
139impl KeySecretBytes for Ed25519KeyPair {
140    fn from_secret_bytes(key: &[u8]) -> Result<Self, Error> {
141        let sk: &[u8; SECRET_KEY_LENGTH] = key.try_into().map_err(|_| err_msg!(InvalidKeyData))?;
142        Ok(Self::from_secret_key(sk))
143    }
144
145    fn with_secret_bytes<O>(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O {
146        f(self.secret.as_ref().map(|sk| &sk[..]))
147    }
148}
149
150impl KeypairMeta for Ed25519KeyPair {
151    type PublicKeySize = U32;
152    type KeypairSize = U64;
153}
154
155impl KeypairBytes for Ed25519KeyPair {
156    fn from_keypair_bytes(kp: &[u8]) -> Result<Self, Error> {
157        if kp.len() != KEYPAIR_LENGTH {
158            return Err(err_msg!(InvalidKeyData));
159        }
160        // NB: this is infallible if the slice is the right length
161        let result = Ed25519KeyPair::from_secret_bytes(&kp[..SECRET_KEY_LENGTH])?;
162        result.check_public_bytes(&kp[SECRET_KEY_LENGTH..])?;
163        Ok(result)
164    }
165
166    fn with_keypair_bytes<O>(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O {
167        if let Some(secret) = self.secret.as_ref() {
168            ArrayKey::<<Self as KeypairMeta>::KeypairSize>::temp(|arr| {
169                arr[..SECRET_KEY_LENGTH].copy_from_slice(secret);
170                arr[SECRET_KEY_LENGTH..].copy_from_slice(&self.public[..]);
171                f(Some(&*arr))
172            })
173        } else {
174            f(None)
175        }
176    }
177}
178
179impl KeyPublicBytes for Ed25519KeyPair {
180    fn from_public_bytes(key: &[u8]) -> Result<Self, Error> {
181        let vk = key
182            .try_into()
183            .ok()
184            .and_then(|k| VerifyingKey::from_bytes(k).ok())
185            .ok_or_else(|| err_msg!(InvalidKeyData))?;
186        Ok(Self {
187            secret: None,
188            public: vk.to_bytes(),
189        })
190    }
191
192    fn with_public_bytes<O>(&self, f: impl FnOnce(&[u8]) -> O) -> O {
193        f(&self.public[..])
194    }
195}
196
197impl KeySign for Ed25519KeyPair {
198    fn write_signature(
199        &self,
200        message: &[u8],
201        sig_type: Option<SignatureType>,
202        out: &mut dyn WriteBuffer,
203    ) -> Result<(), Error> {
204        match sig_type {
205            None | Some(SignatureType::EdDSA) => {
206                if let Some(signer) = self.to_signing_key() {
207                    let sig = signer.sign(message);
208                    out.buffer_write(&sig[..])?;
209                    Ok(())
210                } else {
211                    Err(err_msg!(MissingSecretKey))
212                }
213            }
214            #[allow(unreachable_patterns)]
215            _ => Err(err_msg!(Unsupported, "Unsupported signature type")),
216        }
217    }
218}
219
220impl KeySigVerify for Ed25519KeyPair {
221    fn verify_signature(
222        &self,
223        message: &[u8],
224        signature: &[u8],
225        sig_type: Option<SignatureType>,
226    ) -> Result<bool, Error> {
227        match sig_type {
228            None | Some(SignatureType::EdDSA) => Ok(self.verify_signature(message, signature)),
229            #[allow(unreachable_patterns)]
230            _ => Err(err_msg!(Unsupported, "Unsupported signature type")),
231        }
232    }
233}
234
235impl ToJwk for Ed25519KeyPair {
236    fn encode_jwk(&self, enc: &mut dyn JwkEncoder) -> Result<(), Error> {
237        enc.add_str("crv", JWK_CURVE)?;
238        enc.add_str("kty", JWK_KEY_TYPE)?;
239        self.with_public_bytes(|buf| enc.add_as_base64("x", buf))?;
240        if enc.is_secret() {
241            self.with_secret_bytes(|buf| {
242                if let Some(sk) = buf {
243                    enc.add_as_base64("d", sk)
244                } else {
245                    Ok(())
246                }
247            })?;
248        }
249        Ok(())
250    }
251}
252
253impl FromJwk for Ed25519KeyPair {
254    fn from_jwk_parts(jwk: JwkParts<'_>) -> Result<Self, Error> {
255        if jwk.kty != JWK_KEY_TYPE {
256            return Err(err_msg!(InvalidKeyData, "Unsupported key type"));
257        }
258        if jwk.crv != JWK_CURVE {
259            return Err(err_msg!(InvalidKeyData, "Unsupported key algorithm"));
260        }
261        ArrayKey::<U32>::temp(|pk_arr| {
262            if jwk.x.decode_base64(pk_arr)? != pk_arr.len() {
263                Err(err_msg!(InvalidKeyData))
264            } else if jwk.d.is_some() {
265                ArrayKey::<U32>::temp(|sk_arr| {
266                    if jwk.d.decode_base64(sk_arr)? != sk_arr.len() {
267                        Err(err_msg!(InvalidKeyData))
268                    } else {
269                        let kp = Ed25519KeyPair::from_secret_bytes(sk_arr)?;
270                        kp.check_public_bytes(pk_arr)?;
271                        Ok(kp)
272                    }
273                })
274            } else {
275                Ed25519KeyPair::from_public_bytes(pk_arr)
276            }
277        })
278    }
279}
280
281impl Drop for Ed25519KeyPair {
282    fn drop(&mut self) {
283        self.secret.zeroize();
284        self.public.zeroize();
285    }
286}
287
288impl ZeroizeOnDrop for Ed25519KeyPair {}
289
290/// An Ed25519 expanded secret key used for signing
291// SECURITY: ExpandedSecretKey zeroizes on drop
292#[derive(Debug, Clone)]
293pub struct Ed25519SigningKey(SigningKey);
294
295impl Ed25519SigningKey {
296    /// Sign a message with the secret key
297    pub fn sign(&self, message: &[u8]) -> [u8; EDDSA_SIGNATURE_LENGTH] {
298        self.0.sign(message).to_bytes()
299    }
300}
301
302impl ZeroizeOnDrop for Ed25519SigningKey {}
303
304#[cfg(test)]
305mod tests {
306    use base64::Engine;
307
308    use super::*;
309    use crate::repr::{ToPublicBytes, ToSecretBytes};
310
311    #[test]
312    fn expand_keypair() {
313        let seed = b"000000000000000000000000Trustee1";
314        let test_sk = &hex!("3030303030303030303030303030303030303030303030305472757374656531e33aaf381fffa6109ad591fdc38717945f8fabf7abf02086ae401c63e9913097");
315
316        let kp = Ed25519KeyPair::from_secret_bytes(seed).unwrap();
317        assert_eq!(kp.to_keypair_bytes().unwrap(), &test_sk[..]);
318        assert_eq!(kp.to_secret_bytes().unwrap(), &seed[..]);
319
320        // test round trip
321        let cmp = Ed25519KeyPair::from_keypair_bytes(test_sk).unwrap();
322        assert_eq!(cmp.to_keypair_bytes().unwrap(), &test_sk[..]);
323    }
324
325    #[test]
326    fn ed25519_to_x25519() {
327        let test_keypair = &hex!("1c1179a560d092b90458fe6ab8291215a427fcd6b3927cb240701778ef55201927c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf");
328        let x_sk = &hex!("08e7286c232ec71b37918533ea0229bf0c75d3db4731df1c5c03c45bc909475f");
329        let x_pk = &hex!("9b4260484c889158c128796103dc8d8b883977f2ef7efb0facb12b6ca9b2ae3d");
330        let x_pair = Ed25519KeyPair::from_keypair_bytes(test_keypair)
331            .unwrap()
332            .to_x25519_keypair()
333            .to_keypair_bytes()
334            .unwrap();
335        assert_eq!(&x_pair[..32], x_sk);
336        assert_eq!(&x_pair[32..], x_pk);
337    }
338
339    #[test]
340    fn jwk_expected() {
341        // from https://www.connect2id.com/blog/nimbus-jose-jwt-6
342        // {
343        //     "kty" : "OKP",
344        //     "crv" : "Ed25519",
345        //     "x"   : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
346        //     "d"   : "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"
347        //     "use" : "sig",
348        //     "kid" : "FdFYFzERwC2uCBB46pZQi4GG85LujR8obt-KWRBICVQ"
349        //   }
350        let test_pvt_b64 = "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A";
351        let test_pub_b64 = "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo";
352        let test_pvt = base64::engine::general_purpose::URL_SAFE_NO_PAD
353            .decode(test_pvt_b64)
354            .unwrap();
355        let kp = Ed25519KeyPair::from_secret_bytes(&test_pvt).expect("Error creating signing key");
356        let jwk = kp
357            .to_jwk_public(None)
358            .expect("Error converting public key to JWK");
359        let jwk = JwkParts::try_from_str(&jwk).expect("Error parsing JWK output");
360        assert_eq!(jwk.kty, JWK_KEY_TYPE);
361        assert_eq!(jwk.crv, JWK_CURVE);
362        assert_eq!(jwk.x, "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo");
363        let pk_load = Ed25519KeyPair::from_jwk_parts(jwk).unwrap();
364        assert_eq!(kp.to_public_bytes(), pk_load.to_public_bytes());
365
366        let jwk = kp
367            .to_jwk_secret(None)
368            .expect("Error converting private key to JWK");
369        let jwk = JwkParts::from_slice(&jwk).expect("Error parsing JWK output");
370        assert_eq!(jwk.kty, JWK_KEY_TYPE);
371        assert_eq!(jwk.crv, JWK_CURVE);
372        assert_eq!(jwk.x, test_pub_b64);
373        assert_eq!(jwk.d, test_pvt_b64);
374        let sk_load = Ed25519KeyPair::from_jwk_parts(jwk).unwrap();
375        assert_eq!(
376            kp.to_keypair_bytes().unwrap(),
377            sk_load.to_keypair_bytes().unwrap()
378        );
379    }
380
381    #[test]
382    fn sign_verify_expected() {
383        let test_msg = b"This is a dummy message for use with tests";
384        let test_sig = &hex!(
385            "451b5b8e8725321541954997781de51f4142e4a56bab68d24f6a6b92615de5ee
386            fb74134138315859a32c7cf5fe5a488bc545e2e08e5eedfd1fb10188d532d808"
387        );
388        let test_keypair = &hex!(
389            "1c1179a560d092b90458fe6ab8291215a427fcd6b3927cb240701778ef552019
390            27c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf"
391        );
392        let kp = Ed25519KeyPair::from_keypair_bytes(test_keypair).unwrap();
393        let sig = &kp.sign(test_msg).unwrap();
394        assert_eq!(sig, test_sig);
395        assert!(kp.verify_signature(test_msg, &sig[..]));
396        assert!(!kp.verify_signature(b"Not the message", &sig[..]));
397        assert!(!kp.verify_signature(test_msg, &[0u8; 64]));
398    }
399
400    #[test]
401    fn round_trip_bytes() {
402        let kp = Ed25519KeyPair::random().unwrap();
403        let cmp = Ed25519KeyPair::from_keypair_bytes(&kp.to_keypair_bytes().unwrap()).unwrap();
404        assert_eq!(
405            kp.to_keypair_bytes().unwrap(),
406            cmp.to_keypair_bytes().unwrap()
407        );
408    }
409}