ml_kem/
pkcs8.rs

1//! PKCS#8 encoding support.
2//!
3//! When the `pkcs8` feature of this crate is enabled, the [`DecodePrivateKey`] trait is impl'd for
4//! [`DecapsulationKey`], and the [`DecodePublicKey`] trait is impl'd for [`EncapsulationKey`].
5//!
6//! When both the `pkcs8` and `alloc` features are enabled, the [`EncodePrivateKey`] trait is
7//! impl'd for [`DecapsulationKey`], and the [`EncodePublicKey`] trait is impl'd for
8//! [`EncapsulationKey`].
9
10#![cfg(feature = "pkcs8")]
11
12pub use ::pkcs8::{DecodePrivateKey, DecodePublicKey, spki::AssociatedAlgorithmIdentifier};
13pub use const_oid::AssociatedOid;
14
15#[cfg(feature = "alloc")]
16pub use ::pkcs8::{EncodePrivateKey, EncodePublicKey};
17
18use crate::{
19    MlKem512Params, MlKem768Params, MlKem1024Params,
20    kem::{DecapsulationKey, EncapsulationKey},
21    param::{EncapsulationKeySize, KemParams},
22    pke::EncryptionKey,
23};
24use ::pkcs8::{
25    der::{
26        AnyRef, Reader, SliceReader, TagNumber,
27        asn1::{ContextSpecific, OctetStringRef},
28    },
29    spki,
30};
31use hybrid_array::Array;
32
33#[cfg(feature = "alloc")]
34use {
35    crate::EncodedSizeUser,
36    ::pkcs8::der::{Encode, TagMode, asn1::BitStringRef},
37};
38
39/// Tag number for the seed value.
40const SEED_TAG_NUMBER: TagNumber = TagNumber(0);
41
42/// ML-KEM seed serialized as ASN.1.
43type SeedString<'a> = ContextSpecific<&'a OctetStringRef>;
44
45impl AssociatedOid for MlKem512Params {
46    const OID: ::pkcs8::ObjectIdentifier = const_oid::db::fips203::ID_ALG_ML_KEM_512;
47}
48
49impl AssociatedOid for MlKem768Params {
50    const OID: ::pkcs8::ObjectIdentifier = const_oid::db::fips203::ID_ALG_ML_KEM_768;
51}
52
53impl AssociatedOid for MlKem1024Params {
54    const OID: ::pkcs8::ObjectIdentifier = const_oid::db::fips203::ID_ALG_ML_KEM_1024;
55}
56
57impl AssociatedAlgorithmIdentifier for MlKem512Params {
58    type Params = ::pkcs8::der::AnyRef<'static>;
59
60    const ALGORITHM_IDENTIFIER: spki::AlgorithmIdentifier<Self::Params> =
61        spki::AlgorithmIdentifier {
62            oid: Self::OID,
63            parameters: None,
64        };
65}
66
67impl AssociatedAlgorithmIdentifier for MlKem768Params {
68    type Params = ::pkcs8::der::AnyRef<'static>;
69
70    const ALGORITHM_IDENTIFIER: spki::AlgorithmIdentifier<Self::Params> =
71        spki::AlgorithmIdentifier {
72            oid: Self::OID,
73            parameters: None,
74        };
75}
76
77impl AssociatedAlgorithmIdentifier for MlKem1024Params {
78    type Params = ::pkcs8::der::AnyRef<'static>;
79
80    const ALGORITHM_IDENTIFIER: spki::AlgorithmIdentifier<Self::Params> =
81        spki::AlgorithmIdentifier {
82            oid: Self::OID,
83            parameters: None,
84        };
85}
86
87impl<P> AssociatedAlgorithmIdentifier for EncapsulationKey<P>
88where
89    P: KemParams + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
90{
91    type Params = P::Params;
92
93    const ALGORITHM_IDENTIFIER: spki::AlgorithmIdentifier<Self::Params> = P::ALGORITHM_IDENTIFIER;
94}
95
96#[cfg(feature = "alloc")]
97impl<P> pkcs8::EncodePublicKey for EncapsulationKey<P>
98where
99    P: KemParams + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
100{
101    /// Serialize the given `EncapsulationKey` into DER format.
102    /// Returns a `Document` which wraps the DER document in case of success.
103    fn to_public_key_der(&self) -> spki::Result<pkcs8::Document> {
104        let public_key = self.as_bytes();
105        let subject_public_key = BitStringRef::new(0, &public_key)?;
106
107        ::pkcs8::SubjectPublicKeyInfo {
108            algorithm: P::ALGORITHM_IDENTIFIER,
109            subject_public_key,
110        }
111        .try_into()
112    }
113}
114
115impl<P> TryFrom<::pkcs8::SubjectPublicKeyInfoRef<'_>> for EncapsulationKey<P>
116where
117    P: KemParams + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
118{
119    type Error = spki::Error;
120
121    /// Deserialize the encapsulation key from DER format found in `spki.subject_public_key`.
122    /// Returns an `EncapsulationKey` containing `ek_{pke}` and `h` in case of success.
123    fn try_from(spki: ::pkcs8::SubjectPublicKeyInfoRef<'_>) -> Result<Self, Self::Error> {
124        if spki.algorithm.oid != P::ALGORITHM_IDENTIFIER.oid {
125            return Err(spki::Error::OidUnknown {
126                oid: P::ALGORITHM_IDENTIFIER.oid,
127            });
128        }
129
130        let bitstring_of_encapsulation_key = spki.subject_public_key;
131        let enc_key = match bitstring_of_encapsulation_key.as_bytes() {
132            Some(bytes) => {
133                let arr: Array<u8, EncapsulationKeySize<P>> = match bytes.try_into() {
134                    Ok(array) => array,
135                    Err(_) => return Err(spki::Error::KeyMalformed),
136                };
137                EncryptionKey::from_bytes(&arr)
138            }
139            None => return Err(spki::Error::KeyMalformed),
140        };
141
142        Ok(Self::new(enc_key))
143    }
144}
145
146impl<P> AssociatedAlgorithmIdentifier for DecapsulationKey<P>
147where
148    P: KemParams + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
149{
150    type Params = P::Params;
151
152    const ALGORITHM_IDENTIFIER: spki::AlgorithmIdentifier<Self::Params> = P::ALGORITHM_IDENTIFIER;
153}
154
155#[cfg(feature = "alloc")]
156impl<P> pkcs8::EncodePrivateKey for DecapsulationKey<P>
157where
158    P: KemParams + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
159{
160    /// Serialize the given `DecapsulationKey` into DER format.
161    /// Returns a `SecretDocument` which wraps the DER document in case of success.
162    fn to_pkcs8_der(&self) -> ::pkcs8::Result<pkcs8::SecretDocument> {
163        let seed = self.to_seed().ok_or(pkcs8::Error::KeyMalformed)?;
164
165        let seed_der = SeedString {
166            tag_mode: TagMode::Implicit,
167            tag_number: SEED_TAG_NUMBER,
168            value: OctetStringRef::new(&seed)?,
169        }
170        .to_der()?;
171
172        let private_key = OctetStringRef::new(&seed_der)?;
173        let private_key_info = pkcs8::PrivateKeyInfoRef::new(P::ALGORITHM_IDENTIFIER, private_key);
174        pkcs8::SecretDocument::encode_msg(&private_key_info).map_err(pkcs8::Error::Asn1)
175    }
176}
177
178impl<P> TryFrom<::pkcs8::PrivateKeyInfoRef<'_>> for DecapsulationKey<P>
179where
180    P: KemParams + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
181{
182    type Error = ::pkcs8::Error;
183
184    /// Deserialize the decapsulation key from DER format found in `spki.private_key`.
185    /// Returns a `DecapsulationKey` containing `dk_{pke}`, `ek`, and `z` in case of success.
186    fn try_from(private_key_info_ref: ::pkcs8::PrivateKeyInfoRef<'_>) -> Result<Self, Self::Error> {
187        private_key_info_ref
188            .algorithm
189            .assert_algorithm_oid(P::ALGORITHM_IDENTIFIER.oid)?;
190
191        let mut reader = SliceReader::new(private_key_info_ref.private_key.as_bytes())?;
192        let seed_string = SeedString::decode_implicit(&mut reader, SEED_TAG_NUMBER)?
193            .ok_or(pkcs8::Error::KeyMalformed)?;
194        let seed = seed_string
195            .value
196            .as_bytes()
197            .try_into()
198            .map_err(|_| pkcs8::Error::KeyMalformed)?;
199        reader.finish()?;
200
201        Ok(Self::from_seed(seed))
202    }
203}