ssh_key/private/
rsa.rs
1use crate::{
4 checked::CheckedSum, decode::Decode, encode::Encode, public::RsaPublicKey, reader::Reader,
5 writer::Writer, MPInt, Result,
6};
7use core::fmt;
8use zeroize::Zeroize;
9
10#[cfg(feature = "rsa")]
11use {
12 crate::Error,
13 rand_core::{CryptoRng, RngCore},
14 rsa::PublicKeyParts,
15};
16
17#[cfg(feature = "subtle")]
18use subtle::{Choice, ConstantTimeEq};
19
20#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
22#[derive(Clone)]
23pub struct RsaPrivateKey {
24 pub d: MPInt,
26
27 pub iqmp: MPInt,
29
30 pub p: MPInt,
32
33 pub q: MPInt,
35}
36
37impl Decode for RsaPrivateKey {
38 fn decode(reader: &mut impl Reader) -> Result<Self> {
39 let d = MPInt::decode(reader)?;
40 let iqmp = MPInt::decode(reader)?;
41 let p = MPInt::decode(reader)?;
42 let q = MPInt::decode(reader)?;
43 Ok(Self { d, iqmp, p, q })
44 }
45}
46
47impl Encode for RsaPrivateKey {
48 fn encoded_len(&self) -> Result<usize> {
49 [
50 self.d.encoded_len()?,
51 self.iqmp.encoded_len()?,
52 self.p.encoded_len()?,
53 self.q.encoded_len()?,
54 ]
55 .checked_sum()
56 }
57
58 fn encode(&self, writer: &mut impl Writer) -> Result<()> {
59 self.d.encode(writer)?;
60 self.iqmp.encode(writer)?;
61 self.p.encode(writer)?;
62 self.q.encode(writer)
63 }
64}
65
66impl Drop for RsaPrivateKey {
67 fn drop(&mut self) {
68 self.d.zeroize();
69 self.iqmp.zeroize();
70 self.p.zeroize();
71 self.q.zeroize();
72 }
73}
74
75#[cfg(feature = "subtle")]
76#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
77impl ConstantTimeEq for RsaPrivateKey {
78 fn ct_eq(&self, other: &Self) -> Choice {
79 self.d.ct_eq(&other.d)
80 & self.iqmp.ct_eq(&self.iqmp)
81 & self.p.ct_eq(&other.p)
82 & self.q.ct_eq(&other.q)
83 }
84}
85
86#[cfg(feature = "subtle")]
87#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
88impl PartialEq for RsaPrivateKey {
89 fn eq(&self, other: &Self) -> bool {
90 self.ct_eq(other).into()
91 }
92}
93
94#[cfg(feature = "subtle")]
95#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
96impl Eq for RsaPrivateKey {}
97
98#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
100#[derive(Clone)]
101pub struct RsaKeypair {
102 pub public: RsaPublicKey,
104
105 pub private: RsaPrivateKey,
107}
108
109impl RsaKeypair {
110 #[cfg(feature = "rsa")]
112 pub(crate) const MIN_KEY_SIZE: usize = 2048;
113
114 #[cfg(feature = "rsa")]
116 #[cfg_attr(docsrs, doc(cfg(feature = "rsa")))]
117 pub fn random(mut rng: impl CryptoRng + RngCore, bit_size: usize) -> Result<Self> {
118 if bit_size >= Self::MIN_KEY_SIZE {
119 rsa::RsaPrivateKey::new(&mut rng, bit_size)?.try_into()
120 } else {
121 Err(Error::Crypto)
122 }
123 }
124}
125
126impl Decode for RsaKeypair {
127 fn decode(reader: &mut impl Reader) -> Result<Self> {
128 let n = MPInt::decode(reader)?;
129 let e = MPInt::decode(reader)?;
130 let public = RsaPublicKey { n, e };
131 let private = RsaPrivateKey::decode(reader)?;
132 Ok(RsaKeypair { public, private })
133 }
134}
135
136impl Encode for RsaKeypair {
137 fn encoded_len(&self) -> Result<usize> {
138 [
139 self.public.n.encoded_len()?,
140 self.public.e.encoded_len()?,
141 self.private.encoded_len()?,
142 ]
143 .checked_sum()
144 }
145
146 fn encode(&self, writer: &mut impl Writer) -> Result<()> {
147 self.public.n.encode(writer)?;
148 self.public.e.encode(writer)?;
149 self.private.encode(writer)
150 }
151}
152
153impl From<RsaKeypair> for RsaPublicKey {
154 fn from(keypair: RsaKeypair) -> RsaPublicKey {
155 keypair.public
156 }
157}
158
159impl From<&RsaKeypair> for RsaPublicKey {
160 fn from(keypair: &RsaKeypair) -> RsaPublicKey {
161 keypair.public.clone()
162 }
163}
164
165impl fmt::Debug for RsaKeypair {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 f.debug_struct("RsaKeypair")
168 .field("public", &self.public)
169 .finish_non_exhaustive()
170 }
171}
172
173#[cfg(feature = "rsa")]
174#[cfg_attr(docsrs, doc(cfg(feature = "rsa")))]
175impl TryFrom<RsaKeypair> for rsa::RsaPrivateKey {
176 type Error = Error;
177
178 fn try_from(key: RsaKeypair) -> Result<rsa::RsaPrivateKey> {
179 rsa::RsaPrivateKey::try_from(&key)
180 }
181}
182
183#[cfg(feature = "rsa")]
184#[cfg_attr(docsrs, doc(cfg(feature = "rsa")))]
185impl TryFrom<&RsaKeypair> for rsa::RsaPrivateKey {
186 type Error = Error;
187
188 fn try_from(key: &RsaKeypair) -> Result<rsa::RsaPrivateKey> {
189 let ret = rsa::RsaPrivateKey::from_components(
190 rsa::BigUint::try_from(&key.public.n)?,
191 rsa::BigUint::try_from(&key.public.e)?,
192 rsa::BigUint::try_from(&key.private.d)?,
193 vec![
194 rsa::BigUint::try_from(&key.private.p)?,
195 rsa::BigUint::try_from(&key.private.p)?,
196 ],
197 );
198
199 if ret.size().saturating_mul(8) >= RsaKeypair::MIN_KEY_SIZE {
200 Ok(ret)
201 } else {
202 Err(Error::Crypto)
203 }
204 }
205}
206
207#[cfg(feature = "rsa")]
208#[cfg_attr(docsrs, doc(cfg(feature = "rsa")))]
209impl TryFrom<rsa::RsaPrivateKey> for RsaKeypair {
210 type Error = Error;
211
212 fn try_from(key: rsa::RsaPrivateKey) -> Result<RsaKeypair> {
213 RsaKeypair::try_from(&key)
214 }
215}
216
217#[cfg(feature = "rsa")]
218#[cfg_attr(docsrs, doc(cfg(feature = "rsa")))]
219impl TryFrom<&rsa::RsaPrivateKey> for RsaKeypair {
220 type Error = Error;
221
222 fn try_from(key: &rsa::RsaPrivateKey) -> Result<RsaKeypair> {
223 if key.primes().len() > 2 {
225 return Err(Error::Crypto);
226 }
227
228 let public = RsaPublicKey::try_from(key.to_public_key())?;
229
230 let p = &key.primes()[0];
231 let q = &key.primes()[1];
232 let iqmp = key.crt_coefficient().ok_or(Error::Crypto)?;
233
234 let private = RsaPrivateKey {
235 d: key.d().try_into()?,
236 iqmp: iqmp.try_into()?,
237 p: p.try_into()?,
238 q: q.try_into()?,
239 };
240
241 Ok(RsaKeypair { public, private })
242 }
243}
244
245#[cfg(feature = "subtle")]
246#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
247impl ConstantTimeEq for RsaKeypair {
248 fn ct_eq(&self, other: &Self) -> Choice {
249 Choice::from((self.public == other.public) as u8) & self.private.ct_eq(&other.private)
250 }
251}
252
253#[cfg(feature = "subtle")]
254#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
255impl PartialEq for RsaKeypair {
256 fn eq(&self, other: &Self) -> bool {
257 self.ct_eq(other).into()
258 }
259}
260
261#[cfg(feature = "subtle")]
262#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
263impl Eq for RsaKeypair {}