Skip to main content

ed25519/
pkcs8.rs

1//! PKCS#8 private key support.
2//!
3//! Implements Ed25519 PKCS#8 private keys as described in RFC8410 Section 7:
4//! <https://datatracker.ietf.org/doc/html/rfc8410#section-7>
5//!
6//! ## `SemVer` Notes
7//!
8//! The `pkcs8` module of this crate is exempted from `SemVer` as it uses a
9//! pre-1.0 dependency (the `pkcs8` crate).
10//!
11//! However, breaking changes to this module will be accompanied by a minor
12//! version bump.
13//!
14//! Please lock to a specific minor version of the `ed25519` crate to avoid
15//! breaking changes when using this module.
16
17pub use pkcs8::{
18    DecodePrivateKey, DecodePublicKey, Error, KeyError, ObjectIdentifier, PrivateKeyInfoRef,
19    Result, spki,
20};
21
22#[cfg(feature = "alloc")]
23pub use pkcs8::{EncodePrivateKey, spki::EncodePublicKey};
24
25#[cfg(feature = "alloc")]
26pub use pkcs8::der::{
27    Document, SecretDocument,
28    asn1::{BitStringRef, OctetStringRef},
29};
30
31use core::fmt;
32
33#[cfg(feature = "pem")]
34use core::str;
35
36#[cfg(feature = "zeroize")]
37use zeroize::Zeroize;
38
39#[cfg(feature = "zerocopy")]
40use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
41
42/// Algorithm [`ObjectIdentifier`] for the Ed25519 digital signature algorithm
43/// (`id-Ed25519`).
44///
45/// <http://oid-info.com/get/1.3.101.112>
46pub const ALGORITHM_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.101.112");
47
48/// Ed25519 Algorithm Identifier.
49pub const ALGORITHM_ID: pkcs8::AlgorithmIdentifierRef<'static> = pkcs8::AlgorithmIdentifierRef {
50    oid: ALGORITHM_OID,
51    parameters: None,
52};
53
54/// Ed25519 keypair serialized as bytes.
55///
56/// This type is primarily useful for decoding/encoding PKCS#8 private key
57/// files (either DER or PEM) encoded using the following traits:
58///
59/// - [`DecodePrivateKey`]: decode DER or PEM encoded PKCS#8 private key.
60/// - [`EncodePrivateKey`]: encode DER or PEM encoded PKCS#8 private key.
61///
62/// PKCS#8 private key files encoded with PEM begin with:
63///
64/// ```text
65/// -----BEGIN PRIVATE KEY-----
66/// ```
67///
68/// Note that this type operates on raw bytes and performs no validation that
69/// keys represent valid Ed25519 field elements.
70pub struct KeypairBytes {
71    /// Ed25519 secret key.
72    ///
73    /// Little endian serialization of an element of the Curve25519 scalar
74    /// field, prior to "clamping" (i.e. setting/clearing bits to ensure the
75    /// scalar is actually a valid field element)
76    pub secret_key: [u8; Self::BYTE_SIZE / 2],
77
78    /// Ed25519 public key (if available).
79    ///
80    /// Compressed Edwards-y encoded curve point.
81    pub public_key: Option<PublicKeyBytes>,
82}
83
84impl KeypairBytes {
85    /// Size of an Ed25519 keypair when serialized as bytes.
86    const BYTE_SIZE: usize = 64;
87
88    /// Parse raw keypair from a 64-byte input.
89    #[must_use]
90    #[allow(clippy::missing_panics_doc, reason = "MSRV TODO")]
91    pub fn from_bytes(bytes: &[u8; Self::BYTE_SIZE]) -> Self {
92        // TODO(tarcieri): use `as_chunks` when MSRV is 1.88
93        let (sk, pk) = bytes.split_at(Self::BYTE_SIZE / 2);
94
95        Self {
96            secret_key: sk.try_into().expect("secret key size error"),
97            public_key: Some(PublicKeyBytes(
98                pk.try_into().expect("public key size error"),
99            )),
100        }
101    }
102
103    /// Serialize as a 64-byte keypair.
104    ///
105    /// # Returns
106    /// - `Some(bytes)` if the `public_key` is present.
107    /// - `None` if the `public_key` is absent (i.e. `None`).
108    #[must_use]
109    pub fn to_bytes(&self) -> Option<[u8; Self::BYTE_SIZE]> {
110        if let Some(public_key) = &self.public_key {
111            let mut result = [0u8; Self::BYTE_SIZE];
112            let (sk, pk) = result.split_at_mut(Self::BYTE_SIZE / 2);
113            sk.copy_from_slice(&self.secret_key);
114            pk.copy_from_slice(public_key.as_ref());
115            Some(result)
116        } else {
117            None
118        }
119    }
120}
121
122impl Drop for KeypairBytes {
123    fn drop(&mut self) {
124        #[cfg(feature = "zeroize")]
125        self.secret_key.zeroize();
126    }
127}
128
129#[cfg(feature = "alloc")]
130impl EncodePrivateKey for KeypairBytes {
131    fn to_pkcs8_der(&self) -> Result<SecretDocument> {
132        // Serialize private key as nested OCTET STRING
133        let mut private_key = [0u8; 2 + (Self::BYTE_SIZE / 2)];
134        private_key[0] = 0x04;
135        private_key[1] = 0x20;
136        private_key[2..].copy_from_slice(&self.secret_key);
137
138        let private_key_info = PrivateKeyInfoRef {
139            algorithm: ALGORITHM_ID,
140            private_key: OctetStringRef::new(&private_key)?,
141            public_key: self
142                .public_key
143                .as_ref()
144                .map(|pk| BitStringRef::new(0, &pk.0))
145                .transpose()?,
146        };
147
148        let result = SecretDocument::encode_msg(&private_key_info)?;
149
150        #[cfg(feature = "zeroize")]
151        private_key.zeroize();
152
153        Ok(result)
154    }
155}
156
157impl TryFrom<PrivateKeyInfoRef<'_>> for KeypairBytes {
158    type Error = Error;
159
160    fn try_from(private_key: PrivateKeyInfoRef<'_>) -> Result<Self> {
161        private_key.algorithm.assert_algorithm_oid(ALGORITHM_OID)?;
162
163        if private_key.algorithm.parameters.is_some() {
164            return Err(Error::ParametersMalformed);
165        }
166
167        // Ed25519 PKCS#8 keys are represented as a nested OCTET STRING
168        // (i.e. an OCTET STRING within an OCTET STRING).
169        //
170        // This match statement checks and removes the inner OCTET STRING
171        // header value:
172        //
173        // - 0x04: OCTET STRING tag
174        // - 0x20: 32-byte length
175        let secret_key = match private_key.private_key.as_bytes() {
176            [0x04, 0x20, rest @ ..] => rest.try_into().map_err(|_| KeyError::Invalid),
177            _ => Err(KeyError::Invalid),
178        }?;
179
180        let public_key = private_key
181            .public_key
182            .and_then(|bs| bs.as_bytes())
183            .map(|bytes| bytes.try_into().map_err(|_| KeyError::Invalid))
184            .transpose()?
185            .map(PublicKeyBytes);
186
187        Ok(Self {
188            secret_key,
189            public_key,
190        })
191    }
192}
193
194impl TryFrom<&[u8]> for KeypairBytes {
195    type Error = Error;
196
197    fn try_from(der_bytes: &[u8]) -> Result<Self> {
198        Self::from_pkcs8_der(der_bytes)
199    }
200}
201
202impl fmt::Debug for KeypairBytes {
203    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204        f.debug_struct("KeypairBytes")
205            .field("public_key", &self.public_key)
206            .finish_non_exhaustive()
207    }
208}
209
210#[cfg(feature = "pem")]
211impl str::FromStr for KeypairBytes {
212    type Err = Error;
213
214    fn from_str(pem: &str) -> Result<Self> {
215        Self::from_pkcs8_pem(pem)
216    }
217}
218
219/// Ed25519 public key serialized as bytes.
220///
221/// This type is primarily useful for decoding/encoding SPKI public key
222/// files (either DER or PEM) encoded using the following traits:
223///
224/// - [`DecodePublicKey`]: decode DER or PEM encoded PKCS#8 private key.
225/// - [`EncodePublicKey`]: encode DER or PEM encoded PKCS#8 private key.
226///
227/// SPKI public key files encoded with PEM begin with:
228///
229/// ```text
230/// -----BEGIN PUBLIC KEY-----
231/// ```
232///
233/// Note that this type operates on raw bytes and performs no validation that
234/// public keys represent valid compressed Ed25519 y-coordinates.
235#[derive(Clone, Copy, Eq, Hash, PartialEq)]
236#[cfg_attr(
237    feature = "zerocopy",
238    derive(IntoBytes, FromBytes, Unaligned, KnownLayout, Immutable,)
239)]
240#[repr(transparent)]
241pub struct PublicKeyBytes(pub [u8; Self::BYTE_SIZE]);
242
243impl PublicKeyBytes {
244    /// Size of an Ed25519 public key when serialized as bytes.
245    const BYTE_SIZE: usize = 32;
246
247    /// Returns the raw bytes of the public key.
248    #[must_use]
249    pub fn to_bytes(&self) -> [u8; Self::BYTE_SIZE] {
250        self.0
251    }
252}
253
254impl AsRef<[u8; Self::BYTE_SIZE]> for PublicKeyBytes {
255    fn as_ref(&self) -> &[u8; Self::BYTE_SIZE] {
256        &self.0
257    }
258}
259
260#[cfg(feature = "alloc")]
261impl EncodePublicKey for PublicKeyBytes {
262    fn to_public_key_der(&self) -> spki::Result<Document> {
263        pkcs8::SubjectPublicKeyInfoRef {
264            algorithm: ALGORITHM_ID,
265            subject_public_key: BitStringRef::new(0, &self.0)?,
266        }
267        .try_into()
268    }
269}
270
271impl TryFrom<spki::SubjectPublicKeyInfoRef<'_>> for PublicKeyBytes {
272    type Error = spki::Error;
273
274    fn try_from(spki: spki::SubjectPublicKeyInfoRef<'_>) -> spki::Result<Self> {
275        spki.algorithm.assert_algorithm_oid(ALGORITHM_OID)?;
276
277        if spki.algorithm.parameters.is_some() {
278            return Err(spki::Error::KeyMalformed);
279        }
280
281        spki.subject_public_key
282            .as_bytes()
283            .ok_or(spki::Error::KeyMalformed)?
284            .try_into()
285            .map(Self)
286            .map_err(|_| spki::Error::KeyMalformed)
287    }
288}
289
290impl TryFrom<&[u8]> for PublicKeyBytes {
291    type Error = spki::Error;
292
293    fn try_from(der_bytes: &[u8]) -> spki::Result<Self> {
294        Self::from_public_key_der(der_bytes)
295    }
296}
297
298impl TryFrom<KeypairBytes> for PublicKeyBytes {
299    type Error = spki::Error;
300
301    fn try_from(keypair: KeypairBytes) -> spki::Result<PublicKeyBytes> {
302        PublicKeyBytes::try_from(&keypair)
303    }
304}
305
306impl TryFrom<&KeypairBytes> for PublicKeyBytes {
307    type Error = spki::Error;
308
309    fn try_from(keypair: &KeypairBytes) -> spki::Result<PublicKeyBytes> {
310        keypair.public_key.ok_or(spki::Error::KeyMalformed)
311    }
312}
313
314impl fmt::Debug for PublicKeyBytes {
315    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316        f.write_str("PublicKeyBytes(")?;
317
318        for &byte in self.as_ref() {
319            write!(f, "{byte:02X}")?;
320        }
321
322        f.write_str(")")
323    }
324}
325
326#[cfg(feature = "pem")]
327impl str::FromStr for PublicKeyBytes {
328    type Err = spki::Error;
329
330    fn from_str(pem: &str) -> spki::Result<Self> {
331        Self::from_public_key_pem(pem)
332    }
333}
334
335#[cfg(feature = "pem")]
336impl fmt::Display for PublicKeyBytes {
337    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338        f.write_str(
339            &self
340                .to_public_key_pem(Default::default())
341                .expect("PEM serialization error"),
342        )
343    }
344}
345
346#[cfg(feature = "pem")]
347#[cfg(test)]
348mod tests {
349    use super::{KeypairBytes, PublicKeyBytes};
350    use hex_literal::hex;
351
352    const SECRET_KEY_BYTES: [u8; 32] =
353        hex!("D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842");
354
355    const PUBLIC_KEY_BYTES: [u8; 32] =
356        hex!("19BF44096984CDFE8541BAC167DC3B96C85086AA30B6B6CB0C5C38AD703166E1");
357
358    #[test]
359    fn to_bytes() {
360        let valid_keypair = KeypairBytes {
361            secret_key: SECRET_KEY_BYTES,
362            public_key: Some(PublicKeyBytes(PUBLIC_KEY_BYTES)),
363        };
364
365        assert_eq!(
366            valid_keypair.to_bytes().expect("to_bytes"),
367            hex!(
368                "D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F4475584219BF44096984CDFE8541BAC167DC3B96C85086AA30B6B6CB0C5C38AD703166E1"
369            )
370        );
371
372        let invalid_keypair = KeypairBytes {
373            secret_key: SECRET_KEY_BYTES,
374            public_key: None,
375        };
376
377        assert_eq!(invalid_keypair.to_bytes(), None);
378    }
379}