]> git.feebdaed.xyz Git - 0xmirror/openssh-portable.git/commitdiff
upstream: When certificate support was added to OpenSSH,
authordjm@openbsd.org <djm@openbsd.org>
Mon, 22 Dec 2025 01:49:03 +0000 (01:49 +0000)
committerDamien Miller <djm@mindrot.org>
Mon, 22 Dec 2025 01:51:24 +0000 (12:51 +1100)
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
auth2-pubkey.c
auth2-pubkeyfile.c
ssh-agent.c
ssh-keygen.1
ssh-keygen.c
sshconnect.c
sshkey.c
sshkey.h
sshsig.c

index 9d8b860eb14e71f43327b904d87a41a963563bad..e2ed8b3eb6653f13f03f7255f5fd7e9f72a1d5ee 100644 (file)
@@ -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");
index 15ad3000c6cd3016d5dfcb18261c665da37d569c..5d5d79196a092916b842b07d04639cf9e018d1b9 100644 (file)
@@ -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;
 
index 9d59e566658e2f117591bab243b673edd1160058..896ea19967ded1f1ad05ff6bd8f302dbca916fd2 100644 (file)
@@ -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;
index cd569c33ac101b11f9c7fade89718ad856a0e475..963f4feb32b2c76d01fa919ec8a88e2188f69f21 100644 (file)
@@ -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@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@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);
index 7ceb1db959fbc98b1b91f4d1a1db84a6ed9d322c..c5f3f741017f56c9824b445062c07f05de252e6b 100644 (file)
@@ -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@cs.hut.fi>
 .\" Copyright (c) 1995 Tatu Ylonen <ylo@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.
index 1a05876ab845dcc98c491e8442dc1ceed5d9db64..8d9e5f885db4db4ac210280d2d2d6b525f2f5415 100644 (file)
@@ -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@cs.hut.fi>
  * Copyright (c) 1994 Tatu Ylonen <ylo@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,
index 912a520c51bfdf8a9a856c05019028a53a1a89f8..4b4a9018957c0de2a785953f6021f5ee1f57fa58 100644 (file)
@@ -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@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@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;
index 791361474d6f1e431865a7b7c8d66c1473d67301..51706533206fa94d749a8fa99b1e37a16f538919 100644 (file)
--- 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";
index c3262b896f062b3cbbc516841ae0ea601fdc15f3..37231847916a04802465c1ed0b8417fab0368aa6 100644 (file)
--- 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 *);
index 3789c437baa05c50ecb6c83981d1db77f0fd7b5e..5b267d07d684dff4543f170f1c3d894f81dcc9ac 100644 (file)
--- 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);