I looked around for a while, but couldn't find any code for requiring multiple authentication mechanisms in openssh. So I wrote an implemention. I thought at first I should change the PasswordAuthentication, PubkeyAuthentication, etc. keywords to allow no/yes/required. But there's some funky stuff in auth2.c with respect to keyboard interactive auth that would make this kind of gnarly, semantics-wise. I also thought about providing a new keyword to specify a list of required authentication mechanisms. But then you have to make sure those mechanisms are also enabled, and it's easy to write conflicting configurations. In addition, if a list of required auth mechs is given, then enabling mechanisms that are not required is pointless, because they won't be sufficient. So my final decision, for the sake of simplicity, was to add a "NumRequiredAuthMethods" keyword, which defaults to 1. If you set it to 2, the client must pass at least two of the enabled auth methods. I'm using the term "methods" here because I'm only counting general auth methods as defined in auth2.c's "authmethods" array, namely publickey, password, keyboard-interactive, and hostbased. So there may be multiple types of keyboard-interactive auth, but keyboard-interactive only counts as a single method. So, for example, if you have PasswordAuthentication and PubkeyAuthentication enabled, and set NumRequiredAuthMethods to 2, you will have to pass both types. But PAMAuthenticationViaKbdInt and ChallengeResponseAuthentication are the same authentication method (keyboard-interactive), so if you want to require 2 classes, you'll have to have at least one of the other methods enabled as well. I don't know much about some of the supported authentication types, particularly pam, so I'm not totally sure my approach makes sense for everyone's needs. My particular need was to require both public key and S/KEY factors so that one-time passwords can be combined with a strong electronic authenticator. I don't trust my users not to end up trojaned with a keylogger, so I need OTP, but I also want a public key in case someone loses his S/KEY cheat sheet. The attached patch is designed for Red Hat's openssh-3.1p1-14 SRPM (add as Patch14, use -p1 on patch line in %prep). It should work against openssh-3.8 with slight tweaks (authmethods changed in auth2.c). If people need a patch against 3.8, I can build it; just ask. I would really appreciate it if anyone with interest could vet this for stupid mistakes. -- Jefferson Ogata <Jefferson.Ogata at noaa.gov> NOAA Computer Incident Response Team (N-CIRT) <ncirt at noaa.gov> -------------- next part -------------- An embedded and charset-unspecified text was scrubbed... Name: openssh-3.1p1-multipleauth.patch Url: http://lists.mindrot.org/pipermail/openssh-unix-dev/attachments/20040407/64a39780/attachment.ksh
Jeff, You might also want to look at the code for auth selection that was written here: http://sweb.cz/v_t_m/ (This individual also has written patches for securid that work very well). -Scott Jefferson Ogata wrote:> I looked around for a while, but couldn't find any code for requiring > multiple authentication mechanisms in openssh. So I wrote an implemention. > > I thought at first I should change the PasswordAuthentication, > PubkeyAuthentication, etc. keywords to allow no/yes/required. But > there's some funky stuff in auth2.c with respect to keyboard interactive > auth that would make this kind of gnarly, semantics-wise. > > I also thought about providing a new keyword to specify a list of > required authentication mechanisms. But then you have to make sure those > mechanisms are also enabled, and it's easy to write conflicting > configurations. In addition, if a list of required auth mechs is given, > then enabling mechanisms that are not required is pointless, because > they won't be sufficient. > > So my final decision, for the sake of simplicity, was to add a > "NumRequiredAuthMethods" keyword, which defaults to 1. If you set it to > 2, the client must pass at least two of the enabled auth methods. I'm > using the term "methods" here because I'm only counting general auth > methods as defined in auth2.c's "authmethods" array, namely publickey, > password, keyboard-interactive, and hostbased. So there may be multiple > types of keyboard-interactive auth, but keyboard-interactive only counts > as a single method. > > So, for example, if you have PasswordAuthentication and > PubkeyAuthentication enabled, and set NumRequiredAuthMethods to 2, you > will have to pass both types. But PAMAuthenticationViaKbdInt and > ChallengeResponseAuthentication are the same authentication method > (keyboard-interactive), so if you want to require 2 classes, you'll have > to have at least one of the other methods enabled as well. > > I don't know much about some of the supported authentication types, > particularly pam, so I'm not totally sure my approach makes sense for > everyone's needs. My particular need was to require both public key and > S/KEY factors so that one-time passwords can be combined with a strong > electronic authenticator. I don't trust my users not to end up trojaned > with a keylogger, so I need OTP, but I also want a public key in case > someone loses his S/KEY cheat sheet. > > The attached patch is designed for Red Hat's openssh-3.1p1-14 SRPM (add > as Patch14, use -p1 on patch line in %prep). It should work against > openssh-3.8 with slight tweaks (authmethods changed in auth2.c). If > people need a patch against 3.8, I can build it; just ask. > > I would really appreciate it if anyone with interest could vet this for > stupid mistakes. > > > ------------------------------------------------------------------------ > > --- openssh-3.1p1/auth.h.multipleauth Wed Apr 7 11:34:32 2004 > +++ openssh-3.1p1/auth.h Wed Apr 7 11:34:15 2004 > @@ -51,6 +51,7 @@ > int valid; > int attempt; > int failures; > + int passed; > char *user; > char *service; > struct passwd *pw; > --- openssh-3.1p1/auth2.c.multipleauth Wed Apr 7 12:55:00 2004 > +++ openssh-3.1p1/auth2.c Wed Apr 7 13:42:46 2004 > @@ -74,7 +74,7 @@ > > /* helper */ > static Authmethod *authmethod_lookup(const char *); > -static char *authmethods_get(void); > +static char *authmethods_get(int); > static int user_key_allowed(struct passwd *, Key *); > static int hostbased_key_allowed(struct passwd *, const char *, char *, Key *); > > @@ -229,6 +229,7 @@ > userauth_finish(Authctxt *authctxt, int authenticated, char *method) > { > char *methods; > + int success = 0; > > if (!authctxt->valid && authenticated) > fatal("INTERNAL ERROR: authenticated invalid user %s", > @@ -251,15 +252,22 @@ > if (authctxt->postponed) > return; > > - /* XXX todo: check if multiple auth methods are needed */ > + /* Check if enough auth methods have passed */ > 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; > + Authmethod *a; > + int passed; > + int k; > + > + for (a = authmethods, k = 1, passed = 0; a->name != NULL; a++, k <<= 1) { > + if (strncmp (method, a->name, strlen (a->name)) == 0) > + authctxt->passed |= k; > + if (authctxt->passed & k) > + ++passed; > + } > + if (passed < options.num_required_auth_methods) { > + success = 1; > + authenticated = 0; > + } > } else { > if (authctxt->failures++ > AUTH_FAIL_MAX) { > #ifdef WITH_AIXAUTHENTICATE > @@ -269,10 +277,21 @@ > #endif /* WITH_AIXAUTHENTICATE */ > packet_disconnect(AUTH_FAIL_MSG, authctxt->user); > } > - methods = authmethods_get(); > + } > + > + 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 { > + methods = authmethods_get(authctxt->passed); > packet_start(SSH2_MSG_USERAUTH_FAILURE); > packet_put_cstring(methods); > - packet_put_char(0); /* XXX partial success, unused */ > + packet_put_char(success); > packet_send(); > packet_write_wait(); > xfree(methods); > @@ -599,16 +618,19 @@ > #define DELIM "," > > static char * > -authmethods_get(void) > +authmethods_get(int passed) > { > Authmethod *method = NULL; > Buffer b; > char *list; > + int k; > > buffer_init(&b); > - for (method = authmethods; method->name != NULL; method++) { > + for (method = authmethods, k = 1; method->name != NULL; method++, k <<= 1) { > if (strcmp(method->name, "none") == 0) > continue; > + if (passed & k) > + continue; > if (method->enabled != NULL && *(method->enabled) != 0) { > if (buffer_len(&b) > 0) > buffer_append(&b, ",", 1); > --- openssh-3.1p1/servconf.h.multipleauth Tue Mar 5 01:53:05 2002 > +++ openssh-3.1p1/servconf.h Wed Apr 7 12:53:38 2004 > @@ -95,6 +95,8 @@ > * authentication. */ > int kbd_interactive_authentication; /* If true, permit */ > int challenge_response_authentication; > + int num_required_auth_methods; /* Minimum number of auth methods > + * that must succeed. */ > int permit_empty_passwd; /* If false, do not permit empty > * passwords. */ > int use_login; /* If true, login(1) is used */ > --- openssh-3.1p1/servconf.c.multipleauth Tue Feb 5 01:26:35 2002 > +++ openssh-3.1p1/servconf.c Wed Apr 7 11:42:21 2004 > @@ -89,6 +89,7 @@ > options->password_authentication = -1; > options->kbd_interactive_authentication = -1; > options->challenge_response_authentication = -1; > + options->num_required_auth_methods = -1; > options->permit_empty_passwd = -1; > options->use_login = -1; > options->allow_tcp_forwarding = -1; > @@ -206,6 +207,8 @@ > options->kbd_interactive_authentication = 0; > if (options->challenge_response_authentication == -1) > options->challenge_response_authentication = 1; > + if (options->num_required_auth_methods == -1) > + options->num_required_auth_methods = 1; > if (options->permit_empty_passwd == -1) > options->permit_empty_passwd = 0; > if (options->use_login == -1) > @@ -255,6 +258,7 @@ > #ifdef AFS > sAFSTokenPassing, > #endif > + sNumRequiredAuthMethods, > sChallengeResponseAuthentication, > sPasswordAuthentication, sKbdInteractiveAuthentication, sListenAddress, > sPrintMotd, sPrintLastLog, sIgnoreRhosts, > @@ -310,6 +314,7 @@ > { "kbdinteractiveauthentication", sKbdInteractiveAuthentication }, > { "challengeresponseauthentication", sChallengeResponseAuthentication }, > { "skeyauthentication", sChallengeResponseAuthentication }, /* alias */ > + { "numrequiredauthmethods", sNumRequiredAuthMethods }, > { "checkmail", sDeprecated }, > { "listenaddress", sListenAddress }, > { "printmotd", sPrintMotd }, > @@ -644,6 +649,10 @@ > intptr = &options->challenge_response_authentication; > goto parse_flag; > > + case sNumRequiredAuthMethods: > + intptr = &options->num_required_auth_methods; > + goto parse_int; > + > case sPrintMotd: > intptr = &options->print_motd; > goto parse_flag; > --- openssh-3.1p1/sshd.8.multipleauth Tue Mar 5 01:38:59 2002 > +++ openssh-3.1p1/sshd.8 Wed Apr 7 12:37:34 2004 > @@ -680,6 +680,12 @@ > are refused if the number of unauthenticated connections reaches > .Dq full > (60). > +.It Cm NumRequiredAuthMethods > +Specifies how many authentication methods must succeed during ssh2 > +authentication. There are four potential methods: publickey, password, > +keyboard-interactive, and hostbased. Setting this value to 2 or higher forces > +the client to successfully authenticate in multiple ways, for example, using > +both S/Key and publickey. > .It Cm PAMAuthenticationViaKbdInt > Specifies whether PAM challenge response authentication is allowed. This > allows the use of most PAM challenge response authentication modules, but > --- openssh-3.1p1/sshd_config.multipleauth Wed Apr 7 00:20:43 2004 > +++ openssh-3.1p1/sshd_config Wed Apr 7 12:39:23 2004 > @@ -60,6 +60,10 @@ > # Change to no to disable s/key passwords > #ChallengeResponseAuthentication yes > > +# Change to require multiple authentication types, e.g. password and > +# publickey. > +#NumRequiredAuthMethods 1 > + > # Kerberos options > # KerberosAuthentication automatically enabled if keyfile exists > #KerberosAuthentication yes > > > ------------------------------------------------------------------------ > > _______________________________________________ > openssh-unix-dev mailing list > openssh-unix-dev at mindrot.org > http://www.mindrot.org/mailman/listinfo/openssh-unix-dev
We've ported this patch to OpenSSH 3.8.1p1 (attached). Our initial testing indicates that it doesn't work with privilege separation and only works with PAM (keyboard-interactive) if PAM is the first successful authentication mechanism. Both of these issues are things we'd like to resolve, but I'm not familiar enough with the code to know if there are any show-stoppers. (Any thoughts from the crowd?) To help clarify the PAM issue, when requiring multiple auth mechanisms, PAM will automatically fail if any other mechanism before it succeeds. However, if you force keyboard-interactive to happen first or if everything before it fails, PAM then appears to work fine and other auth mechanisms will continue to work after it. -Mike> I looked around for a while, but couldn't find any code for requiring multiple > authentication mechanisms in openssh. So I wrote an implemention. > > I thought at first I should change the PasswordAuthentication, > PubkeyAuthentication, etc. keywords to allow no/yes/required. But there's some > funky stuff in auth2.c with respect to keyboard interactive auth that would make > this kind of gnarly, semantics-wise. > > I also thought about providing a new keyword to specify a list of required > authentication mechanisms. But then you have to make sure those mechanisms are > also enabled, and it's easy to write conflicting configurations. In addition, if > a list of required auth mechs is given, then enabling mechanisms that are not > required is pointless, because they won't be sufficient. > > So my final decision, for the sake of simplicity, was to add a > "NumRequiredAuthMethods" keyword, which defaults to 1. If you set it to 2, the > client must pass at least two of the enabled auth methods. I'm using the term > "methods" here because I'm only counting general auth methods as defined in > auth2.c's "authmethods" array, namely publickey, password, keyboard-interactive, > and hostbased. So there may be multiple types of keyboard-interactive auth, but > keyboard-interactive only counts as a single method. > > So, for example, if you have PasswordAuthentication and PubkeyAuthentication > enabled, and set NumRequiredAuthMethods to 2, you will have to pass both types. > But PAMAuthenticationViaKbdInt and ChallengeResponseAuthentication are the same > authentication method (keyboard-interactive), so if you want to require 2 > classes, you'll have to have at least one of the other methods enabled as well. > > I don't know much about some of the supported authentication types, particularly > pam, so I'm not totally sure my approach makes sense for everyone's needs. My > particular need was to require both public key and S/KEY factors so that > one-time passwords can be combined with a strong electronic authenticator. I > don't trust my users not to end up trojaned with a keylogger, so I need OTP, but > I also want a public key in case someone loses his S/KEY cheat sheet. > > The attached patch is designed for Red Hat's openssh-3.1p1-14 SRPM (add as > Patch14, use -p1 on patch line in %prep). It should work against openssh-3.8 > with slight tweaks (authmethods changed in auth2.c). If people need a patch > against 3.8, I can build it; just ask. > > I would really appreciate it if anyone with interest could vet this for stupid > mistakes. > >-- -------------- next part -------------- diff -ur openssh-3.8.1p1/auth2.c openssh-3.8.1p1.multiauth/auth2.c --- openssh-3.8.1p1/auth2.c 2004-03-08 06:04:07.000000000 -0600 +++ openssh-3.8.1p1.multiauth/auth2.c 2004-05-25 11:02:34.000000000 -0500 @@ -75,7 +75,7 @@ /* helper */ static Authmethod *authmethod_lookup(const char *); -static char *authmethods_get(void); +static char *authmethods_get(int); int user_key_allowed(struct passwd *, Key *); /* @@ -205,6 +205,7 @@ userauth_finish(Authctxt *authctxt, int authenticated, char *method) { char *methods; + int success = 0; if (!authctxt->valid && authenticated) fatal("INTERNAL ERROR: authenticated invalid user %s", @@ -233,41 +234,62 @@ if (authctxt->postponed) return; - /* XXX todo: check if multiple auth methods are needed */ + /* Check if enough multiple auth methods have passed */ 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; + int passed; + int k; + int j; + + for (j = 0, k = 1, passed = 0; authmethods[j] != NULL; j++, k <<= 1) { + if (strncmp (method, authmethods[j]->name, strlen (authmethods[j]->name)) == 0) + authctxt->passed |= k; + if (authctxt->passed & k) + ++passed; + } + if (passed < options.num_required_auth_methods) { + success = 1; + authenticated = 0; + } } else { - if (authctxt->failures++ > AUTH_FAIL_MAX) - packet_disconnect(AUTH_FAIL_MSG, authctxt->user); - methods = authmethods_get(); - packet_start(SSH2_MSG_USERAUTH_FAILURE); - packet_put_cstring(methods); - packet_put_char(0); /* XXX partial success, unused */ - packet_send(); - packet_write_wait(); - xfree(methods); + if (authctxt->failures++ > AUTH_FAIL_MAX) + packet_disconnect(AUTH_FAIL_MSG, authctxt->user); + } + + 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 { + methods = authmethods_get(authctxt->passed); + packet_start(SSH2_MSG_USERAUTH_FAILURE); + packet_put_cstring(methods); + packet_put_char(success); + packet_send(); + packet_write_wait(); + xfree(methods); } } #define DELIM "," static char * -authmethods_get(void) +authmethods_get(int passed) { Buffer b; char *list; + int k; int i; buffer_init(&b); - for (i = 0; authmethods[i] != NULL; i++) { + for (i = 0, k = 1; authmethods[i] != NULL; i++, k <<= 1) { if (strcmp(authmethods[i]->name, "none") == 0) continue; + if (passed & k) + continue; if (authmethods[i]->enabled != NULL && *(authmethods[i]->enabled) != 0) { if (buffer_len(&b) > 0) diff -ur openssh-3.8.1p1/auth.h openssh-3.8.1p1.multiauth/auth.h --- openssh-3.8.1p1/auth.h 2004-04-16 07:47:55.000000000 -0500 +++ openssh-3.8.1p1.multiauth/auth.h 2004-05-25 12:14:54.000000000 -0500 @@ -52,6 +52,7 @@ int valid; /* user exists and is allowed to login */ int attempt; int failures; + int passed; int force_pwchange; char *user; /* username sent by the client */ char *service; diff -ur openssh-3.8.1p1/servconf.c openssh-3.8.1p1.multiauth/servconf.c --- openssh-3.8.1p1/servconf.c 2004-01-23 05:03:10.000000000 -0600 +++ openssh-3.8.1p1.multiauth/servconf.c 2004-05-25 12:18:11.000000000 -0500 @@ -78,6 +78,7 @@ options->password_authentication = -1; options->kbd_interactive_authentication = -1; options->challenge_response_authentication = -1; + options->num_required_auth_methods = -1; options->permit_empty_passwd = -1; options->permit_user_env = -1; options->use_login = -1; @@ -194,6 +195,8 @@ options->kbd_interactive_authentication = 0; if (options->challenge_response_authentication == -1) options->challenge_response_authentication = 1; + if (options->num_required_auth_methods == -1) + options->num_required_auth_methods = 1; if (options->permit_empty_passwd == -1) options->permit_empty_passwd = 0; if (options->permit_user_env == -1) @@ -253,8 +256,8 @@ sPermitRootLogin, sLogFacility, sLogLevel, sRhostsRSAAuthentication, sRSAAuthentication, sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup, - sKerberosGetAFSToken, - sKerberosTgtPassing, sChallengeResponseAuthentication, + sKerberosGetAFSToken, sKerberosTgtPassing, + sNumRequiredAuthMethods, sChallengeResponseAuthentication, sPasswordAuthentication, sKbdInteractiveAuthentication, sListenAddress, sPrintMotd, sPrintLastLog, sIgnoreRhosts, sX11Forwarding, sX11DisplayOffset, sX11UseLocalhost, @@ -329,6 +332,7 @@ { "kbdinteractiveauthentication", sKbdInteractiveAuthentication }, { "challengeresponseauthentication", sChallengeResponseAuthentication }, { "skeyauthentication", sChallengeResponseAuthentication }, /* alias */ + { "numrequiredauthmethods", sNumRequiredAuthMethods }, { "checkmail", sDeprecated }, { "listenaddress", sListenAddress }, { "printmotd", sPrintMotd }, @@ -664,6 +668,10 @@ intptr = &options->challenge_response_authentication; goto parse_flag; + case sNumRequiredAuthMethods: + intptr = &options->num_required_auth_methods; + goto parse_int; + case sPrintMotd: intptr = &options->print_motd; goto parse_flag; diff -ur openssh-3.8.1p1/servconf.h openssh-3.8.1p1.multiauth/servconf.h --- openssh-3.8.1p1/servconf.h 2003-12-30 18:37:34.000000000 -0600 +++ openssh-3.8.1p1.multiauth/servconf.h 2004-05-25 12:20:42.000000000 -0500 @@ -88,6 +88,8 @@ * authentication. */ int kbd_interactive_authentication; /* If true, permit */ int challenge_response_authentication; + int num_required_auth_methods; /* Minimum number of auth methods + * that must succeed. */ int permit_empty_passwd; /* If false, do not permit empty * passwords. */ int permit_user_env; /* If true, read ~/.ssh/environment */ diff -ur openssh-3.8.1p1/sshd_config openssh-3.8.1p1.multiauth/sshd_config --- openssh-3.8.1p1/sshd_config 2003-12-30 18:38:32.000000000 -0600 +++ openssh-3.8.1p1.multiauth/sshd_config 2004-05-25 12:22:01.000000000 -0500 @@ -57,6 +57,10 @@ # Change to no to disable s/key passwords #ChallengeResponseAuthentication yes +# Change to require multiple authentication types, e.g. password and +# publickey. (Currently you'll need to turn off UsePrivilegeSeparation) +#NumRequiredAuthMethods 1 + # Kerberos options #KerberosAuthentication no #KerberosOrLocalPasswd yes diff -ur openssh-3.8.1p1/sshd_config.5 openssh-3.8.1p1.multiauth/sshd_config.5 --- openssh-3.8.1p1/sshd_config.5 2004-04-13 22:04:36.000000000 -0500 +++ openssh-3.8.1p1.multiauth/sshd_config.5 2004-05-25 12:27:48.000000000 -0500 @@ -411,6 +411,12 @@ are refused if the number of unauthenticated connections reaches .Dq full (60). +.It Cm NumRequiredAuthMethods +Specifies how many authentication methods must succeed during ssh2 +authentication. There are four potential methods: publickey, password, +keyboard-interactive, and hostbased. Setting this value to 2 or higher forces +the client to successfully authenticate in multiple ways, for example, using +both S/Key and publickey. .It Cm PasswordAuthentication Specifies whether password authentication is allowed. The default is