Skip to content

Commit

Permalink
Add minimal keyboard-interactive authentication
Browse files Browse the repository at this point in the history
This should suffice for regular password authentication, where the server asks
only one question.
  • Loading branch information
fmang authored and fwininger committed Jun 2, 2023
1 parent c903ec7 commit 99f5fbd
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 2 deletions.
2 changes: 2 additions & 0 deletions ext/libssh_ruby/libssh_ruby.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ void Init_libssh_ruby(void) {
rb_define_const(rb_mLibSSH, "AUTH_SUCCESS", INT2FIX(SSH_AUTH_SUCCESS));
/* Return value that indicates EAGAIN in nonblocking mode. */
rb_define_const(rb_mLibSSH, "AUTH_AGAIN", INT2FIX(SSH_AUTH_AGAIN));
/* Returned by ssh_userauth_kbdint when the server asks questions. */
rb_define_const(rb_mLibSSH, "AUTH_INFO", INT2FIX(SSH_AUTH_INFO));

/* Major version defined in header. */
rb_define_const(rb_mLibSSH, "LIBSSH_VERSION_MAJOR",
Expand Down
46 changes: 46 additions & 0 deletions ext/libssh_ruby/session.c
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,46 @@ static VALUE m_userauth_publickey_auto(VALUE self) {
return INT2FIX(args.rc);
}

/*
* @overload userauth_kbdint
* Try to authenticate through the "keyboard-interactive" method.
* @return [Fixnum]
* @see http://api.libssh.org/stable/group__libssh__session.html ssh_userauth_kbdint
*/
static VALUE m_userauth_kbdint(VALUE self) {
SessionHolder *holder = libssh_ruby_session_holder(self);
int rc = ssh_userauth_kbdint(holder->session, NULL, NULL);
RAISE_IF_ERROR(rc);
return INT2FIX(rc);
}

/*
* @overload userauth_kbdint_getnprompts
* Get the number of prompts (questions) the server has given.
* @return [Fixnum]
* @see http://api.libssh.org/stable/group__libssh__session.html ssh_userauth_kbdint_getnprompts
*/
static VALUE m_userauth_kbdint_getnpromts(VALUE self) {
SessionHolder *holder = libssh_ruby_session_holder(self);
int n = ssh_userauth_kbdint_getnprompts(holder->session);
return INT2FIX(n);
}

/*
* @overload userauth_kbdint_setanswer(i, answer)
* Set the answer to a prompt.
* @param [Fixnum] i Index of the prompt to answer.
* @param [String] answer
* @return [Fixnum]
* @see http://api.libssh.org/stable/group__libssh__session.html ssh_userauth_kbdint_setanswer
*/
static VALUE m_userauth_kbdint_setanswer(VALUE self, VALUE i, VALUE answer) {
SessionHolder *holder = libssh_ruby_session_holder(self);
int rc = ssh_userauth_kbdint_setanswer(holder->session, FIX2INT(i), StringValueCStr(answer));
RAISE_IF_ERROR(rc);
return Qnil;
}

/*
* @overload get_publickey
* Get the server public key from a session.
Expand Down Expand Up @@ -756,6 +796,12 @@ void Init_libssh_session() {
RUBY_METHOD_FUNC(m_userauth_publickey), 1);
rb_define_method(rb_cLibSSHSession, "userauth_publickey_auto",
RUBY_METHOD_FUNC(m_userauth_publickey_auto), 0);
rb_define_method(rb_cLibSSHSession, "userauth_kbdint",
RUBY_METHOD_FUNC(m_userauth_kbdint), 0);
rb_define_method(rb_cLibSSHSession, "userauth_kbdint_getnprompts",
RUBY_METHOD_FUNC(m_userauth_kbdint_getnpromts), 0);
rb_define_method(rb_cLibSSHSession, "userauth_kbdint_setanswer",
RUBY_METHOD_FUNC(m_userauth_kbdint_setanswer), 2);
rb_define_method(rb_cLibSSHSession, "get_publickey",
RUBY_METHOD_FUNC(m_get_publickey), 0);
rb_define_method(rb_cLibSSHSession, "write_knownhost",
Expand Down
34 changes: 33 additions & 1 deletion spec/integration/session_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
end

it 'returns available methods' do
expect(session.userauth_list).to match_array(%i[publickey password])
expect(session.userauth_list).to match_array(%i[publickey password interactive])
end
end
end
Expand Down Expand Up @@ -124,6 +124,38 @@
end
end

describe '#userauth_kbdint' do
before do
session.host = SshHelper.host
session.port = DockerHelper.port
session.user = SshHelper.user
session.connect
end

def kbdint(password)
loop do
rc = session.userauth_kbdint
return rc if rc != LibSSH::AUTH_INFO

nprompts = session.userauth_kbdint_getnprompts
expect(nprompts).to be <= 1
session.userauth_kbdint_setanswer(0, password) if nprompts == 1
end
end

context 'with wrong password' do
it 'is denied' do
expect(kbdint('12345')).to eq(LibSSH::AUTH_DENIED)
end
end

context 'with valid password' do
it 'access is granted' do
expect(kbdint(SshHelper.password)).to eq(LibSSH::AUTH_SUCCESS)
end
end
end

describe '#server_known' do
before do
session.host = SshHelper.host
Expand Down
2 changes: 1 addition & 1 deletion spec/sshd_config
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
AuthorizedKeysFile .ssh/authorized_keys

UsePAM yes
ChallengeResponseAuthentication no
KbdInteractiveAuthentication yes
PasswordAuthentication yes
PermitEmptyPasswords no
PermitRootLogin no
Expand Down

0 comments on commit 99f5fbd

Please sign in to comment.