Skip to main content

pkcs8/
private_key_info.rs

1//! PKCS#8 `PrivateKeyInfo`.
2
3use crate::{Error, Result, Version};
4use core::fmt;
5use der::{
6    Decode, DecodeValue, Encode, EncodeValue, FixedTag, Header, Length, Reader, Sequence, TagMode,
7    TagNumber, Writer,
8    asn1::{AnyRef, BitStringRef, ContextSpecific, OctetStringRef, SequenceRef},
9};
10use spki::AlgorithmIdentifier;
11
12#[cfg(feature = "ctutils")]
13use ctutils::{Choice, CtEq};
14#[cfg(feature = "pem")]
15use der::pem::PemLabel;
16#[cfg(feature = "alloc")]
17use der::{
18    SecretDocument,
19    asn1::{Any, BitString, OctetString},
20};
21#[cfg(feature = "encryption")]
22use {
23    crate::EncryptedPrivateKeyInfoRef, der::zeroize::Zeroizing, pkcs5::pbes2,
24    rand_core::TryCryptoRng,
25};
26
27/// Context-specific tag number for attributes.
28const ATTRIBUTES_TAG: TagNumber = TagNumber(0);
29
30/// Context-specific tag number for the public key.
31const PUBLIC_KEY_TAG: TagNumber = TagNumber(1);
32
33/// PKCS#8 `PrivateKeyInfo`.
34///
35/// ASN.1 structure containing an `AlgorithmIdentifier`, private key
36/// data in an algorithm specific format, and optional attributes
37/// (ignored by this implementation).
38///
39/// Supports PKCS#8 v1 as described in [RFC 5208] and PKCS#8 v2 as described
40/// in [RFC 5958]. PKCS#8 v2 keys include an additional public key field.
41///
42/// # PKCS#8 v1 `PrivateKeyInfo`
43///
44/// Described in [RFC 5208 Section 5]:
45///
46/// ```text
47/// PrivateKeyInfo ::= SEQUENCE {
48///         version                   Version,
49///         privateKeyAlgorithm       PrivateKeyAlgorithmIdentifier,
50///         privateKey                PrivateKey,
51///         attributes           [0]  IMPLICIT Attributes OPTIONAL }
52///
53/// Version ::= INTEGER
54///
55/// PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
56///
57/// PrivateKey ::= OCTET STRING
58///
59/// Attributes ::= SET OF Attribute
60/// ```
61///
62/// # PKCS#8 v2 `OneAsymmetricKey`
63///
64/// PKCS#8 `OneAsymmetricKey` as described in [RFC 5958 Section 2]:
65///
66/// ```text
67/// PrivateKeyInfo ::= OneAsymmetricKey
68///
69/// OneAsymmetricKey ::= SEQUENCE {
70///     version                   Version,
71///     privateKeyAlgorithm       PrivateKeyAlgorithmIdentifier,
72///     privateKey                PrivateKey,
73///     attributes            [0] Attributes OPTIONAL,
74///     ...,
75///     [[2: publicKey        [1] PublicKey OPTIONAL ]],
76///     ...
77///   }
78///
79/// Version ::= INTEGER { v1(0), v2(1) } (v1, ..., v2)
80///
81/// PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
82///
83/// PrivateKey ::= OCTET STRING
84///
85/// Attributes ::= SET OF Attribute
86///
87/// PublicKey ::= BIT STRING
88/// ```
89///
90/// [RFC 5208]: https://tools.ietf.org/html/rfc5208
91/// [RFC 5958]: https://datatracker.ietf.org/doc/html/rfc5958
92/// [RFC 5208 Section 5]: https://tools.ietf.org/html/rfc5208#section-5
93/// [RFC 5958 Section 2]: https://datatracker.ietf.org/doc/html/rfc5958#section-2
94#[derive(Clone)]
95pub struct PrivateKeyInfo<Params, Key, PubKey> {
96    /// X.509 `AlgorithmIdentifier` for the private key type.
97    pub algorithm: AlgorithmIdentifier<Params>,
98
99    /// Private key data. Exact content format is different between algorithms.
100    pub private_key: Key,
101
102    /// Public key data, optionally available if version is V2.
103    pub public_key: Option<PubKey>,
104}
105
106impl<Params, Key, PubKey> PrivateKeyInfo<Params, Key, PubKey> {
107    /// Create a new PKCS#8 [`PrivateKeyInfo`] message.
108    ///
109    /// This is a helper method which initializes `attributes` and `public_key`
110    /// to `None`, helpful if you aren't using those.
111    pub fn new(algorithm: AlgorithmIdentifier<Params>, private_key: Key) -> Self {
112        Self {
113            algorithm,
114            private_key,
115            public_key: None,
116        }
117    }
118
119    /// Get the PKCS#8 [`Version`] for this structure.
120    ///
121    /// [`Version::V1`] if `public_key` is `None`, [`Version::V2`] if `Some`.
122    pub fn version(&self) -> Version {
123        if self.public_key.is_some() {
124            Version::V2
125        } else {
126            Version::V1
127        }
128    }
129}
130
131impl<'a, Params, Key, PubKey> PrivateKeyInfo<Params, Key, PubKey>
132where
133    Params: der::Choice<'a, Error = der::Error> + Encode,
134    Key: DecodeValue<'a, Error = der::Error> + FixedTag + 'a,
135    Key: EncodeValue,
136    PubKey: DecodeValue<'a, Error = der::Error> + FixedTag + 'a,
137    PubKey: BitStringLike,
138{
139    /// Encrypt this private key using an encryption key derived from the provided password.
140    ///
141    /// Uses the following algorithms for encryption:
142    /// - PBKDF: scrypt with default parameters:
143    ///   - logâ‚‚(N): 15
144    ///   - r: 8
145    ///   - p: 1
146    /// - Cipher: AES-256-CBC (best available option for PKCS#5 encryption)
147    ///
148    /// # Errors
149    /// - Propagates errors from calling [`Encode::to_der`] on `Self`.
150    /// - Returns errors in the event encryption failed.
151    #[cfg(feature = "getrandom")]
152    pub fn encrypt(&self, password: impl AsRef<[u8]>) -> Result<SecretDocument> {
153        let der = Zeroizing::new(self.to_der()?);
154        EncryptedPrivateKeyInfoRef::encrypt(password, der.as_ref())
155    }
156
157    /// Encrypt this private key using an encryption key derived from the provided password.
158    ///
159    /// This function allows the RNG used to derive the salt/IV to be specified directly.
160    ///
161    /// # Errors
162    /// - Propagates errors from calling [`Encode::to_der`] on `Self`.
163    /// - Returns errors in the event encryption failed.
164    #[cfg(feature = "encryption")]
165    pub fn encrypt_with_rng<R: TryCryptoRng>(
166        &self,
167        rng: &mut R,
168        password: impl AsRef<[u8]>,
169    ) -> Result<SecretDocument> {
170        let der = Zeroizing::new(self.to_der()?);
171        EncryptedPrivateKeyInfoRef::encrypt_with_rng(rng, password, der.as_ref())
172    }
173
174    /// Encrypt this private key using a symmetric encryption key derived from the provided password
175    /// and [`pbes2::Parameters`].
176    ///
177    /// # Errors
178    /// - Propagates errors from calling [`Encode::to_der`] on `Self`.
179    /// - Returns errors in the event encryption failed.
180    #[cfg(feature = "encryption")]
181    pub fn encrypt_with_params(
182        &self,
183        pbes2_params: pbes2::Parameters,
184        password: impl AsRef<[u8]>,
185    ) -> Result<SecretDocument> {
186        let der = Zeroizing::new(self.to_der()?);
187        EncryptedPrivateKeyInfoRef::encrypt_with_params(pbes2_params, password, der.as_ref())
188    }
189}
190
191impl<'a, Params, Key, PubKey> PrivateKeyInfo<Params, Key, PubKey>
192where
193    Params: der::Choice<'a> + Encode,
194    PubKey: BitStringLike,
195{
196    /// Get a `BIT STRING` representation of the public key, if present.
197    fn public_key_bit_string(&self) -> Option<ContextSpecific<BitStringRef<'_>>> {
198        self.public_key.as_ref().map(|pk| {
199            let value = pk.as_bit_string();
200            ContextSpecific {
201                tag_number: PUBLIC_KEY_TAG,
202                tag_mode: TagMode::Implicit,
203                value,
204            }
205        })
206    }
207}
208
209impl<'a, Params, Key, PubKey> DecodeValue<'a> for PrivateKeyInfo<Params, Key, PubKey>
210where
211    Params: der::Choice<'a, Error = der::Error> + Encode,
212    Key: DecodeValue<'a, Error = der::Error> + FixedTag + 'a,
213    PubKey: DecodeValue<'a, Error = der::Error> + FixedTag + 'a,
214{
215    type Error = der::Error;
216
217    fn decode_value<R: Reader<'a>>(reader: &mut R, _header: Header) -> der::Result<Self> {
218        // Parse and validate `version` INTEGER.
219        let version = Version::decode(reader)?;
220        let algorithm = reader.decode()?;
221        let private_key = Key::decode(reader)?;
222
223        let _attributes =
224            reader.context_specific::<&SequenceRef>(ATTRIBUTES_TAG, TagMode::Implicit)?;
225
226        let public_key = reader.context_specific::<PubKey>(PUBLIC_KEY_TAG, TagMode::Implicit)?;
227
228        if version.has_public_key() != public_key.is_some() {
229            return Err(reader.error(
230                der::Tag::ContextSpecific {
231                    constructed: true,
232                    number: PUBLIC_KEY_TAG,
233                }
234                .value_error(),
235            ));
236        }
237
238        // Ignore any remaining extension fields
239        while !reader.is_finished() {
240            reader.decode::<ContextSpecific<AnyRef<'_>>>()?;
241        }
242
243        Ok(Self {
244            algorithm,
245            private_key,
246            public_key,
247        })
248    }
249}
250
251impl<'a, Params, Key, PubKey> EncodeValue for PrivateKeyInfo<Params, Key, PubKey>
252where
253    Params: der::Choice<'a, Error = der::Error> + Encode,
254    Key: EncodeValue + FixedTag,
255    PubKey: BitStringLike,
256{
257    fn value_len(&self) -> der::Result<Length> {
258        self.version().encoded_len()?
259            + self.algorithm.encoded_len()?
260            + self.private_key.encoded_len()?
261            + self.public_key_bit_string().encoded_len()?
262    }
263
264    fn encode_value(&self, writer: &mut impl Writer) -> der::Result<()> {
265        self.version().encode(writer)?;
266        self.algorithm.encode(writer)?;
267        self.private_key.encode(writer)?;
268        self.public_key_bit_string().encode(writer)?;
269        Ok(())
270    }
271}
272
273impl<'a, Params, Key, PubKey> Sequence<'a> for PrivateKeyInfo<Params, Key, PubKey>
274where
275    Params: der::Choice<'a, Error = der::Error> + Encode,
276    Key: DecodeValue<'a, Error = der::Error> + FixedTag + 'a,
277    Key: EncodeValue,
278    PubKey: DecodeValue<'a, Error = der::Error> + FixedTag + 'a,
279    PubKey: BitStringLike,
280{
281}
282
283impl<'a, Params, Key, PubKey> TryFrom<&'a [u8]> for PrivateKeyInfo<Params, Key, PubKey>
284where
285    Params: der::Choice<'a, Error = der::Error> + Encode,
286    Key: DecodeValue<'a, Error = der::Error> + FixedTag + 'a,
287    Key: EncodeValue,
288    PubKey: DecodeValue<'a, Error = der::Error> + FixedTag + 'a,
289    PubKey: BitStringLike,
290{
291    type Error = Error;
292
293    fn try_from(bytes: &'a [u8]) -> Result<Self> {
294        Ok(Self::from_der(bytes)?)
295    }
296}
297
298impl<Params, Key, PubKey> fmt::Debug for PrivateKeyInfo<Params, Key, PubKey>
299where
300    Params: fmt::Debug,
301    PubKey: fmt::Debug,
302{
303    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304        f.debug_struct("PrivateKeyInfo")
305            .field("version", &self.version())
306            .field("algorithm", &self.algorithm)
307            .field("public_key", &self.public_key)
308            .finish_non_exhaustive()
309    }
310}
311
312#[cfg(feature = "alloc")]
313impl<'a, Params, Key, PubKey> TryFrom<PrivateKeyInfo<Params, Key, PubKey>> for SecretDocument
314where
315    Params: der::Choice<'a, Error = der::Error> + Encode,
316    Key: DecodeValue<'a, Error = der::Error> + FixedTag + 'a,
317    Key: EncodeValue,
318    PubKey: DecodeValue<'a, Error = der::Error> + FixedTag + 'a,
319    PubKey: BitStringLike,
320{
321    type Error = Error;
322
323    fn try_from(private_key: PrivateKeyInfo<Params, Key, PubKey>) -> Result<SecretDocument> {
324        SecretDocument::try_from(&private_key)
325    }
326}
327
328#[cfg(feature = "alloc")]
329impl<'a, Params, Key, PubKey> TryFrom<&PrivateKeyInfo<Params, Key, PubKey>> for SecretDocument
330where
331    Params: der::Choice<'a, Error = der::Error> + Encode,
332    Key: DecodeValue<'a, Error = der::Error> + FixedTag + 'a,
333    Key: EncodeValue,
334    PubKey: DecodeValue<'a, Error = der::Error> + FixedTag + 'a,
335    PubKey: BitStringLike,
336{
337    type Error = Error;
338
339    fn try_from(private_key: &PrivateKeyInfo<Params, Key, PubKey>) -> Result<SecretDocument> {
340        Ok(Self::encode_msg(private_key)?)
341    }
342}
343
344#[cfg(feature = "pem")]
345impl<Params, Key, PubKey> PemLabel for PrivateKeyInfo<Params, Key, PubKey> {
346    const PEM_LABEL: &'static str = "PRIVATE KEY";
347}
348
349#[cfg(feature = "ctutils")]
350impl<Params, Key, PubKey> CtEq for PrivateKeyInfo<Params, Key, PubKey>
351where
352    Params: Eq,
353    Key: PartialEq + AsRef<[u8]>,
354    PubKey: PartialEq,
355{
356    fn ct_eq(&self, other: &Self) -> Choice {
357        // NOTE: public fields are not compared in constant time
358        let public_fields_eq =
359            self.algorithm == other.algorithm && self.public_key == other.public_key;
360
361        self.private_key.as_ref().ct_eq(other.private_key.as_ref())
362            & Choice::from(u8::from(public_fields_eq))
363    }
364}
365
366#[cfg(feature = "ctutils")]
367impl<Params, Key, PubKey> Eq for PrivateKeyInfo<Params, Key, PubKey>
368where
369    Params: Eq,
370    Key: AsRef<[u8]> + Eq,
371    PubKey: Eq,
372{
373}
374
375#[cfg(feature = "ctutils")]
376impl<Params, Key, PubKey> PartialEq for PrivateKeyInfo<Params, Key, PubKey>
377where
378    Params: Eq,
379    Key: PartialEq + AsRef<[u8]>,
380    PubKey: PartialEq,
381{
382    fn eq(&self, other: &Self) -> bool {
383        self.ct_eq(other).into()
384    }
385}
386
387/// [`PrivateKeyInfo`] with [`AnyRef`] algorithm parameters, and `&[u8]` key.
388pub type PrivateKeyInfoRef<'a> = PrivateKeyInfo<AnyRef<'a>, &'a OctetStringRef, BitStringRef<'a>>;
389
390/// [`BitStringLike`] marks object that will act like a `BitString`.
391///
392/// It will allow to get a [`BitStringRef`] that points back to the underlying bytes.
393// TODO(tarcieri): replace this with `AsRef<BitStringRef>` when we can have `&BitStringRef`.
394pub trait BitStringLike {
395    fn as_bit_string(&self) -> BitStringRef<'_>;
396}
397
398impl BitStringLike for BitStringRef<'_> {
399    fn as_bit_string(&self) -> BitStringRef<'_> {
400        BitStringRef::from(self)
401    }
402}
403
404#[cfg(feature = "alloc")]
405pub(crate) mod allocating {
406    use super::*;
407    use crate::{DecodePrivateKey, EncodePrivateKey};
408    use alloc::borrow::ToOwned;
409    use core::borrow::Borrow;
410    use der::referenced::*;
411
412    #[cfg(feature = "pem")]
413    use der::DecodePem;
414
415    /// [`PrivateKeyInfo`] with [`Any`] algorithm parameters, and `Box<[u8]>` key.
416    pub type PrivateKeyInfoOwned = PrivateKeyInfo<Any, OctetString, BitString>;
417
418    impl DecodePrivateKey for PrivateKeyInfoOwned {
419        fn from_pkcs8_der(bytes: &[u8]) -> Result<Self> {
420            Ok(Self::from_der(bytes)?)
421        }
422
423        #[cfg(feature = "pem")]
424        fn from_pkcs8_pem(pem: &str) -> Result<Self> {
425            Ok(Self::from_pem(pem)?)
426        }
427    }
428
429    impl EncodePrivateKey for PrivateKeyInfoOwned {
430        fn to_pkcs8_der(&self) -> Result<SecretDocument> {
431            self.try_into()
432        }
433    }
434
435    impl EncodePrivateKey for PrivateKeyInfoRef<'_> {
436        fn to_pkcs8_der(&self) -> Result<SecretDocument> {
437            self.try_into()
438        }
439    }
440
441    impl<'a> RefToOwned<'a> for PrivateKeyInfoRef<'a> {
442        type Owned = PrivateKeyInfoOwned;
443        fn ref_to_owned(&self) -> Self::Owned {
444            PrivateKeyInfoOwned {
445                algorithm: self.algorithm.ref_to_owned(),
446                private_key: self.private_key.to_owned(),
447                public_key: self.public_key.ref_to_owned(),
448            }
449        }
450    }
451
452    impl OwnedToRef for PrivateKeyInfoOwned {
453        type Borrowed<'a> = PrivateKeyInfoRef<'a>;
454        fn owned_to_ref(&self) -> Self::Borrowed<'_> {
455            PrivateKeyInfoRef {
456                algorithm: self.algorithm.owned_to_ref(),
457                private_key: self.private_key.borrow(),
458                public_key: self.public_key.owned_to_ref(),
459            }
460        }
461    }
462
463    impl BitStringLike for BitString {
464        fn as_bit_string(&self) -> BitStringRef<'_> {
465            BitStringRef::from(self)
466        }
467    }
468}