Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keyboard interactive auth #147

Merged
merged 7 commits into from
Mar 14, 2023
Merged

Conversation

joshbenz
Copy link
Contributor

@joshbenz joshbenz commented Mar 6, 2023

So I had some time to work on this. I think I am in little over my head here, but maybe you can point me in the right direction. I may just need to stare at the codebase longer, but this is what I have so far.

So it sends a Keyboard-Interactive request and receives the AuthInfoRequest that contains the challenges. The user is given a trait to implement in order to fulfill those challenges/prompts. The results of are then sent back to the Auth request and written to the buffer. I think I am definitely missing something important here because after that the session and handle gets dropped. What should happen next is that the server send another AuthInfoRequest, AuthSuccess or AuthFailure. It's almost like client_encrypted_read needs to be called again.

Another thing is that USERAUTH_PK_OK and USERAUTH_INFO_REQUEST share the same number. For the sake of testing just keyboard interactive, I have it in an else-if before PK_OK. At some point I need to differentiate between the two. I noticed that auth_request.current can hold a CurrentRequest that has a KeyboardInteractive variant, but I am unsure of where that should get set.

I appreciate any direction you can give me. I think I don't yet have a clear understanding of the over flow of the code yet.

@Eugeny
Copy link
Owner

Eugeny commented Mar 7, 2023

Hey, great work! Random disconnects are usually caused by errors inside client_read_encrypted, you can apply this to make them show up in the debug log:

diff --git a/russh/src/client/mod.rs b/russh/src/client/mod.rs
index b4b62d5..e5ad07a 100644
--- a/russh/src/client/mod.rs
+++ b/russh/src/client/mod.rs
@@ -77,6 +77,7 @@
 
 use std::cell::RefCell;
 use std::collections::HashMap;
+use std::fmt::Debug;
 use std::pin::Pin;
 use std::sync::Arc;
 
@@ -1139,7 +1140,10 @@ async fn reply<H: Handler>(
             session.common.kex = Some(kex);
             Ok((handler, session))
         }
-        None => session.client_read_encrypted(handler, buf).await,
+        None => session.client_read_encrypted(handler, buf).await.map_err(|err| {
+            debug!("client_read_encrypted error: {:?}", err);
+            err
+        }),
     }
 }
@@ -1197,7 +1201,7 @@ impl Default for Config {
 /// Note: this is an `async_trait`. Click `[source]` on the right to see actual async function definitions.
 #[async_trait]
 pub trait Handler: Sized + Send {
-    type Error: From<crate::Error> + Send;
+    type Error: From<crate::Error> + Debug + Send;
 
     /// Called when the server sends us an authentication banner. This
     /// is usually meant to be shown to the user, see

I think that the current API is very limiting though. While passing a callback is the way it's done in most libraries, here it would prevent the user from doing anything async during authentication, as well as retain ownership of the handler for the whole duration. I think something like this could work better:

enum KeyboardInteractiveAuthResponse {
    Success,
    Failure,
    InfoRequest {
        prompts: Vec<String>
    }
}

pub async fn authenticate_keyboard_interactive_start(
    &mut self,
    user: U,
    ...
) -> Result<KeyboardInteractiveAuthResponse, ..> {
}

pub async fn authenticate_keyboard_interactive_respond(
    &mut self,
    responses: Vec<String>
    ...
) -> Result<KeyboardInteractiveAuthResponse, ..> {
}

It allows the user to bail out of k-i authentication, keep ownership of the handler, do async stuff and more closely resembles the protocol.

What do you think?

@joshbenz
Copy link
Contributor Author

joshbenz commented Mar 7, 2023

Thanks for the feedback and tips. Yes I definitely see what you mean. Especially since the purpose of keyboard-interactive is expand what authentication methods are possible. Your approach definitely seems simpler to implement and I think it fits with russh better as well. We will just need to inform the user (in the docs or something) that they should use authenticate_keyboard_interactive_respond in some sort of loop since the server can respond with InfoRequest as many times as it wants. I'll have another go at it with that approach next chance I get!

…e user more flexibility by having them deal with processing the chellenges
@joshbenz
Copy link
Contributor Author

joshbenz commented Mar 8, 2023

I think I am still missing something important. After writing the responses, the next tokio::select gives me SELECT ERROR IO(Custom { kind: UnexpectedEof, error: "early eof" }). Particularly it happens at cipher::read at steam.read_exact, which I believe is reading an ID? I'm not really sure what I am missing that would cause that.

@Eugeny
Copy link
Owner

Eugeny commented Mar 9, 2023

Can you check the ssh server debug output? You can start a separate instance in debug mode with /usr/sbin/sshd -d -o Port=3333 -f /etc/ssh/sshd_config

…method and construct the corresponding auth_request containing the correct CurrentRequest. This is used later to determine wheter the current auth_request is public key or keyboard_interactive
@joshbenz
Copy link
Contributor Author

The issue turned out to be my test client not calling authenticte_keyboard_interactive_respond() enough times. Oops

@joshbenz joshbenz changed the title (WIP) Keyboard interactive auth Keyboard interactive auth Mar 10, 2023
@joshbenz
Copy link
Contributor Author

closes #147

@Eugeny Eugeny merged commit e2e6180 into Eugeny:master Mar 14, 2023
@Eugeny
Copy link
Owner

Eugeny commented Mar 14, 2023

Looks great, thank you so much!

@Eugeny
Copy link
Owner

Eugeny commented Mar 14, 2023

@all-contributors please add @joshbenz for code

@allcontributors
Copy link
Contributor

@Eugeny

I've put up a pull request to add @joshbenz! 🎉

@joshbenz
Copy link
Contributor Author

Thanks for working with me through the process and letting me contribute! I really appreciate it. And thank you for taking on this fork of Thrussh. Hopefully I can contribute more in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants