David Woodhouse
2003-Mar-02 09:43 UTC
[RFC][PATCH] Require S/KEY before other authentication methods.
I need a way to make sshd require S/KEY authentication to succeed before allowing either password or public-key authentication. Currently, we can only have S/KEY+password, by using PAM for authentication, and configuring PAM accordingly. But PAM of course can't handle SSH public keys. I thought for a while that ideally we could actually use PAM to tell sshd what methods of authentication to accept at each stage... require pam_ssh_skey.so sufficient pam_ssh_publickey.so sufficient pam_ssh_password.so ...etc. But PAM doesn't actually let us work like that, so it'd end up being something more like... require pam_ssh.so methods=skey require pam_ssh.so methods=publickey,password ...and I suspect it's overkill anyway. I can't really see many other possible combinations that people are likely to want. Instead of going further down this path, I implemented a simple special-case 'ChallengeResponseAuthenticationFirst' option for sshd, which makes it do what I require, offering only skey to a client at first, then offering other auth methods after skey has succeeded. Is there any point in trying to make it more generic? What other setups are people likely to want? Index: auth2-kbdint.c ==================================================================RCS file: /cvs/openssh/auth2-kbdint.c,v retrieving revision 1.1 diff -u -p -r1.1 auth2-kbdint.c --- auth2-kbdint.c 6 Jun 2002 20:27:56 -0000 1.1 +++ auth2-kbdint.c 1 Mar 2003 17:37:41 -0000 @@ -50,7 +50,13 @@ userauth_kbdint(Authctxt *authctxt) authenticated = auth2_challenge(authctxt, devs); #ifdef USE_PAM - if (authenticated == 0 && options.pam_authentication_via_kbd_int) + /* In the normal case, try PAM if challenge-response failed. + However, if this was a prerequisite challenge-response + authentication attempt, and PAM auth is permitted as a + secondary method, then force the client to come back + with a second attempt instead. */ + if (!options.challenge_response_authentication_first && + authenticated == 0 && options.pam_authentication_via_kbd_int) authenticated = auth2_pam(authctxt); #endif xfree(devs); Index: auth2.c ==================================================================RCS file: /cvs/openssh/auth2.c,v retrieving revision 1.112 diff -u -p -r1.112 auth2.c --- auth2.c 24 Feb 2003 00:59:27 -0000 1.112 +++ auth2.c 1 Mar 2003 17:37:41 -0000 @@ -228,16 +228,7 @@ userauth_finish(Authctxt *authctxt, int if (authctxt->postponed) return; - /* XXX todo: check if multiple auth methods are needed */ - if (authenticated == 1) { - /* turn off userauth */ - dispatch_set(SSH2_MSG_USERAUTH_REQUEST, &dispatch_protocol_ignore); - packet_start(SSH2_MSG_USERAUTH_SUCCESS); - packet_send(); - packet_write_wait(); - /* now we can break out */ - authctxt->success = 1; - } else { + if (!authenticated) { if (authctxt->failures++ > AUTH_FAIL_MAX) { packet_disconnect(AUTH_FAIL_MSG, authctxt->user); } @@ -252,6 +243,32 @@ userauth_finish(Authctxt *authctxt, int packet_send(); packet_write_wait(); xfree(methods); + } else if (!options.challenge_response_authentication_first) { + /* Success. turn off userauth */ + dispatch_set(SSH2_MSG_USERAUTH_REQUEST, &dispatch_protocol_ignore); + packet_start(SSH2_MSG_USERAUTH_SUCCESS); + packet_send(); + packet_write_wait(); + /* now we can break out */ + authctxt->success = 1; + } else { + /* Our prereqisite challenge-response authentication has + succeeded. Allow other methods from now on... */ + debug("prerequisite keyboard-interactive auth succeeded"); + + /* And disallow challenge-response authentication so + we don't just accept it twice :) */ + options.challenge_response_authentication_first = 0; + options.challenge_response_authentication = 0; + options.kbd_interactive_authentication = options.pam_authentication_via_kbd_int; + + methods = authmethods_get(); + packet_start(SSH2_MSG_USERAUTH_FAILURE); + packet_put_cstring(methods); + packet_put_char(1); /* XXX partial success, used */ + packet_send(); + packet_write_wait(); + xfree(methods); } } @@ -272,6 +289,11 @@ authmethods_get(void) char *list; int i; + /* If challenge-response is a prerequiste, advertise + that only */ + if (options.challenge_response_authentication_first) + return xstrdup("keyboard-interactive"); + buffer_init(&b); for (i = 0; authmethods[i] != NULL; i++) { if (strcmp(authmethods[i]->name, "none") == 0) @@ -294,6 +316,10 @@ static Authmethod * authmethod_lookup(const char *name) { int i; + + if (options.challenge_response_authentication_first && + strcmp(name, "keyboard-interactive") && strcmp(name, "none")) + return NULL; if (name != NULL) for (i = 0; authmethods[i] != NULL; i++) Index: monitor.c ==================================================================RCS file: /cvs/openssh/monitor.c,v retrieving revision 1.35 diff -u -p -r1.35 monitor.c --- monitor.c 24 Feb 2003 01:03:39 -0000 1.35 +++ monitor.c 1 Mar 2003 17:37:41 -0000 @@ -297,6 +297,15 @@ monitor_child_preauth(struct monitor *pm if (!authenticated) authctxt->failures++; } + if (options.challenge_response_authentication_first && + authenticated && + (!strcmp(auth_method, "skey") || !strcmp(auth_method, "bsdauth"))) { + debug2("prerequisite keyboard-interactive authentication succeeded"); + options.challenge_response_authentication_first = 0; + options.kbd_interactive_authentication = options.pam_authentication_via_kbd_int; + options.challenge_response_authentication = 0; + authenticated = 0; + } } if (!authctxt->valid) Index: servconf.c ==================================================================RCS file: /cvs/openssh/servconf.c,v retrieving revision 1.98 diff -u -p -r1.98 servconf.c --- servconf.c 24 Feb 2003 01:04:34 -0000 1.98 +++ servconf.c 1 Mar 2003 17:37:42 -0000 @@ -100,6 +100,7 @@ initialize_server_options(ServerOptions options->password_authentication = -1; options->kbd_interactive_authentication = -1; options->challenge_response_authentication = -1; + options->challenge_response_authentication_first = -1; options->permit_empty_passwd = -1; options->permit_user_env = -1; options->use_login = -1; @@ -222,6 +223,13 @@ fill_default_server_options(ServerOption options->kbd_interactive_authentication = 0; if (options->challenge_response_authentication == -1) options->challenge_response_authentication = 1; + if (options->challenge_response_authentication_first == -1) + options->challenge_response_authentication_first = 0; + if (options->challenge_response_authentication_first && + !options->challenge_response_authentication) { + error("ChallengeResponse authentication cannot be first if it is disabled"); + options->challenge_response_authentication_first = 0; + } if (options->permit_empty_passwd == -1) options->permit_empty_passwd = 0; if (options->permit_user_env == -1) @@ -289,7 +297,7 @@ typedef enum { #ifdef AFS sAFSTokenPassing, #endif - sChallengeResponseAuthentication, + sChallengeResponseAuthentication, sChallengeResponseAuthenticationFirst, sPasswordAuthentication, sKbdInteractiveAuthentication, sListenAddress, sPrintMotd, sPrintLastLog, sIgnoreRhosts, sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost, @@ -345,6 +353,7 @@ static struct { { "kbdinteractiveauthentication", sKbdInteractiveAuthentication }, { "challengeresponseauthentication", sChallengeResponseAuthentication }, { "skeyauthentication", sChallengeResponseAuthentication }, /* alias */ + { "challengeresponseauthenticationfirst", sChallengeResponseAuthenticationFirst }, { "checkmail", sDeprecated }, { "listenaddress", sListenAddress }, { "printmotd", sPrintMotd }, @@ -679,6 +688,10 @@ parse_flag: case sChallengeResponseAuthentication: intptr = &options->challenge_response_authentication; + goto parse_flag; + + case sChallengeResponseAuthenticationFirst: + intptr = &options->challenge_response_authentication_first; goto parse_flag; case sPrintMotd: Index: servconf.h ==================================================================RCS file: /cvs/openssh/servconf.h,v retrieving revision 1.50 diff -u -p -r1.50 servconf.h --- servconf.h 1 Aug 2002 01:28:39 -0000 1.50 +++ servconf.h 1 Mar 2003 17:37:42 -0000 @@ -95,6 +95,9 @@ typedef struct { * authentication. */ int kbd_interactive_authentication; /* If true, permit */ int challenge_response_authentication; + int challenge_response_authentication_first; /* If true, force + clients to use challenge-response + first, before other methods */ int permit_empty_passwd; /* If false, do not permit empty * passwords. */ int permit_user_env; /* If true, read ~/.ssh/environment */ -- dwmw2