mirror of
https://github.com/openssh/openssh-portable.git
synced 2026-11-06 05:16:43 +00:00
upstream: Account pubkey checks separately to auth attempts
Add a `PubkeyOptions max-pk-ok:nnnn` option to allow PK_OK tests (asking whether the server might accept a given public key) that do not count against MaxAuthTries, defaulting to 6 attempts. After these attempts are exhausted, futher attempts count as failed authentications against MaxAuthTries. ok markus, deraadt OpenBSD-Commit-ID: 4a02d2f303f4d83c10871221dce1db1f9fe2936f
This commit is contained in:
1 parent
8895b5ae1b
commit
14843e73b9
6 files changed
+102
-13
No files matched your search
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: auth.h,v 1.110 2026/07/06 07:44:48 djm Exp $ */
|
||||
/* $OpenBSD: auth.h,v 1.111 2026/09/16 00:16:52 djm Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2000 Markus Friedl. All rights reserved.
|
||||
@@ -59,6 +59,8 @@ struct Authctxt {
|
||||
int valid; /* user exists and is allowed to login */
|
||||
int attempt;
|
||||
int failures;
|
||||
int pk_ok_failures;
|
||||
int auth_failure_already_counted;
|
||||
int server_caused_failure;
|
||||
int force_pwchange;
|
||||
char *user; /* username sent by the client */
|
||||
|
||||
+14
-2
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: auth2-pubkey.c,v 1.127 2026/07/30 03:37:39 djm Exp $ */
|
||||
/* $OpenBSD: auth2-pubkey.c,v 1.128 2026/09/16 00:16:52 djm Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2000 Markus Friedl. All rights reserved.
|
||||
* Copyright (c) 2010 Damien Miller. All rights reserved.
|
||||
@@ -291,7 +291,19 @@ userauth_pubkey(struct ssh *ssh, const char *method)
|
||||
(r = ssh_packet_write_wait(ssh)) != 0)
|
||||
fatal_fr(r, "send packet");
|
||||
authctxt->postponed = 1;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Don't count this as an authentication failure
|
||||
* unless we have already used up the separate
|
||||
* max-pk-ok budget (if any).
|
||||
*/
|
||||
if (authctxt->pk_ok_failures++ < options.max_pubkey_ok)
|
||||
authctxt->auth_failure_already_counted = 1;
|
||||
debug3_f("pubkey test %d of %d%s",
|
||||
authctxt->pk_ok_failures, options.max_pubkey_ok,
|
||||
authctxt->auth_failure_already_counted ?
|
||||
"" : ": treating as authentication failure");
|
||||
}
|
||||
}
|
||||
done:
|
||||
if (authenticated == 1 && auth_activate_options(ssh, authopts) != 0) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: auth2.c,v 1.174 2026/07/06 07:44:48 djm Exp $ */
|
||||
/* $OpenBSD: auth2.c,v 1.175 2026/09/16 00:16:52 djm Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2000 Markus Friedl. All rights reserved.
|
||||
*
|
||||
@@ -344,6 +344,7 @@ input_userauth_request(int type, uint32_t seq, struct ssh *ssh)
|
||||
auth2_authctxt_reset_info(authctxt);
|
||||
authctxt->postponed = 0;
|
||||
authctxt->server_caused_failure = 0;
|
||||
authctxt->auth_failure_already_counted = 0;
|
||||
|
||||
/* try to authenticate user */
|
||||
m = authmethod_lookup(authctxt, method);
|
||||
@@ -448,6 +449,7 @@ userauth_finish(struct ssh *ssh, int authenticated, const char *packet_method,
|
||||
} else {
|
||||
/* Allow initial try of "none" auth without failure penalty */
|
||||
if (!partial && !authctxt->server_caused_failure &&
|
||||
!authctxt->auth_failure_already_counted &&
|
||||
(authctxt->attempt > 1 || strcmp(method, "none") != 0))
|
||||
authctxt->failures++;
|
||||
if (authctxt->failures >= options.max_authtries) {
|
||||
|
||||
+60
-4
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: servconf.c,v 1.453 2026/09/16 00:13:58 djm Exp $ */
|
||||
/* $OpenBSD: servconf.c,v 1.454 2026/09/16 00:16:52 djm Exp $ */
|
||||
/*
|
||||
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||
* All rights reserved
|
||||
@@ -159,6 +159,9 @@ initialize_server_options(ServerOptions *options)
|
||||
options->subsystem_name = NULL; \
|
||||
options->subsystem_command = NULL; \
|
||||
options->subsystem_args = NULL;
|
||||
#define init_pubkey_auth_options(options) \
|
||||
options->pubkey_auth_options = -1; \
|
||||
options->max_pubkey_ok = -1;
|
||||
#define init_timingsecret(options) \
|
||||
options->timing_secret = 0;
|
||||
|
||||
@@ -407,6 +410,10 @@ fill_default_server_options(ServerOptions *options)
|
||||
options->sshd_session_path = xstrdup(_PATH_SSHD_SESSION);
|
||||
if (options->sshd_auth_path == NULL)
|
||||
options->sshd_auth_path = xstrdup(_PATH_SSHD_AUTH);
|
||||
if (options->pubkey_auth_options == -1) {
|
||||
options->pubkey_auth_options = 0;
|
||||
options->max_pubkey_ok = DEFAULT_AUTH_FAIL_MAX;
|
||||
}
|
||||
|
||||
assemble_algorithms(options);
|
||||
|
||||
@@ -1403,6 +1410,7 @@ process_server_config_line_depth(ServerOptions *options, char *line,
|
||||
case sPubkeyAuthOptions:
|
||||
intptr = &options->pubkey_auth_options;
|
||||
value = 0;
|
||||
value2 = -1;
|
||||
while ((arg = argv_next(&ac, &av)) != NULL) {
|
||||
if (strcasecmp(arg, "none") == 0)
|
||||
continue;
|
||||
@@ -1410,14 +1418,24 @@ process_server_config_line_depth(ServerOptions *options, char *line,
|
||||
value |= PUBKEYAUTH_TOUCH_REQUIRED;
|
||||
else if (strcasecmp(arg, "verify-required") == 0)
|
||||
value |= PUBKEYAUTH_VERIFY_REQUIRED;
|
||||
else {
|
||||
else if (strncasecmp(arg, "max-pk-ok:", 10) == 0) {
|
||||
value2 = strtonum(arg + 10, 0, 255, &errstr);
|
||||
if (errstr != NULL) {
|
||||
error("%s line %d: bad %s max-pk-ok "
|
||||
"value: %s", filename, linenum,
|
||||
keyword, errstr);
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
error("%s line %d: unsupported %s option %s",
|
||||
filename, linenum, keyword, arg);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
if (*activep && *intptr == -1)
|
||||
*intptr = value;
|
||||
if (*activep && options->pubkey_auth_options == -1) {
|
||||
options->pubkey_auth_options = value;
|
||||
options->max_pubkey_ok = value2;
|
||||
}
|
||||
break;
|
||||
|
||||
#ifdef KRB5
|
||||
@@ -3060,6 +3078,19 @@ serialise_subsystem(const ServerOptions *options, struct sshbuf *buf)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
serialise_pubkey_auth_options(const ServerOptions *options, struct sshbuf *buf)
|
||||
{
|
||||
int r;
|
||||
|
||||
if ((r = serialise_s32(buf, options->pubkey_auth_options)) != 0 ||
|
||||
(r = serialise_s32(buf, options->max_pubkey_ok)) != 0) {
|
||||
error_fr(r, "serialise");
|
||||
return r;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
serialise_timingsecret(const ServerOptions *options, struct sshbuf *buf)
|
||||
{
|
||||
@@ -3594,6 +3625,19 @@ deserialise_subsystem(ServerOptions *options, struct sshbuf *buf)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
deserialise_pubkey_auth_options(ServerOptions *options, struct sshbuf *buf)
|
||||
{
|
||||
int r;
|
||||
|
||||
if ((r = deserialise_s32(buf, &options->pubkey_auth_options)) != 0 ||
|
||||
(r = deserialise_s32(buf, &options->max_pubkey_ok)) != 0) {
|
||||
error_fr(r, "deserialise");
|
||||
return r;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
deserialise_timingsecret(ServerOptions *options, struct sshbuf *buf)
|
||||
{
|
||||
@@ -3768,6 +3812,7 @@ free_server_options(ServerOptions *options)
|
||||
#define free_persourcenetblocksize(options)
|
||||
#define free_persourcepenalties(options)
|
||||
#define free_rekeylimit(options)
|
||||
#define free_pubkey_auth_options(options)
|
||||
#define free_timingsecret(options)
|
||||
|
||||
SSHD_CONFIG_ENTRIES
|
||||
@@ -3900,6 +3945,15 @@ copy_subsystem(ServerOptions *dst, const ServerOptions *src)
|
||||
dst->num_subsystems = src->num_subsystems;
|
||||
}
|
||||
|
||||
static void
|
||||
copy_pubkey_auth_options(ServerOptions *dst, const ServerOptions *src)
|
||||
{
|
||||
if (src->pubkey_auth_options != -1) {
|
||||
dst->pubkey_auth_options = src->pubkey_auth_options;
|
||||
dst->max_pubkey_ok = src->max_pubkey_ok;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy any supported values that are set.
|
||||
*
|
||||
@@ -4374,6 +4428,8 @@ dump_config(ServerOptions *o)
|
||||
printf(" touch-required");
|
||||
if (o->pubkey_auth_options & PUBKEYAUTH_VERIFY_REQUIRED)
|
||||
printf(" verify-required");
|
||||
if (o->max_pubkey_ok != -1)
|
||||
printf(" max-pk-ok:%d", o->max_pubkey_ok);
|
||||
printf("\n");
|
||||
|
||||
if (o->per_source_penalty.enabled) {
|
||||
|
||||
+5
-2
@@ -1,4 +1,4 @@
|
||||
/* $OpenBSD: servconf.h,v 1.180 2026/09/16 00:13:58 djm Exp $ */
|
||||
/* $OpenBSD: servconf.h,v 1.181 2026/09/16 00:16:52 djm Exp $ */
|
||||
|
||||
/*
|
||||
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||
@@ -161,6 +161,7 @@ SSHCONF_CUSTOM(MaxStartups, maxstartups, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \
|
||||
SSHCONF_CUSTOM(PerSourceNetBlockSize, persourcenetblocksize, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \
|
||||
SSHCONF_CUSTOM(PerSourcePenalties, persourcepenalties, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \
|
||||
SSHCONF_CUSTOM(RekeyLimit, rekeylimit, SSHCFG_ALL, SSHCFG_COPY_MATCH) \
|
||||
SSHCONF_CUSTOM(PubkeyAuthOptions, pubkey_auth_options, SSHCFG_ALL, SSHCFG_COPY_MATCH) \
|
||||
SSHCONF_NONCONF(timingsecret)
|
||||
|
||||
#define SSHD_CONFIG_ENTRIES_MAIN \
|
||||
@@ -192,7 +193,6 @@ SSHCONF_INTFLAG(hostbased_uses_name_from_packet_only, HostbasedUsesNameFromPacke
|
||||
SSHCONF_STRING(hostbased_accepted_algos, HostbasedAcceptedAlgorithms, SSHCFG_ALL, SSHCFG_COPY_MATCH) \
|
||||
SSHCONF_STRING(hostkeyalgorithms, HostKeyAlgorithms, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \
|
||||
SSHCONF_STRING(ca_sign_algorithms, CASignatureAlgorithms, SSHCFG_ALL, SSHCFG_COPY_MATCH) \
|
||||
SSHCONF_INT(pubkey_auth_options, PubkeyAuthOptions, SSHCFG_ALL, NULL, 0, SSHCFG_COPY_MATCH) \
|
||||
SSHCONF_INTFLAG(pubkey_authentication, PubkeyAuthentication, SSHCFG_ALL, 1, SSHCFG_COPY_MATCH) \
|
||||
SSHCONF_STRING(pubkey_accepted_algos, PubkeyAcceptedAlgorithms, SSHCFG_ALL, SSHCFG_COPY_MATCH) \
|
||||
SSHCONF_INTFLAG(password_authentication, PasswordAuthentication, SSHCFG_ALL, 1, SSHCFG_COPY_MATCH) \
|
||||
@@ -394,6 +394,9 @@ typedef struct ServerOptions {
|
||||
/* RekeyLimit */
|
||||
int64_t rekey_limit;
|
||||
int rekey_interval;
|
||||
/* PubkeyAuthOptions */
|
||||
int pubkey_auth_options;
|
||||
int max_pubkey_ok;
|
||||
/* Passed by config but not keyword for this */
|
||||
uint64_t timing_secret;
|
||||
} ServerOptions;
|
||||
|
||||
+17
-3
@@ -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.404 2026/09/16 00:13:58 djm Exp $
|
||||
.\" $OpenBSD: sshd_config.5,v 1.405 2026/09/16 00:16:52 djm Exp $
|
||||
.Dd $Mdocdate: September 16 2026 $
|
||||
.Dt SSHD_CONFIG 5
|
||||
.Os
|
||||
@@ -1782,9 +1782,10 @@ Sets one or more public key authentication options.
|
||||
The supported keywords are:
|
||||
.Cm none
|
||||
(the default; indicating no additional options are enabled),
|
||||
.Cm touch-required
|
||||
.Cm touch-required ,
|
||||
.Cm verify-required
|
||||
and
|
||||
.Cm verify-required .
|
||||
.Cm max-pk-ok .
|
||||
.Pp
|
||||
The
|
||||
.Cm touch-required
|
||||
@@ -1812,6 +1813,19 @@ Neither the
|
||||
or
|
||||
.Cm verify-required
|
||||
options have any effect for other, non-FIDO, public key types.
|
||||
.Pp
|
||||
The
|
||||
.Cm max-pk-ok
|
||||
option takes a numeric argument separated by a colon character, e.g.
|
||||
.Sq max-pk-ok:4
|
||||
to specify the number of public key query attempts are permitted
|
||||
before the attempts are considered failed authentications that count against
|
||||
.Cm MaxAuthTries .
|
||||
Public key queries are a SSH protocol mechanism that allow a client to test
|
||||
whether a server accepts a given public key before an actual authentication
|
||||
attempt, to avoid the need to enter PINs/key passphrases or touch FIDO
|
||||
authenticators unnecessarily.
|
||||
The default is 6.
|
||||
.It Cm PubkeyAuthentication
|
||||
Specifies whether public key authentication is allowed.
|
||||
The default is
|
||||
|
||||
Reference in new issue
Block a user