elliptic_curve/
secret_key.rs

1//! Secret keys for elliptic curves (i.e. private scalars).
2//!
3//! The [`SecretKey`] type is a wrapper around a secret scalar value which is
4//! designed to prevent unintentional exposure (e.g. via `Debug` or other
5//! logging). It also handles zeroing the secret value out of memory securely
6//! on drop.
7
8#[cfg(all(feature = "pkcs8", feature = "sec1"))]
9mod pkcs8;
10
11use crate::{Curve, Error, FieldBytes, Result, ScalarCore};
12use core::fmt::{self, Debug};
13use crypto_bigint::Encoding;
14use generic_array::GenericArray;
15use subtle::{Choice, ConstantTimeEq};
16use zeroize::{Zeroize, ZeroizeOnDrop};
17
18#[cfg(all(feature = "alloc", feature = "arithmetic"))]
19use {
20    crate::{
21        sec1::{FromEncodedPoint, ToEncodedPoint},
22        AffinePoint,
23    },
24    alloc::vec::Vec,
25    der::Encode,
26    zeroize::Zeroizing,
27};
28
29#[cfg(feature = "arithmetic")]
30use crate::{
31    rand_core::{CryptoRng, RngCore},
32    NonZeroScalar, ProjectiveArithmetic, PublicKey,
33};
34
35#[cfg(feature = "jwk")]
36use crate::jwk::{JwkEcKey, JwkParameters};
37
38#[cfg(all(feature = "arithmetic", any(feature = "jwk", feature = "pem")))]
39use alloc::string::String;
40
41#[cfg(all(feature = "arithmetic", feature = "jwk"))]
42use alloc::string::ToString;
43
44#[cfg(feature = "pem")]
45use pem_rfc7468 as pem;
46
47#[cfg(feature = "sec1")]
48use crate::{
49    sec1::{EncodedPoint, ModulusSize, ValidatePublicKey},
50    FieldSize,
51};
52
53#[cfg(all(docsrs, feature = "pkcs8"))]
54use {crate::pkcs8::DecodePrivateKey, core::str::FromStr};
55
56/// Type label for PEM-encoded SEC1 private keys.
57#[cfg(feature = "pem")]
58pub(crate) const SEC1_PEM_TYPE_LABEL: &str = "EC PRIVATE KEY";
59
60/// Elliptic curve secret keys.
61///
62/// This type wraps a secret scalar value, helping to prevent accidental
63/// exposure and securely erasing the value from memory when dropped.
64///
65/// # Parsing PKCS#8 Keys
66///
67/// PKCS#8 is a commonly used format for encoding secret keys (especially ones
68/// generated by OpenSSL).
69///
70/// Keys in PKCS#8 format are either binary (ASN.1 BER/DER), or PEM encoded
71/// (ASCII) and begin with the following:
72///
73/// ```text
74/// -----BEGIN PRIVATE KEY-----
75/// ```
76///
77/// To decode an elliptic curve private key from PKCS#8, enable the `pkcs8`
78/// feature of this crate (or the `pkcs8` feature of a specific RustCrypto
79/// elliptic curve crate) and use the [`DecodePrivateKey`]  trait to parse it.
80///
81/// When the `pem` feature of this crate (or a specific RustCrypto elliptic
82/// curve crate) is enabled, a [`FromStr`] impl is also available.
83#[derive(Clone)]
84pub struct SecretKey<C: Curve> {
85    /// Scalar value
86    inner: ScalarCore<C>,
87}
88
89impl<C> SecretKey<C>
90where
91    C: Curve,
92{
93    /// Generate a random [`SecretKey`].
94    #[cfg(feature = "arithmetic")]
95    #[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))]
96    pub fn random(rng: impl CryptoRng + RngCore) -> Self
97    where
98        C: ProjectiveArithmetic,
99    {
100        Self {
101            inner: NonZeroScalar::<C>::random(rng).into(),
102        }
103    }
104
105    /// Create a new secret key from a scalar value.
106    pub fn new(scalar: ScalarCore<C>) -> Self {
107        Self { inner: scalar }
108    }
109
110    /// Borrow the inner secret [`ScalarCore`] value.
111    ///
112    /// # ⚠️ Warning
113    ///
114    /// This value is key material.
115    ///
116    /// Please treat it with the care it deserves!
117    pub fn as_scalar_core(&self) -> &ScalarCore<C> {
118        &self.inner
119    }
120
121    /// Get the secret [`NonZeroScalar`] value for this key.
122    ///
123    /// # ⚠️ Warning
124    ///
125    /// This value is key material.
126    ///
127    /// Please treat it with the care it deserves!
128    #[cfg(feature = "arithmetic")]
129    #[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))]
130    pub fn to_nonzero_scalar(&self) -> NonZeroScalar<C>
131    where
132        C: Curve + ProjectiveArithmetic,
133    {
134        self.into()
135    }
136
137    /// Get the [`PublicKey`] which corresponds to this secret key
138    #[cfg(feature = "arithmetic")]
139    #[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))]
140    pub fn public_key(&self) -> PublicKey<C>
141    where
142        C: Curve + ProjectiveArithmetic,
143    {
144        PublicKey::from_secret_scalar(&self.to_nonzero_scalar())
145    }
146
147    /// Deserialize raw secret scalar as a big endian integer.
148    pub fn from_be_bytes(bytes: &[u8]) -> Result<Self> {
149        if bytes.len() != C::UInt::BYTE_SIZE {
150            return Err(Error);
151        }
152
153        let inner: ScalarCore<C> = Option::from(ScalarCore::from_be_bytes(
154            GenericArray::clone_from_slice(bytes),
155        ))
156        .ok_or(Error)?;
157
158        if inner.is_zero().into() {
159            return Err(Error);
160        }
161
162        Ok(Self { inner })
163    }
164
165    /// Serialize raw secret scalar as a big endian integer.
166    pub fn to_be_bytes(&self) -> FieldBytes<C> {
167        self.inner.to_be_bytes()
168    }
169
170    /// Deserialize secret key encoded in the SEC1 ASN.1 DER `ECPrivateKey` format.
171    #[cfg(all(feature = "sec1"))]
172    #[cfg_attr(docsrs, doc(cfg(feature = "sec1")))]
173    pub fn from_sec1_der(der_bytes: &[u8]) -> Result<Self>
174    where
175        C: Curve + ValidatePublicKey,
176        FieldSize<C>: ModulusSize,
177    {
178        sec1::EcPrivateKey::try_from(der_bytes)?
179            .try_into()
180            .map_err(|_| Error)
181    }
182
183    /// Serialize secret key in the SEC1 ASN.1 DER `ECPrivateKey` format.
184    #[cfg(all(feature = "alloc", feature = "arithmetic", feature = "sec1"))]
185    #[cfg_attr(
186        docsrs,
187        doc(cfg(all(feature = "alloc", feature = "arithmetic", feature = "sec1")))
188    )]
189    pub fn to_sec1_der(&self) -> der::Result<Zeroizing<Vec<u8>>>
190    where
191        C: Curve + ProjectiveArithmetic,
192        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
193        FieldSize<C>: ModulusSize,
194    {
195        // TODO(tarcieri): wrap `secret_key_bytes` in `Zeroizing`
196        let mut private_key_bytes = self.to_be_bytes();
197        let public_key_bytes = self.public_key().to_encoded_point(false);
198
199        let ec_private_key = Zeroizing::new(
200            sec1::EcPrivateKey {
201                private_key: &private_key_bytes,
202                parameters: None,
203                public_key: Some(public_key_bytes.as_bytes()),
204            }
205            .to_vec()?,
206        );
207
208        // TODO(tarcieri): wrap `private_key_bytes` in `Zeroizing`
209        private_key_bytes.zeroize();
210
211        Ok(ec_private_key)
212    }
213
214    /// Parse [`SecretKey`] from PEM-encoded SEC1 `ECPrivateKey` format.
215    ///
216    /// PEM-encoded SEC1 keys can be identified by the leading delimiter:
217    ///
218    /// ```text
219    /// -----BEGIN EC PRIVATE KEY-----
220    /// ```
221    #[cfg(feature = "pem")]
222    #[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
223    pub fn from_sec1_pem(s: &str) -> Result<Self>
224    where
225        C: Curve + ValidatePublicKey,
226        FieldSize<C>: ModulusSize,
227    {
228        let (label, der_bytes) = pem::decode_vec(s.as_bytes()).map_err(|_| Error)?;
229
230        if label != SEC1_PEM_TYPE_LABEL {
231            return Err(Error);
232        }
233
234        Self::from_sec1_der(&*der_bytes).map_err(|_| Error)
235    }
236
237    /// Serialize private key as self-zeroizing PEM-encoded SEC1 `ECPrivateKey`
238    /// with the given [`pem::LineEnding`].
239    ///
240    /// Pass `Default::default()` to use the OS's native line endings.
241    #[cfg(feature = "pem")]
242    #[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
243    pub fn to_pem(&self, line_ending: pem::LineEnding) -> Result<Zeroizing<String>>
244    where
245        C: Curve + ProjectiveArithmetic,
246        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
247        FieldSize<C>: ModulusSize,
248    {
249        self.to_sec1_der()
250            .ok()
251            .and_then(|der| pem::encode_string(SEC1_PEM_TYPE_LABEL, line_ending, &der).ok())
252            .map(Zeroizing::new)
253            .ok_or(Error)
254    }
255
256    /// Parse a [`JwkEcKey`] JSON Web Key (JWK) into a [`SecretKey`].
257    #[cfg(feature = "jwk")]
258    #[cfg_attr(docsrs, doc(cfg(feature = "jwk")))]
259    pub fn from_jwk(jwk: &JwkEcKey) -> Result<Self>
260    where
261        C: JwkParameters + ValidatePublicKey,
262        FieldSize<C>: ModulusSize,
263    {
264        Self::try_from(jwk)
265    }
266
267    /// Parse a string containing a JSON Web Key (JWK) into a [`SecretKey`].
268    #[cfg(feature = "jwk")]
269    #[cfg_attr(docsrs, doc(cfg(feature = "jwk")))]
270    pub fn from_jwk_str(jwk: &str) -> Result<Self>
271    where
272        C: JwkParameters + ValidatePublicKey,
273        FieldSize<C>: ModulusSize,
274    {
275        jwk.parse::<JwkEcKey>().and_then(|jwk| Self::from_jwk(&jwk))
276    }
277
278    /// Serialize this secret key as [`JwkEcKey`] JSON Web Key (JWK).
279    #[cfg(all(feature = "arithmetic", feature = "jwk"))]
280    #[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))]
281    #[cfg_attr(docsrs, doc(cfg(feature = "jwk")))]
282    pub fn to_jwk(&self) -> JwkEcKey
283    where
284        C: Curve + JwkParameters + ProjectiveArithmetic,
285        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
286        FieldSize<C>: ModulusSize,
287    {
288        self.into()
289    }
290
291    /// Serialize this secret key as JSON Web Key (JWK) string.
292    #[cfg(all(feature = "arithmetic", feature = "jwk"))]
293    #[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))]
294    #[cfg_attr(docsrs, doc(cfg(feature = "jwk")))]
295    pub fn to_jwk_string(&self) -> Zeroizing<String>
296    where
297        C: Curve + JwkParameters + ProjectiveArithmetic,
298        AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
299        FieldSize<C>: ModulusSize,
300    {
301        Zeroizing::new(self.to_jwk().to_string())
302    }
303}
304
305impl<C> ConstantTimeEq for SecretKey<C>
306where
307    C: Curve,
308{
309    fn ct_eq(&self, other: &Self) -> Choice {
310        self.inner.ct_eq(&other.inner)
311    }
312}
313
314impl<C> Debug for SecretKey<C>
315where
316    C: Curve,
317{
318    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319        // TODO(tarcieri): use `debug_struct` and `finish_non_exhaustive` when stable
320        write!(f, "SecretKey<{:?}>{{ ... }}", C::default())
321    }
322}
323
324impl<C> ZeroizeOnDrop for SecretKey<C> where C: Curve {}
325
326impl<C> Drop for SecretKey<C>
327where
328    C: Curve,
329{
330    fn drop(&mut self) {
331        self.inner.zeroize();
332    }
333}
334
335impl<C: Curve> Eq for SecretKey<C> {}
336
337impl<C> PartialEq for SecretKey<C>
338where
339    C: Curve,
340{
341    fn eq(&self, other: &Self) -> bool {
342        self.ct_eq(other).into()
343    }
344}
345
346#[cfg(all(feature = "sec1"))]
347#[cfg_attr(docsrs, doc(cfg(feature = "sec1")))]
348impl<C> TryFrom<sec1::EcPrivateKey<'_>> for SecretKey<C>
349where
350    C: Curve + ValidatePublicKey,
351    FieldSize<C>: ModulusSize,
352{
353    type Error = der::Error;
354
355    fn try_from(sec1_private_key: sec1::EcPrivateKey<'_>) -> der::Result<Self> {
356        let secret_key = Self::from_be_bytes(sec1_private_key.private_key)
357            .map_err(|_| der::Tag::Sequence.value_error())?;
358
359        // TODO(tarcieri): validate `sec1_private_key.params`?
360        if let Some(pk_bytes) = sec1_private_key.public_key {
361            let pk = EncodedPoint::<C>::from_bytes(pk_bytes)
362                .map_err(|_| der::Tag::BitString.value_error())?;
363
364            if C::validate_public_key(&secret_key, &pk).is_err() {
365                return Err(der::Tag::BitString.value_error());
366            }
367        }
368
369        Ok(secret_key)
370    }
371}
372
373#[cfg(feature = "arithmetic")]
374#[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))]
375impl<C> From<NonZeroScalar<C>> for SecretKey<C>
376where
377    C: Curve + ProjectiveArithmetic,
378{
379    fn from(scalar: NonZeroScalar<C>) -> SecretKey<C> {
380        SecretKey::from(&scalar)
381    }
382}
383
384#[cfg(feature = "arithmetic")]
385#[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))]
386impl<C> From<&NonZeroScalar<C>> for SecretKey<C>
387where
388    C: Curve + ProjectiveArithmetic,
389{
390    fn from(scalar: &NonZeroScalar<C>) -> SecretKey<C> {
391        SecretKey {
392            inner: scalar.into(),
393        }
394    }
395}