Skip to main content

ssh_key/private/
ed25519.rs

1//! Ed25519 private keys.
2//!
3//! Edwards Digital Signature Algorithm (EdDSA) over Curve25519.
4
5use crate::{Error, Result, public::Ed25519PublicKey};
6use core::fmt;
7use ctutils::{Choice, CtEq};
8use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
9use zeroize::{Zeroize, Zeroizing};
10
11#[cfg(feature = "rand_core")]
12use rand_core::CryptoRng;
13
14/// Ed25519 private key.
15// TODO(tarcieri): use `ed25519::PrivateKey`? (doesn't exist yet)
16#[derive(Clone)]
17pub struct Ed25519PrivateKey([u8; Self::BYTE_SIZE]);
18
19impl Ed25519PrivateKey {
20    /// Size of an Ed25519 private key in bytes.
21    pub const BYTE_SIZE: usize = 32;
22
23    /// Generate a random Ed25519 private key.
24    #[cfg(feature = "rand_core")]
25    pub fn random<R: CryptoRng + ?Sized>(rng: &mut R) -> Self {
26        let mut key_bytes = [0u8; Self::BYTE_SIZE];
27        rng.fill_bytes(&mut key_bytes);
28        Self(key_bytes)
29    }
30
31    /// Parse Ed25519 private key from bytes.
32    #[must_use]
33    pub fn from_bytes(bytes: &[u8; Self::BYTE_SIZE]) -> Self {
34        Self(*bytes)
35    }
36
37    /// Convert to the inner byte array.
38    #[must_use]
39    pub fn to_bytes(&self) -> [u8; Self::BYTE_SIZE] {
40        self.0
41    }
42}
43
44impl AsRef<[u8; Self::BYTE_SIZE]> for Ed25519PrivateKey {
45    fn as_ref(&self) -> &[u8; Self::BYTE_SIZE] {
46        &self.0
47    }
48}
49
50impl CtEq for Ed25519PrivateKey {
51    fn ct_eq(&self, other: &Self) -> Choice {
52        self.as_ref().ct_eq(other.as_ref())
53    }
54}
55
56impl Eq for Ed25519PrivateKey {}
57
58impl PartialEq for Ed25519PrivateKey {
59    fn eq(&self, other: &Self) -> bool {
60        self.ct_eq(other).into()
61    }
62}
63
64impl TryFrom<&[u8]> for Ed25519PrivateKey {
65    type Error = Error;
66
67    fn try_from(bytes: &[u8]) -> Result<Self> {
68        Ok(Ed25519PrivateKey::from_bytes(bytes.try_into()?))
69    }
70}
71
72impl fmt::Debug for Ed25519PrivateKey {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        f.debug_struct("Ed25519PrivateKey").finish_non_exhaustive()
75    }
76}
77
78impl fmt::LowerHex for Ed25519PrivateKey {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        for byte in self.as_ref() {
81            write!(f, "{byte:02x}")?;
82        }
83        Ok(())
84    }
85}
86
87impl fmt::UpperHex for Ed25519PrivateKey {
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        for byte in self.as_ref() {
90            write!(f, "{byte:02X}")?;
91        }
92        Ok(())
93    }
94}
95
96impl Drop for Ed25519PrivateKey {
97    fn drop(&mut self) {
98        self.0.zeroize();
99    }
100}
101
102#[cfg(feature = "ed25519")]
103impl From<Ed25519PrivateKey> for ed25519_dalek::SigningKey {
104    fn from(key: Ed25519PrivateKey) -> ed25519_dalek::SigningKey {
105        ed25519_dalek::SigningKey::from(&key)
106    }
107}
108
109#[cfg(feature = "ed25519")]
110impl From<&Ed25519PrivateKey> for ed25519_dalek::SigningKey {
111    fn from(key: &Ed25519PrivateKey) -> ed25519_dalek::SigningKey {
112        ed25519_dalek::SigningKey::from_bytes(key.as_ref())
113    }
114}
115
116#[cfg(feature = "ed25519")]
117impl From<ed25519_dalek::SigningKey> for Ed25519PrivateKey {
118    fn from(key: ed25519_dalek::SigningKey) -> Ed25519PrivateKey {
119        Ed25519PrivateKey::from(&key)
120    }
121}
122
123#[cfg(feature = "ed25519")]
124impl From<&ed25519_dalek::SigningKey> for Ed25519PrivateKey {
125    fn from(key: &ed25519_dalek::SigningKey) -> Ed25519PrivateKey {
126        Ed25519PrivateKey(key.to_bytes())
127    }
128}
129
130#[cfg(feature = "ed25519")]
131impl From<Ed25519PrivateKey> for Ed25519PublicKey {
132    fn from(private: Ed25519PrivateKey) -> Ed25519PublicKey {
133        Ed25519PublicKey::from(&private)
134    }
135}
136
137#[cfg(feature = "ed25519")]
138impl From<&Ed25519PrivateKey> for Ed25519PublicKey {
139    fn from(private: &Ed25519PrivateKey) -> Ed25519PublicKey {
140        ed25519_dalek::SigningKey::from(private)
141            .verifying_key()
142            .into()
143    }
144}
145
146/// Ed25519 private/public keypair.
147#[derive(Clone)]
148pub struct Ed25519Keypair {
149    /// Public key.
150    pub public: Ed25519PublicKey,
151
152    /// Private key.
153    pub private: Ed25519PrivateKey,
154}
155
156impl Ed25519Keypair {
157    /// Size of an Ed25519 keypair in bytes.
158    pub const BYTE_SIZE: usize = 64;
159
160    /// Generate a random Ed25519 private keypair.
161    #[cfg(feature = "ed25519")]
162    pub fn random<R: CryptoRng + ?Sized>(rng: &mut R) -> Self {
163        Ed25519PrivateKey::random(rng).into()
164    }
165
166    /// Expand a keypair from a 32-byte seed value.
167    #[cfg(feature = "ed25519")]
168    #[must_use]
169    pub fn from_seed(seed: &[u8; Ed25519PrivateKey::BYTE_SIZE]) -> Self {
170        Ed25519PrivateKey::from_bytes(seed).into()
171    }
172
173    /// Parse Ed25519 keypair from 64-bytes which comprise the serialized private and public keys.
174    ///
175    /// # Errors
176    /// Returns [`Error::Crypto`] if the public key does not match the private key.
177    pub fn from_bytes(bytes: &[u8; Self::BYTE_SIZE]) -> Result<Self> {
178        let (priv_bytes, pub_bytes) = bytes.split_at(Ed25519PrivateKey::BYTE_SIZE);
179        let private = Ed25519PrivateKey::try_from(priv_bytes)?;
180        let public = Ed25519PublicKey::try_from(pub_bytes)?;
181
182        // Validate the public key if possible
183        #[cfg(feature = "ed25519")]
184        if Ed25519PublicKey::from(&private) != public {
185            return Err(Error::Crypto);
186        }
187
188        Ok(Ed25519Keypair { private, public })
189    }
190
191    /// Serialize an Ed25519 keypair as bytes.
192    #[must_use]
193    #[allow(clippy::integer_division_remainder_used, reason = "constant")]
194    pub fn to_bytes(&self) -> [u8; Self::BYTE_SIZE] {
195        let mut result = [0u8; Self::BYTE_SIZE];
196        result[..(Self::BYTE_SIZE / 2)].copy_from_slice(self.private.as_ref());
197        result[(Self::BYTE_SIZE / 2)..].copy_from_slice(self.public.as_ref());
198        result
199    }
200}
201
202impl CtEq for Ed25519Keypair {
203    fn ct_eq(&self, other: &Self) -> Choice {
204        Choice::from(u8::from(self.public == other.public)) & self.private.ct_eq(&other.private)
205    }
206}
207
208impl Eq for Ed25519Keypair {}
209
210impl PartialEq for Ed25519Keypair {
211    fn eq(&self, other: &Self) -> bool {
212        self.ct_eq(other).into()
213    }
214}
215
216impl Decode for Ed25519Keypair {
217    type Error = Error;
218
219    fn decode(reader: &mut impl Reader) -> Result<Self> {
220        // Decode private key
221        let public = Ed25519PublicKey::decode(reader)?;
222
223        // The OpenSSH serialization of Ed25519 keys is repetitive and includes
224        // a serialization of `private_key[32] || public_key[32]` immediately
225        // following the public key.
226        let mut bytes = Zeroizing::new([0u8; Self::BYTE_SIZE]);
227        reader.read_prefixed(|reader| reader.read(&mut *bytes))?;
228
229        let keypair = Self::from_bytes(&bytes)?;
230
231        // Ensure public key matches the one one the keypair
232        if keypair.public == public {
233            Ok(keypair)
234        } else {
235            Err(Error::Crypto)
236        }
237    }
238}
239
240impl Encode for Ed25519Keypair {
241    fn encoded_len(&self) -> encoding::Result<usize> {
242        [4, self.public.encoded_len()?, Self::BYTE_SIZE].checked_sum()
243    }
244
245    fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
246        self.public.encode(writer)?;
247        Zeroizing::new(self.to_bytes()).as_slice().encode(writer)?;
248        Ok(())
249    }
250}
251
252impl From<Ed25519Keypair> for Ed25519PublicKey {
253    fn from(keypair: Ed25519Keypair) -> Ed25519PublicKey {
254        keypair.public
255    }
256}
257
258impl From<&Ed25519Keypair> for Ed25519PublicKey {
259    fn from(keypair: &Ed25519Keypair) -> Ed25519PublicKey {
260        keypair.public
261    }
262}
263
264impl TryFrom<&[u8]> for Ed25519Keypair {
265    type Error = Error;
266
267    fn try_from(bytes: &[u8]) -> Result<Self> {
268        Ed25519Keypair::from_bytes(bytes.try_into()?)
269    }
270}
271
272impl fmt::Debug for Ed25519Keypair {
273    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274        f.debug_struct("Ed25519Keypair")
275            .field("public", &self.public)
276            .finish_non_exhaustive()
277    }
278}
279
280#[cfg(feature = "ed25519")]
281impl From<Ed25519PrivateKey> for Ed25519Keypair {
282    fn from(private: Ed25519PrivateKey) -> Ed25519Keypair {
283        let secret = ed25519_dalek::SigningKey::from(&private);
284        let public = secret.verifying_key().into();
285        Ed25519Keypair { private, public }
286    }
287}
288
289#[cfg(feature = "ed25519")]
290impl TryFrom<Ed25519Keypair> for ed25519_dalek::SigningKey {
291    type Error = Error;
292
293    fn try_from(key: Ed25519Keypair) -> Result<ed25519_dalek::SigningKey> {
294        ed25519_dalek::SigningKey::try_from(&key)
295    }
296}
297
298#[cfg(feature = "ed25519")]
299impl TryFrom<&Ed25519Keypair> for ed25519_dalek::SigningKey {
300    type Error = Error;
301
302    fn try_from(key: &Ed25519Keypair) -> Result<ed25519_dalek::SigningKey> {
303        let signing_key = ed25519_dalek::SigningKey::from(&key.private);
304        let verifying_key = ed25519_dalek::VerifyingKey::try_from(&key.public)?;
305
306        if signing_key.verifying_key() == verifying_key {
307            Ok(signing_key)
308        } else {
309            Err(Error::PublicKey)
310        }
311    }
312}
313
314#[cfg(feature = "ed25519")]
315impl From<ed25519_dalek::SigningKey> for Ed25519Keypair {
316    fn from(key: ed25519_dalek::SigningKey) -> Ed25519Keypair {
317        Ed25519Keypair::from(&key)
318    }
319}
320
321#[cfg(feature = "ed25519")]
322impl From<&ed25519_dalek::SigningKey> for Ed25519Keypair {
323    fn from(key: &ed25519_dalek::SigningKey) -> Ed25519Keypair {
324        Ed25519Keypair {
325            private: key.into(),
326            public: key.verifying_key().into(),
327        }
328    }
329}