Skip to main content

xml_sec/
rsa_encoding.rs

1//! RSA container codecs for the existing `sad-rsa` key types.
2//!
3//! Only serialization lives here; RSA operations, validation and blinding stay
4//! in `sad-rsa`. Adapted from sadco-io/sad-rsa 0.10.2, src/encoding.rs,
5//! licensed MIT OR Apache-2.0, copyright its original contributors.
6
7// Copyright (c) 2025-2026 Sadco Security Team
8// Copyright (c) 2015-2025 RustCrypto Developers
9//
10// Permission is hereby granted, free of charge, to any person obtaining a copy
11// of this software and associated documentation files (the "Software"), to deal
12// in the Software without restriction, including without limitation the rights
13// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
14// of the Software, and to permit persons to whom the Software is furnished to do
15// so, subject to the following conditions:
16//
17// The above copyright notice and this permission notice shall be included in
18// all copies or substantial portions of the Software.
19//
20// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26// THE SOFTWARE.
27
28use crypto_bigint::{BoxedUint, NonZero, Resize};
29use pkcs8::{
30    Document, SecretDocument,
31    der::{
32        Decode,
33        asn1::OctetStringRef,
34        pem::{LineEnding, PemLabel},
35    },
36};
37use rsa::{
38    RsaPrivateKey, RsaPublicKey,
39    traits::{PrivateKeyParts, PublicKeyParts},
40};
41use zeroize::Zeroizing;
42
43/// PKCS#1 and PKCS#8 codecs without enabling the RSA dependency's codecs.
44pub trait RsaPrivateKeyEncoding: Sized {
45    /// Decode a two-prime PKCS#1 key and validate it with `sad-rsa`.
46    fn from_pkcs1_der(bytes: &[u8]) -> pkcs1::Result<Self>;
47    /// Encode a borrowed key as PKCS#1 in a zeroizing document.
48    fn to_pkcs1_der(&self) -> pkcs1::Result<SecretDocument>;
49    /// Decode a PKCS#8 key with a validated algorithm identifier.
50    fn from_pkcs8_der(bytes: &[u8]) -> pkcs8::Result<Self>;
51    /// Encode a borrowed key as PKCS#8 in a zeroizing document.
52    fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument>;
53    /// Decode a correctly labeled PKCS#1 PEM key.
54    fn from_pkcs1_pem(pem: &str) -> pkcs1::Result<Self> {
55        let (label, doc) = SecretDocument::from_pem(pem)?;
56        pkcs1::RsaPrivateKeyRef::validate_pem_label(label)?;
57        Self::from_pkcs1_der(doc.as_bytes())
58    }
59    /// Encode a PKCS#1 PEM key with explicit line endings.
60    fn to_pkcs1_pem(&self, ending: LineEnding) -> pkcs1::Result<Zeroizing<String>> {
61        Ok(self
62            .to_pkcs1_der()?
63            .to_pem(pkcs1::RsaPrivateKeyRef::PEM_LABEL, ending)?)
64    }
65    /// Decode a correctly labeled PKCS#8 PEM key.
66    fn from_pkcs8_pem(pem: &str) -> pkcs8::Result<Self> {
67        let (label, doc) = SecretDocument::from_pem(pem)?;
68        pkcs8::PrivateKeyInfoRef::validate_pem_label(label)?;
69        Self::from_pkcs8_der(doc.as_bytes())
70    }
71    /// Encode a PKCS#8 PEM key with explicit line endings.
72    fn to_pkcs8_pem(&self, ending: LineEnding) -> pkcs8::Result<Zeroizing<String>> {
73        Ok(self
74            .to_pkcs8_der()?
75            .to_pem(pkcs8::PrivateKeyInfoRef::PEM_LABEL, ending)?)
76    }
77    /// Decrypt a protected PKCS#8 container before parsing its key.
78    fn from_pkcs8_encrypted_der(bytes: &[u8], password: impl AsRef<[u8]>) -> pkcs8::Result<Self> {
79        let plaintext = pkcs8::EncryptedPrivateKeyInfoRef::try_from(bytes)?.decrypt(password)?;
80        Self::from_pkcs8_der(plaintext.as_bytes())
81    }
82    /// Decode and decrypt a correctly labeled protected PKCS#8 PEM key.
83    fn from_pkcs8_encrypted_pem(pem: &str, password: impl AsRef<[u8]>) -> pkcs8::Result<Self> {
84        let (label, doc) = SecretDocument::from_pem(pem)?;
85        pkcs8::EncryptedPrivateKeyInfoRef::validate_pem_label(label)?;
86        Self::from_pkcs8_encrypted_der(doc.as_bytes(), password)
87    }
88}
89
90/// PKCS#1 and SubjectPublicKeyInfo codecs for the existing RSA public key.
91pub trait RsaPublicKeyEncoding: Sized {
92    /// Decode and validate a PKCS#1 public key.
93    fn from_pkcs1_der(bytes: &[u8]) -> pkcs1::Result<Self>;
94    /// Encode a borrowed public key as PKCS#1.
95    fn to_pkcs1_der(&self) -> pkcs1::Result<Document>;
96    /// Decode a SubjectPublicKeyInfo with a validated algorithm identifier.
97    fn from_public_key_der(bytes: &[u8]) -> pkcs8::spki::Result<Self>;
98    /// Encode a borrowed public key as SubjectPublicKeyInfo.
99    fn to_public_key_der(&self) -> pkcs8::spki::Result<Document>;
100    /// Decode a correctly labeled PKCS#1 PEM public key.
101    fn from_pkcs1_pem(pem: &str) -> pkcs1::Result<Self> {
102        let (label, doc) = Document::from_pem(pem)?;
103        pkcs1::RsaPublicKeyRef::validate_pem_label(label)?;
104        Self::from_pkcs1_der(doc.as_bytes())
105    }
106    /// Encode a PKCS#1 public PEM key with explicit line endings.
107    fn to_pkcs1_pem(&self, ending: LineEnding) -> pkcs1::Result<String> {
108        Ok(self
109            .to_pkcs1_der()?
110            .to_pem(pkcs1::RsaPublicKeyRef::PEM_LABEL, ending)?)
111    }
112    /// Decode a correctly labeled public-key PEM container.
113    fn from_public_key_pem(pem: &str) -> pkcs8::spki::Result<Self> {
114        let (label, doc) = Document::from_pem(pem)?;
115        pkcs8::SubjectPublicKeyInfoRef::validate_pem_label(label)?;
116        Self::from_public_key_der(doc.as_bytes())
117    }
118    /// Encode a public-key PEM container with explicit line endings.
119    fn to_public_key_pem(&self, ending: LineEnding) -> pkcs8::spki::Result<String> {
120        Ok(self
121            .to_public_key_der()?
122            .to_pem(pkcs8::SubjectPublicKeyInfoRef::PEM_LABEL, ending)?)
123    }
124}
125
126fn uint(data: &[u8], bits: u32) -> pkcs1::Result<BoxedUint> {
127    BoxedUint::from_be_slice(data, bits).map_err(|_| pkcs1::Error::KeyMalformed)
128}
129
130fn width(data: &[u8]) -> pkcs1::Result<u32> {
131    u32::try_from(data.len())
132        .ok()
133        .and_then(|n| n.checked_mul(8))
134        .ok_or(pkcs1::Error::KeyMalformed)
135}
136
137impl RsaPrivateKeyEncoding for RsaPrivateKey {
138    fn from_pkcs1_der(bytes: &[u8]) -> pkcs1::Result<Self> {
139        let key = pkcs1::RsaPrivateKeyRef::from_der(bytes)?;
140        if key.version() != pkcs1::Version::TwoPrime {
141            return Err(pkcs1::Error::Version);
142        }
143        let bits = width(key.modulus.as_bytes())?;
144        // All private fields must fit the existing decoder's modulus-width
145        // integers. Reject before constructing any secret bigint workspace.
146        for component in [key.private_exponent, key.prime1, key.prime2] {
147            if component.as_bytes().len() > key.modulus.as_bytes().len() {
148                return Err(pkcs1::Error::KeyMalformed);
149            }
150        }
151        let n = uint(key.modulus.as_bytes(), bits)?;
152        let e = uint(
153            key.public_exponent.as_bytes(),
154            width(key.public_exponent.as_bytes())?,
155        )?;
156        let d = uint(key.private_exponent.as_bytes(), bits)?;
157        let primes = vec![
158            uint(key.prime1.as_bytes(), bits)?,
159            uint(key.prime2.as_bytes(), bits)?,
160        ];
161        Self::from_components(n, e, d, primes).map_err(|_| pkcs1::Error::KeyMalformed)
162    }
163
164    fn to_pkcs1_der(&self) -> pkcs1::Result<SecretDocument> {
165        if self.primes().len() != 2 {
166            return Err(pkcs1::Error::Crypto);
167        }
168        let modulus = self.n().to_be_bytes();
169        let public_exponent = self.e().to_be_bytes();
170        let private_exponent = Zeroizing::new(self.d().to_be_bytes());
171        let prime1 = Zeroizing::new(self.primes()[0].to_be_bytes());
172        let prime2 = Zeroizing::new(self.primes()[1].to_be_bytes());
173        let bits = self.d().bits_precision();
174        let exponent1 = Zeroizing::new(
175            (self.d()
176                % NonZero::new((&self.primes()[0]).resize_unchecked(bits) - &BoxedUint::one())
177                    .unwrap())
178            .to_be_bytes(),
179        );
180        let exponent2 = Zeroizing::new(
181            (self.d()
182                % NonZero::new((&self.primes()[1]).resize_unchecked(bits) - &BoxedUint::one())
183                    .unwrap())
184            .to_be_bytes(),
185        );
186        let coefficient = Zeroizing::new(
187            self.crt_coefficient()
188                .ok_or(pkcs1::Error::Crypto)?
189                .to_be_bytes(),
190        );
191        Ok(SecretDocument::encode_msg(&pkcs1::RsaPrivateKeyRef {
192            modulus: pkcs1::UintRef::new(&modulus)?,
193            public_exponent: pkcs1::UintRef::new(&public_exponent)?,
194            private_exponent: pkcs1::UintRef::new(&private_exponent)?,
195            prime1: pkcs1::UintRef::new(&prime1)?,
196            prime2: pkcs1::UintRef::new(&prime2)?,
197            exponent1: pkcs1::UintRef::new(&exponent1)?,
198            exponent2: pkcs1::UintRef::new(&exponent2)?,
199            coefficient: pkcs1::UintRef::new(&coefficient)?,
200            other_prime_infos: None,
201        })?)
202    }
203
204    fn from_pkcs8_der(bytes: &[u8]) -> pkcs8::Result<Self> {
205        let info = pkcs8::PrivateKeyInfoRef::from_der(bytes)?;
206        verify_algorithm_id(&info.algorithm)?;
207        Self::from_pkcs1_der(info.private_key.as_bytes()).map_err(private_error)
208    }
209
210    fn to_pkcs8_der(&self) -> pkcs8::Result<SecretDocument> {
211        let key = self.to_pkcs1_der().map_err(private_error)?;
212        pkcs8::PrivateKeyInfoRef::new(pkcs1::ALGORITHM_ID, OctetStringRef::new(key.as_bytes())?)
213            .try_into()
214    }
215}
216
217impl RsaPublicKeyEncoding for RsaPublicKey {
218    fn from_pkcs1_der(bytes: &[u8]) -> pkcs1::Result<Self> {
219        let key = pkcs1::RsaPublicKeyRef::from_der(bytes)?;
220        let n = uint(key.modulus.as_bytes(), width(key.modulus.as_bytes())?)?;
221        let e = uint(
222            key.public_exponent.as_bytes(),
223            width(key.public_exponent.as_bytes())?,
224        )?;
225        Self::new(n, e).map_err(|_| pkcs1::Error::KeyMalformed)
226    }
227
228    fn to_pkcs1_der(&self) -> pkcs1::Result<Document> {
229        let modulus = self.n().to_be_bytes();
230        let exponent = self.e().to_be_bytes();
231        Ok(Document::encode_msg(&pkcs1::RsaPublicKeyRef {
232            modulus: pkcs1::UintRef::new(&modulus)?,
233            public_exponent: pkcs1::UintRef::new(&exponent)?,
234        })?)
235    }
236
237    fn from_public_key_der(bytes: &[u8]) -> pkcs8::spki::Result<Self> {
238        let info = pkcs8::SubjectPublicKeyInfoRef::from_der(bytes)?;
239        verify_algorithm_id(&info.algorithm)?;
240        Self::from_pkcs1_der(
241            info.subject_public_key
242                .as_bytes()
243                .ok_or(pkcs8::spki::Error::KeyMalformed)?,
244        )
245        .map_err(public_error)
246    }
247
248    fn to_public_key_der(&self) -> pkcs8::spki::Result<Document> {
249        let key = self.to_pkcs1_der().map_err(public_error)?;
250        pkcs8::SubjectPublicKeyInfoRef {
251            algorithm: pkcs1::ALGORITHM_ID,
252            subject_public_key: pkcs8::der::asn1::BitStringRef::new(0, key.as_bytes())?,
253        }
254        .try_into()
255    }
256}
257
258fn verify_algorithm_id(algorithm: &pkcs8::AlgorithmIdentifierRef<'_>) -> pkcs8::spki::Result<()> {
259    match algorithm.oid {
260        pkcs1::ALGORITHM_OID => {
261            // RFC 3279 section 2.3.1: rsaEncryption parameters SHALL be NULL.
262            // https://www.rfc-editor.org/rfc/rfc3279#section-2.3.1
263            if algorithm.parameters_any()? != pkcs8::der::asn1::Null.into() {
264                return Err(pkcs8::spki::Error::KeyMalformed);
265            }
266        }
267        oid if oid == pkcs8::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.10") => {
268            // RFC 4055 sections 1.2 and 3.1 allow absent PSS parameters.
269            // https://www.rfc-editor.org/rfc/rfc4055#section-3.1
270            // Preserve sad-rsa's parameter-unconstrained import contract;
271            // explicit restrictions cannot be retained by its bare RSA type
272            // and are therefore not silently discarded by this decoder.
273            if algorithm.parameters.is_some() {
274                return Err(pkcs8::spki::Error::KeyMalformed);
275            }
276        }
277        oid => return Err(pkcs8::spki::Error::OidUnknown { oid }),
278    }
279    Ok(())
280}
281
282fn private_error(error: pkcs1::Error) -> pkcs8::Error {
283    match error {
284        pkcs1::Error::Asn1(error) => pkcs8::Error::Asn1(error),
285        _ => pkcs8::Error::KeyMalformed(pkcs8::KeyError::Invalid),
286    }
287}
288
289fn public_error(error: pkcs1::Error) -> pkcs8::spki::Error {
290    match error {
291        pkcs1::Error::Asn1(error) => pkcs8::spki::Error::Asn1(error),
292        _ => pkcs8::spki::Error::KeyMalformed,
293    }
294}