Skip to main content

pq_mayo/
pkcs8.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3//! PKCS#8 and SPKI encoding/decoding support for MAYO keys.
4//!
5//! This module implements the standard key encoding traits from the
6//! [`pkcs8`] and [`spki`] crates, enabling DER-encoded
7//! key serialization compatible with X.509 and PKCS#8.
8//!
9//! Since MAYO has not yet been standardized by NIST, this module uses
10//! **experimental OIDs** assigned by the [Open Quantum Safe](https://openquantumsafe.org/)
11//! project under the `1.3.9999.8` arc. These OIDs will be replaced with
12//! official NIST OIDs once MAYO is standardized.
13
14use crate::{
15    KeyPair, Mayo1, Mayo2, Mayo3, Mayo5, MayoParameter, Signature, SigningKey, VerifyingKey,
16};
17use ::pkcs8::{
18    AlgorithmIdentifierRef, EncodePrivateKey, ObjectIdentifier, PrivateKeyInfoRef,
19    der::{
20        self, AnyRef, Encode, Reader, TagMode, TagNumber,
21        asn1::{BitStringRef, ContextSpecific, OctetStringRef},
22    },
23    spki::{
24        self, AlgorithmIdentifier, AssociatedAlgorithmIdentifier, EncodePublicKey,
25        SignatureAlgorithmIdentifier, SignatureBitStringEncoding, SubjectPublicKeyInfo,
26        SubjectPublicKeyInfoRef,
27    },
28};
29
30/// Tag number for the seed value in the PKCS#8 private key encoding.
31const SEED_TAG_NUMBER: TagNumber = TagNumber(0);
32
33/// Seed serialized as ASN.1 context-specific implicit OCTET STRING.
34type SeedString<'a> = ContextSpecific<&'a OctetStringRef>;
35
36// ============================================================================
37// Experimental OIDs from the Open Quantum Safe (OQS) project.
38//
39// Arc: 1.3.9999.8.{variant}.3
40//
41// These are NOT officially registered and are intended for interoperability
42// testing only. They will be replaced with NIST-assigned OIDs once MAYO
43// is standardized under a FIPS publication.
44//
45// Reference: https://github.com/open-quantum-safe/oqs-provider
46// ============================================================================
47
48/// Experimental OID for MAYO-1 (`1.3.9999.8.1.3`).
49pub const MAYO1_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.9999.8.1.3");
50
51/// Experimental OID for MAYO-2 (`1.3.9999.8.2.3`).
52pub const MAYO2_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.9999.8.2.3");
53
54/// Experimental OID for MAYO-3 (`1.3.9999.8.3.3`).
55pub const MAYO3_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.9999.8.3.3");
56
57/// Experimental OID for MAYO-5 (`1.3.9999.8.5.3`).
58pub const MAYO5_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.9999.8.5.3");
59
60// ============================================================================
61// AssociatedAlgorithmIdentifier for parameter set types
62// ============================================================================
63
64macro_rules! impl_algorithm_id {
65    ($type:ty, $oid:expr) => {
66        impl AssociatedAlgorithmIdentifier for $type {
67            type Params = AnyRef<'static>;
68
69            const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = AlgorithmIdentifierRef {
70                oid: $oid,
71                parameters: None,
72            };
73        }
74    };
75}
76
77impl_algorithm_id!(Mayo1, MAYO1_OID);
78impl_algorithm_id!(Mayo2, MAYO2_OID);
79impl_algorithm_id!(Mayo3, MAYO3_OID);
80impl_algorithm_id!(Mayo5, MAYO5_OID);
81
82// ============================================================================
83// AssociatedAlgorithmIdentifier + SignatureBitStringEncoding for Signature<P>
84// ============================================================================
85
86impl<P> AssociatedAlgorithmIdentifier for Signature<P>
87where
88    P: MayoParameter + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
89{
90    type Params = AnyRef<'static>;
91
92    const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = P::ALGORITHM_IDENTIFIER;
93}
94
95impl<P: MayoParameter> SignatureBitStringEncoding for Signature<P> {
96    fn to_bitstring(&self) -> der::Result<der::asn1::BitString> {
97        der::asn1::BitString::new(0, self.as_ref().to_vec())
98    }
99}
100
101// ============================================================================
102// SignatureAlgorithmIdentifier for key types
103// ============================================================================
104
105impl<P> SignatureAlgorithmIdentifier for KeyPair<P>
106where
107    P: MayoParameter + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
108{
109    type Params = AnyRef<'static>;
110
111    const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier<Self::Params> =
112        Signature::<P>::ALGORITHM_IDENTIFIER;
113}
114
115impl<P> SignatureAlgorithmIdentifier for SigningKey<P>
116where
117    P: MayoParameter + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
118{
119    type Params = AnyRef<'static>;
120
121    const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier<Self::Params> =
122        Signature::<P>::ALGORITHM_IDENTIFIER;
123}
124
125impl<P> SignatureAlgorithmIdentifier for VerifyingKey<P>
126where
127    P: MayoParameter + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
128{
129    type Params = AnyRef<'static>;
130
131    const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier<Self::Params> =
132        Signature::<P>::ALGORITHM_IDENTIFIER;
133}
134
135// ============================================================================
136// KeyPair: EncodePrivateKey + TryFrom<PrivateKeyInfoRef> (DecodePrivateKey)
137// ============================================================================
138
139impl<P> TryFrom<PrivateKeyInfoRef<'_>> for KeyPair<P>
140where
141    P: MayoParameter + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
142{
143    type Error = ::pkcs8::Error;
144
145    fn try_from(private_key_info: PrivateKeyInfoRef<'_>) -> ::pkcs8::Result<Self> {
146        private_key_info
147            .algorithm
148            .assert_algorithm_oid(P::ALGORITHM_IDENTIFIER.oid)?;
149
150        if private_key_info.algorithm.parameters.is_some() {
151            return Err(::pkcs8::Error::ParametersMalformed);
152        }
153
154        let mut reader = der::SliceReader::new(private_key_info.private_key.as_bytes())?;
155        let seed_string = SeedString::decode_implicit(&mut reader, SEED_TAG_NUMBER)?
156            .ok_or(::pkcs8::Error::KeyMalformed(::pkcs8::KeyError::Invalid))?;
157        let seed = seed_string.value.as_bytes();
158        reader.finish()?;
159
160        KeyPair::from_seed(seed)
161            .map_err(|_| ::pkcs8::Error::KeyMalformed(::pkcs8::KeyError::Invalid))
162    }
163}
164
165impl<P> EncodePrivateKey for KeyPair<P>
166where
167    P: MayoParameter + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
168{
169    fn to_pkcs8_der(&self) -> ::pkcs8::Result<der::SecretDocument> {
170        let seed = self.signing_key().as_ref();
171        let seed_der = SeedString {
172            tag_mode: TagMode::Implicit,
173            tag_number: SEED_TAG_NUMBER,
174            value: OctetStringRef::new(seed)?,
175        }
176        .to_der()?;
177
178        let private_key = OctetStringRef::new(&seed_der)?;
179        let private_key_info = PrivateKeyInfoRef::new(P::ALGORITHM_IDENTIFIER, private_key);
180        ::pkcs8::SecretDocument::encode_msg(&private_key_info).map_err(::pkcs8::Error::Asn1)
181    }
182}
183
184// ============================================================================
185// SigningKey: TryFrom<PrivateKeyInfoRef> (DecodePrivateKey via blanket impl)
186// ============================================================================
187
188impl<P> TryFrom<PrivateKeyInfoRef<'_>> for SigningKey<P>
189where
190    P: MayoParameter + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
191{
192    type Error = ::pkcs8::Error;
193
194    fn try_from(private_key_info: PrivateKeyInfoRef<'_>) -> ::pkcs8::Result<Self> {
195        let keypair = KeyPair::<P>::try_from(private_key_info)?;
196        Ok(keypair.signing_key().clone())
197    }
198}
199
200// ============================================================================
201// VerifyingKey: EncodePublicKey + TryFrom<SubjectPublicKeyInfoRef>
202// ============================================================================
203
204impl<P> TryFrom<SubjectPublicKeyInfoRef<'_>> for VerifyingKey<P>
205where
206    P: MayoParameter + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
207{
208    type Error = spki::Error;
209
210    fn try_from(spki: SubjectPublicKeyInfoRef<'_>) -> spki::Result<Self> {
211        spki.algorithm
212            .assert_algorithm_oid(P::ALGORITHM_IDENTIFIER.oid)?;
213
214        if spki.algorithm.parameters.is_some() {
215            return Err(spki::Error::KeyMalformed);
216        }
217
218        let pk_bytes = spki
219            .subject_public_key
220            .as_bytes()
221            .ok_or(spki::Error::KeyMalformed)?;
222
223        VerifyingKey::try_from(pk_bytes).map_err(|_| spki::Error::KeyMalformed)
224    }
225}
226
227impl<P> EncodePublicKey for VerifyingKey<P>
228where
229    P: MayoParameter + AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
230{
231    fn to_public_key_der(&self) -> spki::Result<der::Document> {
232        let subject_public_key = BitStringRef::new(0, self.as_ref())?;
233
234        SubjectPublicKeyInfo {
235            algorithm: P::ALGORITHM_IDENTIFIER,
236            subject_public_key,
237        }
238        .try_into()
239    }
240}