Coverage Report

Created: 2026-08-14 06:55

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/elliptic-curve-0.13.8/src/secret_key.rs
Line
Count
Source
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"))]
9
mod pkcs8;
10
11
use crate::{Curve, Error, FieldBytes, Result, ScalarPrimitive};
12
use core::fmt::{self, Debug};
13
use generic_array::typenum::Unsigned;
14
use subtle::{Choice, ConstantTimeEq};
15
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
16
17
#[cfg(feature = "arithmetic")]
18
use crate::{rand_core::CryptoRngCore, CurveArithmetic, NonZeroScalar, PublicKey};
19
20
#[cfg(feature = "jwk")]
21
use crate::jwk::{JwkEcKey, JwkParameters};
22
23
#[cfg(feature = "pem")]
24
use pem_rfc7468::{self as pem, PemLabel};
25
26
#[cfg(feature = "sec1")]
27
use {
28
    crate::{
29
        sec1::{EncodedPoint, ModulusSize, ValidatePublicKey},
30
        FieldBytesSize,
31
    },
32
    sec1::der,
33
};
34
35
#[cfg(all(feature = "alloc", feature = "arithmetic", feature = "sec1"))]
36
use {
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")))]
46
use alloc::string::String;
47
48
#[cfg(all(feature = "arithmetic", feature = "jwk"))]
49
use alloc::string::ToString;
50
51
#[cfg(all(doc, feature = "pkcs8"))]
52
use {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)]
78
pub struct SecretKey<C: Curve> {
79
    /// Scalar value
80
    inner: ScalarPrimitive<C>,
81
}
82
83
impl<C> SecretKey<C>
84
where
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
83
    pub fn as_scalar_primitive(&self) -> &ScalarPrimitive<C> {
116
83
        &self.inner
117
83
    }
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
83
    pub fn to_nonzero_scalar(&self) -> NonZeroScalar<C>
128
83
    where
129
83
        C: CurveArithmetic,
130
    {
131
83
        self.into()
132
83
    }
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
83
    pub fn from_bytes(bytes: &FieldBytes<C>) -> Result<Self> {
145
83
        let inner: ScalarPrimitive<C> =
146
83
            Option::from(ScalarPrimitive::from_bytes(bytes)).ok_or(Error)?;
147
148
83
        if inner.is_zero().into() {
149
0
            return Err(Error);
150
83
        }
151
152
83
        Ok(Self { inner })
153
83
    }
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
83
    pub fn from_slice(slice: &[u8]) -> Result<Self> {
162
83
        if slice.len() == C::FieldBytesSize::USIZE {
163
83
            Self::from_bytes(FieldBytes::<C>::from_slice(slice))
164
0
        } else if (Self::MIN_SIZE..C::FieldBytesSize::USIZE).contains(&slice.len()) {
165
0
            let mut bytes = Zeroizing::new(FieldBytes::<C>::default());
166
0
            let offset = C::FieldBytesSize::USIZE.saturating_sub(slice.len());
167
0
            bytes[offset..].copy_from_slice(slice);
168
0
            Self::from_bytes(&bytes)
169
        } else {
170
0
            Err(Error)
171
        }
172
83
    }
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
299
impl<C> ConstantTimeEq for SecretKey<C>
300
where
301
    C: Curve,
302
{
303
    fn ct_eq(&self, other: &Self) -> Choice {
304
        self.inner.ct_eq(&other.inner)
305
    }
306
}
307
308
impl<C> Debug for SecretKey<C>
309
where
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
318
impl<C> ZeroizeOnDrop for SecretKey<C> where C: Curve {}
319
320
impl<C> Drop for SecretKey<C>
321
where
322
    C: Curve,
323
{
324
83
    fn drop(&mut self) {
325
83
        self.inner.zeroize();
326
83
    }
327
}
328
329
impl<C: Curve> Eq for SecretKey<C> {}
330
331
impl<C> PartialEq for SecretKey<C>
332
where
333
    C: Curve,
334
{
335
    fn eq(&self, other: &Self) -> bool {
336
        self.ct_eq(other).into()
337
    }
338
}
339
340
#[cfg(feature = "sec1")]
341
impl<C> TryFrom<sec1::EcPrivateKey<'_>> for SecretKey<C>
342
where
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")]
367
impl<C> From<NonZeroScalar<C>> for SecretKey<C>
368
where
369
    C: CurveArithmetic,
370
{
371
    fn from(scalar: NonZeroScalar<C>) -> SecretKey<C> {
372
        SecretKey::from(&scalar)
373
    }
374
}
375
376
#[cfg(feature = "arithmetic")]
377
impl<C> From<&NonZeroScalar<C>> for SecretKey<C>
378
where
379
    C: CurveArithmetic,
380
{
381
    fn from(scalar: &NonZeroScalar<C>) -> SecretKey<C> {
382
        SecretKey {
383
            inner: scalar.into(),
384
        }
385
    }
386
}