Skip to main content

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, ScalarValue, bigint::ByteOrder};
12use array::typenum::Unsigned;
13use common::{Generate, InvalidKey, KeySizeUser, TryKeyInit};
14use core::fmt::{self, Debug};
15use rand_core::{CryptoRng, TryCryptoRng};
16use subtle::{Choice, ConstantTimeEq, CtOption};
17use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
18
19#[cfg(feature = "ecdh")]
20use crate::ecdh;
21#[cfg(feature = "arithmetic")]
22use crate::{CurveArithmetic, NonZeroScalar, PublicKey};
23#[cfg(all(feature = "arithmetic", feature = "pem"))]
24use alloc::string::String;
25#[cfg(feature = "pem")]
26use pem_rfc7468::{self as pem, PemLabel};
27#[cfg(all(feature = "alloc", feature = "arithmetic", feature = "sec1"))]
28use {
29    crate::{
30        AffinePoint,
31        sec1::{FromSec1Point, ToSec1Point},
32    },
33    alloc::vec::Vec,
34    sec1::der::Encode,
35};
36#[cfg(feature = "sec1")]
37use {
38    crate::{
39        DecodeError, DecodeResult, FieldBytesSize,
40        sec1::{ModulusSize, Sec1Point, ValidatePublicKey},
41    },
42    sec1::der::{self, Decode, oid::AssociatedOid},
43};
44
45#[cfg(all(doc, feature = "pkcs8"))]
46use {crate::pkcs8::DecodePrivateKey, core::str::FromStr};
47
48/// Elliptic curve secret keys.
49///
50/// This type wraps a secret scalar value, helping to prevent accidental exposure and securely
51/// erasing the value from memory when dropped.
52///
53/// # Generating secret keys
54/// [`SecretKey`]s can be generated using the [`Generate`] trait.
55///
56/// # Parsing PKCS#8 Keys
57/// PKCS#8 is a commonly used format for encoding secret keys (especially ones
58/// generated by OpenSSL).
59///
60/// Keys in PKCS#8 format are either binary (ASN.1 BER/DER), or PEM encoded
61/// (ASCII) and begin with the following:
62///
63/// ```text
64/// -----BEGIN PRIVATE KEY-----
65/// ```
66///
67/// To decode an elliptic curve private key from PKCS#8, enable the `pkcs8`
68/// feature of this crate (or the `pkcs8` feature of a specific RustCrypto
69/// elliptic curve crate) and use the [`DecodePrivateKey`]  trait to parse it.
70///
71/// When the `pem` feature of this crate (or a specific RustCrypto elliptic
72/// curve crate) is enabled, a [`FromStr`] impl is also available.
73#[derive(Clone)]
74pub struct SecretKey<C: Curve> {
75    /// Scalar value
76    inner: ScalarValue<C>,
77}
78
79impl<C> SecretKey<C>
80where
81    C: Curve,
82{
83    /// Minimum allowed size of an elliptic curve secret key in bytes.
84    ///
85    /// This provides the equivalent of 96-bits of symmetric security.
86    pub const MIN_SIZE: usize = 24;
87
88    /// Create a new secret key from a scalar value.
89    ///
90    /// # Returns
91    ///
92    /// This will return a none if the scalar is all-zero.
93    pub fn from_scalar(scalar: impl Into<ScalarValue<C>>) -> CtOption<Self> {
94        let inner = scalar.into();
95        CtOption::new(Self { inner }, !inner.is_zero())
96    }
97
98    /// Borrow the inner secret [`ScalarValue`] value.
99    ///
100    /// <div class="warning">
101    /// <b>Security Warning</b>
102    ///
103    /// This value is key material. Please treat it with the care it deserves!
104    /// </div>
105    pub fn as_scalar_value(&self) -> &ScalarValue<C> {
106        &self.inner
107    }
108
109    /// Get the secret [`NonZeroScalar`] value for this key.
110    ///
111    /// <div class="warning">
112    /// <b>Security Warning</b>
113    ///
114    /// This value is key material. Please treat it with the care it deserves!
115    /// </div>
116    #[cfg(feature = "arithmetic")]
117    pub fn to_nonzero_scalar(&self) -> NonZeroScalar<C>
118    where
119        C: CurveArithmetic,
120    {
121        self.into()
122    }
123
124    /// Get the [`PublicKey`] which corresponds to this secret key
125    #[cfg(feature = "arithmetic")]
126    pub fn public_key(&self) -> PublicKey<C>
127    where
128        C: CurveArithmetic,
129    {
130        PublicKey::from_secret_scalar(&self.to_nonzero_scalar())
131    }
132
133    /// Deserialize secret key from an encoded secret scalar.
134    ///
135    /// # Errors
136    /// Returns [`Error`] if `bytes` overflows the scalar modulus.
137    pub fn from_bytes(bytes: &FieldBytes<C>) -> Result<Self> {
138        let inner = ScalarValue::<C>::from_bytes(bytes)
139            .into_option()
140            .ok_or(Error)?;
141
142        if inner.is_zero().into() {
143            return Err(Error);
144        }
145
146        Ok(Self { inner })
147    }
148
149    /// Deserialize secret key from an encoded secret scalar passed as a byte slice.
150    ///
151    /// The slice is expected to be a minimum of 24 bytes (192 bits) and at most
152    /// `C::FieldBytesSize` bytes in length.
153    ///
154    /// Byte slices shorter than the field size are padded with zeros at the most-significant end,
155    /// according to [`Curve::FIELD_ENDIANNESS`].
156    ///
157    /// NOTE: this function is variable-time with respect to the input length. To avoid a timing
158    /// sidechannel, always ensure that the input has been pre-padded to `C::FieldBytesSize`.
159    ///
160    /// # Errors
161    /// - if `slice` is shorter than [`Self::MIN_SIZE`].
162    /// - if `slice` is not sized appropriately for the scalar modulus.
163    /// - if the decoded scalar field element overflows the scalar modulus.
164    pub fn from_slice(slice: &[u8]) -> Result<Self> {
165        if let Ok(field_bytes) = <&FieldBytes<C>>::try_from(slice) {
166            Self::from_bytes(field_bytes)
167        } else if (Self::MIN_SIZE..C::FieldBytesSize::USIZE).contains(&slice.len()) {
168            let mut bytes = Zeroizing::new(FieldBytes::<C>::default());
169
170            match C::FIELD_ENDIANNESS {
171                ByteOrder::BigEndian => {
172                    let offset = C::FieldBytesSize::USIZE - slice.len();
173                    bytes[offset..].copy_from_slice(slice);
174                }
175                ByteOrder::LittleEndian => bytes[..slice.len()].copy_from_slice(slice),
176            }
177
178            Self::from_bytes(&bytes)
179        } else {
180            Err(Error)
181        }
182    }
183
184    /// Serialize the raw secret scalar using the curve's configured field endianness.
185    pub fn to_bytes(&self) -> FieldBytes<C> {
186        self.inner.to_bytes()
187    }
188
189    /// Perform Elliptic Curve Diffie-Hellman with the given public key, returning a shared secret.
190    ///
191    /// See the documentation in the [`ecdh`] module for more information.
192    #[cfg(feature = "ecdh")]
193    pub fn diffie_hellman(&self, public_key: &PublicKey<C>) -> ecdh::SharedSecret<C>
194    where
195        C: CurveArithmetic,
196    {
197        ecdh::diffie_hellman(self.to_nonzero_scalar(), public_key.as_affine())
198    }
199
200    /// Decode [`SecretKey`] from DER-encoded private key.
201    ///
202    /// # Supported formats
203    /// - PKCS#8: requires `pkcs8` feature
204    /// - SEC1: requires `sec1` feature
205    ///
206    /// If you are expecting one format over the other, it's better to use a more specific method:
207    /// - PKCS#8: [`::pkcs8::DecodePrivateKey::from_pkcs8_der`]
208    /// - SEC1: [`::sec1::DecodeEcPrivateKey::from_sec1_der`]
209    ///
210    /// # Errors
211    /// - Returns [`DecodeError::Pkcs8`] if PKCS#8 key could not be decoded
212    /// - Returns [`DecodeError::Sec1`] if SEC1 key could not be decoded
213    #[cfg(any(feature = "pkcs8", feature = "sec1"))]
214    #[allow(clippy::missing_panics_doc, reason = "should not panic")]
215    pub fn from_der(der_bytes: &[u8]) -> DecodeResult<Self>
216    where
217        C: AssociatedOid + Curve + ValidatePublicKey,
218        FieldBytesSize<C>: ModulusSize,
219    {
220        #[allow(unused_assignments)]
221        let mut err: Option<DecodeError> = None;
222
223        #[cfg(feature = "pkcs8")]
224        match ::pkcs8::DecodePrivateKey::from_pkcs8_der(der_bytes) {
225            Ok(sk) => return Ok(sk),
226            Err(e) => err = Some(e.into()),
227        }
228
229        #[cfg(feature = "sec1")]
230        match Self::from_sec1_der(der_bytes) {
231            Ok(sk) => return Ok(sk),
232            Err(e) => {
233                // Insert if we don't already have a PKCS#8 error
234                let _ = err.get_or_insert(e);
235            }
236        }
237
238        Err(err.expect("should be set"))
239    }
240
241    /// Decode [`SecretKey`] from PEM-encoded private key.
242    ///
243    /// If you are expecting one format over the other, it's better to use a more specific method:
244    /// - PKCS#8: [`::pkcs8::DecodePrivateKey::from_pkcs8_pem`]
245    /// - SEC1: [`::sec1::DecodeEcPrivateKey::from_sec1_pem`]
246    ///
247    /// # Errors
248    /// - Returns [`DecodeError::Pem`] if PEM label is invalid or otherwise malformed
249    /// - Returns [`DecodeError::Pkcs8`] if PKCS#8 key detected but could not be decoded
250    /// - Returns [`DecodeError::Sec1`] if SEC1 key detected but could not be decoded
251    #[cfg(feature = "pem")]
252    pub fn from_pem(pem: &str) -> DecodeResult<Self>
253    where
254        C: AssociatedOid + Curve + ValidatePublicKey,
255        FieldBytesSize<C>: ModulusSize,
256    {
257        let label = pem_rfc7468::decode_label(pem.as_bytes()).map_err(DecodeError::Pem)?;
258
259        if ::pkcs8::PrivateKeyInfoRef::validate_pem_label(label).is_ok() {
260            return ::pkcs8::DecodePrivateKey::from_pkcs8_pem(pem).map_err(DecodeError::Pkcs8);
261        } else if ::sec1::EcPrivateKey::validate_pem_label(label).is_ok() {
262            return ::sec1::DecodeEcPrivateKey::from_sec1_pem(pem).map_err(DecodeError::Sec1);
263        }
264
265        Err(pem_rfc7468::Error::Label.into())
266    }
267
268    /// Deserialize secret key encoded in the SEC1 ASN.1 DER `ECPrivateKey` format.
269    ///
270    /// # Errors
271    /// - if `der_bytes` does not encode a valid SEC1 private key
272    /// - if the contained document does not encode a valid key for this curve
273    #[cfg(feature = "sec1")]
274    pub fn from_sec1_der(der_bytes: &[u8]) -> DecodeResult<Self>
275    where
276        C: AssociatedOid + Curve + ValidatePublicKey,
277        FieldBytesSize<C>: ModulusSize,
278    {
279        let sec1_key = sec1::EcPrivateKey::try_from(der_bytes)?;
280        Self::try_from(sec1_key).map_err(|e| DecodeError::Sec1(e.into()))
281    }
282
283    /// Serialize secret key in the SEC1 ASN.1 DER `ECPrivateKey` format.
284    ///
285    /// # Errors
286    /// If an internal error occurs encoding this key.
287    #[cfg(all(feature = "alloc", feature = "arithmetic", feature = "sec1"))]
288    pub fn to_sec1_der(&self) -> der::Result<Zeroizing<Vec<u8>>>
289    where
290        C: AssociatedOid + CurveArithmetic,
291        AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
292        FieldBytesSize<C>: ModulusSize,
293    {
294        let private_key_bytes = Zeroizing::new(self.to_bytes());
295        let public_key_bytes = self.public_key().to_sec1_point(false);
296        let parameters = sec1::EcParameters::NamedCurve(C::OID);
297
298        let ec_private_key = Zeroizing::new(
299            sec1::EcPrivateKey {
300                private_key: &private_key_bytes,
301                parameters: Some(parameters),
302                public_key: Some(public_key_bytes.as_bytes()),
303            }
304            .to_der()?,
305        );
306
307        Ok(ec_private_key)
308    }
309
310    /// Parse [`SecretKey`] from PEM-encoded SEC1 `ECPrivateKey` format.
311    ///
312    /// PEM-encoded SEC1 keys can be identified by the leading delimiter:
313    ///
314    /// ```text
315    /// -----BEGIN EC PRIVATE KEY-----
316    /// ```
317    ///
318    /// # Errors
319    /// - if the document cannot be decoded as PEM
320    /// - if the PEM document does not encode a valid private key for this curve
321    #[cfg(feature = "pem")]
322    pub fn from_sec1_pem(s: &str) -> DecodeResult<Self>
323    where
324        C: AssociatedOid + Curve + ValidatePublicKey,
325        FieldBytesSize<C>: ModulusSize,
326    {
327        let (label, der_bytes) = pem::decode_vec(s.as_bytes()).map_err(DecodeError::Pem)?;
328
329        if label != sec1::EcPrivateKey::PEM_LABEL {
330            return Err(pem_rfc7468::Error::Label.into());
331        }
332
333        Self::from_sec1_der(&der_bytes)
334    }
335
336    /// Serialize private key as self-zeroizing PEM-encoded SEC1 `ECPrivateKey`
337    /// with the given [`pem::LineEnding`].
338    ///
339    /// Pass `Default::default()` to use the OS's native line endings.
340    ///
341    /// # Errors
342    /// If an internal error occurs encoding this key.
343    #[cfg(feature = "pem")]
344    pub fn to_sec1_pem(&self, line_ending: pem::LineEnding) -> Result<Zeroizing<String>>
345    where
346        C: AssociatedOid + CurveArithmetic,
347        AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
348        FieldBytesSize<C>: ModulusSize,
349    {
350        self.to_sec1_der()
351            .ok()
352            .and_then(|der| {
353                pem::encode_string(sec1::EcPrivateKey::PEM_LABEL, line_ending, &der).ok()
354            })
355            .map(Zeroizing::new)
356            .ok_or(Error)
357    }
358
359    /// Deprecated: Generate a random [`SecretKey`].
360    #[deprecated(since = "0.14.0", note = "use the `Generate` trait instead")]
361    pub fn random<R: CryptoRng + ?Sized>(rng: &mut R) -> Self {
362        Self::generate_from_rng(rng)
363    }
364}
365
366impl<C> ConstantTimeEq for SecretKey<C>
367where
368    C: Curve,
369{
370    fn ct_eq(&self, other: &Self) -> Choice {
371        self.inner.ct_eq(&other.inner)
372    }
373}
374
375impl<C> Debug for SecretKey<C>
376where
377    C: Curve,
378{
379    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380        f.debug_struct(core::any::type_name::<Self>())
381            .finish_non_exhaustive()
382    }
383}
384
385impl<C> Drop for SecretKey<C>
386where
387    C: Curve,
388{
389    fn drop(&mut self) {
390        self.inner.zeroize();
391    }
392}
393impl<C> ZeroizeOnDrop for SecretKey<C> where C: Curve {}
394
395impl<C: Curve> Eq for SecretKey<C> {}
396
397impl<C> PartialEq for SecretKey<C>
398where
399    C: Curve,
400{
401    fn eq(&self, other: &Self) -> bool {
402        self.ct_eq(other).into()
403    }
404}
405
406impl<C> Generate for SecretKey<C>
407where
408    C: Curve,
409{
410    fn try_generate_from_rng<R: TryCryptoRng + ?Sized>(
411        rng: &mut R,
412    ) -> core::result::Result<Self, R::Error> {
413        Ok(Self {
414            inner: ScalarValue::<C>::try_generate_from_rng(rng)?,
415        })
416    }
417}
418
419impl<C> KeySizeUser for SecretKey<C>
420where
421    C: Curve,
422{
423    type KeySize = C::FieldBytesSize;
424}
425
426impl<C> TryKeyInit for SecretKey<C>
427where
428    C: Curve,
429{
430    fn new(key_bytes: &FieldBytes<C>) -> core::result::Result<Self, InvalidKey> {
431        Self::from_bytes(key_bytes).map_err(|_| InvalidKey)
432    }
433}
434
435#[cfg(feature = "sec1")]
436impl<C> sec1::DecodeEcPrivateKey for SecretKey<C>
437where
438    C: AssociatedOid + Curve + ValidatePublicKey,
439    FieldBytesSize<C>: ModulusSize,
440{
441    fn from_sec1_der(bytes: &[u8]) -> sec1::Result<Self> {
442        Ok(sec1::EcPrivateKey::from_der(bytes)?.try_into()?)
443    }
444}
445
446#[cfg(all(feature = "alloc", feature = "arithmetic", feature = "sec1"))]
447impl<C> sec1::EncodeEcPrivateKey for SecretKey<C>
448where
449    C: AssociatedOid + CurveArithmetic,
450    AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
451    FieldBytesSize<C>: ModulusSize,
452{
453    fn to_sec1_der(&self) -> sec1::Result<der::SecretDocument> {
454        let private_key_bytes = Zeroizing::new(self.to_bytes());
455        let public_key_bytes = self.public_key().to_sec1_point(false);
456
457        Ok(der::SecretDocument::encode_msg(&sec1::EcPrivateKey {
458            private_key: &private_key_bytes,
459            parameters: Some(C::OID.into()),
460            public_key: Some(public_key_bytes.as_bytes()),
461        })?)
462    }
463}
464
465#[cfg(feature = "sec1")]
466impl<C> TryFrom<sec1::EcPrivateKey<'_>> for SecretKey<C>
467where
468    C: AssociatedOid + Curve + ValidatePublicKey,
469    FieldBytesSize<C>: ModulusSize,
470{
471    type Error = der::Error;
472
473    fn try_from(sec1_private_key: sec1::EcPrivateKey<'_>) -> der::Result<Self> {
474        if let Some(sec1::EcParameters::NamedCurve(curve_oid)) = sec1_private_key.parameters {
475            if C::OID != curve_oid {
476                return Err(der::Tag::ObjectIdentifier.value_error().into());
477            }
478        }
479
480        let secret_key = Self::from_slice(sec1_private_key.private_key)
481            .map_err(|_| der::Tag::OctetString.value_error())?;
482
483        if let Some(pk_bytes) = sec1_private_key.public_key {
484            let pk = Sec1Point::<C>::from_bytes(pk_bytes)
485                .map_err(|_| der::Tag::BitString.value_error())?;
486
487            if C::validate_public_key(&secret_key, &pk).is_err() {
488                return Err(der::Tag::BitString.value_error().into());
489            }
490        }
491
492        Ok(secret_key)
493    }
494}
495
496#[cfg(feature = "arithmetic")]
497impl<C> From<NonZeroScalar<C>> for SecretKey<C>
498where
499    C: CurveArithmetic,
500{
501    fn from(scalar: NonZeroScalar<C>) -> SecretKey<C> {
502        SecretKey::from(&scalar)
503    }
504}
505
506#[cfg(feature = "arithmetic")]
507impl<C> From<&NonZeroScalar<C>> for SecretKey<C>
508where
509    C: CurveArithmetic,
510{
511    fn from(scalar: &NonZeroScalar<C>) -> SecretKey<C> {
512        SecretKey {
513            inner: scalar.into(),
514        }
515    }
516}