[openssh-commits] [openssh] 03/05: upstream: When certificate support was added to OpenSSH,

git+noreply at mindrot.org git+noreply at mindrot.org
Mon Dec 22 12:51:41 AEDT 2025


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

djm pushed a commit to branch master
in repository openssh.

commit 5166b6cbf2b6103117a79f90a68068e89e02bf66
Author: djm at openbsd.org <djm at openbsd.org>
AuthorDate: Mon Dec 22 01:49:03 2025 +0000

    upstream: When certificate support was added to OpenSSH,
    
    certificates were originally specified to represent any principal if the
    principals list was empty.
    
    This was, in retrospect, a mistake as it created a fail-open
    situation if a CA could be convinced to accidentally sign a
    certificate with no principals. This actually happened in a 3rd-
    party CA product (CVE-2024-7594).
    
    Somewhat fortunately, the main pathway for using certificates in
    sshd (TrustedUserCAKeys) never supported empty-principals
    certificates, so the blast radius of such mistakes was
    substantially reduced.
    
    This change removes this footcannon and requires all certificates
    include principals sections. It also fixes interpretation of
    wildcard principals, and properly enables them for host
    certificates only.
    
    This is a behaviour change that will permanently break uses of
    certificates with empty principals sections.
    
    ok markus@
    
    OpenBSD-Commit-ID: 0a901f03c567c100724a492cf91e02939904712e
---
 auth2-hostbased.c  |  6 +++---
 auth2-pubkey.c     |  4 ++--
 auth2-pubkeyfile.c |  4 ++--
 ssh-agent.c        |  4 ++--
 ssh-keygen.1       | 34 ++++++++++++++++++++++----------
 ssh-keygen.c       | 11 ++++++++++-
 sshconnect.c       |  4 ++--
 sshkey.c           | 57 ++++++++++++++++++++++++++----------------------------
 sshkey.h           |  8 ++++----
 sshsig.c           |  8 ++++----
 10 files changed, 80 insertions(+), 60 deletions(-)

diff --git a/auth2-hostbased.c b/auth2-hostbased.c
index 9d8b860eb..e2ed8b3eb 100644
--- a/auth2-hostbased.c
+++ b/auth2-hostbased.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth2-hostbased.c,v 1.55 2025/08/14 09:26:53 dtucker Exp $ */
+/* $OpenBSD: auth2-hostbased.c,v 1.56 2025/12/22 01:49:03 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  *
@@ -211,8 +211,8 @@ hostbased_key_allowed(struct ssh *ssh, struct passwd *pw,
 	}
 	debug2_f("access allowed by auth_rhosts2");
 
-	if (sshkey_is_cert(key) &&
-	    sshkey_cert_check_authority_now(key, 1, 0, 0, lookup, &reason)) {
+	if (sshkey_is_cert(key) && sshkey_cert_check_host(key, lookup,
+	     options.ca_sign_algorithms, &reason) != 0) {
 		if ((fp = sshkey_fingerprint(key->cert->signature_key,
 		    options.fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
 			fatal_f("sshkey_fingerprint fail");
diff --git a/auth2-pubkey.c b/auth2-pubkey.c
index 15ad3000c..5d5d79196 100644
--- a/auth2-pubkey.c
+++ b/auth2-pubkey.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth2-pubkey.c,v 1.124 2025/08/14 09:44:39 dtucker Exp $ */
+/* $OpenBSD: auth2-pubkey.c,v 1.125 2025/12/22 01:49:03 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  * Copyright (c) 2010 Damien Miller.  All rights reserved.
@@ -562,7 +562,7 @@ user_cert_trusted_ca(struct passwd *pw, struct sshkey *key,
 	}
 	if (use_authorized_principals && principals_opts == NULL)
 		fatal_f("internal error: missing principals_opts");
-	if (sshkey_cert_check_authority_now(key, 0, 1, 0,
+	if (sshkey_cert_check_authority_now(key, 0, 0,
 	    use_authorized_principals ? NULL : pw->pw_name, &reason) != 0)
 		goto fail_reason;
 
diff --git a/auth2-pubkeyfile.c b/auth2-pubkeyfile.c
index 9d59e5666..896ea1996 100644
--- a/auth2-pubkeyfile.c
+++ b/auth2-pubkeyfile.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth2-pubkeyfile.c,v 1.6 2025/08/14 10:03:44 dtucker Exp $ */
+/* $OpenBSD: auth2-pubkeyfile.c,v 1.7 2025/12/22 01:49:03 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  * Copyright (c) 2010 Damien Miller.  All rights reserved.
@@ -364,7 +364,7 @@ auth_check_authkey_line(struct passwd *pw, struct sshkey *key,
 		reason = "Certificate does not contain an authorized principal";
 		goto cert_fail_reason;
 	}
-	if (sshkey_cert_check_authority_now(key, 0, 0, 0,
+	if (sshkey_cert_check_authority_now(key, 0, 0,
 	    keyopts->cert_principals == NULL ? pw->pw_name : NULL,
 	    &reason) != 0)
 		goto cert_fail_reason;
diff --git a/ssh-agent.c b/ssh-agent.c
index cd569c33a..963f4feb3 100644
--- a/ssh-agent.c
+++ b/ssh-agent.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-agent.c,v 1.315 2025/11/13 10:35:14 dtucker Exp $ */
+/* $OpenBSD: ssh-agent.c,v 1.316 2025/12/22 01:49:03 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -392,7 +392,7 @@ match_key_hop(const char *tag, const struct sshkey *key,
 			return -1; /* shouldn't happen */
 		if (!sshkey_equal(key->cert->signature_key, dch->keys[i]))
 			continue;
-		if (sshkey_cert_check_host(key, hostname, 1,
+		if (sshkey_cert_check_host(key, hostname,
 		    SSH_ALLOWED_CA_SIGALGS, &reason) != 0) {
 			debug_f("cert %s / hostname %s rejected: %s",
 			    key->cert->key_id, hostname, reason);
diff --git a/ssh-keygen.1 b/ssh-keygen.1
index 7ceb1db95..c5f3f7410 100644
--- a/ssh-keygen.1
+++ b/ssh-keygen.1
@@ -1,4 +1,4 @@
-.\"	$OpenBSD: ssh-keygen.1,v 1.236 2025/10/04 21:41:35 naddy Exp $
+.\"	$OpenBSD: ssh-keygen.1,v 1.237 2025/12/22 01:49:03 djm Exp $
 .\"
 .\" Author: Tatu Ylonen <ylo at cs.hut.fi>
 .\" Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -35,7 +35,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.
 .\"
-.Dd $Mdocdate: October 4 2025 $
+.Dd $Mdocdate: December 22 2025 $
 .Dt SSH-KEYGEN 1
 .Os
 .Sh NAME
@@ -902,15 +902,29 @@ User certificates authenticate users to servers, whereas host certificates
 authenticate server hosts to users.
 To generate a user certificate:
 .Pp
-.Dl $ ssh-keygen -s /path/to/ca_key -I key_id /path/to/user_key.pub
+.Dl $ ssh-keygen -s /path/to/ca_key -I id -n user \e
+.Dl \ \ \ \ \ \ /path/to/user_key.pub
 .Pp
 The resultant certificate will be placed in
 .Pa /path/to/user_key-cert.pub .
+The argument to
+.Fl I
+is a key identifier that will be used in logs and may be used to revoke
+keys.
+The argument to
+.Fl n
+is one or more (comma-separated) principals, typically usernames, that
+the certificate represents.
 A host certificate requires the
 .Fl h
 option:
 .Pp
-.Dl $ ssh-keygen -s /path/to/ca_key -I key_id -h /path/to/host_key.pub
+.Dl $ ssh-keygen -s /path/to/ca_key -I id -h -n foo.example.org \e
+.Dl \ \ \ \ \ \ /path/to/host_key.pub
+.Pp
+For host certificates, the principals specified using the
+.Fl n
+argument are hostnames and may contain wildcard characters.
 .Pp
 The host certificate will be output to
 .Pa /path/to/host_key-cert.pub .
@@ -922,7 +936,8 @@ and identifying the CA key by providing its public half as an argument
 to
 .Fl s :
 .Pp
-.Dl $ ssh-keygen -s ca_key.pub -D libpkcs11.so -I key_id user_key.pub
+.Dl $ ssh-keygen -s ca_key.pub -D libpkcs11.so -I id -n user \e
+.Dl \ \ \ \ \ \ user_key.pub
 .Pp
 Similarly, it is possible for the CA key to be hosted in an
 .Xr ssh-agent 1 .
@@ -930,20 +945,19 @@ This is indicated by the
 .Fl U
 flag and, again, the CA key must be identified by its public half.
 .Pp
-.Dl $ ssh-keygen -Us ca_key.pub -I key_id user_key.pub
+.Dl $ ssh-keygen -Us ca_key.pub -I id -n user user_key.pub
 .Pp
 In all cases,
 .Ar key_id
 is a "key identifier" that is logged by the server when the certificate
 is used for authentication.
 .Pp
-Certificates may be limited to be valid for a set of principal (user/host)
+Certificates are limited to be valid for a set of principal (user/host)
 names.
-By default, generated certificates are valid for all users or hosts.
 To generate a certificate for a specified set of principals:
 .Pp
-.Dl $ ssh-keygen -s ca_key -I key_id -n user1,user2 user_key.pub
-.Dl "$ ssh-keygen -s ca_key -I key_id -h -n host.domain host_key.pub"
+.Dl $ ssh-keygen -s ca_key -I id -n user1,user2 user_key.pub
+.Dl $ ssh-keygen -s ca_key -I id -h -n host.domain host_key.pub
 .Pp
 Additional limitations on the validity and use of user certificates may
 be specified through certificate options.
diff --git a/ssh-keygen.c b/ssh-keygen.c
index 1a05876ab..8d9e5f885 100644
--- a/ssh-keygen.c
+++ b/ssh-keygen.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-keygen.c,v 1.487 2025/11/13 10:35:14 dtucker Exp $ */
+/* $OpenBSD: ssh-keygen.c,v 1.488 2025/12/22 01:49:03 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
  * Copyright (c) 1994 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -3665,6 +3665,15 @@ main(int argc, char **argv)
 	if (ca_key_path != NULL) {
 		if (cert_key_id == NULL)
 			fatal("Must specify key id (-I) when certifying");
+		if (cert_principals == NULL) {
+			/*
+			 * Ideally this would be a fatal(), but we need to
+			 * be able to generate such certificates for testing
+			 * even though they will be rejected.
+			 */
+			error("Warning: certificate will contain no "
+			    "principals (-n)");
+		}
 		for (i = 0; i < nopts; i++)
 			add_cert_option(opts[i]);
 		do_ca_sign(pw, ca_key_path, prefer_agent,
diff --git a/sshconnect.c b/sshconnect.c
index 912a520c5..4b4a90189 100644
--- a/sshconnect.c
+++ b/sshconnect.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshconnect.c,v 1.376 2025/09/25 06:23:19 jsg Exp $ */
+/* $OpenBSD: sshconnect.c,v 1.377 2025/12/22 01:49:03 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, Espoo, Finland
@@ -1084,7 +1084,7 @@ check_host_key(char *hostname, const struct ssh_conn_info *cinfo,
 		if (want_cert) {
 			if (sshkey_cert_check_host(host_key,
 			    options.host_key_alias == NULL ?
-			    hostname : options.host_key_alias, 0,
+			    hostname : options.host_key_alias,
 			    options.ca_sign_algorithms, &fail_reason) != 0) {
 				error("%s", fail_reason);
 				goto fail;
diff --git a/sshkey.c b/sshkey.c
index 791361474..517065332 100644
--- a/sshkey.c
+++ b/sshkey.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshkey.c,v 1.158 2025/11/25 01:08:35 djm Exp $ */
+/* $OpenBSD: sshkey.c,v 1.159 2025/12/22 01:49:03 djm Exp $ */
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
  * Copyright (c) 2008 Alexander von Gernler.  All rights reserved.
@@ -2386,8 +2386,8 @@ sshkey_certify(struct sshkey *k, struct sshkey *ca, const char *alg,
 
 int
 sshkey_cert_check_authority(const struct sshkey *k,
-    int want_host, int require_principal, int wildcard_pattern,
-    uint64_t verify_time, const char *name, const char **reason)
+    int want_host, int wildcard_pattern, uint64_t verify_time,
+    const char *name, const char **reason)
 {
 	u_int i, principal_matches;
 
@@ -2417,37 +2417,36 @@ sshkey_cert_check_authority(const struct sshkey *k,
 		return SSH_ERR_KEY_CERT_INVALID;
 	}
 	if (k->cert->nprincipals == 0) {
-		if (require_principal) {
-			*reason = "Certificate lacks principal list";
-			return SSH_ERR_KEY_CERT_INVALID;
-		}
-	} else if (name != NULL) {
-		principal_matches = 0;
-		for (i = 0; i < k->cert->nprincipals; i++) {
-			if (wildcard_pattern) {
-				if (match_pattern(k->cert->principals[i],
-				    name)) {
-					principal_matches = 1;
-					break;
-				}
-			} else if (strcmp(name, k->cert->principals[i]) == 0) {
+		*reason = "Certificate lacks principal list";
+		return SSH_ERR_KEY_CERT_INVALID;
+	}
+	if (name == NULL)
+		return 0; /* principal matching not requested */
+
+	principal_matches = 0;
+	for (i = 0; i < k->cert->nprincipals; i++) {
+		if (wildcard_pattern) {
+			if (match_pattern(name, k->cert->principals[i])) {
 				principal_matches = 1;
 				break;
 			}
+		} else if (strcmp(name, k->cert->principals[i]) == 0) {
+			principal_matches = 1;
+			break;
 		}
-		if (!principal_matches) {
-			*reason = "Certificate invalid: name is not a listed "
-			    "principal";
-			return SSH_ERR_KEY_CERT_INVALID;
-		}
+	}
+	if (!principal_matches) {
+		*reason = "Certificate invalid: name is not a listed "
+		    "principal";
+		return SSH_ERR_KEY_CERT_INVALID;
 	}
 	return 0;
 }
 
 int
 sshkey_cert_check_authority_now(const struct sshkey *k,
-    int want_host, int require_principal, int wildcard_pattern,
-    const char *name, const char **reason)
+    int want_host, int wildcard_pattern, const char *name,
+    const char **reason)
 {
 	time_t now;
 
@@ -2456,19 +2455,17 @@ sshkey_cert_check_authority_now(const struct sshkey *k,
 		*reason = "Certificate invalid: not yet valid";
 		return SSH_ERR_KEY_CERT_INVALID;
 	}
-	return sshkey_cert_check_authority(k, want_host, require_principal,
-	    wildcard_pattern, (uint64_t)now, name, reason);
+	return sshkey_cert_check_authority(k, want_host, wildcard_pattern,
+	    (uint64_t)now, name, reason);
 }
 
 int
 sshkey_cert_check_host(const struct sshkey *key, const char *host,
-    int wildcard_principals, const char *ca_sign_algorithms,
-    const char **reason)
+    const char *ca_sign_algorithms, const char **reason)
 {
 	int r;
 
-	if ((r = sshkey_cert_check_authority_now(key, 1, 0, wildcard_principals,
-	    host, reason)) != 0)
+	if ((r = sshkey_cert_check_authority_now(key, 1, 1, host, reason)) != 0)
 		return r;
 	if (sshbuf_len(key->cert->critical) != 0) {
 		*reason = "Certificate contains unsupported critical options";
diff --git a/sshkey.h b/sshkey.h
index c3262b896..372318479 100644
--- a/sshkey.h
+++ b/sshkey.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshkey.h,v 1.70 2025/08/29 03:50:38 djm Exp $ */
+/* $OpenBSD: sshkey.h,v 1.71 2025/12/22 01:49:03 djm Exp $ */
 
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
@@ -218,12 +218,12 @@ int	 sshkey_match_keyname_to_sigalgs(const char *, const char *);
 int	 sshkey_to_certified(struct sshkey *);
 int	 sshkey_drop_cert(struct sshkey *);
 int	 sshkey_cert_copy(const struct sshkey *, struct sshkey *);
-int	 sshkey_cert_check_authority(const struct sshkey *, int, int, int,
+int	 sshkey_cert_check_authority(const struct sshkey *, int, int,
     uint64_t, const char *, const char **);
-int	 sshkey_cert_check_authority_now(const struct sshkey *, int, int, int,
+int	 sshkey_cert_check_authority_now(const struct sshkey *, int, int,
     const char *, const char **);
 int	 sshkey_cert_check_host(const struct sshkey *, const char *,
-    int , const char *, const char **);
+    const char *, const char **);
 size_t	 sshkey_format_cert_validity(const struct sshkey_cert *,
     char *, size_t) __attribute__((__bounded__(__string__, 2, 3)));
 int	 sshkey_check_cert_sigtype(const struct sshkey *, const char *);
diff --git a/sshsig.c b/sshsig.c
index 3789c437b..5b267d07d 100644
--- a/sshsig.c
+++ b/sshsig.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshsig.c,v 1.40 2025/09/25 06:23:19 jsg Exp $ */
+/* $OpenBSD: sshsig.c,v 1.41 2025/12/22 01:49:03 djm Exp $ */
 /*
  * Copyright (c) 2019 Google LLC
  *
@@ -854,8 +854,8 @@ cert_filter_principals(const char *path, u_long linenum,
 
 	while ((cp = strsep(&principals, ",")) != NULL && *cp != '\0') {
 		/* Check certificate validity */
-		if ((r = sshkey_cert_check_authority(cert, 0, 1, 0,
-		    verify_time, NULL, &reason)) != 0) {
+		if ((r = sshkey_cert_check_authority(cert, 0, 0, verify_time,
+		    NULL, &reason)) != 0) {
 			debug("%s:%lu: principal \"%s\" not authorized: %s",
 			    path, linenum, cp, reason);
 			continue;
@@ -920,7 +920,7 @@ check_allowed_keys_line(const char *path, u_long linenum, char *line,
 	    sshkey_equal_public(sign_key->cert->signature_key, found_key)) {
 		if (principal) {
 			/* Match certificate CA key with specified principal */
-			if ((r = sshkey_cert_check_authority(sign_key, 0, 1, 0,
+			if ((r = sshkey_cert_check_authority(sign_key, 0, 0,
 			    verify_time, principal, &reason)) != 0) {
 				error("%s:%lu: certificate not authorized: %s",
 				    path, linenum, reason);

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


More information about the openssh-commits mailing list