- djm@cvs.openbsd.org 2012/11/04 11:09:15

[auth.h auth1.c auth2.c monitor.c servconf.c servconf.h sshd.c]
     [sshd_config.5]
     Support multiple required authentication via an AuthenticationMethods
     option. This option lists one or more comma-separated lists of
     authentication method names. Successful completion of all the methods in
     any list is required for authentication to complete;
     feedback and ok markus@
This commit is contained in:
Damien Miller 2012-11-04 23:21:40 +11:00
parent d0d1099b3b
commit a6e3f01d1e
9 changed files with 328 additions and 28 deletions

View File

@ -7,6 +7,14 @@
[auth2-pubkey.c sshd.c sshd_config.5]
Remove default of AuthorizedCommandUser. Administrators are now expected
to explicitly specify a user. feedback and ok markus@
- djm@cvs.openbsd.org 2012/11/04 11:09:15
[auth.h auth1.c auth2.c monitor.c servconf.c servconf.h sshd.c]
[sshd_config.5]
Support multiple required authentication via an AuthenticationMethods
option. This option lists one or more comma-separated lists of
authentication method names. Successful completion of all the methods in
any list is required for authentication to complete;
feedback and ok markus@
20121030
- (djm) OpenBSD CVS Sync

7
auth.h
View File

@ -1,4 +1,4 @@
/* $OpenBSD: auth.h,v 1.70 2012/10/30 21:29:54 djm Exp $ */
/* $OpenBSD: auth.h,v 1.71 2012/11/04 11:09:15 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
@ -64,6 +64,8 @@ struct Authctxt {
#ifdef BSD_AUTH
auth_session_t *as;
#endif
char **auth_methods; /* modified from server config */
u_int num_auth_methods;
#ifdef KRB5
krb5_context krb5_ctx;
krb5_ccache krb5_fwd_ccache;
@ -152,6 +154,9 @@ void userauth_send_banner(const char *);
int auth_root_allowed(char *);
char *auth2_read_banner(void);
int auth2_methods_valid(const char *, int);
int auth2_update_methods_lists(Authctxt *, const char *);
int auth2_setup_methods_lists(Authctxt *);
void privsep_challenge_enable(void);

View File

@ -1,4 +1,4 @@
/* $OpenBSD: auth1.c,v 1.75 2010/08/31 09:58:37 djm Exp $ */
/* $OpenBSD: auth1.c,v 1.76 2012/11/04 11:09:15 djm Exp $ */
/*
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
* All rights reserved
@ -406,6 +406,11 @@ do_authentication(Authctxt *authctxt)
authctxt->pw = fakepw();
}
/* Configuration may have changed as a result of Match */
if (options.num_auth_methods != 0)
fatal("AuthenticationMethods is not supported with SSH "
"protocol 1");
setproctitle("%s%s", authctxt->valid ? user : "unknown",
use_privsep ? " [net]" : "");

218
auth2.c
View File

@ -1,4 +1,4 @@
/* $OpenBSD: auth2.c,v 1.124 2011/12/07 05:44:38 djm Exp $ */
/* $OpenBSD: auth2.c,v 1.125 2012/11/04 11:09:15 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
*
@ -96,8 +96,10 @@ static void input_service_request(int, u_int32_t, void *);
static void input_userauth_request(int, u_int32_t, void *);
/* helper */
static Authmethod *authmethod_lookup(const char *);
static char *authmethods_get(void);
static Authmethod *authmethod_lookup(Authctxt *, const char *);
static char *authmethods_get(Authctxt *authctxt);
static int method_allowed(Authctxt *, const char *);
static int list_starts_with(const char *, const char *);
char *
auth2_read_banner(void)
@ -255,6 +257,8 @@ input_userauth_request(int type, u_int32_t seq, void *ctxt)
if (use_privsep)
mm_inform_authserv(service, style);
userauth_banner();
if (auth2_setup_methods_lists(authctxt) != 0)
packet_disconnect("no authentication methods enabled");
} else if (strcmp(user, authctxt->user) != 0 ||
strcmp(service, authctxt->service) != 0) {
packet_disconnect("Change of username or service not allowed: "
@ -277,7 +281,7 @@ input_userauth_request(int type, u_int32_t seq, void *ctxt)
authctxt->server_caused_failure = 0;
/* try to authenticate user */
m = authmethod_lookup(method);
m = authmethod_lookup(authctxt, method);
if (m != NULL && authctxt->failures < options.max_authtries) {
debug2("input_userauth_request: try method %s", method);
authenticated = m->userauth(authctxt);
@ -293,6 +297,7 @@ void
userauth_finish(Authctxt *authctxt, int authenticated, char *method)
{
char *methods;
int partial = 0;
if (!authctxt->valid && authenticated)
fatal("INTERNAL ERROR: authenticated invalid user %s",
@ -335,7 +340,13 @@ userauth_finish(Authctxt *authctxt, int authenticated, char *method)
if (authctxt->postponed)
return;
/* XXX todo: check if multiple auth methods are needed */
if (authenticated && options.num_auth_methods != 0) {
if (!auth2_update_methods_lists(authctxt, method)) {
authenticated = 0;
partial = 1;
}
}
if (authenticated == 1) {
/* turn off userauth */
dispatch_set(SSH2_MSG_USERAUTH_REQUEST, &dispatch_protocol_ignore);
@ -356,34 +367,61 @@ userauth_finish(Authctxt *authctxt, int authenticated, char *method)
#endif
packet_disconnect(AUTH_FAIL_MSG, authctxt->user);
}
methods = authmethods_get();
methods = authmethods_get(authctxt);
debug3("%s: failure partial=%d next methods=\"%s\"", __func__,
partial, methods);
packet_start(SSH2_MSG_USERAUTH_FAILURE);
packet_put_cstring(methods);
packet_put_char(0); /* XXX partial success, unused */
packet_put_char(partial);
packet_send();
packet_write_wait();
xfree(methods);
}
}
/*
* Checks whether method is allowed by at least one AuthenticationMethods
* methods list. Returns 1 if allowed, or no methods lists configured.
* 0 otherwise.
*/
static int
method_allowed(Authctxt *authctxt, const char *method)
{
u_int i;
/*
* NB. authctxt->num_auth_methods might be zero as a result of
* auth2_setup_methods_lists(), so check the configuration.
*/
if (options.num_auth_methods == 0)
return 1;
for (i = 0; i < authctxt->num_auth_methods; i++) {
if (list_starts_with(authctxt->auth_methods[i], method))
return 1;
}
return 0;
}
static char *
authmethods_get(void)
authmethods_get(Authctxt *authctxt)
{
Buffer b;
char *list;
int i;
u_int i;
buffer_init(&b);
for (i = 0; authmethods[i] != NULL; i++) {
if (strcmp(authmethods[i]->name, "none") == 0)
continue;
if (authmethods[i]->enabled != NULL &&
*(authmethods[i]->enabled) != 0) {
if (buffer_len(&b) > 0)
buffer_append(&b, ",", 1);
buffer_append(&b, authmethods[i]->name,
strlen(authmethods[i]->name));
}
if (authmethods[i]->enabled == NULL ||
*(authmethods[i]->enabled) == 0)
continue;
if (!method_allowed(authctxt, authmethods[i]->name))
continue;
if (buffer_len(&b) > 0)
buffer_append(&b, ",", 1);
buffer_append(&b, authmethods[i]->name,
strlen(authmethods[i]->name));
}
buffer_append(&b, "\0", 1);
list = xstrdup(buffer_ptr(&b));
@ -392,7 +430,7 @@ authmethods_get(void)
}
static Authmethod *
authmethod_lookup(const char *name)
authmethod_lookup(Authctxt *authctxt, const char *name)
{
int i;
@ -400,10 +438,154 @@ authmethod_lookup(const char *name)
for (i = 0; authmethods[i] != NULL; i++)
if (authmethods[i]->enabled != NULL &&
*(authmethods[i]->enabled) != 0 &&
strcmp(name, authmethods[i]->name) == 0)
strcmp(name, authmethods[i]->name) == 0 &&
method_allowed(authctxt, authmethods[i]->name))
return authmethods[i];
debug2("Unrecognized authentication method name: %s",
name ? name : "NULL");
return NULL;
}
/*
* Check a comma-separated list of methods for validity. Is need_enable is
* non-zero, then also require that the methods are enabled.
* Returns 0 on success or -1 if the methods list is invalid.
*/
int
auth2_methods_valid(const char *_methods, int need_enable)
{
char *methods, *omethods, *method;
u_int i, found;
int ret = -1;
if (*_methods == '\0') {
error("empty authentication method list");
return -1;
}
omethods = methods = xstrdup(_methods);
while ((method = strsep(&methods, ",")) != NULL) {
for (found = i = 0; !found && authmethods[i] != NULL; i++) {
if (strcmp(method, authmethods[i]->name) != 0)
continue;
if (need_enable) {
if (authmethods[i]->enabled == NULL ||
*(authmethods[i]->enabled) == 0) {
error("Disabled method \"%s\" in "
"AuthenticationMethods list \"%s\"",
method, _methods);
goto out;
}
}
found = 1;
break;
}
if (!found) {
error("Unknown authentication method \"%s\" in list",
method);
goto out;
}
}
ret = 0;
out:
free(omethods);
return ret;
}
/*
* Prune the AuthenticationMethods supplied in the configuration, removing
* any methods lists that include disabled methods. Note that this might
* leave authctxt->num_auth_methods == 0, even when multiple required auth
* has been requested. For this reason, all tests for whether multiple is
* enabled should consult options.num_auth_methods directly.
*/
int
auth2_setup_methods_lists(Authctxt *authctxt)
{
u_int i;
if (options.num_auth_methods == 0)
return 0;
debug3("%s: checking methods", __func__);
authctxt->auth_methods = xcalloc(options.num_auth_methods,
sizeof(*authctxt->auth_methods));
authctxt->num_auth_methods = 0;
for (i = 0; i < options.num_auth_methods; i++) {
if (auth2_methods_valid(options.auth_methods[i], 1) != 0) {
logit("Authentication methods list \"%s\" contains "
"disabled method, skipping",
options.auth_methods[i]);
continue;
}
debug("authentication methods list %d: %s",
authctxt->num_auth_methods, options.auth_methods[i]);
authctxt->auth_methods[authctxt->num_auth_methods++] =
xstrdup(options.auth_methods[i]);
}
if (authctxt->num_auth_methods == 0) {
error("No AuthenticationMethods left after eliminating "
"disabled methods");
return -1;
}
return 0;
}
static int
list_starts_with(const char *methods, const char *method)
{
size_t l = strlen(method);
if (strncmp(methods, method, l) != 0)
return 0;
if (methods[l] != ',' && methods[l] != '\0')
return 0;
return 1;
}
/*
* Remove method from the start of a comma-separated list of methods.
* Returns 0 if the list of methods did not start with that method or 1
* if it did.
*/
static int
remove_method(char **methods, const char *method)
{
char *omethods = *methods;
size_t l = strlen(method);
if (!list_starts_with(omethods, method))
return 0;
*methods = xstrdup(omethods + l + (omethods[l] == ',' ? 1 : 0));
free(omethods);
return 1;
}
/*
* Called after successful authentication. Will remove the successful method
* from the start of each list in which it occurs. If it was the last method
* in any list, then authentication is deemed successful.
* Returns 1 if the method completed any authentication list or 0 otherwise.
*/
int
auth2_update_methods_lists(Authctxt *authctxt, const char *method)
{
u_int i, found = 0;
debug3("%s: updating methods list after \"%s\"", __func__, method);
for (i = 0; i < authctxt->num_auth_methods; i++) {
if (!remove_method(&(authctxt->auth_methods[i]), method))
continue;
found = 1;
if (*authctxt->auth_methods[i] == '\0') {
debug2("authentication methods list %d complete", i);
return 1;
}
debug3("authentication methods list %d remaining: \"%s\"",
i, authctxt->auth_methods[i]);
}
/* This should not happen, but would be bad if it did */
if (!found)
fatal("%s: method not in AuthenticationMethods", __func__);
return 0;
}

View File

@ -1,4 +1,4 @@
/* $OpenBSD: monitor.c,v 1.117 2012/06/22 12:30:26 dtucker Exp $ */
/* $OpenBSD: monitor.c,v 1.118 2012/11/04 11:09:15 djm Exp $ */
/*
* Copyright 2002 Niels Provos <provos@citi.umich.edu>
* Copyright 2002 Markus Friedl <markus@openbsd.org>
@ -381,6 +381,21 @@ monitor_child_preauth(Authctxt *_authctxt, struct monitor *pmonitor)
while (!authenticated) {
auth_method = "unknown";
authenticated = (monitor_read(pmonitor, mon_dispatch, &ent) == 1);
/* Special handling for multiple required authentications */
if (options.num_auth_methods != 0) {
if (!compat20)
fatal("AuthenticationMethods is not supported"
"with SSH protocol 1");
if (authenticated &&
!auth2_update_methods_lists(authctxt,
auth_method)) {
debug3("%s: method %s: partial", __func__,
auth_method);
authenticated = 0;
}
}
if (authenticated) {
if (!(ent->flags & MON_AUTHDECIDE))
fatal("%s: unexpected authentication from %d",
@ -401,7 +416,6 @@ monitor_child_preauth(Authctxt *_authctxt, struct monitor *pmonitor)
}
#endif
}
if (ent->flags & (MON_AUTHDECIDE|MON_ALOG)) {
auth_log(authctxt, authenticated, auth_method,
compat20 ? " ssh2" : "");
@ -781,7 +795,17 @@ mm_answer_pwnamallow(int sock, Buffer *m)
COPY_MATCH_STRING_OPTS();
#undef M_CP_STROPT
#undef M_CP_STRARRAYOPT
/* Create valid auth method lists */
if (compat20 && auth2_setup_methods_lists(authctxt) != 0) {
/*
* The monitor will continue long enough to let the child
* run to it's packet_disconnect(), but it must not allow any
* authentication to succeed.
*/
debug("%s: no valid authentication method lists", __func__);
}
debug3("%s: sending MONITOR_ANS_PWNAM: %d", __func__, allowed);
mm_request_send(sock, MONITOR_ANS_PWNAM, m);
@ -918,7 +942,10 @@ mm_answer_bsdauthrespond(int sock, Buffer *m)
debug3("%s: sending authenticated: %d", __func__, authok);
mm_request_send(sock, MONITOR_ANS_BSDAUTHRESPOND, m);
auth_method = "bsdauth";
if (compat20)
auth_method = "keyboard-interactive";
else
auth_method = "bsdauth";
return (authok != 0);
}

View File

@ -1,5 +1,5 @@
/* $OpenBSD: servconf.c,v 1.231 2012/10/30 21:29:54 djm Exp $ */
/* $OpenBSD: servconf.c,v 1.232 2012/11/04 11:09:15 djm Exp $ */
/*
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
* All rights reserved
@ -48,6 +48,8 @@
#include "groupaccess.h"
#include "canohost.h"
#include "packet.h"
#include "hostfile.h"
#include "auth.h"
static void add_listen_addr(ServerOptions *, char *, int);
static void add_one_listen_addr(ServerOptions *, char *, int);
@ -332,6 +334,7 @@ typedef enum {
sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile,
sKexAlgorithms, sIPQoS, sVersionAddendum,
sAuthorizedKeysCommand, sAuthorizedKeysCommandUser,
sAuthenticationMethods,
sDeprecated, sUnsupported
} ServerOpCodes;
@ -459,6 +462,7 @@ static struct {
{ "authorizedkeyscommand", sAuthorizedKeysCommand, SSHCFG_ALL },
{ "authorizedkeyscommanduser", sAuthorizedKeysCommandUser, SSHCFG_ALL },
{ "versionaddendum", sVersionAddendum, SSHCFG_GLOBAL },
{ "authenticationmethods", sAuthenticationMethods, SSHCFG_ALL },
{ NULL, sBadOption, 0 }
};
@ -1522,6 +1526,24 @@ process_server_config_line(ServerOptions *options, char *line,
*charptr = xstrdup(arg);
break;
case sAuthenticationMethods:
if (*activep && options->num_auth_methods == 0) {
while ((arg = strdelim(&cp)) && *arg != '\0') {
if (options->num_auth_methods >=
MAX_AUTH_METHODS)
fatal("%s line %d: "
"too many authentication methods.",
filename, linenum);
if (auth2_methods_valid(arg, 0) != 0)
fatal("%s line %d: invalid "
"authentication method list.",
filename, linenum);
options->auth_methods[
options->num_auth_methods++] = xstrdup(arg);
}
}
return 0;
case sDeprecated:
logit("%s line %d: Deprecated option %s",
filename, linenum, arg);
@ -1953,6 +1975,8 @@ dump_config(ServerOptions *o)
dump_cfg_strarray(sAllowGroups, o->num_allow_groups, o->allow_groups);
dump_cfg_strarray(sDenyGroups, o->num_deny_groups, o->deny_groups);
dump_cfg_strarray(sAcceptEnv, o->num_accept_env, o->accept_env);
dump_cfg_strarray_oneline(sAuthenticationMethods,
o->num_auth_methods, o->auth_methods);
/* other arguments */
for (i = 0; i < o->num_subsystems; i++)

View File

@ -1,4 +1,4 @@
/* $OpenBSD: servconf.h,v 1.104 2012/10/30 21:29:55 djm Exp $ */
/* $OpenBSD: servconf.h,v 1.105 2012/11/04 11:09:15 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@ -28,6 +28,7 @@
#define MAX_ACCEPT_ENV 256 /* Max # of env vars. */
#define MAX_MATCH_GROUPS 256 /* Max # of groups for Match. */
#define MAX_AUTHKEYS_FILES 256 /* Max # of authorized_keys files. */
#define MAX_AUTH_METHODS 256 /* Max # of AuthenticationMethods. */
/* permit_root_login */
#define PERMIT_NOT_SET -1
@ -170,6 +171,9 @@ typedef struct {
char *authorized_keys_command_user;
char *version_addendum; /* Appended to SSH banner */
u_int num_auth_methods;
char *auth_methods[MAX_AUTH_METHODS];
} ServerOptions;
/* Information about the incoming connection as used by Match */
@ -199,6 +203,7 @@ struct connection_info {
M_CP_STRARRAYOPT(allow_groups, num_allow_groups); \
M_CP_STRARRAYOPT(deny_groups, num_deny_groups); \
M_CP_STRARRAYOPT(accept_env, num_accept_env); \
M_CP_STRARRAYOPT(auth_methods, num_auth_methods); \
} while (0)
struct connection_info *get_connection_info(int, int);

23
sshd.c
View File

@ -1,4 +1,4 @@
/* $OpenBSD: sshd.c,v 1.395 2012/11/04 10:38:43 djm Exp $ */
/* $OpenBSD: sshd.c,v 1.396 2012/11/04 11:09:15 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -1337,6 +1337,7 @@ main(int ac, char **av)
int remote_port;
char *line;
int config_s[2] = { -1 , -1 };
u_int n;
u_int64_t ibytes, obytes;
mode_t new_umask;
Key *key;
@ -1566,6 +1567,26 @@ main(int ac, char **av)
fatal("AuthorizedKeysCommand set without "
"AuthorizedKeysCommandUser");
/*
* Check whether there is any path through configured auth methods.
* Unfortunately it is not possible to verify this generally before
* daemonisation in the presence of Match block, but this catches
* and warns for trivial misconfigurations that could break login.
*/
if (options.num_auth_methods != 0) {
if ((options.protocol & SSH_PROTO_1))
fatal("AuthenticationMethods is not supported with "
"SSH protocol 1");
for (n = 0; n < options.num_auth_methods; n++) {
if (auth2_methods_valid(options.auth_methods[n],
1) == 0)
break;
}
if (n >= options.num_auth_methods)
fatal("AuthenticationMethods cannot be satisfied by "
"enabled authentication methods");
}
/* set default channel AF */
channel_set_af(options.address_family);

View File

@ -33,7 +33,7 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
.\" $OpenBSD: sshd_config.5,v 1.148 2012/11/04 10:38:43 djm Exp $
.\" $OpenBSD: sshd_config.5,v 1.149 2012/11/04 11:09:15 djm Exp $
.Dd $Mdocdate: November 4 2012 $
.Dt SSHD_CONFIG 5
.Os
@ -151,6 +151,28 @@ See
in
.Xr ssh_config 5
for more information on patterns.
.It Cm AuthenticationMethods
Specifies the authentication methods that must be successfully completed
for a user to be granted access.
This option must be followed by one or more comma-separated lists of
authentication method names.
Successful authentication requires completion of every method in at least
one of these lists.
.Pp
For example, an argument of
.Dq publickey,password publickey,keyboard-interactive
would require the user to complete public key authentication, followed by
either password or keyboard interactive authentication.
Only methods that are next in one or more lists are offered at each stage,
so for this example, it would not be possible to attempt password or
keyboard-interactive authentication before public key.
.Pp
This option is only available for SSH protocol 2 and will yield a fatal
error if enabled if protocol 1 is also enabled.
Note that each authentication method listed should also be explicitly enabled
in the configuration.
The default is not to require multiple authentication; successful completion
of a single authentication method is sufficient.
.It Cm AuthorizedKeysCommand
Specifies a program to be used to look up the user's public keys.
The program will be invoked with a single argument of the username
@ -728,6 +750,7 @@ Available keywords are
.Cm AllowGroups ,
.Cm AllowTcpForwarding ,
.Cm AllowUsers ,
.Cm AuthenticationMethods ,
.Cm AuthorizedKeysCommand ,
.Cm AuthorizedKeysCommandUser ,
.Cm AuthorizedKeysFile ,