summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDamien Miller <djm@mindrot.org>2012-11-04 13:21:40 +0100
committerDamien Miller <djm@mindrot.org>2012-11-04 13:21:40 +0100
commita6e3f01d1e230b8acfdd6b4cf3096459d2a325e0 (patch)
tree577022d2b31e9519d26bc614c3f5396e17d58ec6
parent - djm@cvs.openbsd.org 2012/11/04 10:38:43 (diff)
downloadopenssh-a6e3f01d1e230b8acfdd6b4cf3096459d2a325e0.tar.xz
openssh-a6e3f01d1e230b8acfdd6b4cf3096459d2a325e0.zip
- 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@
-rw-r--r--ChangeLog8
-rw-r--r--auth.h7
-rw-r--r--auth1.c7
-rw-r--r--auth2.c218
-rw-r--r--monitor.c35
-rw-r--r--servconf.c26
-rw-r--r--servconf.h7
-rw-r--r--sshd.c23
-rw-r--r--sshd_config.525
9 files changed, 328 insertions, 28 deletions
diff --git a/ChangeLog b/ChangeLog
index 120c132af..85c92eb7f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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
diff --git a/auth.h b/auth.h
index 063404167..8920c7dae 100644
--- a/auth.h
+++ b/auth.h
@@ -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);
diff --git a/auth1.c b/auth1.c
index cc85aec74..fb37fadfe 100644
--- a/auth1.c
+++ b/auth1.c
@@ -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]" : "");
diff --git a/auth2.c b/auth2.c
index b66bef64c..8114ec863 100644
--- a/auth2.c
+++ b/auth2.c
@@ -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;
+}
+
+
diff --git a/monitor.c b/monitor.c
index e9802a3fd..0adbf3a65 100644
--- a/monitor.c
+++ b/monitor.c
@@ -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);
}
diff --git a/servconf.c b/servconf.c
index 8e69ea5ce..b90dba63b 100644
--- a/servconf.c
+++ b/servconf.c
@@ -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++)
diff --git a/servconf.h b/servconf.h
index 0064c9bc5..68fcdb764 100644
--- a/servconf.h
+++ b/servconf.h
@@ -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);
diff --git a/sshd.c b/sshd.c
index 4ad1a4bd1..af7ff91ba 100644
--- a/sshd.c
+++ b/sshd.c
@@ -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);
diff --git a/sshd_config.5 b/sshd_config.5
index 0fb0b837d..05f3374fb 100644
--- a/sshd_config.5
+++ b/sshd_config.5
@@ -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 ,