Skip to content

Commit 74ba29d

Browse files
committed
feat: add a feature toggle for rsa
The `rsa` crate has an [open CVE](https://www.cvedetails.com/cve/CVE-2023-49092/). which seems [non-trivial to fix](RustCrypto/RSA#19). There has been some recent progress, but it's still unclear if it will actually fix the issue. I'm actively working on getting native SSH support in gitoxide [here](GitoxideLabs/gitoxide#2081) and this particular CVE may become an issue for that integration. This commit adds a feature toggle to enable/disable the `rsa` dependency. This also fixes a race condition in the `test_agent` test.
1 parent 5c3ac6e commit 74ba29d

File tree

8 files changed

+67
-26
lines changed

8 files changed

+67
-26
lines changed

Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,12 @@ home = "0.5"
1515
hmac = "0.12"
1616
log = "0.4.11"
1717
rand = "0.8"
18-
rsa = "0.9"
1918
sha1 = { version = "0.10.5", features = ["oid"] }
2019
sha2 = { version = "0.10.6", features = ["oid"] }
2120
signature = "2.2"
2221
ssh-encoding = { version = "0.2", features = ["bytes"] }
2322
ssh-key = { version = "=0.6.11", features = [
2423
"ed25519",
25-
"rsa",
26-
"rsa-sha1",
2724
"p256",
2825
"p384",
2926
"p521",

russh/Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ version = "0.53.0"
1313
rust-version = "1.75"
1414

1515
[features]
16-
default = ["flate2", "aws-lc-rs"]
16+
default = ["flate2", "aws-lc-rs", "rsa"]
1717
aws-lc-rs = ["dep:aws-lc-rs"]
1818
async-trait = ["dep:async-trait"]
1919
legacy-ed25519-pkcs8-parser = ["yasna"]
@@ -22,6 +22,7 @@ des = ["dep:des"]
2222
# Danger: DSA algorithm is insecure.
2323
dsa = ["ssh-key/dsa"]
2424
ring = ["dep:ring"]
25+
rsa = ["dep:rsa", "dep:pkcs1", "ssh-key/rsa", "ssh-key/rsa-sha1"]
2526
_bench = ["dep:criterion"]
2627

2728
[dependencies]
@@ -60,13 +61,13 @@ p256 = { version = "0.13", features = ["ecdh"] }
6061
p384 = { version = "0.13", features = ["ecdh"] }
6162
p521 = { version = "0.13", features = ["ecdh"] }
6263
pbkdf2 = "0.12"
63-
pkcs1 = "0.7"
64+
pkcs1 = { version = "0.7", optional = true }
6465
pkcs5 = "0.7"
6566
pkcs8 = { version = "0.10", features = ["pkcs5", "encryption"] }
6667
rand_core = { version = "0.6.4", features = ["getrandom", "std"] }
6768
rand.workspace = true
6869
ring = { version = "0.17.14", optional = true }
69-
rsa.workspace = true
70+
rsa = { version = "0.9", optional = true }
7071
russh-cryptovec = { version = "0.52.0", path = "../cryptovec", features = [
7172
"ssh-encoding",
7273
] }

russh/src/helpers.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
use std::fmt::Debug;
22

33
use ssh_encoding::{Decode, Encode};
4-
use ssh_key::private::KeypairData;
5-
use ssh_key::Algorithm;
64

75
#[doc(hidden)]
86
pub trait EncodedExt {
@@ -68,8 +66,9 @@ pub use map_err;
6866
#[doc(hidden)]
6967
pub fn sign_with_hash_alg(key: &PrivateKeyWithHashAlg, data: &[u8]) -> ssh_key::Result<Vec<u8>> {
7068
Ok(match key.key_data() {
71-
KeypairData::Rsa(rsa_keypair) => {
72-
let Algorithm::Rsa { hash } = key.algorithm() else {
69+
#[cfg(feature = "rsa")]
70+
ssh_key::private::KeypairData::Rsa(rsa_keypair) => {
71+
let ssh_key::Algorithm::Rsa { hash } = key.algorithm() else {
7372
unreachable!();
7473
};
7574
signature::Signer::try_sign(&(rsa_keypair, hash), data)?.encoded()?

russh/src/keys/format/mod.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
use std::convert::TryInto;
21
use std::io::Write;
32

43
use data_encoding::{BASE64_MIME, HEXLOWER_PERMISSIVE};
5-
use pkcs1::DecodeRsaPrivateKey;
6-
use ssh_key::private::RsaKeypair;
74
use ssh_key::PrivateKey;
85

96
use super::is_base64_char;
@@ -37,6 +34,7 @@ pub enum Encryption {
3734

3835
#[derive(Clone, Debug)]
3936
enum Format {
37+
#[cfg(feature = "rsa")]
4038
Rsa,
4139
Openssh,
4240
Pkcs5Encrypted(Encryption),
@@ -76,8 +74,18 @@ pub fn decode_secret_key(secret: &str, password: Option<&str>) -> Result<Private
7674
started = true;
7775
format = Some(Format::Openssh);
7876
} else if l == "-----BEGIN RSA PRIVATE KEY-----" {
79-
started = true;
80-
format = Some(Format::Rsa);
77+
#[cfg(feature = "rsa")]
78+
{
79+
started = true;
80+
format = Some(Format::Rsa);
81+
}
82+
#[cfg(not(feature = "rsa"))]
83+
{
84+
return Err(Error::UnsupportedKeyType {
85+
key_type_string: "RSA".to_string(),
86+
key_type_raw: vec![],
87+
});
88+
}
8189
} else if l == "-----BEGIN ENCRYPTED PRIVATE KEY-----" {
8290
started = true;
8391
format = Some(Format::Pkcs8Encrypted);
@@ -92,6 +100,7 @@ pub fn decode_secret_key(secret: &str, password: Option<&str>) -> Result<Private
92100
let secret = BASE64_MIME.decode(secret.as_bytes())?;
93101
match format {
94102
Some(Format::Openssh) => decode_openssh(&secret, password),
103+
#[cfg(feature = "rsa")]
95104
Some(Format::Rsa) => Ok(decode_rsa_pkcs1_der(&secret)?.into()),
96105
Some(Format::Pkcs5Encrypted(enc)) => decode_pkcs5(&secret, password, enc),
97106
Some(Format::Pkcs8Encrypted) | Some(Format::Pkcs8) => {
@@ -133,6 +142,10 @@ pub fn encode_pkcs8_pem_encrypted<W: Write>(
133142
Ok(())
134143
}
135144

136-
fn decode_rsa_pkcs1_der(secret: &[u8]) -> Result<RsaKeypair, Error> {
145+
#[cfg(feature = "rsa")]
146+
fn decode_rsa_pkcs1_der(secret: &[u8]) -> Result<ssh_key::private::RsaKeypair, Error> {
147+
use pkcs1::DecodeRsaPrivateKey;
148+
use std::convert::TryInto;
149+
137150
Ok(rsa::RsaPrivateKey::from_pkcs1_der(secret)?.try_into()?)
138151
}

russh/src/keys/format/pkcs5.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,18 @@ pub fn decode_pkcs5(
2929
}
3030
Encryption::Aes256Cbc(_) => unimplemented!(),
3131
};
32-
super::decode_rsa_pkcs1_der(&sec).map(Into::into)
32+
// TODO: presumably pkcs5 could contain non-RSA keys?
33+
#[cfg(feature = "rsa")]
34+
{
35+
super::decode_rsa_pkcs1_der(&sec).map(Into::into)
36+
}
37+
#[cfg(not(feature = "rsa"))]
38+
{
39+
Err(Error::UnsupportedKeyType {
40+
key_type_string: "RSA".to_string(),
41+
key_type_raw: vec![],
42+
})
43+
}
3344
} else {
3445
Err(Error::KeyIsEncrypted)
3546
}

russh/src/keys/format/pkcs8.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ fn pkcs8_pki_into_keypair_data(pki: PrivateKeyInfo<'_>) -> Result<KeypairData, E
4949
private: pk,
5050
}))
5151
}
52+
#[cfg(feature = "rsa")]
5253
pkcs1::ALGORITHM_OID => {
5354
let sk = &pkcs1::RsaPrivateKey::try_from(pki.private_key)?;
5455
let pk = rsa::RsaPrivateKey::from_components(
@@ -137,6 +138,7 @@ pub fn encode_pkcs8(key: &ssh_key::PrivateKey) -> Result<Vec<u8>, Error> {
137138
let sk: ed25519_dalek::SigningKey = pair.try_into()?;
138139
sk.to_pkcs8_der()?
139140
}
141+
#[cfg(feature = "rsa")]
140142
ssh_key::private::KeypairData::Rsa(ref pair) => {
141143
let sk: rsa::RsaPrivateKey = pair.try_into()?;
142144
sk.to_pkcs8_der()?

russh/src/keys/key.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
//
1515
use ssh_encoding::Decode;
1616
use ssh_key::public::KeyData;
17-
use ssh_key::{Algorithm, EcdsaCurve, HashAlg, PublicKey};
17+
use ssh_key::{Algorithm, EcdsaCurve, PublicKey};
1818

1919
use crate::keys::Error;
2020

@@ -109,12 +109,15 @@ pub const ALL_KEY_TYPES: &[Algorithm] = &[
109109
curve: EcdsaCurve::NistP521,
110110
},
111111
Algorithm::Ed25519,
112+
#[cfg(feature = "rsa")]
112113
Algorithm::Rsa { hash: None },
114+
#[cfg(feature = "rsa")]
113115
Algorithm::Rsa {
114-
hash: Some(HashAlg::Sha256),
116+
hash: Some(ssh_key::HashAlg::Sha256),
115117
},
118+
#[cfg(feature = "rsa")]
116119
Algorithm::Rsa {
117-
hash: Some(HashAlg::Sha512),
120+
hash: Some(ssh_key::HashAlg::Sha512),
118121
},
119122
Algorithm::SkEcdsaSha2NistP256,
120123
Algorithm::SkEd25519,

russh/src/keys/mod.rs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//!
66
//! The following example shows how to do all these in a single example:
77
//! start and SSH agent server, connect to it with a client, decipher
8-
//! an encrypted private key (the password is `b"blabla"`), send it to
8+
//! an encrypted ED25519 private key (the password is `b"blabla"`), send it to
99
//! the agent, and ask the agent to sign a piece of data
1010
//! (`b"Please sign this"`, below).
1111
//!
@@ -21,7 +21,7 @@
2121
//! }
2222
//! }
2323
//!
24-
//! const PKCS8_ENCRYPTED: &'static str = "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQITo1O0b8YrS0CAggA\nMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBtLH4T1KOfo1GGr7salhR8BIIE\n0KN9ednYwcTGSX3hg7fROhTw7JAJ1D4IdT1fsoGeNu2BFuIgF3cthGHe6S5zceI2\nMpkfwvHbsOlDFWMUIAb/VY8/iYxhNmd5J6NStMYRC9NC0fVzOmrJqE1wITqxtORx\nIkzqkgFUbaaiFFQPepsh5CvQfAgGEWV329SsTOKIgyTj97RxfZIKA+TR5J5g2dJY\nj346SvHhSxJ4Jc0asccgMb0HGh9UUDzDSql0OIdbnZW5KzYJPOx+aDqnpbz7UzY/\nP8N0w/pEiGmkdkNyvGsdttcjFpOWlLnLDhtLx8dDwi/sbEYHtpMzsYC9jPn3hnds\nTcotqjoSZ31O6rJD4z18FOQb4iZs3MohwEdDd9XKblTfYKM62aQJWH6cVQcg+1C7\njX9l2wmyK26Tkkl5Qg/qSfzrCveke5muZgZkFwL0GCcgPJ8RixSB4GOdSMa/hAMU\nkvFAtoV2GluIgmSe1pG5cNMhurxM1dPPf4WnD+9hkFFSsMkTAuxDZIdDk3FA8zof\nYhv0ZTfvT6V+vgH3Hv7Tqcxomy5Qr3tj5vvAqqDU6k7fC4FvkxDh2mG5ovWvc4Nb\nXv8sed0LGpYitIOMldu6650LoZAqJVv5N4cAA2Edqldf7S2Iz1QnA/usXkQd4tLa\nZ80+sDNv9eCVkfaJ6kOVLk/ghLdXWJYRLenfQZtVUXrPkaPpNXgD0dlaTN8KuvML\nUw/UGa+4ybnPsdVflI0YkJKbxouhp4iB4S5ACAwqHVmsH5GRnujf10qLoS7RjDAl\no/wSHxdT9BECp7TT8ID65u2mlJvH13iJbktPczGXt07nBiBse6OxsClfBtHkRLzE\nQF6UMEXsJnIIMRfrZQnduC8FUOkfPOSXc8r9SeZ3GhfbV/DmWZvFPCpjzKYPsM5+\nN8Bw/iZ7NIH4xzNOgwdp5BzjH9hRtCt4sUKVVlWfEDtTnkHNOusQGKu7HkBF87YZ\nRN/Nd3gvHob668JOcGchcOzcsqsgzhGMD8+G9T9oZkFCYtwUXQU2XjMN0R4VtQgZ\nrAxWyQau9xXMGyDC67gQ5xSn+oqMK0HmoW8jh2LG/cUowHFAkUxdzGadnjGhMOI2\nzwNJPIjF93eDF/+zW5E1l0iGdiYyHkJbWSvcCuvTwma9FIDB45vOh5mSR+YjjSM5\nnq3THSWNi7Cxqz12Q1+i9pz92T2myYKBBtu1WDh+2KOn5DUkfEadY5SsIu/Rb7ub\n5FBihk2RN3y/iZk+36I69HgGg1OElYjps3D+A9AjVby10zxxLAz8U28YqJZm4wA/\nT0HLxBiVw+rsHmLP79KvsT2+b4Diqih+VTXouPWC/W+lELYKSlqnJCat77IxgM9e\nYIhzD47OgWl33GJ/R10+RDoDvY4koYE+V5NLglEhbwjloo9Ryv5ywBJNS7mfXMsK\n/uf+l2AscZTZ1mhtL38efTQCIRjyFHc3V31DI0UdETADi+/Omz+bXu0D5VvX+7c6\nb1iVZKpJw8KUjzeUV8yOZhvGu3LrQbhkTPVYL555iP1KN0Eya88ra+FUKMwLgjYr\nJkUx4iad4dTsGPodwEP/Y9oX/Qk3ZQr+REZ8lg6IBoKKqqrQeBJ9gkm1jfKE6Xkc\nCog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux\n-----END ENCRYPTED PRIVATE KEY-----\n";
24+
//! const PKCS8_ENCRYPTED: &'static str = "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIGjMF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBAWQiUHKoocuxfoZ/hF\nYTjkAgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQ83d1d5/S2wz475uC\nCUrE7QRAvdVpD5e3zKH/MZjilWrMOm6cyI1LKBCssLztPyvOALtroLAPlp7WYWfu\n9Sncmm7u14n2lia7r1r5I3VBsVuH0g==\n-----END ENCRYPTED PRIVATE KEY-----\n";
2525
//!
2626
//! #[cfg(unix)]
2727
//! fn main() {
@@ -137,6 +137,7 @@ pub enum Error {
137137
#[error(transparent)]
138138
IO(#[from] std::io::Error),
139139

140+
#[cfg(feature = "rsa")]
140141
#[error("Rsa: {0}")]
141142
Rsa(#[from] rsa::Error),
142143

@@ -152,6 +153,7 @@ pub enum Error {
152153
Der(#[from] der::Error),
153154
#[error("Spki: {0}")]
154155
Spki(#[from] spki::Error),
156+
#[cfg(feature = "rsa")]
155157
#[error("Pkcs1: {0}")]
156158
Pkcs1(#[from] pkcs1::Error),
157159
#[error("Pkcs8: {0}")]
@@ -301,6 +303,7 @@ dP3jryYgvsCIBAA5jMWSjrmnOTXhidqcOy4xYCrAttzSnZ/cUadfBenL+DQq6neffw7j8r
301303
sJWR7W+cGvJ/vLsw==
302304
-----END OPENSSH PRIVATE KEY-----";
303305

306+
#[cfg(feature = "rsa")]
304307
const RSA_KEY: &str = "-----BEGIN OPENSSH PRIVATE KEY-----
305308
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
306309
NhAAAAAwEAAQAAAQEAuSvQ9m76zhRB4m0BUKPf17lwccj7KQ1Qtse63AOqP/VYItqEH8un
@@ -377,6 +380,7 @@ Z9w7lshQhqowtrbLDFw4rXAxZuE=
377380
// We can't encode attributes, skip test_decode_encode_symmetry.
378381
}
379382

383+
#[cfg(feature = "rsa")]
380384
#[test]
381385
fn test_decode_rsa_secret_key() {
382386
env_logger::try_init().unwrap_or(());
@@ -509,6 +513,7 @@ Ve0k2ddxoEsSE15H4lgNHM2iuYKzIqZJOReHRCTff6QGgMYPDqDfFfL1Hc1Ntql0pwAAAA
509513
parse_public_key_base64(key).unwrap();
510514
}
511515

516+
#[cfg(feature = "rsa")]
512517
#[test]
513518
fn test_nikao() {
514519
env_logger::try_init().unwrap_or(());
@@ -543,6 +548,7 @@ QaChXiDsryJZwsRnruvMRX9nedtqHrgnIsJLTXjppIhGhq5Kg4RQfOU=
543548
decode_secret_key(key, None).unwrap();
544549
}
545550

551+
#[cfg(feature = "rsa")]
546552
#[test]
547553
fn test_decode_pkcs8_rsa_secret_key() {
548554
// Generated using: ssh-keygen -t rsa -b 1024 -m pkcs8 -f $file
@@ -680,6 +686,7 @@ ocyR
680686
assert_eq!(original_key_bytes, encoded_key_bytes);
681687
}
682688

689+
#[cfg(feature = "rsa")]
683690
#[test]
684691
fn test_o01eg() {
685692
env_logger::try_init().unwrap_or(());
@@ -718,6 +725,7 @@ br8gXU8KyiY9sZVbmplRPF+ar462zcI2kt0a18mr0vbrdqp2eMjb37QDbVBJ+rPE
718725
decode_secret_key(key, Some("12345")).unwrap();
719726
}
720727

728+
#[cfg(feature = "rsa")]
721729
pub const PKCS8_RSA: &str = "-----BEGIN RSA PRIVATE KEY-----
722730
MIIEpAIBAAKCAQEAwBGetHjW+3bDQpVktdemnk7JXgu1NBWUM+ysifYLDBvJ9ttX
723731
GNZSyQKA4v/dNr0FhAJ8I9BuOTjYCy1YfKylhl5D/DiSSXFPsQzERMmGgAlYvU2U
@@ -747,13 +755,15 @@ xV/JrzLAwPoKk3bkqys3bUmgo6DxVC/6RmMwPQ0rmpw78kOgEej90g==
747755
-----END RSA PRIVATE KEY-----
748756
";
749757

758+
#[cfg(feature = "rsa")]
750759
#[test]
751760
fn test_pkcs8() {
752761
env_logger::try_init().unwrap_or(());
753762
println!("test");
754763
decode_secret_key(PKCS8_RSA, Some("blabla")).unwrap();
755764
}
756765

766+
#[cfg(feature = "rsa")]
757767
const PKCS8_ENCRYPTED: &str = "-----BEGIN ENCRYPTED PRIVATE KEY-----
758768
MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQITo1O0b8YrS0CAggA
759769
MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBtLH4T1KOfo1GGr7salhR8BIIE
@@ -815,6 +825,7 @@ Cog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux
815825
ssh_key::PublicKey::decode(&key).unwrap();
816826
}
817827

828+
#[cfg(feature = "rsa")]
818829
#[test]
819830
fn test_pkcs8_encrypted() {
820831
env_logger::try_init().unwrap_or(());
@@ -877,21 +888,21 @@ Cog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux
877888
}
878889

879890
#[tokio::test]
880-
#[cfg(unix)]
891+
#[cfg(all(unix, feature = "rsa"))]
881892
async fn test_client_agent_rsa() {
882893
let key = decode_secret_key(PKCS8_ENCRYPTED, Some("blabla")).unwrap();
883894
test_client_agent(key).await.expect("ssh-agent test failed")
884895
}
885896

886897
#[tokio::test]
887-
#[cfg(unix)]
898+
#[cfg(all(unix, feature = "rsa"))]
888899
async fn test_client_agent_openssh_rsa() {
889900
let key = decode_secret_key(RSA_KEY, None).unwrap();
890901
test_client_agent(key).await.expect("ssh-agent test failed")
891902
}
892903

893904
#[test]
894-
#[cfg(unix)]
905+
#[cfg(all(unix, feature = "rsa"))]
895906
fn test_agent() {
896907
env_logger::try_init().unwrap_or(());
897908
let dir = tempfile::tempdir().unwrap();
@@ -912,9 +923,10 @@ Cog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux
912923
}
913924
}
914925
let agent_path_ = agent_path.clone();
926+
let (tx, rx) = tokio::sync::oneshot::channel();
915927
core.spawn(async move {
916928
let mut listener = tokio::net::UnixListener::bind(&agent_path_).unwrap();
917-
929+
let _ = tx.send(());
918930
agent::server::serve(
919931
Incoming {
920932
listener: &mut listener,
@@ -923,9 +935,12 @@ Cog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux
923935
)
924936
.await
925937
});
938+
926939
let key = decode_secret_key(PKCS8_ENCRYPTED, Some("blabla")).unwrap();
927940
core.block_on(async move {
928941
let public = key.public_key();
942+
// make sure the listener created the file handle
943+
rx.await.unwrap();
929944
let stream = tokio::net::UnixStream::connect(&agent_path).await.unwrap();
930945
let mut client = agent::client::AgentClient::connect(stream);
931946
client

0 commit comments

Comments
 (0)