Skip to main content

ssh_key/private/
rsa.rs

1//! Rivest–Shamir–Adleman (RSA) private keys.
2
3use crate::{Error, Mpint, Result, public::RsaPublicKey};
4use core::fmt::{self, Debug};
5use ctutils::{Choice, CtEq};
6use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
7use zeroize::Zeroize;
8
9#[cfg(feature = "rsa")]
10use {
11    encoding::Uint,
12    rand_core::CryptoRng,
13    rsa::{pkcs1v15, traits::PrivateKeyParts},
14    sha2::{Digest, digest::const_oid::AssociatedOid},
15};
16
17/// RSA private key.
18#[derive(Clone)]
19pub struct RsaPrivateKey {
20    /// RSA private exponent.
21    d: Mpint,
22
23    /// CRT coefficient: `(inverse of q) mod p`.
24    iqmp: Mpint,
25
26    /// First prime factor of `n`.
27    p: Mpint,
28
29    /// Second prime factor of `n`.
30    q: Mpint,
31}
32
33impl RsaPrivateKey {
34    /// Create a new RSA private key with the following components:
35    ///
36    /// - `d`: RSA private exponent.
37    /// - `iqmp`: CRT coefficient: `(inverse of q) mod p`.
38    /// - `p`: First prime factor of `n`.
39    /// - `q`: Second prime factor of `n`.
40    ///
41    /// # Errors
42    /// Returns [`Error::FormatEncoding`] if any of the provided values are negative.
43    pub fn new(d: Mpint, iqmp: Mpint, p: Mpint, q: Mpint) -> Result<Self> {
44        if d.is_positive() && iqmp.is_positive() && p.is_positive() && q.is_positive() {
45            Ok(Self { d, iqmp, p, q })
46        } else {
47            Err(Error::FormatEncoding)
48        }
49    }
50
51    /// RSA private exponent.
52    #[must_use]
53    pub fn d(&self) -> &Mpint {
54        &self.d
55    }
56
57    /// CRT coefficient: `(inverse of q) mod p`.
58    #[must_use]
59    pub fn iqmp(&self) -> &Mpint {
60        &self.iqmp
61    }
62
63    /// First prime factor of `n`.
64    #[must_use]
65    pub fn p(&self) -> &Mpint {
66        &self.p
67    }
68
69    /// Second prime factor of `n`.
70    #[must_use]
71    pub fn q(&self) -> &Mpint {
72        &self.q
73    }
74}
75
76impl CtEq for RsaPrivateKey {
77    fn ct_eq(&self, other: &Self) -> Choice {
78        self.d.ct_eq(&other.d)
79            & self.iqmp.ct_eq(&self.iqmp)
80            & self.p.ct_eq(&other.p)
81            & self.q.ct_eq(&other.q)
82    }
83}
84
85impl Debug for RsaPrivateKey {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        f.debug_struct("RsaPrivateKey").finish_non_exhaustive()
88    }
89}
90
91impl Drop for RsaPrivateKey {
92    fn drop(&mut self) {
93        self.d.zeroize();
94        self.iqmp.zeroize();
95        self.p.zeroize();
96        self.q.zeroize();
97    }
98}
99
100impl Decode for RsaPrivateKey {
101    type Error = Error;
102
103    fn decode(reader: &mut impl Reader) -> Result<Self> {
104        let d = Mpint::decode(reader)?;
105        let iqmp = Mpint::decode(reader)?;
106        let p = Mpint::decode(reader)?;
107        let q = Mpint::decode(reader)?;
108        Self::new(d, iqmp, p, q)
109    }
110}
111
112impl Encode for RsaPrivateKey {
113    fn encoded_len(&self) -> encoding::Result<usize> {
114        [
115            self.d.encoded_len()?,
116            self.iqmp.encoded_len()?,
117            self.p.encoded_len()?,
118            self.q.encoded_len()?,
119        ]
120        .checked_sum()
121    }
122
123    fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
124        self.d.encode(writer)?;
125        self.iqmp.encode(writer)?;
126        self.p.encode(writer)?;
127        self.q.encode(writer)?;
128        Ok(())
129    }
130}
131
132impl Eq for RsaPrivateKey {}
133impl PartialEq for RsaPrivateKey {
134    fn eq(&self, other: &Self) -> bool {
135        self.ct_eq(other).into()
136    }
137}
138
139/// RSA private/public keypair.
140#[derive(Clone)]
141pub struct RsaKeypair {
142    /// Public key.
143    public: RsaPublicKey,
144
145    /// Private key.
146    private: RsaPrivateKey,
147}
148
149impl RsaKeypair {
150    /// Generate a random RSA keypair of the given size.
151    #[cfg(feature = "rsa")]
152    #[expect(clippy::missing_errors_doc, reason = "TODO")]
153    pub fn random<R: CryptoRng + ?Sized>(rng: &mut R, bit_size: usize) -> Result<Self> {
154        rsa::RsaPrivateKey::new(rng, bit_size)?.try_into()
155    }
156
157    /// Create a new keypair from the given `public` and `private` key components.
158    ///
159    /// # Errors
160    /// Returns [`Error::Crypto`] if the `public` key does not match the `private` key (TODO).
161    pub fn new(public: RsaPublicKey, private: RsaPrivateKey) -> Result<Self> {
162        // TODO(tarcieri): perform validation that the public and private components match?
163        Ok(Self { public, private })
164    }
165
166    /// Get the size of the RSA modulus in bits.
167    #[must_use]
168    pub fn key_size(&self) -> u32 {
169        self.public.key_size()
170    }
171
172    /// Get the public component of the keypair.
173    #[must_use]
174    pub fn public(&self) -> &RsaPublicKey {
175        &self.public
176    }
177
178    /// Get the private component of the keypair.
179    #[must_use]
180    pub fn private(&self) -> &RsaPrivateKey {
181        &self.private
182    }
183}
184
185impl CtEq for RsaKeypair {
186    fn ct_eq(&self, other: &Self) -> Choice {
187        Choice::from(u8::from(self.public == other.public)) & self.private.ct_eq(&other.private)
188    }
189}
190
191impl Debug for RsaKeypair {
192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193        f.debug_struct("RsaKeypair")
194            .field("public", &self.public)
195            .finish_non_exhaustive()
196    }
197}
198
199impl Decode for RsaKeypair {
200    type Error = Error;
201
202    fn decode(reader: &mut impl Reader) -> Result<Self> {
203        let n = Mpint::decode(reader)?;
204        let e = Mpint::decode(reader)?;
205        let public = RsaPublicKey::new(e, n)?;
206        let private = RsaPrivateKey::decode(reader)?;
207        Self::new(public, private)
208    }
209}
210
211impl Encode for RsaKeypair {
212    fn encoded_len(&self) -> encoding::Result<usize> {
213        [
214            self.public.n().encoded_len()?,
215            self.public.e().encoded_len()?,
216            self.private.encoded_len()?,
217        ]
218        .checked_sum()
219    }
220
221    fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
222        self.public.n().encode(writer)?;
223        self.public.e().encode(writer)?;
224        self.private.encode(writer)
225    }
226}
227
228impl Eq for RsaKeypair {}
229impl PartialEq for RsaKeypair {
230    fn eq(&self, other: &Self) -> bool {
231        self.ct_eq(other).into()
232    }
233}
234
235impl From<RsaKeypair> for RsaPublicKey {
236    fn from(keypair: RsaKeypair) -> RsaPublicKey {
237        keypair.public
238    }
239}
240
241impl From<&RsaKeypair> for RsaPublicKey {
242    fn from(keypair: &RsaKeypair) -> RsaPublicKey {
243        keypair.public.clone()
244    }
245}
246
247#[cfg(feature = "rsa")]
248impl TryFrom<RsaKeypair> for rsa::RsaPrivateKey {
249    type Error = Error;
250
251    fn try_from(key: RsaKeypair) -> Result<rsa::RsaPrivateKey> {
252        rsa::RsaPrivateKey::try_from(&key)
253    }
254}
255
256#[cfg(feature = "rsa")]
257impl TryFrom<&RsaKeypair> for rsa::RsaPrivateKey {
258    type Error = Error;
259
260    fn try_from(key: &RsaKeypair) -> Result<rsa::RsaPrivateKey> {
261        let ret = rsa::RsaPrivateKey::from_components(
262            Uint::try_from(key.public.n())?,
263            Uint::try_from(key.public.e())?,
264            Uint::try_from(&key.private.d)?,
265            vec![
266                Uint::try_from(&key.private.p)?,
267                Uint::try_from(&key.private.q)?,
268            ],
269        )?;
270
271        Ok(ret)
272    }
273}
274
275#[cfg(feature = "rsa")]
276impl TryFrom<rsa::RsaPrivateKey> for RsaKeypair {
277    type Error = Error;
278
279    fn try_from(key: rsa::RsaPrivateKey) -> Result<RsaKeypair> {
280        RsaKeypair::try_from(&key)
281    }
282}
283
284#[cfg(feature = "rsa")]
285impl TryFrom<&rsa::RsaPrivateKey> for RsaKeypair {
286    type Error = Error;
287
288    fn try_from(key: &rsa::RsaPrivateKey) -> Result<RsaKeypair> {
289        // Multi-prime keys are not supported
290        if key.primes().len() > 2 {
291            return Err(Error::Crypto);
292        }
293
294        let public = RsaPublicKey::try_from(key.to_public_key())?;
295
296        let p = &key.primes()[0];
297        let q = &key.primes()[1];
298        let iqmp = key.crt_coefficient().ok_or(Error::Crypto)?;
299
300        let private = RsaPrivateKey {
301            d: key.d().into(),
302            iqmp: iqmp.into(),
303            p: p.into(),
304            q: q.into(),
305        };
306
307        Ok(RsaKeypair { public, private })
308    }
309}
310
311#[cfg(feature = "rsa")]
312impl<D> TryFrom<&RsaKeypair> for pkcs1v15::SigningKey<D>
313where
314    D: Digest + AssociatedOid,
315{
316    type Error = Error;
317
318    fn try_from(keypair: &RsaKeypair) -> Result<pkcs1v15::SigningKey<D>> {
319        Ok(pkcs1v15::SigningKey::new(keypair.try_into()?))
320    }
321}