Skip to content

Commit

Permalink
ssh-key: make all RSA private key fields private
Browse files Browse the repository at this point in the history
Also adds fallible constructors, and uses those in the `Decode` impls.

The new constructors ensure the provided `Mpints` are non-negative.
  • Loading branch information
tarcieri committed Aug 13, 2024
1 parent bdb91e9 commit a4cb46b
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 23 deletions.
2 changes: 1 addition & 1 deletion ssh-key/src/private/keypair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ impl KeypairData {
#[cfg(feature = "alloc")]
Self::Encrypted(ciphertext) => ciphertext.as_ref(),
#[cfg(feature = "alloc")]
Self::Rsa(rsa) => rsa.private.d.as_bytes(),
Self::Rsa(rsa) => rsa.private().d().as_bytes(),
#[cfg(all(feature = "alloc", feature = "ecdsa"))]
Self::SkEcdsaSha2NistP256(sk) => sk.key_handle(),
#[cfg(feature = "alloc")]
Expand Down
73 changes: 65 additions & 8 deletions ssh-key/src/private/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,52 @@ use {
#[derive(Clone)]
pub struct RsaPrivateKey {
/// RSA private exponent.
pub d: Mpint,
d: Mpint,

/// CRT coefficient: `(inverse of q) mod p`.
pub iqmp: Mpint,
iqmp: Mpint,

/// First prime factor of `n`.
pub p: Mpint,
p: Mpint,

/// Second prime factor of `n`.
pub q: Mpint,
q: Mpint,
}

impl RsaPrivateKey {
/// Create a new RSA private key with the following components:
///
/// - `d`: RSA private exponent.
/// - `iqmp`: CRT coefficient: `(inverse of q) mod p`.
/// - `p`: First prime factor of `n`.
/// - `q`: Second prime factor of `n`.
pub fn new(d: Mpint, iqmp: Mpint, p: Mpint, q: Mpint) -> Result<Self> {
if d.is_positive() && iqmp.is_positive() && p.is_positive() && q.is_positive() {
Ok(Self { d, iqmp, p, q })
} else {
Err(Error::FormatEncoding)
}
}

/// RSA private exponent.
pub fn d(&self) -> &Mpint {
&self.d
}

/// CRT coefficient: `(inverse of q) mod p`.
pub fn iqmp(&self) -> &Mpint {
&self.iqmp
}

/// First prime factor of `n`.
pub fn p(&self) -> &Mpint {
&self.p
}

/// Second prime factor of `n`.
pub fn q(&self) -> &Mpint {
&self.q
}
}

impl ConstantTimeEq for RsaPrivateKey {
Expand Down Expand Up @@ -57,7 +93,7 @@ impl Decode for RsaPrivateKey {
let iqmp = Mpint::decode(reader)?;
let p = Mpint::decode(reader)?;
let q = Mpint::decode(reader)?;
Ok(Self { d, iqmp, p, q })
Self::new(d, iqmp, p, q)
}
}

Expand Down Expand Up @@ -94,10 +130,10 @@ impl Drop for RsaPrivateKey {
#[derive(Clone)]
pub struct RsaKeypair {
/// Public key.
pub public: RsaPublicKey,
public: RsaPublicKey,

/// Private key.
pub private: RsaPrivateKey,
private: RsaPrivateKey,
}

impl RsaKeypair {
Expand All @@ -114,6 +150,27 @@ impl RsaKeypair {
Err(Error::Crypto)
}
}

/// Create a new keypair from the given `public` and `private` key components.
pub fn new(public: RsaPublicKey, private: RsaPrivateKey) -> Result<Self> {
// TODO(tarcieri): perform validation that the public and private components match?
Ok(Self { public, private })
}

/// Get the size of the RSA modulus in bits.
pub fn key_size(&self) -> u32 {
self.public.key_size()
}

/// Get the public component of the keypair.
pub fn public(&self) -> &RsaPublicKey {
&self.public
}

/// Get the private component of the keypair.
pub fn private(&self) -> &RsaPrivateKey {
&self.private
}
}

impl ConstantTimeEq for RsaKeypair {
Expand All @@ -138,7 +195,7 @@ impl Decode for RsaKeypair {
let e = Mpint::decode(reader)?;
let public = RsaPublicKey::new(e, n)?;
let private = RsaPrivateKey::decode(reader)?;
Ok(RsaKeypair { public, private })
Self::new(public, private)
}
}

Expand Down
28 changes: 14 additions & 14 deletions ssh-key/tests/private_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,8 @@ fn decode_rsa_3072_openssh() {
assert!(key.kdf().is_none());

let rsa_keypair = key.key_data().rsa().unwrap();
assert_eq!(3072, rsa_keypair.public.key_size());
assert_eq!(&hex!("010001"), rsa_keypair.public.e().as_bytes());
assert_eq!(3072, rsa_keypair.key_size());
assert_eq!(&hex!("010001"), rsa_keypair.public().e().as_bytes());
assert_eq!(
&hex!(
"00a68e478c9bc93726436b7f5e9e6f9a46e1b73bec1e8cb7754de2c6a5b6c455f2f012a7259afcf94181d69
Expand All @@ -282,7 +282,7 @@ fn decode_rsa_3072_openssh() {
0549d3174b85bd7f6624c3753cf235b650d0e4228f32be7b54a590d869fb7786559bb7a4d66f9d3a69c085e
fdf083a915d47a1d9161a08756b263b06e739d99f2890362abc96ade42cce8f939a40daff9"
),
rsa_keypair.public.n().as_bytes(),
rsa_keypair.public().n().as_bytes(),
);
assert_eq!(
&hex!(
Expand All @@ -296,7 +296,7 @@ fn decode_rsa_3072_openssh() {
325f3fb37326623dbbec63b3d984830b2dd27bebb6bd2ed5345dfff18df1806adebceda9845804968930681
ac3e523138c5216cb135997e3e143a7816acc3d8741eacec7e15f53da0f0810691708d9d"
),
rsa_keypair.private.d.as_bytes()
rsa_keypair.private().d().as_bytes()
);
assert_eq!(
&hex!(
Expand All @@ -306,7 +306,7 @@ fn decode_rsa_3072_openssh() {
88dd6f72eef5cff1927cde573a4ca0d43c2b6c6a95721445122e1cf6aa5f05b65cb9c86124f9f79fa29f05e
3f06f3b83edca9941f571650e0fb468aae4c"
),
rsa_keypair.private.iqmp.as_bytes()
rsa_keypair.private().iqmp().as_bytes()
);
assert_eq!(
&hex!(
Expand All @@ -316,7 +316,7 @@ fn decode_rsa_3072_openssh() {
990e48b93fd1d284fcc8c1c07fbefc04d02925b2f159a9a9f567073e1c94fdc6e472f48963be16c5c545385
6ad2bf7916e42c36e75a5018910d8dad038d73"
),
rsa_keypair.private.p.as_bytes()
rsa_keypair.private().p().as_bytes()
);
assert_eq!(
&hex!(
Expand All @@ -326,7 +326,7 @@ fn decode_rsa_3072_openssh() {
9d136d085c1d05c39f4c36cf89fdc3c66a755e63f446e16302b13599400f0a83321a2e6b9153df02f03de31
8ea09039282853f2011e0905d1157667caf1e3"
),
rsa_keypair.private.q.as_bytes()
rsa_keypair.private().q().as_bytes()
);
assert_eq!("[email protected]", key.comment());
}
Expand All @@ -341,8 +341,8 @@ fn decode_rsa_4096_openssh() {
assert!(key.kdf().is_none());

let rsa_keypair = key.key_data().rsa().unwrap();
assert_eq!(4096, rsa_keypair.public.key_size());
assert_eq!(&hex!("010001"), rsa_keypair.public.e().as_bytes());
assert_eq!(4096, rsa_keypair.key_size());
assert_eq!(&hex!("010001"), rsa_keypair.public().e().as_bytes());
assert_eq!(
&hex!(
"00b45911edc6ec5e7d2261a48c46ab889b1858306271123e6f02dc914cf3c0352492e8a6b7a7925added527
Expand All @@ -358,7 +358,7 @@ fn decode_rsa_4096_openssh() {
4814140f75cac08079431043222fb91f075d76be55cbe138e3b99a605c561c49dea50e253c8306c4f4f77d9
96f898db64c5d8a0a15c6efa28b0934bf0b6f2b01950d877230fe4401078420fd6dd3"
),
rsa_keypair.public.n().as_bytes(),
rsa_keypair.public().n().as_bytes(),
);
assert_eq!(
&hex!(
Expand All @@ -375,7 +375,7 @@ fn decode_rsa_4096_openssh() {
822cdcf88b44cd6aea8bfc646ce52f9d05e7e867a32d462e35a163c15b7df00e9c0a870345b86e7882971b4
d79d42507ade7c26e6db29f52fbe58430f915c554145faa950ae6b6e4f87bf24a61"
),
rsa_keypair.private.d.as_bytes()
rsa_keypair.private().d().as_bytes()
);
assert_eq!(
&hex!(
Expand All @@ -386,7 +386,7 @@ fn decode_rsa_4096_openssh() {
235833fdc93947a2b4ed45d43df51087d91d59eb0bf09fe6f45036b23c944addce2976b805425c6841129be
5b17c4dcc41d62daa053d06a1fbfd3c20543a63066ad69933ae64538c305ae645d81557a6f3c9"
),
rsa_keypair.private.iqmp.as_bytes()
rsa_keypair.private().iqmp().as_bytes()
);
assert_eq!(
&hex!(
Expand All @@ -397,7 +397,7 @@ fn decode_rsa_4096_openssh() {
0613082e7d1830f936e29c7865c2b8acd30870dd20679788e0b2aaa2285d35ea7347c4083e2ee9c92dcb11e
ea114245c5f22d7afeb9d51cbc0ca17116261fac8a8f3c3054da1f53ad297f8ce184663ec4e617d"
),
rsa_keypair.private.p.as_bytes()
rsa_keypair.private().p().as_bytes()
);
assert_eq!(
&hex!(
Expand All @@ -408,7 +408,7 @@ fn decode_rsa_4096_openssh() {
3d09881ed7fa5f49b374bcab97dafa067e8eb63bc9ddf2668bf3ebb2bb585d7b12ff591e6ff34889196b9e5
293809f168d681bb7b09680fef093c8a28ef0d25568fce4ab5e879fee21a7525ac08caf9efa2d8f"
),
rsa_keypair.private.q.as_bytes()
rsa_keypair.private().q().as_bytes()
);
assert_eq!("[email protected]", key.comment());
}
Expand Down

0 comments on commit a4cb46b

Please sign in to comment.