Skip to content

Commit

Permalink
added best_supported_rsa_hash
Browse files Browse the repository at this point in the history
  • Loading branch information
Eugeny committed Jan 25, 2025
1 parent 72847a7 commit 8926f96
Show file tree
Hide file tree
Showing 12 changed files with 112 additions and 110 deletions.
8 changes: 7 additions & 1 deletion russh/examples/client_exec_interactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,13 @@ impl Session {
// use publickey authentication, with or without certificate
if openssh_cert.is_none() {
let auth_res = session
.authenticate_publickey(user, Arc::new(key_pair))
.authenticate_publickey(
user,
PrivateKeyWithHashAlg::new(
Arc::new(key_pair),
session.best_supported_rsa_hash().await?.flatten(),
),
)
.await?;

if !auth_res.success() {
Expand Down
8 changes: 7 additions & 1 deletion russh/examples/client_exec_simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,13 @@ impl Session {

let mut session = client::connect(config, addrs, sh).await?;
let auth_res = session
.authenticate_publickey(user, Arc::new(key_pair))
.authenticate_publickey(
user,
PrivateKeyWithHashAlg::new(
Arc::new(key_pair),
session.best_supported_rsa_hash().await.unwrap().flatten(),
),
)
.await?;

if !auth_res.success() {
Expand Down
6 changes: 2 additions & 4 deletions russh/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use thiserror::Error;
use tokio::io::{AsyncRead, AsyncWrite};

use crate::helpers::NameList;
use crate::keys::PrivateKeyWithHashAlg;
use crate::CryptoVec;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -191,10 +192,7 @@ pub enum Method {
password: String,
},
PublicKey {
key: Arc<PrivateKey>,
/// None = based on server-sig-algs
/// Some(None) = SHA1
hash_alg: Option<Option<HashAlg>>,
key: PrivateKeyWithHashAlg,
},
OpenSshCertificate {
key: Arc<PrivateKey>,
Expand Down
71 changes: 14 additions & 57 deletions russh/src/client/encrypted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,18 @@ use std::cell::RefCell;
use std::convert::TryInto;
use std::ops::Deref;
use std::str::FromStr;
use std::sync::Arc;

use bytes::Bytes;
use log::{debug, error, info, trace, warn};
use ssh_encoding::{Decode, Encode, Reader};
use ssh_key::{Algorithm, HashAlg, PrivateKey};
use ssh_key::Algorithm;

use super::IncomingSshPacket;
use crate::auth::AuthRequest;
use crate::cert::PublicKeyOrCertificate;
use crate::client::{Handler, Msg, Prompt, Reply, Session};
use crate::helpers::{map_err, sign_with_hash_alg, AlgorithmExt, EncodedExt, NameList};
use crate::keys::key::parse_public_key;
use crate::keys::PrivateKeyWithHashAlg;
use crate::parsing::{ChannelOpenConfirmation, ChannelType, OpenChannelMessage};
use crate::session::{Encrypted, EncryptedState, GlobalRequestResponse};
use crate::{
Expand Down Expand Up @@ -291,17 +289,15 @@ impl Session {
fn handle_server_sig_algs_ext(&mut self, r: &mut impl Reader) -> Result<(), Error> {
let algs = NameList::decode(r)?;
debug!("* server-sig-algs");
if let Some(ref mut enc) = self.common.encrypted {
enc.server_sig_algs = Some(
algs.0
.iter()
.filter_map(|x| Algorithm::from_str(x).ok())
.inspect(|x| {
debug!(" * {x:?}");
})
.collect::<Vec<_>>(),
);
}
self.server_sig_algs = Some(
algs.0
.iter()
.filter_map(|x| Algorithm::from_str(x).ok())
.inspect(|x| {
debug!(" * {x:?}");
})
.collect::<Vec<_>>(),
);
Ok(())
}

Expand Down Expand Up @@ -848,39 +844,6 @@ impl Session {
}

impl Encrypted {
fn pick_hash_alg_for_key(
&self,
key: Arc<PrivateKey>,
hash_alg_choice: Option<Option<HashAlg>>,
) -> Result<PrivateKeyWithHashAlg, Error> {
Ok(match hash_alg_choice {
Some(hash_alg) => PrivateKeyWithHashAlg::new(key.clone(), hash_alg)?,
None => {
if key.algorithm().is_rsa() {
PrivateKeyWithHashAlg::new(key.clone(), self.best_key_hash_alg())?
} else {
PrivateKeyWithHashAlg::new(key.clone(), None)?
}
}
})
}

fn best_key_hash_alg(&self) -> Option<HashAlg> {
if let Some(ref ssa) = self.server_sig_algs {
let possible_algs = [
Some(ssh_key::HashAlg::Sha512),
Some(ssh_key::HashAlg::Sha256),
None,
];
for alg in possible_algs.into_iter() {
if ssa.contains(&Algorithm::Rsa { hash: alg }) {
return alg;
}
}
}
None
}

fn write_auth_request(
&mut self,
user: &str,
Expand All @@ -905,12 +868,7 @@ impl Encrypted {
password.encode(&mut self.write)?;
true
}
auth::Method::PublicKey {
ref key,
ref hash_alg,
} => {
let key = self.pick_hash_alg_for_key(key.clone(), *hash_alg)?;

auth::Method::PublicKey { ref key } => {
user.encode(&mut self.write)?;
"ssh-connection".encode(&mut self.write)?;
"publickey".encode(&mut self.write)?;
Expand Down Expand Up @@ -994,13 +952,12 @@ impl Encrypted {
buffer: &mut CryptoVec,
) -> Result<(), crate::Error> {
match method {
auth::Method::PublicKey { key, hash_alg } => {
let key = self.pick_hash_alg_for_key(key.clone(), *hash_alg)?;
auth::Method::PublicKey { key } => {
let i0 =
self.client_make_to_sign(user, &PublicKeyOrCertificate::from(&key), buffer)?;
self.client_make_to_sign(user, &PublicKeyOrCertificate::from(key), buffer)?;

// Extend with self-signature.
sign_with_hash_alg(&key, buffer)?.encode(&mut *buffer)?;
sign_with_hash_alg(key, buffer)?.encode(&mut *buffer)?;

push_packet!(self.write, {
#[allow(clippy::indexing_slicing)] // length checked
Expand Down
82 changes: 51 additions & 31 deletions russh/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ use kex::ClientKex;
use log::{debug, error, trace};
use russh_util::time::Instant;
use ssh_encoding::Decode;
use ssh_key::{Certificate, HashAlg, PrivateKey, PublicKey};
use ssh_key::{Algorithm, Certificate, HashAlg, PrivateKey, PublicKey};
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, ReadHalf, WriteHalf};
use tokio::pin;
use tokio::sync::mpsc::{
Expand All @@ -59,6 +59,7 @@ pub use crate::auth::AuthResult;
use crate::channels::{Channel, ChannelMsg, ChannelRef, WindowSizeRef};
use crate::cipher::{self, clear, OpeningKey};
use crate::kex::{KexCause, KexProgress, SessionKexState};
use crate::keys::PrivateKeyWithHashAlg;
use crate::msg::{is_kex_msg, validate_server_msg_strict_kex};
use crate::session::{CommonSession, EncryptedState, GlobalRequestResponse, NewKeys};
use crate::ssh_read::SshRead;
Expand Down Expand Up @@ -90,6 +91,7 @@ pub struct Session {
inbound_channel_sender: Sender<Msg>,
inbound_channel_receiver: Receiver<Msg>,
open_global_requests: VecDeque<GlobalRequestResponse>,
server_sig_algs: Option<Vec<Algorithm>>,
}

impl Drop for Session {
Expand Down Expand Up @@ -180,6 +182,9 @@ pub enum Msg {
},
Channel(ChannelId, ChannelMsg),
Rekey,
GetServerSigAlgs {
reply_channel: oneshot::Sender<Option<Vec<Algorithm>>>,
},
}

impl From<(ChannelId, ChannelMsg)> for Msg {
Expand Down Expand Up @@ -359,44 +364,22 @@ impl<H: Handler> Handle<H> {
}

/// Perform public key-based SSH authentication.
/// This method will automatically select the best hash function
/// if the server supports the `server-sig-algs` protocol extension
/// and will fall back to SHA-1 otherwise.
///
/// For RSA keys, you'll need to decide on which hash algorithm to use.
/// This is the difference between what is also known as
/// `ssh-rsa`, `rsa-sha2-256`, and `rsa-sha2-512` "keys" in OpenSSH.
/// You can use [Handle::best_supported_rsa_hash] to automatically
/// figure out the best hash algorithm for RSA keys.
pub async fn authenticate_publickey<U: Into<String>>(
&mut self,
user: U,
key: Arc<PrivateKey>,
key: PrivateKeyWithHashAlg,
) -> Result<AuthResult, crate::Error> {
let user = user.into();
self.sender
.send(Msg::Authenticate {
user,
method: auth::Method::PublicKey {
key,
hash_alg: None,
},
})
.await
.map_err(|_| crate::Error::SendError)?;
self.wait_recv_reply().await
}

/// Perform public key-based SSH authentication
/// with an explicit hash algorithm selection (for RSA keys).
pub async fn authenticate_publickey_with_hash<U: Into<String>>(
&mut self,
user: U,
key: Arc<PrivateKey>,
hash_alg: Option<HashAlg>,
) -> Result<AuthResult, crate::Error> {
let user = user.into();
self.sender
.send(Msg::Authenticate {
user,
method: auth::Method::PublicKey {
key,
hash_alg: Some(hash_alg),
},
method: auth::Method::PublicKey { key },
})
.await
.map_err(|_| crate::Error::SendError)?;
Expand Down Expand Up @@ -507,6 +490,39 @@ impl<H: Handler> Handle<H> {
}
}

/// Returns the best RSA hash algorithm supported by the server,
/// as indicated by the `server-sig-algs` extension.
/// If the server does not support the extension,
/// `None` is returned. In this case you may still attempt an authentication
/// with `rsa-sha2-256` or `rsa-sha2-512` and hope for the best.
/// If the server supports the extension, but does not support `rsa-sha2-*`,
/// `Some(None)` is returned.
pub async fn best_supported_rsa_hash(&self) -> Result<Option<Option<HashAlg>>, Error> {
let (sender, receiver) = oneshot::channel();

self.sender
.send(Msg::GetServerSigAlgs {
reply_channel: sender,
})
.await
.map_err(|_| crate::Error::SendError)?;

if let Some(ssa) = receiver.await.map_err(|_| Error::Inconsistent)? {
let possible_algs = [
Some(ssh_key::HashAlg::Sha512),
Some(ssh_key::HashAlg::Sha256),
None,
];
for alg in possible_algs.into_iter() {
if ssa.contains(&Algorithm::Rsa { hash: alg }) {
return Ok(Some(alg));
}
}
}

Ok(None)
}

/// Request a session channel (the most basic type of
/// channel). This function returns `Some(..)` immediately if the
/// connection is authenticated, but the channel only becomes
Expand Down Expand Up @@ -896,6 +912,7 @@ impl Session {
pending_reads: Vec::new(),
pending_len: 0,
open_global_requests: VecDeque::new(),
server_sig_algs: None,
}
}

Expand Down Expand Up @@ -1255,6 +1272,9 @@ impl Session {
}
Msg::Channel(id, ChannelMsg::Close) => self.close(id)?,
Msg::Rekey => self.initiate_rekey()?,
Msg::GetServerSigAlgs { reply_channel } => {
let _ = reply_channel.send(self.server_sig_algs.clone());
}
msg => {
// should be unreachable, since the receiver only gets
// messages from methods implemented within russh
Expand Down
2 changes: 1 addition & 1 deletion russh/src/keys/agent/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ impl<S: AsyncRead + AsyncWrite + Send + Unpin + 'static, A: Agent + Send + Sync
writebuf.push(msg::SIGN_RESPONSE);
let data = Bytes::decode(r)?;

sign_with_hash_alg(&PrivateKeyWithHashAlg::new(key, None)?, &data)?.encode(writebuf)?;
sign_with_hash_alg(&PrivateKeyWithHashAlg::new(key, None), &data)?.encode(writebuf)?;

let len = writebuf.len();
BigEndian::write_u32(writebuf, (len - 4) as u32);
Expand Down
12 changes: 6 additions & 6 deletions russh/src/keys/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,16 @@ mod private_key_with_hash_alg {
impl PrivateKeyWithHashAlg {
/// Direct constructor.
///
/// Will fail if you specify a `hash_alg` for a key type other than RSA.
/// For RSA, passing `None` is mapped to the legacy `sha-rsa` (SHA-1).
/// For other keys, `hash_alg` is ignored.
pub fn new(
key: Arc<crate::keys::PrivateKey>,
hash_alg: Option<crate::keys::HashAlg>,
) -> Result<Self, crate::keys::Error> {
if hash_alg.is_some() && !key.algorithm().is_rsa() {
return Err(crate::keys::Error::InvalidParameters);
mut hash_alg: Option<crate::keys::HashAlg>,
) -> Self {
if !key.algorithm().is_rsa() {
hash_alg = None;
}
Ok(Self { key, hash_alg })
Self { key, hash_alg }
}

pub fn algorithm(&self) -> Algorithm {
Expand Down
3 changes: 1 addition & 2 deletions russh/src/server/kex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,7 @@ impl ServerKex {
// Hash signature
debug!("signing with key {:?}", key);
let signature = sign_with_hash_alg(
&PrivateKeyWithHashAlg::new(Arc::new(key.clone()), signature_hash_alg)
.map_err(Into::into)?,
&PrivateKeyWithHashAlg::new(Arc::new(key.clone()), signature_hash_alg),
&hash,
)
.map_err(Into::into)?;
Expand Down
3 changes: 0 additions & 3 deletions russh/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ use std::num::Wrapping;
use byteorder::{BigEndian, ByteOrder};
use log::{debug, trace};
use ssh_encoding::Encode;
use ssh_key::Algorithm;
use tokio::sync::oneshot;

use crate::cipher::OpeningKey;
Expand Down Expand Up @@ -53,7 +52,6 @@ pub(crate) struct Encrypted {
pub client_compression: crate::compression::Compression,
pub decompress: crate::compression::Decompress,
pub rekey_wanted: bool,
pub server_sig_algs: Option<Vec<Algorithm>>,
}

pub(crate) struct CommonSession<Config> {
Expand Down Expand Up @@ -153,7 +151,6 @@ impl<C> CommonSession<C> {
client_compression: newkeys.names.client_compression,
decompress: crate::compression::Decompress::None,
rekey_wanted: false,
server_sig_algs: None,
});
self.remote_to_local = newkeys.cipher.remote_to_local;
self.packet_writer
Expand Down
Loading

0 comments on commit 8926f96

Please sign in to comment.