]> git.ipfire.org Git - thirdparty/openssh-portable.git/commitdiff
upstream: ssh(1): add a warning when the connection negotiates a master anongit/master
authordjm@openbsd.org <djm@openbsd.org>
Mon, 11 Aug 2025 10:55:38 +0000 (10:55 +0000)
committerDamien Miller <djm@mindrot.org>
Mon, 11 Aug 2025 11:03:29 +0000 (21:03 +1000)
non-post quantum safe key agreement algorithm.

Controlled via a new WarnWeakCrypto ssh_config option, defaulting
to on. This option might grow additional weak crypto warnings in
the future.

More details at https://openssh.com/pq.html

mostly by deraadt@ feedback dtucker@ ok deraadt@

OpenBSD-Commit-ID: 974ff243a1eccceac6a1a9d8fab3bcc89d74a2a4

kex-names.c
kex.c
kex.h
readconf.c
readconf.h
ssh_config.5
sshconnect.c

index ec840c1f9dbc2261d923a6f8d50f9743707582e4..96deb8817dbbb32b86dc8c06f611c64014438e57 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: kex-names.c,v 1.4 2024/09/09 02:39:57 djm Exp $ */
+/* $OpenBSD: kex-names.c,v 1.5 2025/08/11 10:55:38 djm Exp $ */
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
  *
@@ -50,44 +50,45 @@ struct kexalg {
        u_int type;
        int ec_nid;
        int hash_alg;
+       int pq_alg;
 };
 static const struct kexalg kexalgs[] = {
 #ifdef WITH_OPENSSL
-       { KEX_DH1, KEX_DH_GRP1_SHA1, 0, SSH_DIGEST_SHA1 },
-       { KEX_DH14_SHA1, KEX_DH_GRP14_SHA1, 0, SSH_DIGEST_SHA1 },
-       { KEX_DH14_SHA256, KEX_DH_GRP14_SHA256, 0, SSH_DIGEST_SHA256 },
-       { KEX_DH16_SHA512, KEX_DH_GRP16_SHA512, 0, SSH_DIGEST_SHA512 },
-       { KEX_DH18_SHA512, KEX_DH_GRP18_SHA512, 0, SSH_DIGEST_SHA512 },
-       { KEX_DHGEX_SHA1, KEX_DH_GEX_SHA1, 0, SSH_DIGEST_SHA1 },
+       { KEX_DH1, KEX_DH_GRP1_SHA1, 0, SSH_DIGEST_SHA1, KEX_NOT_PQ },
+       { KEX_DH14_SHA1, KEX_DH_GRP14_SHA1, 0, SSH_DIGEST_SHA1, KEX_NOT_PQ },
+       { KEX_DH14_SHA256, KEX_DH_GRP14_SHA256, 0, SSH_DIGEST_SHA256, KEX_NOT_PQ },
+       { KEX_DH16_SHA512, KEX_DH_GRP16_SHA512, 0, SSH_DIGEST_SHA512, KEX_NOT_PQ },
+       { KEX_DH18_SHA512, KEX_DH_GRP18_SHA512, 0, SSH_DIGEST_SHA512, KEX_NOT_PQ },
+       { KEX_DHGEX_SHA1, KEX_DH_GEX_SHA1, 0, SSH_DIGEST_SHA1, KEX_NOT_PQ },
 #ifdef HAVE_EVP_SHA256
-       { KEX_DHGEX_SHA256, KEX_DH_GEX_SHA256, 0, SSH_DIGEST_SHA256 },
+       { KEX_DHGEX_SHA256, KEX_DH_GEX_SHA256, 0, SSH_DIGEST_SHA256, KEX_NOT_PQ },
 #endif /* HAVE_EVP_SHA256 */
 #ifdef OPENSSL_HAS_ECC
        { KEX_ECDH_SHA2_NISTP256, KEX_ECDH_SHA2,
-           NID_X9_62_prime256v1, SSH_DIGEST_SHA256 },
+           NID_X9_62_prime256v1, SSH_DIGEST_SHA256, KEX_NOT_PQ },
        { KEX_ECDH_SHA2_NISTP384, KEX_ECDH_SHA2, NID_secp384r1,
-           SSH_DIGEST_SHA384 },
+           SSH_DIGEST_SHA384, KEX_NOT_PQ },
 # ifdef OPENSSL_HAS_NISTP521
        { KEX_ECDH_SHA2_NISTP521, KEX_ECDH_SHA2, NID_secp521r1,
-           SSH_DIGEST_SHA512 },
+           SSH_DIGEST_SHA512, KEX_NOT_PQ },
 # endif /* OPENSSL_HAS_NISTP521 */
 #endif /* OPENSSL_HAS_ECC */
 #endif /* WITH_OPENSSL */
 #if defined(HAVE_EVP_SHA256) || !defined(WITH_OPENSSL)
-       { KEX_CURVE25519_SHA256, KEX_C25519_SHA256, 0, SSH_DIGEST_SHA256 },
-       { KEX_CURVE25519_SHA256_OLD, KEX_C25519_SHA256, 0, SSH_DIGEST_SHA256 },
+       { KEX_CURVE25519_SHA256, KEX_C25519_SHA256, 0, SSH_DIGEST_SHA256, KEX_NOT_PQ },
+       { KEX_CURVE25519_SHA256_OLD, KEX_C25519_SHA256, 0, SSH_DIGEST_SHA256, KEX_NOT_PQ },
 #ifdef USE_SNTRUP761X25519
        { KEX_SNTRUP761X25519_SHA512, KEX_KEM_SNTRUP761X25519_SHA512, 0,
-           SSH_DIGEST_SHA512 },
+           SSH_DIGEST_SHA512, KEX_IS_PQ },
        { KEX_SNTRUP761X25519_SHA512_OLD, KEX_KEM_SNTRUP761X25519_SHA512, 0,
-           SSH_DIGEST_SHA512 },
+           SSH_DIGEST_SHA512, KEX_IS_PQ },
 #endif
 #ifdef USE_MLKEM768X25519
        { KEX_MLKEM768X25519_SHA256, KEX_KEM_MLKEM768X25519_SHA256, 0,
-           SSH_DIGEST_SHA256 },
+           SSH_DIGEST_SHA256, KEX_IS_PQ },
 #endif
 #endif /* HAVE_EVP_SHA256 || !WITH_OPENSSL */
-       { NULL, 0, -1, -1},
+       { NULL, 0, -1, -1, 0 },
 };
 
 char *
@@ -130,6 +131,16 @@ kex_name_valid(const char *name)
        return kex_alg_by_name(name) != NULL;
 }
 
+int
+kex_is_pq_from_name(const char *name)
+{
+       const struct kexalg *k;
+
+       if ((k = kex_alg_by_name(name)) == NULL)
+               return 0;
+       return k->pq_alg == KEX_IS_PQ;
+}
+
 u_int
 kex_type_from_name(const char *name)
 {
diff --git a/kex.c b/kex.c
index 6b957e5e18f57281fc7caf461589db5f7bb69a60..f8eaa8c970c6052e5b0812c631fa185358d44c0d 100644 (file)
--- a/kex.c
+++ b/kex.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: kex.c,v 1.187 2024/08/23 04:51:00 deraadt Exp $ */
+/* $OpenBSD: kex.c,v 1.188 2025/08/11 10:55:38 djm Exp $ */
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
  *
@@ -563,8 +563,6 @@ kex_input_newkeys(int type, u_int32_t seq, struct ssh *ssh)
        kex->flags &= ~KEX_INITIAL;
        sshbuf_reset(kex->peer);
        kex->flags &= ~KEX_INIT_SENT;
-       free(kex->name);
-       kex->name = NULL;
        return 0;
 }
 
@@ -620,6 +618,8 @@ kex_input_kexinit(int type, u_int32_t seq, struct ssh *ssh)
                error_f("no kex");
                return SSH_ERR_INTERNAL_ERROR;
        }
+       free(kex->name);
+       kex->name = NULL;
        ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_protocol_error);
        ptr = sshpkt_ptr(ssh, &dlen);
        if ((r = sshbuf_put(kex->peer, ptr, dlen)) != 0)
diff --git a/kex.h b/kex.h
index d08988b3e141fcaa2f247992e9dee54ddd835697..55baa6a1e641c0fbd5006b2a7f2d6800ea9421b2 100644 (file)
--- a/kex.h
+++ b/kex.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: kex.h,v 1.126 2024/09/02 12:13:56 djm Exp $ */
+/* $OpenBSD: kex.h,v 1.127 2025/08/11 10:55:38 djm Exp $ */
 
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
@@ -115,6 +115,10 @@ enum kex_exchange {
 #define KEX_HAS_PING                   0x0020
 #define KEX_HAS_EXT_INFO_IN_AUTH       0x0040
 
+/* kex->pq */
+#define KEX_NOT_PQ                     0
+#define KEX_IS_PQ                      1
+
 struct sshenc {
        char    *name;
        const struct sshcipher *cipher;
@@ -189,6 +193,7 @@ int  kex_name_valid(const char *);
 u_int   kex_type_from_name(const char *);
 int     kex_hash_from_name(const char *);
 int     kex_nid_from_name(const char *);
+int     kex_is_pq_from_name(const char *);
 int     kex_names_valid(const char *);
 char   *kex_alg_list(char);
 char   *kex_names_cat(const char *, const char *);
index 781e5b004068d9800173a0c290da894c51338fd4..c7701d8c26331d03d7add2275237721a65fd58be 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.c,v 1.404 2025/08/05 09:08:16 job Exp $ */
+/* $OpenBSD: readconf.c,v 1.405 2025/08/11 10:55:38 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -180,7 +180,7 @@ typedef enum {
        oPubkeyAcceptedAlgorithms, oCASignatureAlgorithms, oProxyJump,
        oSecurityKeyProvider, oKnownHostsCommand, oRequiredRSASize,
        oEnableEscapeCommandline, oObscureKeystrokeTiming, oChannelTimeout,
-       oVersionAddendum, oRefuseConnection,
+       oVersionAddendum, oRefuseConnection, oWarnWeakCrypto,
        oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported
 } OpCodes;
 
@@ -333,6 +333,7 @@ static struct {
        { "channeltimeout", oChannelTimeout },
        { "versionaddendum", oVersionAddendum },
        { "refuseconnection", oRefuseConnection },
+       { "warnweakcrypto", oWarnWeakCrypto },
 
        { NULL, oBadOption }
 };
@@ -1101,6 +1102,15 @@ static const struct multistate multistate_compression[] = {
        { "no",                         COMP_NONE },
        { NULL, -1 }
 };
+/* XXX this will need to be replaced with a bitmask if we add more flags */
+static const struct multistate multistate_warnweakcrypto[] = {
+       { "true",                       1 },
+       { "false",                      0 },
+       { "yes",                        1 },
+       { "no",                         0 },
+       { "no-pq-kex",                  0 },
+       { NULL, -1 }
+};
 
 static int
 parse_multistate_value(const char *arg, const char *filename, int linenum,
@@ -2427,6 +2437,11 @@ parse_pubkey_algos:
                intptr = &options->required_rsa_size;
                goto parse_int;
 
+       case oWarnWeakCrypto:
+               intptr = &options->warn_weak_crypto;
+               multistate_ptr = multistate_warnweakcrypto;
+               goto parse_multistate;
+
        case oObscureKeystrokeTiming:
                value = -1;
                while ((arg = argv_next(&ac, &av)) != NULL) {
@@ -2786,6 +2801,7 @@ initialize_options(Options * options)
        options->pubkey_accepted_algos = NULL;
        options->known_hosts_command = NULL;
        options->required_rsa_size = -1;
+       options->warn_weak_crypto = -1;
        options->enable_escape_commandline = -1;
        options->obscure_keystroke_timing_interval = -1;
        options->tag = NULL;
@@ -2989,6 +3005,8 @@ fill_default_options(Options * options)
 #endif
        if (options->required_rsa_size == -1)
                options->required_rsa_size = SSH_RSA_MINIMUM_MODULUS_SIZE;
+       if (options->warn_weak_crypto == -1)
+               options->warn_weak_crypto = 1;
        if (options->enable_escape_commandline == -1)
                options->enable_escape_commandline = 0;
        if (options->obscure_keystroke_timing_interval == -1) {
@@ -3016,6 +3034,7 @@ fill_default_options(Options * options)
                        goto fail; \
                } \
        } while (0)
+       options->kex_algorithms_set = options->kex_algorithms != NULL;
        ASSEMBLE(ciphers, def_cipher, all_cipher);
        ASSEMBLE(macs, def_mac, all_mac);
        ASSEMBLE(kex_algorithms, def_kex, all_kex);
@@ -3703,6 +3722,7 @@ dump_client_config(Options *o, const char *host)
        dump_cfg_fmtint(oVisualHostKey, o->visual_host_key);
        dump_cfg_fmtint(oUpdateHostkeys, o->update_hostkeys);
        dump_cfg_fmtint(oEnableEscapeCommandline, o->enable_escape_commandline);
+       dump_cfg_fmtint(oWarnWeakCrypto, o->warn_weak_crypto);
 
        /* Integer options */
        dump_cfg_int(oCanonicalizeMaxDots, o->canonicalize_max_dots);
index 153fa62260d3ac0bd75d2b4f722566a060ba15e6..942149f9ae3f01e0ae20e03d9bfdd1cf750d3633 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.h,v 1.160 2025/07/31 11:23:39 job Exp $ */
+/* $OpenBSD: readconf.h,v 1.161 2025/08/11 10:55:38 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -67,6 +67,7 @@ typedef struct {
        char   *macs;           /* SSH2 macs in order of preference. */
        char   *hostkeyalgorithms;      /* SSH2 server key types in order of preference. */
        char   *kex_algorithms; /* SSH2 kex methods in order of preference. */
+       int     kex_algorithms_set; /* KexAlgorithms was set by the user */
        char   *ca_sign_algorithms;     /* Allowed CA signature algorithms */
        char   *hostname;       /* Real host to connect. */
        char   *tag;            /* Configuration tag name. */
@@ -180,6 +181,7 @@ typedef struct {
        int     required_rsa_size;      /* minimum size of RSA keys */
        int     enable_escape_commandline;      /* ~C commandline */
        int     obscure_keystroke_timing_interval;
+       int     warn_weak_crypto;
 
        char    **channel_timeouts;     /* inactivity timeout by channel type */
        u_int   num_channel_timeouts;
index f1673e0149105bc88cf18419f0939705104f9718..4cbe986310519e05494c6193a4bcec6d14557f5f 100644 (file)
@@ -33,8 +33,8 @@
 .\" (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: ssh_config.5,v 1.417 2025/08/05 09:08:16 job Exp $
-.Dd $Mdocdate: August 5 2025 $
+.\" $OpenBSD: ssh_config.5,v 1.418 2025/08/11 10:55:38 djm Exp $
+.Dd $Mdocdate: August 11 2025 $
 .Dt SSH_CONFIG 5
 .Os
 .Sh NAME
@@ -2229,6 +2229,20 @@ If this flag is set to
 (the default),
 no fingerprint strings are printed at login and
 only the fingerprint string will be printed for unknown host keys.
+.It Cm WarnWeakCrypto
+controls whether the user is warned when the cryptographic algorithms
+negotiated for the connection are weak or otherwise recommended against.
+Warnings may be disabled by turning off a specific warning or by disabling
+all warnings.
+Warnings that the connection is using a non-post quantum safe key exchange
+may be disabled using the
+.Cm no-pq-kex
+flag.
+.Cm no
+will disable all warnings.
+The default, equivalent to
+.Cm yes ,
+is to enable all warnings.
 .It Cm XAuthLocation
 Specifies the full pathname of the
 .Xr xauth 1
index a90167fd6daef1c2c218bff55baaf14b9f1a1c81..09e937c9e64fde7f8b53f6817b07f644bd84cc49 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshconnect.c,v 1.371 2025/05/24 09:46:16 djm Exp $ */
+/* $OpenBSD: sshconnect.c,v 1.372 2025/08/11 10:55:38 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -1580,6 +1580,14 @@ out:
        return r;
 }
 
+static void
+warn_nonpq_kex(void)
+{
+       logit("** WARNING: connection is not using a post-quantum kex exchange algorithm.");
+       logit("** This session may be vulnerable to \"store now, decrypt later\" attacks.");
+       logit("** The server may need to be upgraded. See https://openssh.com/pq.html");
+}
+
 /*
  * Starts a dialog with the server, and authenticates the current user on the
  * server.  This does not need any extra privileges.  The basic connection
@@ -1615,6 +1623,10 @@ ssh_login(struct ssh *ssh, Sensitive *sensitive, const char *orighost,
        /* authenticate user */
        debug("Authenticating to %s:%d as '%s'", host, port, server_user);
        ssh_kex2(ssh, host, hostaddr, port, cinfo);
+       if (!options.kex_algorithms_set && ssh->kex != NULL &&
+           ssh->kex->name != NULL && options.warn_weak_crypto &&
+           !kex_is_pq_from_name(ssh->kex->name))
+               warn_nonpq_kex();
        ssh_userauth2(ssh, local_user, server_user, host, sensitive);
        free(local_user);
        free(host);