Skip to content

Commit 6748879

Browse files
joshbenzEugeny
authored andcommitted
(WIP) Keyboard Interactive Auth
1 parent 6049d51 commit 6748879

File tree

3 files changed

+125
-2
lines changed

3 files changed

+125
-2
lines changed

russh/src/auth.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ pub enum Method {
100100
Password { password: String },
101101
PublicKey { key: Arc<key::KeyPair> },
102102
FuturePublicKey { key: key::PublicKey },
103+
KeyboardInteractive { submethods: String },
103104
// Hostbased,
104105
}
105106

@@ -150,3 +151,4 @@ pub enum CurrentRequest {
150151
submethods: String,
151152
},
152153
}
154+

russh/src/client/encrypted.rs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@
1313
// limitations under the License.
1414
//
1515
use std::cell::RefCell;
16+
use std::convert::TryInto;
1617

1718
use russh_cryptovec::CryptoVec;
1819
use russh_keys::encoding::{Encoding, Reader};
1920
use russh_keys::key::parse_public_key;
2021
use tokio::sync::mpsc::unbounded_channel;
2122
use log::{debug, error, info, trace, warn};
2223

23-
use crate::client::{Handler, Msg, Reply, Session};
24+
use crate::client::{Handler, Msg, Reply, Session, Prompt};
2425
use crate::key::PubKey;
2526
use crate::negotiation::{Named, Select};
2627
use crate::parsing::{ChannelOpenConfirmation, ChannelType, OpenChannelMessage};
@@ -230,13 +231,51 @@ impl Session {
230231
if no_more_methods {
231232
return Err(crate::Error::NoAuthMethod.into());
232233
}
233-
} else if buf.first() == Some(&msg::USERAUTH_PK_OK) {
234+
235+
} else if buf.first() == Some(&msg::USERAUTH_INFO_REQUEST) {
236+
let mut r = buf.reader(1);
237+
238+
// read fields
239+
let name = String::from_utf8_lossy(r.read_string().map_err(crate::Error::from)?).to_string();
240+
let instructions = String::from_utf8_lossy(r.read_string().map_err(crate::Error::from)?).to_string();
241+
let _lang = r.read_string().map_err(crate::Error::from)?;
242+
let n_prompts = r.read_u32().map_err(crate::Error::from)?;
243+
244+
// read prompts
245+
let mut prompts = Vec::with_capacity(n_prompts.try_into().unwrap_or(0));
246+
for _i in 0..n_prompts {
247+
let prompt = String::from_utf8_lossy(r.read_string().map_err(crate::Error::from)?);
248+
let echo = r.read_byte().map_err(crate::Error::from)? != 0;
249+
prompts.push(Prompt{prompt: prompt.to_string(), echo});
250+
}
251+
252+
// send challenges to call handler
253+
self.sender
254+
.send(Reply::AuthInfoRequest { name, instructions, prompts })
255+
.map_err(|_| crate::Error::SendError)?;
256+
257+
// wait for response from handler
258+
let responses = loop {
259+
match self.receiver.recv().await {
260+
Some(Msg::AuthInfoResponse{ responses }) => break responses,
261+
_ => {}
262+
}
263+
};
264+
// write responses
265+
enc.client_send_auth_response(&responses)?;
266+
267+
return Ok((client, self));
268+
} else if buf.first() == Some(&msg::USERAUTH_INFO_REQUEST_OR_USERAUTH_PK_OK) {
234269
debug!("userauth_pk_ok");
235270
if let Some(auth::CurrentRequest::PublicKey {
236271
ref mut sent_pk_ok, ..
237272
}) = auth_request.current
238273
{
239274
*sent_pk_ok = true;
275+
} else if let Some(auth::CurrentRequest::KeyboardInteractive { ref submethods }) = auth_request.current {
276+
277+
} else {
278+
unreachable!()
240279
}
241280

242281
match self.common.auth_method.take() {
@@ -766,6 +805,15 @@ impl Encrypted {
766805
key.push_to(&mut self.write);
767806
true
768807
}
808+
auth::Method::KeyboardInteractive{ ref submethods } => {
809+
debug!("KEYBOARD INTERACTIVE");
810+
self.write.extend_ssh_string(user.as_bytes());
811+
self.write.extend_ssh_string(b"ssh-connection");
812+
self.write.extend_ssh_string(b"keyboard-interactive");
813+
self.write.extend_ssh_string(b""); // lang tag is deprecated. Should be empty
814+
self.write.extend_ssh_string(submethods.as_bytes());
815+
true
816+
}
769817
}
770818
})
771819
}
@@ -810,4 +858,19 @@ impl Encrypted {
810858
}
811859
Ok(())
812860
}
861+
862+
//TODO
863+
fn client_send_auth_response(
864+
&mut self,
865+
responses: &[String]
866+
) -> Result<(), crate::Error> {
867+
push_packet!(self.write, {
868+
self.write.push(msg::USERAUTH_INFO_RESPONSE);
869+
self.write.push(responses.len().try_into().unwrap_or(0)); // number of responses
870+
for r in responses {
871+
self.write.extend_ssh_string(r.as_bytes()); // write the reponses
872+
}
873+
});
874+
Ok(())
875+
}
813876
}

russh/src/client/mod.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,15 @@ mod encrypted;
109109
mod kex;
110110
mod session;
111111

112+
#[derive(Debug)]
113+
pub struct Prompt {
114+
pub prompt: String,
115+
pub echo: bool,
116+
}
117+
pub trait KeyboardInteractiveChallenge {
118+
fn prompt(&mut self, name: String, instructions: String, prompts: Vec<Prompt>) -> Vec<String>;
119+
}
120+
112121
/// Actual client session's state.
113122
///
114123
/// It is in charge of multiplexing and keeping track of various channels
@@ -132,6 +141,7 @@ impl Drop for Session {
132141
}
133142
}
134143

144+
135145
#[derive(Debug)]
136146
#[allow(clippy::large_enum_variant)]
137147
enum Reply {
@@ -142,6 +152,11 @@ enum Reply {
142152
key: key::PublicKey,
143153
data: CryptoVec,
144154
},
155+
AuthInfoRequest {
156+
name: String,
157+
instructions: String,
158+
prompts: Vec<Prompt>,
159+
}
145160
}
146161

147162
#[derive(Debug)]
@@ -150,6 +165,9 @@ pub enum Msg {
150165
user: String,
151166
method: auth::Method,
152167
},
168+
AuthInfoResponse {
169+
responses: Vec<String>,
170+
},
153171
Signed {
154172
data: CryptoVec,
155173
},
@@ -254,6 +272,46 @@ impl<H: Handler> Handle<H> {
254272
self.wait_recv_reply().await
255273
}
256274

275+
/// Perform keyboard-interactive-based SSH authentication.
276+
///
277+
/// * `submethods` - Hnts to the server the preffered methods to be used for authentication
278+
pub async fn authenticate_keyboard_interactive<U: Into<String>, S: Into<Option<String>>, K: KeyboardInteractiveChallenge>(
279+
&mut self,
280+
user: U,
281+
handler: K,
282+
submethods: S,
283+
) -> Result<bool, crate::Error> {
284+
let user = user.into();
285+
self.sender
286+
.send(Msg::Authenticate {
287+
user: user.clone(),
288+
method: auth::Method::KeyboardInteractive {
289+
submethods: submethods.into().unwrap_or("".to_owned())
290+
},
291+
})
292+
.await
293+
.map_err(|_| crate::Error::SendError)?;
294+
self.wait_recv_keyboard_interactive_reply(handler, user).await
295+
}
296+
297+
async fn wait_recv_keyboard_interactive_reply<K: KeyboardInteractiveChallenge>(&mut self, mut challenge_handler: K, user: String) -> Result<bool, crate::Error> {
298+
loop {
299+
match self.receiver.recv().await {
300+
Some(Reply::AuthSuccess) => return Ok(true),
301+
Some(Reply::AuthFailure) => return Ok(false),
302+
Some(Reply::AuthInfoRequest { name, instructions, prompts }) => {
303+
let responses = challenge_handler.prompt(name, instructions, prompts);
304+
self.sender
305+
.send(Msg::AuthInfoResponse { responses })
306+
.await
307+
.map_err(|_| crate::Error::SendError)?;
308+
}
309+
None => return Ok(false),
310+
_ => {}
311+
}
312+
}
313+
}
314+
257315
async fn wait_recv_reply(&mut self) -> Result<bool, crate::Error> {
258316
loop {
259317
match self.receiver.recv().await {

0 commit comments

Comments
 (0)