Skip to main content

ed448/
pkcs8.rs

1//! PKCS#8 private key support.
2//!
3//! Implements Ed448 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 `ed448` 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
31#[cfg(feature = "zeroize")]
32use zeroize::Zeroize;
33
34use core::fmt;
35
36/// Algorithm [`ObjectIdentifier`] for the Ed448 digital signature algorithm
37/// (`id-Ed448`).
38///
39/// <http://oid-info.com/get/1.3.101.113>
40pub const ALGORITHM_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.101.113");
41
42/// Ed448 Algorithm Identifier.
43pub const ALGORITHM_ID: pkcs8::AlgorithmIdentifierRef<'static> = pkcs8::AlgorithmIdentifierRef {
44    oid: ALGORITHM_OID,
45    parameters: None,
46};
47
48/// Ed448 keypair serialized as bytes.
49///
50/// This type is primarily useful for decoding/encoding PKCS#8 private key
51/// files (either DER or PEM) encoded using the following traits:
52///
53/// - [`DecodePrivateKey`]: decode DER or PEM encoded PKCS#8 private key.
54/// - [`EncodePrivateKey`]: encode DER or PEM encoded PKCS#8 private key.
55///
56/// PKCS#8 private key files encoded with PEM begin with:
57///
58/// ```text
59/// -----BEGIN PRIVATE KEY-----
60/// ```
61///
62/// Note that this type operates on raw bytes and performs no validation that
63/// keys represent valid Ed448 field elements.
64pub struct KeypairBytes {
65    /// Ed448 secret key.
66    ///
67    /// Little endian serialization of an element of the Curve448 scalar
68    /// field, prior to "clamping" (i.e. setting/clearing bits to ensure the
69    /// scalar is actually a valid field element)
70    pub secret_key: [u8; Self::BYTE_SIZE / 2],
71
72    /// Ed448 public key (if available).
73    ///
74    /// Compressed Edwards-y encoded curve point.
75    pub public_key: Option<PublicKeyBytes>,
76}
77
78impl KeypairBytes {
79    /// Size of an Ed448 keypair when serialized as bytes.
80    const BYTE_SIZE: usize = 114;
81
82    /// Parse raw keypair from a 114-byte input.
83    #[must_use]
84    #[allow(clippy::missing_panics_doc, reason = "MSRV TODO")]
85    pub fn from_bytes(bytes: &[u8; Self::BYTE_SIZE]) -> Self {
86        // TODO(tarcieri): use `as_chunks` when MSRV is 1.88
87        let (sk, pk) = bytes.split_at(Self::BYTE_SIZE / 2);
88
89        Self {
90            secret_key: sk.try_into().expect("secret key size error"),
91            public_key: Some(PublicKeyBytes(
92                pk.try_into().expect("public key size error"),
93            )),
94        }
95    }
96
97    /// Serialize as a 114-byte keypair.
98    ///
99    /// # Returns
100    /// - `Some(bytes)` if the `public_key` is present.
101    /// - `None` if the `public_key` is absent (i.e. `None`).
102    #[must_use]
103    pub fn to_bytes(&self) -> Option<[u8; Self::BYTE_SIZE]> {
104        if let Some(public_key) = &self.public_key {
105            let mut result = [0u8; Self::BYTE_SIZE];
106            let (sk, pk) = result.split_at_mut(Self::BYTE_SIZE / 2);
107            sk.copy_from_slice(&self.secret_key);
108            pk.copy_from_slice(public_key.as_ref());
109            Some(result)
110        } else {
111            None
112        }
113    }
114}
115
116impl Drop for KeypairBytes {
117    fn drop(&mut self) {
118        #[cfg(feature = "zeroize")]
119        self.secret_key.zeroize();
120    }
121}
122
123#[cfg(feature = "alloc")]
124impl EncodePrivateKey for KeypairBytes {
125    fn to_pkcs8_der(&self) -> Result<SecretDocument> {
126        // Serialize private key as nested OCTET STRING
127        let mut private_key = [0u8; 2 + (Self::BYTE_SIZE / 2)];
128        private_key[0] = 0x04;
129        private_key[1] = 0x39;
130        private_key[2..].copy_from_slice(&self.secret_key);
131
132        let private_key_info = PrivateKeyInfoRef {
133            algorithm: ALGORITHM_ID,
134            private_key: OctetStringRef::new(&private_key)?,
135            public_key: self
136                .public_key
137                .as_ref()
138                .map(|pk| BitStringRef::new(0, &pk.0))
139                .transpose()?,
140        };
141        let result = SecretDocument::encode_msg(&private_key_info)?;
142
143        #[cfg(feature = "zeroize")]
144        private_key.zeroize();
145
146        Ok(result)
147    }
148}
149
150impl TryFrom<PrivateKeyInfoRef<'_>> for KeypairBytes {
151    type Error = Error;
152
153    fn try_from(private_key: PrivateKeyInfoRef<'_>) -> Result<Self> {
154        private_key.algorithm.assert_algorithm_oid(ALGORITHM_OID)?;
155
156        if private_key.algorithm.parameters.is_some() {
157            return Err(Error::ParametersMalformed);
158        }
159
160        // Ed448 PKCS#8 keys are represented as a nested OCTET STRING
161        // (i.e. an OCTET STRING within an OCTET STRING).
162        //
163        // This match statement checks and removes the inner OCTET STRING
164        // header value:
165        //
166        // - 0x04: OCTET STRING tag
167        // - 0x39: 57-byte length
168        let secret_key = match private_key.private_key.as_bytes() {
169            [0x04, 0x39, rest @ ..] => rest.try_into().map_err(|_| KeyError::Invalid),
170            _ => Err(KeyError::Invalid),
171        }?;
172
173        let public_key = private_key
174            .public_key
175            .and_then(|bs| bs.as_bytes())
176            .map(|bytes| bytes.try_into().map_err(|_| KeyError::Invalid))
177            .transpose()?
178            .map(PublicKeyBytes);
179
180        Ok(Self {
181            secret_key,
182            public_key,
183        })
184    }
185}
186
187impl fmt::Debug for KeypairBytes {
188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189        f.debug_struct("KeypairBytes")
190            .field("public_key", &self.public_key)
191            .finish_non_exhaustive()
192    }
193}
194
195/// Ed448 public key serialized as bytes.
196///
197/// This type is primarily useful for decoding/encoding SPKI public key
198/// files (either DER or PEM) encoded using the following traits:
199///
200/// - [`DecodePublicKey`]: decode DER or PEM encoded PKCS#8 private key.
201/// - [`EncodePublicKey`]: encode DER or PEM encoded PKCS#8 private key.
202///
203/// SPKI public key files encoded with PEM begin with:
204///
205/// ```text
206/// -----BEGIN PUBLIC KEY-----
207/// ```
208///
209/// Note that this type operates on raw bytes and performs no validation that
210/// public keys represent valid compressed Ed448 y-coordinates.
211#[derive(Clone, Copy, Eq, Hash, PartialEq)]
212pub struct PublicKeyBytes(pub [u8; Self::BYTE_SIZE]);
213
214impl PublicKeyBytes {
215    /// Size of an Ed448 public key when serialized as bytes.
216    const BYTE_SIZE: usize = 57;
217
218    /// Returns the raw bytes of the public key.
219    #[must_use]
220    pub fn to_bytes(&self) -> [u8; Self::BYTE_SIZE] {
221        self.0
222    }
223}
224
225impl AsRef<[u8; Self::BYTE_SIZE]> for PublicKeyBytes {
226    fn as_ref(&self) -> &[u8; Self::BYTE_SIZE] {
227        &self.0
228    }
229}
230
231#[cfg(feature = "alloc")]
232impl EncodePublicKey for PublicKeyBytes {
233    fn to_public_key_der(&self) -> spki::Result<Document> {
234        pkcs8::SubjectPublicKeyInfoRef {
235            algorithm: ALGORITHM_ID,
236            subject_public_key: BitStringRef::new(0, &self.0)?,
237        }
238        .try_into()
239    }
240}
241
242impl TryFrom<spki::SubjectPublicKeyInfoRef<'_>> for PublicKeyBytes {
243    type Error = spki::Error;
244
245    fn try_from(spki: spki::SubjectPublicKeyInfoRef<'_>) -> spki::Result<Self> {
246        spki.algorithm.assert_algorithm_oid(ALGORITHM_OID)?;
247
248        if spki.algorithm.parameters.is_some() {
249            return Err(spki::Error::KeyMalformed);
250        }
251
252        spki.subject_public_key
253            .as_bytes()
254            .ok_or(spki::Error::KeyMalformed)?
255            .try_into()
256            .map(Self)
257            .map_err(|_| spki::Error::KeyMalformed)
258    }
259}
260
261impl fmt::Debug for PublicKeyBytes {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        f.write_str("PublicKeyBytes(")?;
264
265        for &byte in self.as_ref() {
266            write!(f, "{byte:02X}")?;
267        }
268
269        f.write_str(")")
270    }
271}
272
273#[cfg(feature = "pem")]
274#[cfg(test)]
275mod tests {
276    use super::{KeypairBytes, PublicKeyBytes};
277    use hex_literal::hex;
278
279    const SECRET_KEY_BYTES: [u8; 57] = hex!(
280        "8A57471AA375074DC7D75EA2252E9933BB15C107E4F9A2F9CFEA6C418BEBB0774D1ABB671B58B96EFF95F35D63F2418422A59C7EAE3E00D70F"
281    );
282
283    const PUBLIC_KEY_BYTES: [u8; 57] = hex!(
284        "f27f9809412035541b681c69fbe69b9d25a6af506d914ecef7d973fca04ccd33a8b96a0868211382ca08fe06b72e8c0cb3297f3a9d6bc02380"
285    );
286
287    #[test]
288    fn to_bytes() {
289        let valid_keypair = KeypairBytes {
290            secret_key: SECRET_KEY_BYTES,
291            public_key: Some(PublicKeyBytes(PUBLIC_KEY_BYTES)),
292        };
293
294        assert_eq!(
295            valid_keypair.to_bytes().expect("to_bytes"),
296            hex!(
297                "8A57471AA375074DC7D75EA2252E9933BB15C107E4F9A2F9CFEA6C418BEBB0774D1ABB671B58B96EFF95F35D63F2418422A59C7EAE3E00D70Ff27f9809412035541b681c69fbe69b9d25a6af506d914ecef7d973fca04ccd33a8b96a0868211382ca08fe06b72e8c0cb3297f3a9d6bc02380"
298            )
299        );
300
301        let invalid_keypair = KeypairBytes {
302            secret_key: SECRET_KEY_BYTES,
303            public_key: None,
304        };
305
306        assert_eq!(invalid_keypair.to_bytes(), None);
307    }
308}