-/* $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.
*
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 *
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)
{
-/* $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.
*
kex->flags &= ~KEX_INITIAL;
sshbuf_reset(kex->peer);
kex->flags &= ~KEX_INIT_SENT;
- free(kex->name);
- kex->name = NULL;
return 0;
}
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)
-/* $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.
#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;
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 *);
-/* $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
oPubkeyAcceptedAlgorithms, oCASignatureAlgorithms, oProxyJump,
oSecurityKeyProvider, oKnownHostsCommand, oRequiredRSASize,
oEnableEscapeCommandline, oObscureKeystrokeTiming, oChannelTimeout,
- oVersionAddendum, oRefuseConnection,
+ oVersionAddendum, oRefuseConnection, oWarnWeakCrypto,
oIgnore, oIgnoredUnknownOption, oDeprecated, oUnsupported
} OpCodes;
{ "channeltimeout", oChannelTimeout },
{ "versionaddendum", oVersionAddendum },
{ "refuseconnection", oRefuseConnection },
+ { "warnweakcrypto", oWarnWeakCrypto },
{ NULL, oBadOption }
};
{ "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,
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) {
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;
#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) {
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);
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);
-/* $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>
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. */
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;
.\" (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
(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
-/* $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
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
/* 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);