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