ssh_key/private/
ecdsa.rs

1//! Elliptic Curve Digital Signature Algorithm (ECDSA) private keys.
2
3use crate::{
4    checked::CheckedSum, decode::Decode, encode::Encode, public::EcdsaPublicKey, reader::Reader,
5    writer::Writer, Algorithm, EcdsaCurve, Error, Result,
6};
7use core::fmt;
8use sec1::consts::{U32, U48, U66};
9use zeroize::Zeroize;
10
11#[cfg(feature = "rand_core")]
12use rand_core::{CryptoRng, RngCore};
13
14#[cfg(feature = "subtle")]
15use subtle::{Choice, ConstantTimeEq};
16
17/// Elliptic Curve Digital Signature Algorithm (ECDSA) private key.
18#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
19#[derive(Clone)]
20pub struct EcdsaPrivateKey<const SIZE: usize> {
21    /// Byte array containing serialized big endian private scalar.
22    bytes: [u8; SIZE],
23}
24
25impl<const SIZE: usize> EcdsaPrivateKey<SIZE> {
26    /// Borrow the inner byte array as a slice.
27    pub fn as_slice(&self) -> &[u8] {
28        self.bytes.as_ref()
29    }
30
31    /// Convert to the inner byte array.
32    pub fn into_bytes(self) -> [u8; SIZE] {
33        self.bytes
34    }
35
36    /// Decode ECDSA private key using the provided Base64 reader.
37    fn decode(reader: &mut impl Reader) -> Result<Self> {
38        reader.read_nested(|reader| {
39            if reader.remaining_len() == SIZE.checked_add(1).ok_or(Error::Length)? {
40                // Strip leading zero
41                // TODO(tarcieri): make sure leading zero was necessary
42                if u8::decode(reader)? != 0 {
43                    return Err(Error::FormatEncoding);
44                }
45            }
46
47            let mut bytes = [0u8; SIZE];
48            reader.read(&mut bytes)?;
49            Ok(Self { bytes })
50        })
51    }
52
53    /// Does this private key need to be prefixed with a leading zero?
54    fn needs_leading_zero(&self) -> bool {
55        self.bytes[0] >= 0x80
56    }
57}
58
59impl<const SIZE: usize> Encode for EcdsaPrivateKey<SIZE> {
60    fn encoded_len(&self) -> Result<usize> {
61        [4, self.needs_leading_zero().into(), SIZE].checked_sum()
62    }
63
64    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
65        [self.needs_leading_zero().into(), SIZE]
66            .checked_sum()?
67            .encode(writer)?;
68
69        if self.needs_leading_zero() {
70            writer.write(&[0])?;
71        }
72
73        writer.write(&self.bytes)
74    }
75}
76
77impl<const SIZE: usize> AsRef<[u8; SIZE]> for EcdsaPrivateKey<SIZE> {
78    fn as_ref(&self) -> &[u8; SIZE] {
79        &self.bytes
80    }
81}
82
83impl<const SIZE: usize> fmt::Debug for EcdsaPrivateKey<SIZE> {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        f.debug_struct("Ed25519PrivateKey").finish_non_exhaustive()
86    }
87}
88
89impl<const SIZE: usize> fmt::LowerHex for EcdsaPrivateKey<SIZE> {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        for byte in self.as_ref() {
92            write!(f, "{:02x}", byte)?;
93        }
94        Ok(())
95    }
96}
97
98impl<const SIZE: usize> fmt::UpperHex for EcdsaPrivateKey<SIZE> {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        for byte in self.as_ref() {
101            write!(f, "{:02X}", byte)?;
102        }
103        Ok(())
104    }
105}
106
107impl<const SIZE: usize> Drop for EcdsaPrivateKey<SIZE> {
108    fn drop(&mut self) {
109        self.bytes.zeroize();
110    }
111}
112
113#[cfg(feature = "p256")]
114#[cfg_attr(docsrs, doc(cfg(feature = "p256")))]
115impl From<p256::SecretKey> for EcdsaPrivateKey<32> {
116    fn from(sk: p256::SecretKey) -> EcdsaPrivateKey<32> {
117        EcdsaPrivateKey {
118            bytes: sk.to_be_bytes().into(),
119        }
120    }
121}
122
123#[cfg(feature = "subtle")]
124#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
125impl<const SIZE: usize> ConstantTimeEq for EcdsaPrivateKey<SIZE> {
126    fn ct_eq(&self, other: &Self) -> Choice {
127        self.as_ref().ct_eq(other.as_ref())
128    }
129}
130
131#[cfg(feature = "subtle")]
132#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
133impl<const SIZE: usize> PartialEq for EcdsaPrivateKey<SIZE> {
134    fn eq(&self, other: &Self) -> bool {
135        self.ct_eq(other).into()
136    }
137}
138
139#[cfg(feature = "subtle")]
140#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
141impl<const SIZE: usize> Eq for EcdsaPrivateKey<SIZE> {}
142
143/// Elliptic Curve Digital Signature Algorithm (ECDSA) private/public keypair.
144#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
145#[derive(Clone, Debug)]
146pub enum EcdsaKeypair {
147    /// NIST P-256 ECDSA keypair.
148    NistP256 {
149        /// Public key.
150        public: sec1::EncodedPoint<U32>,
151
152        /// Private key.
153        private: EcdsaPrivateKey<32>,
154    },
155
156    /// NIST P-384 ECDSA keypair.
157    NistP384 {
158        /// Public key.
159        public: sec1::EncodedPoint<U48>,
160
161        /// Private key.
162        private: EcdsaPrivateKey<48>,
163    },
164
165    /// NIST P-521 ECDSA keypair.
166    NistP521 {
167        /// Public key.
168        public: sec1::EncodedPoint<U66>,
169
170        /// Private key.
171        private: EcdsaPrivateKey<66>,
172    },
173}
174
175impl EcdsaKeypair {
176    /// Generate a random ECDSA private key.
177    #[cfg(feature = "rand_core")]
178    #[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))]
179    #[allow(unused_variables)]
180    pub fn random(rng: impl CryptoRng + RngCore, curve: EcdsaCurve) -> Result<Self> {
181        match curve {
182            #[cfg(feature = "p256")]
183            EcdsaCurve::NistP256 => {
184                let private = p256::SecretKey::random(rng);
185                let public = private.public_key();
186                Ok(EcdsaKeypair::NistP256 {
187                    private: private.into(),
188                    public: public.into(),
189                })
190            }
191            _ => Err(Error::Algorithm),
192        }
193    }
194
195    /// Get the [`Algorithm`] for this public key type.
196    pub fn algorithm(&self) -> Algorithm {
197        Algorithm::Ecdsa {
198            curve: self.curve(),
199        }
200    }
201
202    /// Get the [`EcdsaCurve`] for this key.
203    pub fn curve(&self) -> EcdsaCurve {
204        match self {
205            Self::NistP256 { .. } => EcdsaCurve::NistP256,
206            Self::NistP384 { .. } => EcdsaCurve::NistP384,
207            Self::NistP521 { .. } => EcdsaCurve::NistP521,
208        }
209    }
210
211    /// Get the bytes representing the public key.
212    pub fn public_key_bytes(&self) -> &[u8] {
213        match self {
214            Self::NistP256 { public, .. } => public.as_ref(),
215            Self::NistP384 { public, .. } => public.as_ref(),
216            Self::NistP521 { public, .. } => public.as_ref(),
217        }
218    }
219
220    /// Get the bytes representing the private key.
221    pub fn private_key_bytes(&self) -> &[u8] {
222        match self {
223            Self::NistP256 { private, .. } => private.as_ref(),
224            Self::NistP384 { private, .. } => private.as_ref(),
225            Self::NistP521 { private, .. } => private.as_ref(),
226        }
227    }
228}
229
230impl Decode for EcdsaKeypair {
231    fn decode(reader: &mut impl Reader) -> Result<Self> {
232        match EcdsaPublicKey::decode(reader)? {
233            EcdsaPublicKey::NistP256(public) => {
234                let private = EcdsaPrivateKey::<32>::decode(reader)?;
235                Ok(Self::NistP256 { public, private })
236            }
237            EcdsaPublicKey::NistP384(public) => {
238                let private = EcdsaPrivateKey::<48>::decode(reader)?;
239                Ok(Self::NistP384 { public, private })
240            }
241            EcdsaPublicKey::NistP521(public) => {
242                let private = EcdsaPrivateKey::<66>::decode(reader)?;
243                Ok(Self::NistP521 { public, private })
244            }
245        }
246    }
247}
248
249impl Encode for EcdsaKeypair {
250    fn encoded_len(&self) -> Result<usize> {
251        let public_len = EcdsaPublicKey::from(self).encoded_len()?;
252
253        let private_len = match self {
254            Self::NistP256 { private, .. } => private.encoded_len()?,
255            Self::NistP384 { private, .. } => private.encoded_len()?,
256            Self::NistP521 { private, .. } => private.encoded_len()?,
257        };
258
259        [public_len, private_len].checked_sum()
260    }
261
262    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
263        EcdsaPublicKey::from(self).encode(writer)?;
264
265        match self {
266            Self::NistP256 { private, .. } => private.encode(writer)?,
267            Self::NistP384 { private, .. } => private.encode(writer)?,
268            Self::NistP521 { private, .. } => private.encode(writer)?,
269        }
270
271        Ok(())
272    }
273}
274
275impl From<EcdsaKeypair> for EcdsaPublicKey {
276    fn from(keypair: EcdsaKeypair) -> EcdsaPublicKey {
277        EcdsaPublicKey::from(&keypair)
278    }
279}
280
281impl From<&EcdsaKeypair> for EcdsaPublicKey {
282    fn from(keypair: &EcdsaKeypair) -> EcdsaPublicKey {
283        match keypair {
284            EcdsaKeypair::NistP256 { public, .. } => EcdsaPublicKey::NistP256(*public),
285            EcdsaKeypair::NistP384 { public, .. } => EcdsaPublicKey::NistP384(*public),
286            EcdsaKeypair::NistP521 { public, .. } => EcdsaPublicKey::NistP521(*public),
287        }
288    }
289}
290
291#[cfg(feature = "subtle")]
292#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
293impl ConstantTimeEq for EcdsaKeypair {
294    fn ct_eq(&self, other: &Self) -> Choice {
295        let public_eq =
296            Choice::from((EcdsaPublicKey::from(self) == EcdsaPublicKey::from(other)) as u8);
297
298        let private_key_a = match self {
299            Self::NistP256 { private, .. } => private.as_slice(),
300            Self::NistP384 { private, .. } => private.as_slice(),
301            Self::NistP521 { private, .. } => private.as_slice(),
302        };
303
304        let private_key_b = match other {
305            Self::NistP256 { private, .. } => private.as_slice(),
306            Self::NistP384 { private, .. } => private.as_slice(),
307            Self::NistP521 { private, .. } => private.as_slice(),
308        };
309
310        public_eq & private_key_a.ct_eq(private_key_b)
311    }
312}
313
314#[cfg(feature = "subtle")]
315#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
316impl PartialEq for EcdsaKeypair {
317    fn eq(&self, other: &Self) -> bool {
318        self.ct_eq(other).into()
319    }
320}
321
322#[cfg(feature = "subtle")]
323#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
324impl Eq for EcdsaKeypair {}