Skip to main content

crypto/encoding/
pkcs8.rs

1use super::der::{DerError, Reader};
2use crate::{
3    curve25519::ed25519,
4    p256::{PUBLIC_KEY_UNCOMPRESSED_SIZE, SECRET_KEY_SIZE, SecretKey},
5    p384,
6};
7
8const PKCS8_DER_LEN: usize = 138;
9
10const EC_PUBLIC_KEY_OID: &[u8] = &[0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01];
11const SECP256R1_OID: &[u8] = &[0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07];
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum Pkcs8Error {
15    InvalidLength,
16    InvalidSequence,
17    InvalidVersion,
18    InvalidAlgorithmIdentifier,
19    InvalidOctetString,
20    InvalidEcSecretKey,
21    InvalidEcVersion,
22    InvalidSecretKeyOctet,
23    InvalidPublicKeyExplicit,
24    InvalidPublicKeyBitString,
25    InvalidPublicKeyPrefix,
26    InvalidPublicKeyMismatch,
27    KeyDerivationFailed,
28    InvalidDer(DerError),
29    UnsupportedAlgorithm,
30    UnsupportedCurve,
31    InvalidEd25519PrivateKey,
32    InvalidMlDsaPrivateKey,
33    MlDsaExpandedKeyNotSupported,
34}
35
36impl From<DerError> for Pkcs8Error {
37    fn from(err: DerError) -> Self {
38        Pkcs8Error::InvalidDer(err)
39    }
40}
41
42#[cfg(feature = "alloc")]
43impl core::fmt::Display for Pkcs8Error {
44    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
45        match self {
46            Pkcs8Error::InvalidLength => write!(f, "invalid DER length"),
47            Pkcs8Error::InvalidSequence => write!(f, "invalid outer SEQUENCE"),
48            Pkcs8Error::InvalidVersion => write!(f, "invalid version"),
49            Pkcs8Error::InvalidAlgorithmIdentifier => write!(f, "invalid AlgorithmIdentifier"),
50            Pkcs8Error::InvalidOctetString => write!(f, "invalid OCTET STRING wrapping"),
51            Pkcs8Error::InvalidEcSecretKey => write!(f, "invalid ECSecretKey SEQUENCE"),
52            Pkcs8Error::InvalidEcVersion => write!(f, "invalid EC version"),
53            Pkcs8Error::InvalidSecretKeyOctet => write!(f, "invalid private key OCTET STRING"),
54            Pkcs8Error::InvalidPublicKeyExplicit => write!(f, "invalid public key [1] EXPLICIT"),
55            Pkcs8Error::InvalidPublicKeyBitString => write!(f, "invalid public key BIT STRING"),
56            Pkcs8Error::InvalidPublicKeyPrefix => write!(f, "invalid public key prefix"),
57            Pkcs8Error::InvalidPublicKeyMismatch => write!(f, "embedded public key does not match private key"),
58            Pkcs8Error::KeyDerivationFailed => write!(f, "key derivation failed"),
59            Pkcs8Error::InvalidDer(err) => write!(f, "invalid DER: {err}"),
60            Pkcs8Error::UnsupportedAlgorithm => write!(f, "unsupported private key algorithm"),
61            Pkcs8Error::UnsupportedCurve => write!(f, "unsupported or missing named curve"),
62            Pkcs8Error::InvalidEd25519PrivateKey => write!(f, "invalid Ed25519 private key"),
63            Pkcs8Error::InvalidMlDsaPrivateKey => write!(f, "invalid ML-DSA private key"),
64            Pkcs8Error::MlDsaExpandedKeyNotSupported => write!(
65                f,
66                "ML-DSA private keys stored in expanded form cannot be loaded; \
67                 provide the key in seed form (RFC 9881)"
68            ),
69        }
70    }
71}
72
73fn validate_fixed_prefix(der: &[u8]) -> Result<(), Pkcs8Error> {
74    if der.len() != PKCS8_DER_LEN {
75        return Err(Pkcs8Error::InvalidLength);
76    }
77
78    // Outer SEQUENCE: 30 81 87
79    if der[0] != 0x30 || der[1] != 0x81 || der[2] != 0x87 {
80        return Err(Pkcs8Error::InvalidSequence);
81    }
82    // INTEGER version=0: 02 01 00
83    if der[3] != 0x02 || der[4] != 0x01 || der[5] != 0x00 {
84        return Err(Pkcs8Error::InvalidVersion);
85    }
86    // AlgorithmIdentifier SEQUENCE: 30 13
87    if der[6] != 0x30 || der[7] != 0x13 {
88        return Err(Pkcs8Error::InvalidAlgorithmIdentifier);
89    }
90    // ecPublicKey OID: 06 07 <7 bytes>
91    if der[8] != 0x06 || der[9] != 0x07 || der[10..17] != *EC_PUBLIC_KEY_OID {
92        return Err(Pkcs8Error::InvalidAlgorithmIdentifier);
93    }
94    // secp256r1 OID: 06 08 <8 bytes>
95    if der[17] != 0x06 || der[18] != 0x08 || der[19..27] != *SECP256R1_OID {
96        return Err(Pkcs8Error::InvalidAlgorithmIdentifier);
97    }
98    // OCTET STRING: 04 6d (109 bytes)
99    if der[27] != 0x04 || der[28] != 0x6d {
100        return Err(Pkcs8Error::InvalidOctetString);
101    }
102    // ECSecretKey SEQUENCE: 30 6b
103    if der[29] != 0x30 || der[30] != 0x6b {
104        return Err(Pkcs8Error::InvalidEcSecretKey);
105    }
106    // EC version: 02 01 01
107    if der[31] != 0x02 || der[32] != 0x01 || der[33] != 0x01 {
108        return Err(Pkcs8Error::InvalidEcVersion);
109    }
110    // private key OCTET STRING: 04 20
111    if der[34] != 0x04 || der[35] != 0x20 {
112        return Err(Pkcs8Error::InvalidSecretKeyOctet);
113    }
114    // [1] EXPLICIT: a1 44
115    if der[68] != 0xa1 || der[69] != 0x44 {
116        return Err(Pkcs8Error::InvalidPublicKeyExplicit);
117    }
118    // BIT STRING: 03 42 00
119    if der[70] != 0x03 || der[71] != 0x42 || der[72] != 0x00 {
120        return Err(Pkcs8Error::InvalidPublicKeyBitString);
121    }
122    // public key must start with 0x04 (uncompressed)
123    if der[73] != 0x04 {
124        return Err(Pkcs8Error::InvalidPublicKeyPrefix);
125    }
126
127    Ok(())
128}
129
130static TEMPLATE: [u8; PKCS8_DER_LEN] = [
131    // SecretKeyInfo SEQUENCE (135 bytes content)
132    0x30, 0x81, 0x87, // version INTEGER 0
133    0x02, 0x01, 0x00, // AlgorithmIdentifier SEQUENCE (19 bytes)
134    0x30, 0x13, // ecPublicKey OID (1.2.840.10045.2.1)
135    0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // secp256r1 OID (1.2.840.10045.3.1.7)
136    0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07,
137    // OCTET STRING wrapping ECSecretKey (109 bytes)
138    0x04, 0x6d, // ECSecretKey SEQUENCE (107 bytes)
139    0x30, 0x6b, // EC version INTEGER 1
140    0x02, 0x01, 0x01, // private key OCTET STRING (32 bytes) -- placeholder zeros
141    0x04, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
142    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
143    // [1] EXPLICIT public key wrapper (68 bytes)
144    0xa1, 0x44, // BIT STRING with unused_bits=0, length=66 (65 bytes data + 1 byte unused_bits)
145    0x03, 0x42, 0x00,
146    // uncompressed public key (65 bytes = 0x04 || 32-byte X || 32-byte Y) -- placeholder zeros
147    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
148    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
149    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
150    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
151];
152
153const PRIVATE_KEY_OFFSET: usize = 36;
154const PUBLIC_KEY_OFFSET: usize = 73;
155
156/// Encodes a P-256 secret key as PKCS#8 DER (the fixed 138-byte
157/// `PrivateKeyInfo` template used by OpenSSL for `secp256r1`).
158///
159/// The embedded public key is derived from `key`, so the output always
160/// contains a consistent private/public key pair.
161pub fn encode_p256_pkcs8_der(key: &SecretKey) -> Result<[u8; PKCS8_DER_LEN], Pkcs8Error> {
162    let public_key = key.public_key();
163    let pub_bytes = public_key.to_bytes();
164    let priv_bytes = key.to_bytes();
165
166    let mut der = TEMPLATE;
167    der[PUBLIC_KEY_OFFSET..PUBLIC_KEY_OFFSET + PUBLIC_KEY_UNCOMPRESSED_SIZE].copy_from_slice(&pub_bytes);
168    der[PRIVATE_KEY_OFFSET..PRIVATE_KEY_OFFSET + SECRET_KEY_SIZE].copy_from_slice(&priv_bytes);
169
170    Ok(der)
171}
172
173/// Decodes a P-256 secret key from PKCS#8 DER in the fixed 138-byte
174/// `PrivateKeyInfo` template produced by [`encode_p256_pkcs8_der`].
175///
176/// The embedded public key is checked against the one derived from the
177/// private scalar.
178///
179/// Returns [`Pkcs8Error`] when the input is not exactly the expected
180/// template, when the private scalar is invalid, or when the embedded
181/// public key does not correspond to the private key
182/// ([`Pkcs8Error::InvalidPublicKeyMismatch`]).
183pub fn decode_p256_pkcs8_der(der: &[u8]) -> Result<SecretKey, Pkcs8Error> {
184    validate_fixed_prefix(der)?;
185
186    let mut private_key = [0u8; SECRET_KEY_SIZE];
187    private_key.copy_from_slice(&der[PRIVATE_KEY_OFFSET..PRIVATE_KEY_OFFSET + SECRET_KEY_SIZE]);
188
189    let mut public_key = [0u8; PUBLIC_KEY_UNCOMPRESSED_SIZE];
190    public_key.copy_from_slice(&der[PUBLIC_KEY_OFFSET..PUBLIC_KEY_OFFSET + PUBLIC_KEY_UNCOMPRESSED_SIZE]);
191
192    let key = SecretKey::from_bytes(&private_key).map_err(|_| Pkcs8Error::KeyDerivationFailed)?;
193
194    if key.public_key().to_bytes() != public_key {
195        return Err(Pkcs8Error::InvalidPublicKeyMismatch);
196    }
197
198    Ok(key)
199}
200
201////////////////////////////////////////////////////////////////////////////////////////////////////
202// Generic EC and Ed25519 private key parsing
203////////////////////////////////////////////////////////////////////////////////////////////////////
204
205/// Object identifier for `id-ecPublicKey` (1.2.840.10045.2.1).
206pub const OID_EC_PUBLIC_KEY: &[u8] = &[0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01];
207/// Object identifier for `secp256r1` / NIST P-256 (1.2.840.10045.3.1.7).
208pub const OID_SECP256R1: &[u8] = &[0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07];
209/// Object identifier for `secp384r1` / NIST P-384 (1.3.132.0.34).
210pub const OID_SECP384R1: &[u8] = &[0x2b, 0x81, 0x04, 0x00, 0x22];
211/// Object identifier for Ed25519 (1.3.101.112).
212pub const OID_ED25519: &[u8] = &[0x2b, 0x65, 0x70];
213/// Object identifier for `rsaEncryption` (1.2.840.113549.1.1.1).
214pub const OID_RSA_ENCRYPTION: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01];
215
216/// A supported elliptic-curve private key parsed from DER.
217pub enum EcPrivateKey {
218    /// NIST P-256 (secp256r1).
219    P256(crate::p256::SecretKey),
220    /// NIST P-384 (secp384r1).
221    P384(crate::p384::SecretKey),
222}
223
224#[derive(Clone, Copy, PartialEq, Eq)]
225enum Curve {
226    P256,
227    P384,
228}
229
230fn curve_from_oid(oid: &[u8]) -> Result<Curve, Pkcs8Error> {
231    if oid == OID_SECP256R1 {
232        Ok(Curve::P256)
233    } else if oid == OID_SECP384R1 {
234        Ok(Curve::P384)
235    } else {
236        Err(Pkcs8Error::UnsupportedCurve)
237    }
238}
239
240fn build_ec_key(curve: Curve, scalar: &[u8]) -> Result<EcPrivateKey, Pkcs8Error> {
241    match curve {
242        Curve::P256 => {
243            let scalar: [u8; crate::p256::SECRET_KEY_SIZE] =
244                scalar.try_into().map_err(|_| Pkcs8Error::InvalidEcSecretKey)?;
245            let key = crate::p256::SecretKey::from_bytes(&scalar).map_err(|_| Pkcs8Error::KeyDerivationFailed)?;
246            Ok(EcPrivateKey::P256(key))
247        }
248        Curve::P384 => {
249            let scalar: [u8; crate::p384::SECRET_KEY_SIZE] =
250                scalar.try_into().map_err(|_| Pkcs8Error::InvalidEcSecretKey)?;
251            let key = crate::p384::SecretKey::from_bytes(&scalar).map_err(|_| Pkcs8Error::KeyDerivationFailed)?;
252            Ok(EcPrivateKey::P384(key))
253        }
254    }
255}
256
257/// Parses an RFC 5915 `ECPrivateKey`, returning the named curve (when the
258/// optional `[0] parameters` field is present) and the private scalar.
259fn parse_sec1(der: &[u8]) -> Result<(Option<Curve>, &[u8]), Pkcs8Error> {
260    let mut outer = Reader::new(der);
261    let mut seq = outer.read_sequence()?;
262    outer.finish()?;
263
264    let version = seq.read_integer()?;
265    if version != [0x01] {
266        return Err(Pkcs8Error::InvalidEcVersion);
267    }
268    let scalar = seq.read_octet_string()?;
269
270    let mut curve = None;
271    while !seq.is_empty() {
272        match seq.peek_tag()? {
273            0xa0 => {
274                let mut params = seq.read_explicit(0xa0)?;
275                let oid = params.read_oid()?;
276                params.finish()?;
277                let parsed = curve_from_oid(oid)?;
278                if let Some(previous) = curve {
279                    if previous != parsed {
280                        return Err(Pkcs8Error::InvalidAlgorithmIdentifier);
281                    }
282                }
283                curve = Some(parsed);
284            }
285            0xa1 => {
286                let mut public = seq.read_explicit(0xa1)?;
287                let _ = public.read_bit_string()?;
288                public.finish()?;
289            }
290            _ => return Err(Pkcs8Error::InvalidEcSecretKey),
291        }
292    }
293
294    Ok((curve, scalar))
295}
296
297/// Decodes a SEC1 / RFC 5915 `ECPrivateKey` DER into a P-256 or P-384 key.
298///
299/// The named curve must be present in the `[0] parameters` field; the optional
300/// embedded public key is ignored.
301///
302/// Returns [`Pkcs8Error`] when the structure is malformed, the curve is not
303/// P-256 or P-384, or the scalar is not a valid private key.
304pub fn decode_ec_sec1_der(der: &[u8]) -> Result<EcPrivateKey, Pkcs8Error> {
305    let (curve, scalar) = parse_sec1(der)?;
306    let curve = curve.ok_or(Pkcs8Error::UnsupportedCurve)?;
307    build_ec_key(curve, scalar)
308}
309
310/// Decodes a PKCS#8 `PrivateKeyInfo` DER containing a P-256 or P-384 key.
311///
312/// The curve is taken from the `AlgorithmIdentifier`; when the inner SEC1
313/// structure also carries a curve, both must agree.
314///
315/// Returns [`Pkcs8Error`] when the structure is malformed, the algorithm is
316/// not `id-ecPublicKey`, the curve is not P-256 or P-384, or the scalar is
317/// not a valid private key.
318pub fn decode_ec_pkcs8_der(der: &[u8]) -> Result<EcPrivateKey, Pkcs8Error> {
319    let mut outer = Reader::new(der);
320    let mut seq = outer.read_sequence()?;
321    outer.finish()?;
322
323    let version = seq.read_integer()?;
324    if version != [0x00] {
325        return Err(Pkcs8Error::InvalidVersion);
326    }
327
328    let mut algorithm = seq.read_sequence()?;
329    let algorithm_oid = algorithm.read_oid()?;
330    if algorithm_oid != OID_EC_PUBLIC_KEY {
331        return Err(Pkcs8Error::UnsupportedAlgorithm);
332    }
333    let curve_oid = algorithm.read_oid()?;
334    algorithm.finish()?;
335    let outer_curve = curve_from_oid(curve_oid)?;
336
337    let private_key = seq.read_octet_string()?;
338
339    // Skip the optional attributes field.
340    while !seq.is_empty() {
341        match seq.peek_tag()? {
342            0xa0 => {
343                let _attributes = seq.read_explicit(0xa0)?;
344            }
345            _ => return Err(Pkcs8Error::InvalidDer(DerError::UnexpectedTag)),
346        }
347    }
348
349    let (inner_curve, scalar) = parse_sec1(private_key)?;
350    if let Some(inner_curve) = inner_curve {
351        if inner_curve != outer_curve {
352            return Err(Pkcs8Error::InvalidAlgorithmIdentifier);
353        }
354    }
355    build_ec_key(outer_curve, scalar)
356}
357
358/// Decodes a PKCS#8 `PrivateKeyInfo` DER containing an Ed25519 key.
359///
360/// Returns the key's 32-byte seed via [`ed25519::SecretKey`].
361///
362/// Both the RFC 8410 nested `OCTET STRING` form and the bare 32-byte form are
363/// accepted. An optional `[1]` public key field is ignored.
364///
365/// Returns [`Pkcs8Error`] when the structure is malformed, the algorithm is
366/// not Ed25519, or the seed has the wrong length.
367pub fn decode_ed25519_pkcs8_der(der: &[u8]) -> Result<ed25519::SecretKey, Pkcs8Error> {
368    let mut outer = Reader::new(der);
369    let mut seq = outer.read_sequence()?;
370    outer.finish()?;
371
372    let version = seq.read_integer()?;
373    if version != [0x00] && version != [0x01] {
374        return Err(Pkcs8Error::InvalidVersion);
375    }
376
377    let mut algorithm = seq.read_sequence()?;
378    let algorithm_oid = algorithm.read_oid()?;
379    if algorithm_oid != OID_ED25519 {
380        return Err(Pkcs8Error::UnsupportedAlgorithm);
381    }
382    // Ed25519 AlgorithmIdentifier has no parameters.
383    algorithm.finish()?;
384
385    let private_key = seq.read_octet_string()?;
386
387    while !seq.is_empty() {
388        match seq.peek_tag()? {
389            0xa0 => {
390                let _attributes = seq.read_explicit(0xa0)?;
391            }
392            0xa1 => {
393                let mut public = seq.read_explicit(0xa1)?;
394                let _ = public.read_bit_string()?;
395                public.finish()?;
396            }
397            _ => return Err(Pkcs8Error::InvalidDer(DerError::UnexpectedTag)),
398        }
399    }
400
401    let seed = if private_key.len() == ed25519::SECRET_KEY_SIZE {
402        private_key
403    } else {
404        let mut nested = Reader::new(private_key);
405        let seed = nested.read_octet_string()?;
406        nested.finish()?;
407        seed
408    };
409
410    let seed: [u8; ed25519::SECRET_KEY_SIZE] = seed.try_into().map_err(|_| Pkcs8Error::InvalidEd25519PrivateKey)?;
411    Ok(ed25519::SecretKey::from_bytes(&seed))
412}
413
414/// Reports whether `der` is a PKCS#8 `PrivateKeyInfo` whose
415/// `AlgorithmIdentifier` is `rsaEncryption` (1.2.840.113549.1.1.1).
416///
417/// This crate does not implement RSA private keys. The predicate lets callers
418/// recognise an RSA key and report a clear "unsupported" error rather than a
419/// generic parse failure. It only inspects the algorithm identifier, so it
420/// returns `false` for malformed input.
421pub fn is_rsa_pkcs8_der(der: &[u8]) -> bool {
422    fn inner(der: &[u8]) -> Result<bool, Pkcs8Error> {
423        let mut outer = Reader::new(der);
424        let mut seq = outer.read_sequence()?;
425        outer.finish()?;
426
427        let _version = seq.read_integer()?;
428        let mut algorithm = seq.read_sequence()?;
429        // RSA's AlgorithmIdentifier carries NULL parameters, so only the OID
430        // needs to be read; the rest of the structure is not inspected.
431        let algorithm_oid = algorithm.read_oid()?;
432
433        Ok(algorithm_oid == OID_RSA_ENCRYPTION)
434    }
435
436    inner(der).unwrap_or(false)
437}
438
439////////////////////////////////////////////////////////////////////////////////////////////////////
440// ML-DSA private keys (RFC 9881)
441////////////////////////////////////////////////////////////////////////////////////////////////////
442
443/// Object identifier for `id-ml-dsa-44` (2.16.840.1.101.3.4.3.17).
444pub const OID_ML_DSA_44: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11];
445/// Object identifier for `id-ml-dsa-65` (2.16.840.1.101.3.4.3.18).
446pub const OID_ML_DSA_65: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x12];
447/// Object identifier for `id-ml-dsa-87` (2.16.840.1.101.3.4.3.19).
448pub const OID_ML_DSA_87: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x13];
449
450/// A supported ML-DSA private key parsed from DER.
451///
452/// Only the seed is retained: the expanded key material is recomputed from it,
453/// so keys stored purely in `expandedKey` form cannot be represented.
454pub enum MlDsaPrivateKey {
455    /// ML-DSA-44.
456    Dsa44(crate::mldsa::MlDsa44SecretKey),
457    /// ML-DSA-65.
458    Dsa65(crate::mldsa::MlDsa65SecretKey),
459    /// ML-DSA-87.
460    Dsa87(crate::mldsa::MlDsa87SecretKey),
461}
462
463/// Extracts the 32-byte seed from the RFC 9881 `ML-DSA-XX-PrivateKey` CHOICE.
464///
465/// The choice is dispatched by tag, as required by RFC 9881 section 6:
466/// `[0]` (`0x80`) is the seed, `OCTET STRING` (`0x04`) is the expanded key,
467/// and `SEQUENCE` (`0x30`) is the `both` form (seed followed by the expanded
468/// key). An explicit `[0]` (`0xa0`) wrapping is also accepted.
469fn parse_mldsa_private_key_seed(der: &[u8]) -> Result<[u8; 32], Pkcs8Error> {
470    let mut reader = Reader::new(der);
471    match reader.peek_tag()? {
472        // seed [0] OCTET STRING, implicitly tagged.
473        0x80 => {
474            let seed = reader.read_expected(0x80)?;
475            reader.finish()?;
476            seed.try_into().map_err(|_| Pkcs8Error::InvalidMlDsaPrivateKey)
477        }
478        // seed [0] OCTET STRING, explicitly tagged (lenient interoperability).
479        0xa0 => {
480            let mut explicit = reader.read_explicit(0xa0)?;
481            reader.finish()?;
482            let seed = explicit.read_octet_string()?;
483            explicit.finish()?;
484            seed.try_into().map_err(|_| Pkcs8Error::InvalidMlDsaPrivateKey)
485        }
486        // expandedKey only: the seed is not recoverable.
487        0x04 => Err(Pkcs8Error::MlDsaExpandedKeyNotSupported),
488        // both: SEQUENCE { seed OCTET STRING (32), expandedKey OCTET STRING }.
489        0x30 => {
490            let mut both = reader.read_sequence()?;
491            reader.finish()?;
492            let seed = both.read_octet_string()?;
493            let _expanded = both.read_octet_string()?;
494            both.finish()?;
495            seed.try_into().map_err(|_| Pkcs8Error::InvalidMlDsaPrivateKey)
496        }
497        _ => Err(Pkcs8Error::InvalidMlDsaPrivateKey),
498    }
499}
500
501/// Decodes a PKCS#8 `PrivateKeyInfo` DER containing an ML-DSA private key.
502///
503/// Supports ML-DSA-44, ML-DSA-65 and ML-DSA-87 in the seed and `both` forms
504/// defined by RFC 9881. If the optional `[1]` public key is present it is
505/// checked against the key derived from the seed. When the `both` form is
506/// used, the seed is retained and the embedded expanded key is not compared
507/// against it (RFC 9881's seed-consistency check is optional).
508///
509/// Returns [`Pkcs8Error::MlDsaExpandedKeyNotSupported`] when the key is stored
510/// purely in expanded form (the seed cannot be recovered), and other
511/// [`Pkcs8Error`] variants when the structure is malformed or the algorithm is
512/// not ML-DSA.
513pub fn decode_mldsa_pkcs8_der(der: &[u8]) -> Result<MlDsaPrivateKey, Pkcs8Error> {
514    let mut outer = Reader::new(der);
515    let mut seq = outer.read_sequence()?;
516    outer.finish()?;
517
518    let version = seq.read_integer()?;
519    if version != [0x00] && version != [0x01] {
520        return Err(Pkcs8Error::InvalidVersion);
521    }
522
523    let mut algorithm = seq.read_sequence()?;
524    let oid = algorithm.read_oid()?;
525    // ML-DSA AlgorithmIdentifiers carry no parameters.
526    algorithm.finish()?;
527
528    let private_key = seq.read_octet_string()?;
529
530    let mut public_key = None;
531    while !seq.is_empty() {
532        match seq.peek_tag()? {
533            0xa0 => {
534                let _attributes = seq.read_explicit(0xa0)?;
535            }
536            0xa1 => {
537                let mut public = seq.read_explicit(0xa1)?;
538                public_key = Some(public.read_bit_string()?);
539                public.finish()?;
540            }
541            _ => return Err(Pkcs8Error::InvalidDer(DerError::UnexpectedTag)),
542        }
543    }
544
545    let seed = parse_mldsa_private_key_seed(private_key)?;
546
547    match oid {
548        OID_ML_DSA_44 => {
549            let key = crate::mldsa::MlDsa44SecretKey::new(&seed);
550            check_mldsa_public_key(public_key, &key.public_key().to_bytes())?;
551            Ok(MlDsaPrivateKey::Dsa44(key))
552        }
553        OID_ML_DSA_65 => {
554            let key = crate::mldsa::MlDsa65SecretKey::new(&seed);
555            check_mldsa_public_key(public_key, &key.public_key().to_bytes())?;
556            Ok(MlDsaPrivateKey::Dsa65(key))
557        }
558        OID_ML_DSA_87 => {
559            let key = crate::mldsa::MlDsa87SecretKey::new(&seed);
560            check_mldsa_public_key(public_key, &key.public_key().to_bytes())?;
561            Ok(MlDsaPrivateKey::Dsa87(key))
562        }
563        _ => Err(Pkcs8Error::UnsupportedAlgorithm),
564    }
565}
566
567fn check_mldsa_public_key(public_key: Option<&[u8]>, derived: &[u8]) -> Result<(), Pkcs8Error> {
568    match public_key {
569        Some(public_key) if public_key != derived => Err(Pkcs8Error::InvalidPublicKeyMismatch),
570        _ => Ok(()),
571    }
572}
573
574/// Encodes an ML-DSA-44 secret key as PKCS#8 DER in the RFC 9881 seed format.
575pub fn encode_mldsa44_pkcs8_der(key: &crate::mldsa::MlDsa44SecretKey) -> [u8; ML_DSA_SEED_PKCS8_LEN] {
576    encode_mldsa_seed_pkcs8(OID_ML_DSA_44, key.seed())
577}
578
579/// Encodes an ML-DSA-65 secret key as PKCS#8 DER in the RFC 9881 seed format.
580pub fn encode_mldsa65_pkcs8_der(key: &crate::mldsa::MlDsa65SecretKey) -> [u8; ML_DSA_SEED_PKCS8_LEN] {
581    encode_mldsa_seed_pkcs8(OID_ML_DSA_65, key.seed())
582}
583
584/// Encodes an ML-DSA-87 secret key as PKCS#8 DER in the RFC 9881 seed format.
585pub fn encode_mldsa87_pkcs8_der(key: &crate::mldsa::MlDsa87SecretKey) -> [u8; ML_DSA_SEED_PKCS8_LEN] {
586    encode_mldsa_seed_pkcs8(OID_ML_DSA_87, key.seed())
587}
588
589const ML_DSA_SEED_PKCS8_LEN: usize = 54;
590
591fn encode_mldsa_seed_pkcs8(oid: &[u8], seed: &[u8; 32]) -> [u8; ML_DSA_SEED_PKCS8_LEN] {
592    debug_assert_eq!(oid.len(), 9);
593    let mut der = [0u8; ML_DSA_SEED_PKCS8_LEN];
594    der[0] = 0x30;
595    der[1] = 0x34;
596    der[2] = 0x02;
597    der[3] = 0x01;
598    der[4] = 0x00;
599    der[5] = 0x30;
600    der[6] = 0x0b;
601    der[7] = 0x06;
602    der[8] = 0x09;
603    der[9..18].copy_from_slice(oid);
604    der[18] = 0x04;
605    der[19] = 0x22;
606    der[20] = 0x80;
607    der[21] = 0x20;
608    der[22..54].copy_from_slice(seed);
609    der
610}
611
612const PKCS8_P384_DER_LEN: usize = 185;
613const PKCS8_ED25519_DER_LEN: usize = 48;
614
615static P384_TEMPLATE: [u8; PKCS8_P384_DER_LEN] = [
616    // PrivateKeyInfo SEQUENCE (182 bytes content)
617    0x30, 0x81, 0xb6, // version INTEGER 0
618    0x02, 0x01, 0x00, // AlgorithmIdentifier SEQUENCE (16 bytes content)
619    0x30, 0x10, // ecPublicKey OID (1.2.840.10045.2.1)
620    0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // secp384r1 OID (1.3.132.0.34)
621    0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, // privateKey OCTET STRING (158 bytes content)
622    0x04, 0x81, 0x9e, // ECPrivateKey SEQUENCE (155 bytes content)
623    0x30, 0x81, 0x9b, // EC version INTEGER 1
624    0x02, 0x01, 0x01, // private key OCTET STRING (48 bytes) -- placeholder zeros
625    0x04, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
626    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
627    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
628    // [1] EXPLICIT public key wrapper
629    0xa1, 0x64, // BIT STRING with unused_bits=0, length=98 (97 bytes data + 1 byte unused_bits)
630    0x03, 0x62, 0x00, // uncompressed public key (97 bytes = 0x04 || 48-byte X || 48-byte Y) -- placeholder zeros
631    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
632    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
633    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
634    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
635    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
636    0x00, 0x00,
637];
638
639static ED25519_TEMPLATE: [u8; PKCS8_ED25519_DER_LEN] = [
640    // PrivateKeyInfo SEQUENCE (44 bytes content)
641    0x30, 0x2e, // version INTEGER 0
642    0x02, 0x01, 0x00, // AlgorithmIdentifier SEQUENCE (5 bytes content)
643    0x30, 0x05, // id-Ed25519 OID (1.3.101.112)
644    0x06, 0x03, 0x2b, 0x65, 0x70, // privateKey OCTET STRING (34 bytes content)
645    0x04, 0x22, // CurvePrivateKey OCTET STRING (32 bytes) -- placeholder zeros
646    0x04, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
647    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
648];
649
650const P384_PRIVATE_KEY_OFFSET: usize = 35;
651const P384_PUBLIC_KEY_OFFSET: usize = 88;
652const ED25519_SEED_OFFSET: usize = 16;
653
654/// Encodes a P-384 secret key as PKCS#8 DER in the fixed 185-byte
655/// `PrivateKeyInfo` form used by OpenSSL for `secp384r1`.
656///
657/// The embedded public key is derived from `key`, so the output always
658/// contains a consistent private/public key pair.
659pub fn encode_p384_pkcs8_der(key: &p384::SecretKey) -> [u8; PKCS8_P384_DER_LEN] {
660    let mut der = P384_TEMPLATE;
661    der[P384_PRIVATE_KEY_OFFSET..P384_PRIVATE_KEY_OFFSET + p384::SECRET_KEY_SIZE].copy_from_slice(&key.to_bytes());
662    der[P384_PUBLIC_KEY_OFFSET..P384_PUBLIC_KEY_OFFSET + p384::PUBLIC_KEY_UNCOMPRESSED_SIZE]
663        .copy_from_slice(&key.public_key().to_bytes());
664    der
665}
666
667/// Encodes an Ed25519 secret key as PKCS#8 DER in the fixed 48-byte
668/// `PrivateKeyInfo` form defined by RFC 8410.
669pub fn encode_ed25519_pkcs8_der(key: &ed25519::SecretKey) -> [u8; PKCS8_ED25519_DER_LEN] {
670    let mut der = ED25519_TEMPLATE;
671    der[ED25519_SEED_OFFSET..ED25519_SEED_OFFSET + ed25519::SECRET_KEY_SIZE].copy_from_slice(&key.to_bytes());
672    der
673}
674
675#[cfg(test)]
676mod tests {
677    use super::*;
678
679    // Known test vector from acme crate:
680    // Private key: 255582fd0cce4c24b9bedb09a76206f940dcf1c7437dea0ab71499c5ace733e9
681    // Public key:  041e9c23e81a03c54dd3c9ed52cb2ade7a4713e5613d703579c7739e0f132060dcbc4a687d3eb09917d262fc2f23c476c7cbfcecf84f11e458b246ad756d3617c7
682    const TEST_PRIVATE_KEY: [u8; 32] = [
683        0x25, 0x55, 0x82, 0xfd, 0x0c, 0xce, 0x4c, 0x24, 0xb9, 0xbe, 0xdb, 0x09, 0xa7, 0x62, 0x06, 0xf9, 0x40, 0xdc,
684        0xf1, 0xc7, 0x43, 0x7d, 0xea, 0x0a, 0xb7, 0x14, 0x99, 0xc5, 0xac, 0xe7, 0x33, 0xe9,
685    ];
686
687    const TEST_PUBLIC_KEY: [u8; 65] = [
688        0x04, 0x1e, 0x9c, 0x23, 0xe8, 0x1a, 0x03, 0xc5, 0x4d, 0xd3, 0xc9, 0xed, 0x52, 0xcb, 0x2a, 0xde, 0x7a, 0x47,
689        0x13, 0xe5, 0x61, 0x3d, 0x70, 0x35, 0x79, 0xc7, 0x73, 0x9e, 0x0f, 0x13, 0x20, 0x60, 0xdc, 0xbc, 0x4a, 0x68,
690        0x7d, 0x3e, 0xb0, 0x99, 0x17, 0xd2, 0x62, 0xfc, 0x2f, 0x23, 0xc4, 0x76, 0xc7, 0xcb, 0xfc, 0xec, 0xf8, 0x4f,
691        0x11, 0xe4, 0x58, 0xb2, 0x46, 0xad, 0x75, 0x6d, 0x36, 0x17, 0xc7,
692    ];
693
694    fn decode_hex(hex_str: &str) -> Vec<u8> {
695        (0..hex_str.len())
696            .step_by(2)
697            .map(|i| u8::from_str_radix(&hex_str[i..i + 2], 16).unwrap())
698            .collect()
699    }
700
701    const TEST_DER_HEX: &str = "308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b0201010420255582fd0cce4c24b9bedb09a76206f940dcf1c7437dea0ab71499c5ace733e9a144034200041e9c23e81a03c54dd3c9ed52cb2ade7a4713e5613d703579c7739e0f132060dcbc4a687d3eb09917d262fc2f23c476c7cbfcecf84f11e458b246ad756d3617c7";
702
703    #[test]
704    fn encode_round_trip() {
705        let key = SecretKey::from_bytes(&TEST_PRIVATE_KEY).unwrap();
706        let der = encode_p256_pkcs8_der(&key).unwrap();
707        let decoded = decode_p256_pkcs8_der(&der).unwrap();
708        assert_eq!(decoded.to_bytes(), TEST_PRIVATE_KEY);
709        assert_eq!(decoded.public_key().to_bytes(), TEST_PUBLIC_KEY);
710    }
711
712    #[test]
713    fn decode_known_vector() {
714        let der_bytes = decode_hex(TEST_DER_HEX);
715        let key = decode_p256_pkcs8_der(&der_bytes).unwrap();
716        assert_eq!(key.to_bytes(), TEST_PRIVATE_KEY);
717        assert_eq!(key.public_key().to_bytes(), TEST_PUBLIC_KEY);
718    }
719
720    #[test]
721    fn encode_matches_known_der() {
722        let key = SecretKey::from_bytes(&TEST_PRIVATE_KEY).unwrap();
723        let der = encode_p256_pkcs8_der(&key).unwrap();
724        let expected = decode_hex(TEST_DER_HEX);
725        assert_eq!(der.as_slice(), expected.as_slice());
726    }
727
728    #[test]
729    fn detects_rsa_pkcs8() {
730        // A minimal PrivateKeyInfo with the rsaEncryption OID and NULL params.
731        let rsa = decode_hex("3016020100300d06092a864886f70d010101050004023000");
732        assert!(is_rsa_pkcs8_der(&rsa));
733
734        // A P-256 key is not classified as RSA.
735        assert!(!is_rsa_pkcs8_der(&decode_hex(TEST_DER_HEX)));
736
737        // Malformed input is not classified as RSA.
738        assert!(!is_rsa_pkcs8_der(&[0x30, 0x00]));
739    }
740
741    #[test]
742    fn decode_too_short() {
743        assert!(matches!(decode_p256_pkcs8_der(&[0u8; 100]), Err(Pkcs8Error::InvalidLength)));
744    }
745
746    #[test]
747    fn decode_too_long() {
748        assert!(matches!(decode_p256_pkcs8_der(&[0u8; 200]), Err(Pkcs8Error::InvalidLength)));
749    }
750
751    #[test]
752    fn decode_bad_sequence() {
753        let mut der = decode_hex(TEST_DER_HEX);
754        der[0] = 0x31;
755        assert!(matches!(decode_p256_pkcs8_der(&der), Err(Pkcs8Error::InvalidSequence)));
756    }
757
758    #[test]
759    fn decode_bad_version() {
760        let mut der = decode_hex(TEST_DER_HEX);
761        der[5] = 0x01;
762        assert!(matches!(decode_p256_pkcs8_der(&der), Err(Pkcs8Error::InvalidVersion)));
763    }
764
765    #[test]
766    fn decode_bad_algo_identifier() {
767        let mut der = decode_hex(TEST_DER_HEX);
768        der[10] = 0x00;
769        assert!(matches!(
770            decode_p256_pkcs8_der(&der),
771            Err(Pkcs8Error::InvalidAlgorithmIdentifier)
772        ));
773    }
774
775    #[test]
776    fn decode_bad_ec_version() {
777        let mut der = decode_hex(TEST_DER_HEX);
778        der[33] = 0x02;
779        assert!(matches!(decode_p256_pkcs8_der(&der), Err(Pkcs8Error::InvalidEcVersion)));
780    }
781
782    #[test]
783    fn decode_missing_public_key() {
784        let mut der = decode_hex(TEST_DER_HEX);
785        der[68] = 0x00;
786        assert!(matches!(decode_p256_pkcs8_der(&der), Err(Pkcs8Error::InvalidPublicKeyExplicit)));
787    }
788
789    #[test]
790    fn decode_bad_public_key_prefix() {
791        let mut der = decode_hex(TEST_DER_HEX);
792        der[73] = 0x02;
793        assert!(matches!(decode_p256_pkcs8_der(&der), Err(Pkcs8Error::InvalidPublicKeyPrefix)));
794    }
795
796    #[test]
797    fn decode_mismatched_public_key() {
798        let mut der = decode_hex(TEST_DER_HEX);
799        der[80] ^= 0xff;
800        assert!(matches!(decode_p256_pkcs8_der(&der), Err(Pkcs8Error::InvalidPublicKeyMismatch)));
801    }
802
803    #[test]
804    fn encode_round_trip_random_keys() {
805        for _ in 0..10 {
806            let priv_key: [u8; 32] = rand::random();
807            let key = match SecretKey::from_bytes(&priv_key) {
808                Ok(k) => k,
809                Err(_) => continue,
810            };
811            let der = encode_p256_pkcs8_der(&key).unwrap();
812            let decoded = decode_p256_pkcs8_der(&der).unwrap();
813            assert_eq!(decoded.to_bytes(), key.to_bytes());
814            assert_eq!(decoded.public_key().to_bytes().len(), 65);
815            assert_eq!(decoded.public_key().to_bytes()[0], 0x04);
816        }
817    }
818
819    #[test]
820    fn encode_rejects_invalid_key() {
821        let invalid = [0u8; 32];
822        assert!(SecretKey::from_bytes(&invalid).is_err());
823    }
824
825    // Generated with OpenSSL 3.5 (`genpkey -algorithm EC -pkeyopt
826    // ec_paramgen_curve:P-384`).
827    const P384_PRIVATE_KEY_HEX: &str =
828        "78eff649cf18e8211af8581aa79c9f3271decb2d416a18b96c4efe305ccba836f34b68cb2138798bf3d07ee6ffb306dc";
829    const P384_PUBLIC_KEY_HEX: &str = "049fd48843177dcffdded82c7946bc174bbc14e672bb3f8dfad3b27814b686eb366a2ccdd8e898b397355ae71a09bad802c92f5a4d11a844e28dfa763b5450ff0ae67fa555698cb02121d0457acf44d9ee5d09307fdf1b58f0089d6b43e32c9500";
830    const P384_PKCS8_DER_HEX: &str = "3081b6020100301006072a8648ce3d020106052b8104002204819e30819b020101043078eff649cf18e8211af8581aa79c9f3271decb2d416a18b96c4efe305ccba836f34b68cb2138798bf3d07ee6ffb306dca164036200049fd48843177dcffdded82c7946bc174bbc14e672bb3f8dfad3b27814b686eb366a2ccdd8e898b397355ae71a09bad802c92f5a4d11a844e28dfa763b5450ff0ae67fa555698cb02121d0457acf44d9ee5d09307fdf1b58f0089d6b43e32c9500";
831    const P384_SEC1_DER_HEX: &str = "3081a4020101043078eff649cf18e8211af8581aa79c9f3271decb2d416a18b96c4efe305ccba836f34b68cb2138798bf3d07ee6ffb306dca00706052b81040022a164036200049fd48843177dcffdded82c7946bc174bbc14e672bb3f8dfad3b27814b686eb366a2ccdd8e898b397355ae71a09bad802c92f5a4d11a844e28dfa763b5450ff0ae67fa555698cb02121d0457acf44d9ee5d09307fdf1b58f0089d6b43e32c9500";
832
833    // Generated with OpenSSL 3.5 (`genpkey -algorithm ED25519`).
834    const ED25519_SEED_HEX: &str = "cef4aba318713bb028bb60247b458aea67ce12f9fa394afbc185b298ffc98974";
835    const ED25519_DER_HEX: &str =
836        "302e020100300506032b657004220420cef4aba318713bb028bb60247b458aea67ce12f9fa394afbc185b298ffc98974";
837
838    #[test]
839    fn decode_ec_pkcs8_p256_known_vector() {
840        let der = decode_hex(TEST_DER_HEX);
841        match decode_ec_pkcs8_der(&der).unwrap() {
842            EcPrivateKey::P256(key) => {
843                assert_eq!(key.to_bytes(), TEST_PRIVATE_KEY);
844                assert_eq!(key.public_key().to_bytes(), TEST_PUBLIC_KEY);
845            }
846            EcPrivateKey::P384(_) => panic!("expected P-256"),
847        }
848    }
849
850    #[test]
851    fn decode_ec_pkcs8_p384_known_vector() {
852        let der = decode_hex(P384_PKCS8_DER_HEX);
853        match decode_ec_pkcs8_der(&der).unwrap() {
854            EcPrivateKey::P384(key) => {
855                assert_eq!(key.to_bytes().as_slice(), decode_hex(P384_PRIVATE_KEY_HEX));
856                assert_eq!(key.public_key().to_bytes().as_slice(), decode_hex(P384_PUBLIC_KEY_HEX));
857            }
858            EcPrivateKey::P256(_) => panic!("expected P-384"),
859        }
860    }
861
862    #[test]
863    fn decode_ec_sec1_p384_known_vector() {
864        let der = decode_hex(P384_SEC1_DER_HEX);
865        match decode_ec_sec1_der(&der).unwrap() {
866            EcPrivateKey::P384(key) => {
867                assert_eq!(key.to_bytes().as_slice(), decode_hex(P384_PRIVATE_KEY_HEX));
868                assert_eq!(key.public_key().to_bytes().as_slice(), decode_hex(P384_PUBLIC_KEY_HEX));
869            }
870            EcPrivateKey::P256(_) => panic!("expected P-384"),
871        }
872    }
873
874    #[test]
875    fn encode_p384_matches_known_der() {
876        let scalar: [u8; 48] = decode_hex(P384_PRIVATE_KEY_HEX).try_into().unwrap();
877        let key = p384::SecretKey::from_bytes(&scalar).unwrap();
878        let der = encode_p384_pkcs8_der(&key);
879        assert_eq!(der.as_slice(), decode_hex(P384_PKCS8_DER_HEX).as_slice());
880    }
881
882    #[test]
883    fn encode_p384_round_trip() {
884        let scalar: [u8; 48] = decode_hex(P384_PRIVATE_KEY_HEX).try_into().unwrap();
885        let key = p384::SecretKey::from_bytes(&scalar).unwrap();
886        let der = encode_p384_pkcs8_der(&key);
887        match decode_ec_pkcs8_der(&der).unwrap() {
888            EcPrivateKey::P384(decoded) => assert_eq!(decoded.to_bytes(), scalar),
889            EcPrivateKey::P256(_) => panic!("expected P-384"),
890        }
891    }
892
893    #[test]
894    fn decode_ed25519_known_vector() {
895        let der = decode_hex(ED25519_DER_HEX);
896        let key = decode_ed25519_pkcs8_der(&der).unwrap();
897        assert_eq!(key.to_bytes().as_slice(), decode_hex(ED25519_SEED_HEX));
898    }
899
900    #[test]
901    fn encode_ed25519_matches_known_der() {
902        let seed: [u8; 32] = decode_hex(ED25519_SEED_HEX).try_into().unwrap();
903        let key = crate::curve25519::ed25519::SecretKey::from_bytes(&seed);
904        let der = encode_ed25519_pkcs8_der(&key);
905        assert_eq!(der.as_slice(), decode_hex(ED25519_DER_HEX).as_slice());
906    }
907
908    #[test]
909    fn decode_ec_pkcs8_rejects_unsupported_curve() {
910        // Replace the secp384r1 OID with an unsupported one.
911        let mut der = decode_hex(P384_PKCS8_DER_HEX);
912        der[21] = 0x01;
913        assert!(matches!(decode_ec_pkcs8_der(&der), Err(Pkcs8Error::UnsupportedCurve)));
914    }
915
916    #[test]
917    fn decode_ec_pkcs8_rejects_trailing_data() {
918        let mut der = decode_hex(P384_PKCS8_DER_HEX);
919        der.push(0x00);
920        assert!(matches!(
921            decode_ec_pkcs8_der(&der),
922            Err(Pkcs8Error::InvalidDer(DerError::TrailingData))
923        ));
924    }
925
926    #[test]
927    fn decode_ed25519_rejects_wrong_algorithm() {
928        let mut der = decode_hex(ED25519_DER_HEX);
929        der[9] = 0x2c;
930        assert!(matches!(decode_ed25519_pkcs8_der(&der), Err(Pkcs8Error::UnsupportedAlgorithm)));
931    }
932
933    // RFC 9881, appendix C.1.1: ML-DSA-44 seed-form private key whose seed is
934    // 000102...1e1f.
935    const ML_DSA_SEED: [u8; 32] = [
936        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
937        0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
938    ];
939    const ML_DSA_44_SEED_PKCS8_HEX: &str =
940        "3034020100300b060960864801650304031104228020000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
941
942    fn der_length(out: &mut Vec<u8>, len: usize) {
943        if len < 0x80 {
944            out.push(len as u8);
945        } else if len < 0x100 {
946            out.push(0x81);
947            out.push(len as u8);
948        } else {
949            out.push(0x82);
950            out.extend_from_slice(&(len as u16).to_be_bytes());
951        }
952    }
953
954    fn der_tlv(tag: u8, content: &[u8]) -> Vec<u8> {
955        let mut out = vec![tag];
956        der_length(&mut out, content.len());
957        out.extend_from_slice(content);
958        out
959    }
960
961    /// Builds a PKCS#8 `PrivateKeyInfo` around an RFC 9881 `ML-DSA-XX-PrivateKey`
962    /// CHOICE, with an optional public key field.
963    fn mldsa_pkcs8(oid: &[u8], choice: &[u8], public_key: Option<&[u8]>) -> Vec<u8> {
964        let algorithm = der_tlv(0x30, &der_tlv(0x06, oid));
965
966        let mut pki = der_tlv(0x02, &[0x00]);
967        pki.extend(algorithm);
968        pki.extend(der_tlv(0x04, choice));
969        if let Some(public_key) = public_key {
970            let mut bit_string = vec![0x00];
971            bit_string.extend_from_slice(public_key);
972            pki.extend(der_tlv(0xa1, &der_tlv(0x03, &bit_string)));
973        }
974        der_tlv(0x30, &pki)
975    }
976
977    fn mldsa_both_choice(seed: &[u8; 32], expanded_len: usize) -> Vec<u8> {
978        let mut content = der_tlv(0x04, seed);
979        content.extend(der_tlv(0x04, &vec![0xab; expanded_len]));
980        der_tlv(0x30, &content)
981    }
982
983    #[test]
984    fn encode_mldsa44_matches_rfc9881_example() {
985        let key = crate::mldsa::MlDsa44SecretKey::new(&ML_DSA_SEED);
986        assert_eq!(
987            encode_mldsa44_pkcs8_der(&key).as_slice(),
988            decode_hex(ML_DSA_44_SEED_PKCS8_HEX).as_slice()
989        );
990    }
991
992    #[test]
993    fn decode_mldsa44_seed_example() {
994        let der = decode_hex(ML_DSA_44_SEED_PKCS8_HEX);
995        match decode_mldsa_pkcs8_der(&der).unwrap() {
996            MlDsaPrivateKey::Dsa44(key) => assert_eq!(key.seed(), &ML_DSA_SEED),
997            _ => panic!("expected ML-DSA-44"),
998        }
999    }
1000
1001    #[test]
1002    fn mldsa_seed_round_trip_all_variants() {
1003        let der = encode_mldsa44_pkcs8_der(&crate::mldsa::MlDsa44SecretKey::new(&ML_DSA_SEED));
1004        match decode_mldsa_pkcs8_der(&der).unwrap() {
1005            MlDsaPrivateKey::Dsa44(key) => assert_eq!(key.seed(), &ML_DSA_SEED),
1006            _ => panic!("expected ML-DSA-44"),
1007        }
1008
1009        let der = encode_mldsa65_pkcs8_der(&crate::mldsa::MlDsa65SecretKey::new(&ML_DSA_SEED));
1010        match decode_mldsa_pkcs8_der(&der).unwrap() {
1011            MlDsaPrivateKey::Dsa65(key) => assert_eq!(key.seed(), &ML_DSA_SEED),
1012            _ => panic!("expected ML-DSA-65"),
1013        }
1014
1015        let der = encode_mldsa87_pkcs8_der(&crate::mldsa::MlDsa87SecretKey::new(&ML_DSA_SEED));
1016        match decode_mldsa_pkcs8_der(&der).unwrap() {
1017            MlDsaPrivateKey::Dsa87(key) => assert_eq!(key.seed(), &ML_DSA_SEED),
1018            _ => panic!("expected ML-DSA-87"),
1019        }
1020    }
1021
1022    #[test]
1023    fn decode_mldsa_both_form() {
1024        // The form OpenSSL emits: SEQUENCE { seed OCTET STRING, expandedKey OCTET STRING }.
1025        let choice = mldsa_both_choice(&ML_DSA_SEED, 2560);
1026        let der = mldsa_pkcs8(OID_ML_DSA_44, &choice, None);
1027        match decode_mldsa_pkcs8_der(&der).unwrap() {
1028            MlDsaPrivateKey::Dsa44(key) => assert_eq!(key.seed(), &ML_DSA_SEED),
1029            _ => panic!("expected ML-DSA-44"),
1030        }
1031    }
1032
1033    #[test]
1034    fn decode_mldsa_rejects_expanded_only() {
1035        let choice = der_tlv(0x04, &vec![0xcd; 4032]);
1036        let der = mldsa_pkcs8(OID_ML_DSA_65, &choice, None);
1037        assert!(matches!(
1038            decode_mldsa_pkcs8_der(&der),
1039            Err(Pkcs8Error::MlDsaExpandedKeyNotSupported)
1040        ));
1041    }
1042
1043    #[test]
1044    fn decode_mldsa_checks_public_key() {
1045        // Matching public key.
1046        let mut seed_only = vec![0x80];
1047        seed_only.push(0x20);
1048        seed_only.extend_from_slice(&ML_DSA_SEED);
1049        let key = crate::mldsa::MlDsa44SecretKey::new(&ML_DSA_SEED);
1050        let der = mldsa_pkcs8(OID_ML_DSA_44, &seed_only, Some(&key.public_key().to_bytes()));
1051        assert!(decode_mldsa_pkcs8_der(&der).is_ok());
1052
1053        // Mismatching public key.
1054        let mut wrong = key.public_key().to_bytes();
1055        wrong[0] ^= 0xff;
1056        let der = mldsa_pkcs8(OID_ML_DSA_44, &seed_only, Some(&wrong));
1057        assert!(matches!(
1058            decode_mldsa_pkcs8_der(&der),
1059            Err(Pkcs8Error::InvalidPublicKeyMismatch)
1060        ));
1061    }
1062
1063    #[test]
1064    fn decode_mldsa_rejects_wrong_algorithm() {
1065        let mut seed_only = vec![0x80, 0x20];
1066        seed_only.extend_from_slice(&ML_DSA_SEED);
1067        let der = mldsa_pkcs8(OID_EC_PUBLIC_KEY, &seed_only, None);
1068        assert!(matches!(decode_mldsa_pkcs8_der(&der), Err(Pkcs8Error::UnsupportedAlgorithm)));
1069    }
1070
1071    #[test]
1072    fn decode_mldsa_rejects_trailing_data() {
1073        let mut der = decode_hex(ML_DSA_44_SEED_PKCS8_HEX);
1074        der.push(0x00);
1075        assert!(matches!(
1076            decode_mldsa_pkcs8_der(&der),
1077            Err(Pkcs8Error::InvalidDer(DerError::TrailingData))
1078        ));
1079    }
1080}