[openssh-commits] [openssh] 03/14: upstream: Account pubkey checks separately to auth attempts

git+noreply at mindrot.org git+noreply at mindrot.org
Wed Sep 16 11:15:57 AEST 2026


This is an automated email from the git hooks/post-receive script.

djm pushed a commit to branch master
in repository openssh.

commit 14843e73b970e248b785d84df0e54ae9f3992cff
Author: djm at openbsd.org <djm at openbsd.org>
AuthorDate: Wed Sep 16 00:16:52 2026 +0000

    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
---
 auth.h         |  4 +++-
 auth2-pubkey.c | 16 +++++++++++++--
 auth2.c        |  4 +++-
 servconf.c     | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 servconf.h     |  7 +++++--
 sshd_config.5  | 20 +++++++++++++++---
 6 files changed, 102 insertions(+), 13 deletions(-)

diff --git a/auth.h b/auth.h
index 0f11458ca..17227cffb 100644
--- a/auth.h
+++ b/auth.h
@@ -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 */
diff --git a/auth2-pubkey.c b/auth2-pubkey.c
index c15b1ba71..97d248795 100644
--- a/auth2-pubkey.c
+++ b/auth2-pubkey.c
@@ -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) {
diff --git a/auth2.c b/auth2.c
index 3f353a719..6971b933b 100644
--- a/auth2.c
+++ b/auth2.c
@@ -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) {
diff --git a/servconf.c b/servconf.c
index d6f344989..a5bf3479a 100644
--- a/servconf.c
+++ b/servconf.c
@@ -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 at 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) {
diff --git a/servconf.h b/servconf.h
index a394274e2..089d600ad 100644
--- a/servconf.h
+++ b/servconf.h
@@ -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 at 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;
diff --git a/sshd_config.5 b/sshd_config.5
index 1bb4a3791..0d9f88061 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.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

-- 
To stop receiving notification emails like this one, please contact
djm at mindrot.org.


More information about the openssh-commits mailing list