diff --git a/kex.c b/kex.c index b2c3d9720..af9108b7b 100644 --- a/kex.c +++ b/kex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.c,v 1.194 2026/05/31 04:44:38 djm Exp $ */ +/* $OpenBSD: kex.c,v 1.195 2026/09/16 00:29:44 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * @@ -289,6 +289,40 @@ kex_set_server_sig_algs(struct ssh *ssh, const char *allowed_algs) ssh->kex->server_sig_algs = xstrdup(""); } +void +kex_set_warn_weak_crypto(struct ssh *ssh, int warn_weak_crypto) +{ + if (ssh != NULL && ssh->kex != NULL) + ssh->kex->warn_weak_crypto = warn_weak_crypto != 0; +} + +void +kex_check_warn_weak_crypto(struct ssh *ssh) +{ + char remote_id[512]; + int rekeyed = 0; + + if (ssh == NULL || ssh->kex == NULL || ssh->kex->name == NULL || + !ssh->kex->warn_weak_crypto || ssh->kex->non_pq_kex_warned) + return; + if (kex_is_pq_from_name(ssh->kex->name)) { + ssh->kex->pq_kex_negotiated = 1; + return; + } + + if (ssh->kex->pq_kex_negotiated) + rekeyed = 1; + if (!rekeyed && !ssh->kex->server) + return; /* client logs initial KEX warning separately */ + + sshpkt_fmt_connection_id(ssh, remote_id, sizeof(remote_id)); + logit("WARNING: %sconnection%s%s is not using a post-quantum " + "key exchange algorithm: \"%s\"", rekeyed ? "rekeyed " : "", + ssh->kex->server ? " from " : "", + ssh->kex->server ? remote_id : "", ssh->kex->name); + ssh->kex->non_pq_kex_warned = 1; +} + static int kex_compose_ext_info_server(struct ssh *ssh, struct sshbuf *m) { @@ -567,6 +601,7 @@ kex_input_newkeys(int type, uint32_t seq, struct ssh *ssh) kex->flags &= ~KEX_INITIAL; sshbuf_reset(kex->peer); kex->flags &= ~(KEX_INIT_SENT|KEX_INIT_RECVD); + kex_check_warn_weak_crypto(ssh); return 0; } diff --git a/kex.h b/kex.h index 9816b1d83..4cd6e0b83 100644 --- a/kex.h +++ b/kex.h @@ -1,4 +1,4 @@ -/* $OpenBSD: kex.h,v 1.134 2026/08/08 07:27:54 djm Exp $ */ +/* $OpenBSD: kex.h,v 1.135 2026/09/16 00:29:44 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -164,6 +164,9 @@ struct kex { struct sshkey *initial_hostkey; sig_atomic_t done; u_int flags; + u_int warn_weak_crypto; + u_int pq_kex_negotiated; + u_int non_pq_kex_warned; int hash_alg; int ec_nid; char *failed_choice; @@ -223,6 +226,8 @@ int kex_send_newkeys(struct ssh *); int kex_start_rekex(struct ssh *); int kex_server_update_ext_info(struct ssh *); void kex_set_server_sig_algs(struct ssh *, const char *); +void kex_set_warn_weak_crypto(struct ssh *, int); +void kex_check_warn_weak_crypto(struct ssh *); int kexgex_client(struct ssh *); int kexgex_server(struct ssh *); diff --git a/monitor_wrap.c b/monitor_wrap.c index 859e10f72..3d68f804f 100644 --- a/monitor_wrap.c +++ b/monitor_wrap.c @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor_wrap.c,v 1.149 2026/09/16 00:13:58 djm Exp $ */ +/* $OpenBSD: monitor_wrap.c,v 1.150 2026/09/16 00:29:44 djm Exp $ */ /* * Copyright 2002 Niels Provos * Copyright 2002 Markus Friedl @@ -386,6 +386,7 @@ out: channel_set_tcp_keepalives(ssh, options.tcp_keep_alive == SSH_KEEPALIVES_ALL); kex_set_server_sig_algs(ssh, options.pubkey_accepted_algos); + kex_set_warn_weak_crypto(ssh, options.warn_weak_crypto); ssh_packet_set_rekey_limits(ssh, options.rekey_limit, options.rekey_interval); sshbuf_free(m); diff --git a/packet.c b/packet.c index 1909a6670..fe78097d2 100644 --- a/packet.c +++ b/packet.c @@ -1,4 +1,4 @@ -/* $OpenBSD: packet.c,v 1.342 2026/09/14 02:40:27 djm Exp $ */ +/* $OpenBSD: packet.c,v 1.343 2026/09/16 00:29:44 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -2432,7 +2432,10 @@ kex_to_blob(struct sshbuf *m, struct kex *kex) (r = sshbuf_put_stringb(m, kex->client_version)) != 0 || (r = sshbuf_put_stringb(m, kex->server_version)) != 0 || (r = sshbuf_put_stringb(m, kex->session_id)) != 0 || - (r = sshbuf_put_u32(m, kex->flags)) != 0) + (r = sshbuf_put_u32(m, kex->flags)) != 0 || + (r = sshbuf_put_u32(m, kex->warn_weak_crypto)) != 0 || + (r = sshbuf_put_u32(m, kex->pq_kex_negotiated)) != 0 || + (r = sshbuf_put_u32(m, kex->non_pq_kex_warned)) != 0) return r; return 0; } @@ -2606,7 +2609,10 @@ kex_from_blob(struct sshbuf *m, struct kex **kexp) (r = sshbuf_get_stringb(m, kex->client_version)) != 0 || (r = sshbuf_get_stringb(m, kex->server_version)) != 0 || (r = sshbuf_get_stringb(m, kex->session_id)) != 0 || - (r = sshbuf_get_u32(m, &kex->flags)) != 0) + (r = sshbuf_get_u32(m, &kex->flags)) != 0 || + (r = sshbuf_get_u32(m, &kex->warn_weak_crypto)) != 0 || + (r = sshbuf_get_u32(m, &kex->pq_kex_negotiated)) != 0 || + (r = sshbuf_get_u32(m, &kex->non_pq_kex_warned)) != 0) goto out; if (kex->we_need > 1024) { r = SSH_ERR_INVALID_FORMAT; diff --git a/servconf.c b/servconf.c index becf72b81..e7826958f 100644 --- a/servconf.c +++ b/servconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: servconf.c,v 1.455 2026/09/16 00:25:50 djm Exp $ */ +/* $OpenBSD: servconf.c,v 1.456 2026/09/16 00:29:44 djm Exp $ */ /* * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved @@ -1107,6 +1107,13 @@ static const struct multistate multistate_keepalives[] = { { "no", SSH_KEEPALIVES_OFF }, { "transport", SSH_KEEPALIVES_TRANSPORT }, { "all", SSH_KEEPALIVES_ALL }, +}; +static const struct multistate multistate_warnweakcrypto[] = { + { "true", 1 }, + { "false", 0 }, + { "yes", 1 }, + { "no", 0 }, + { "no-pq-kex", 0 }, { NULL, -1 } }; @@ -2606,6 +2613,11 @@ process_server_config_line_depth(ServerOptions *options, char *line, multistate_ptr = multistate_flag; goto parse_multistate; + case sWarnWeakCrypto: + intptr = &options->warn_weak_crypto; + multistate_ptr = multistate_warnweakcrypto; + goto parse_multistate; + case sDeprecated: case sIgnore: case sUnsupported: @@ -4140,6 +4152,8 @@ fmt_intarg(ServerOpCodes code, int val) return fmt_multistate_int(val, multistate_ignore_rhosts); case sTCPKeepAlive: return fmt_multistate_int(val, multistate_keepalives); + case sWarnWeakCrypto: + return fmt_multistate_int(val, multistate_warnweakcrypto); case sFingerprintHash: return ssh_digest_alg_name(val); default: @@ -4334,6 +4348,7 @@ dump_config(ServerOptions *o) dump_cfg_fmtint(sStreamLocalBindUnlink, o->fwd_opts.streamlocal_bind_unlink); dump_cfg_fmtint(sFingerprintHash, o->fingerprint_hash); dump_cfg_fmtint(sExposeAuthInfo, o->expose_userauth_info); + dump_cfg_fmtint(sWarnWeakCrypto, o->warn_weak_crypto); dump_cfg_fmtint(sRefuseConnection, o->refuse_connection); /* string arguments */ diff --git a/servconf.h b/servconf.h index e54e56c62..f31668549 100644 --- a/servconf.h +++ b/servconf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: servconf.h,v 1.182 2026/09/16 00:25:50 djm Exp $ */ +/* $OpenBSD: servconf.h,v 1.183 2026/09/16 00:29:44 djm Exp $ */ /* * Author: Tatu Ylonen @@ -241,7 +241,8 @@ SSHCONF_INT(unused_connection_timeout, UnusedConnectionTimeout, SSHCFG_ALL, NULL SSHCONF_STRING(sshd_session_path, SshdSessionPath, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \ SSHCONF_STRING(sshd_auth_path, SshdAuthPath, SSHCFG_GLOBAL, SSHCFG_COPY_NONE) \ SSHCONF_INTFLAG(refuse_connection, RefuseConnection, SSHCFG_ALL, 0, SSHCFG_COPY_MATCH) \ -SSHCONF_STRING(agent_socket_path, AgentSocketPath, SSHCFG_ALL, SSHCFG_COPY_MATCH) +SSHCONF_STRING(agent_socket_path, AgentSocketPath, SSHCFG_ALL, SSHCFG_COPY_MATCH) \ +SSHCONF_INT(warn_weak_crypto, WarnWeakCrypto, SSHCFG_GLOBAL, multistate_warnweakcrypto, 1, SSHCFG_COPY_NONE) #define SSHD_CONFIG_ENTRIES_LEGACY \ SSHCONF_DEPRECATE(ServerKeyBits, SSHCFG_GLOBAL, SSHCONF_DEPRECATED) \ diff --git a/sshconnect.c b/sshconnect.c index c8f8e24e1..3349d8e39 100644 --- a/sshconnect.c +++ b/sshconnect.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect.c,v 1.385 2026/09/16 00:13:58 djm Exp $ */ +/* $OpenBSD: sshconnect.c,v 1.386 2026/09/16 00:29:44 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1657,10 +1657,14 @@ 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); + kex_set_warn_weak_crypto(ssh, options.warn_weak_crypto && + !options.kex_algorithms_set); if (!options.kex_algorithms_set && ssh->kex != NULL && ssh->kex->name != NULL && options.warn_weak_crypto && - !kex_is_pq_from_name(ssh->kex->name)) + !kex_is_pq_from_name(ssh->kex->name)) { warn_nonpq_kex(); + ssh->kex->non_pq_kex_warned = 1; + } ssh_userauth2(ssh, local_user, server_user, host, sensitive); free(local_user); free(host); diff --git a/sshd-auth.c b/sshd-auth.c index f1ff432be..3adf1a41e 100644 --- a/sshd-auth.c +++ b/sshd-auth.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshd-auth.c,v 1.19 2026/09/16 00:13:58 djm Exp $ */ +/* $OpenBSD: sshd-auth.c,v 1.20 2026/09/16 00:29:44 djm Exp $ */ /* * SSH2 implementation: * Privilege Separation: @@ -798,6 +798,7 @@ do_ssh2_kex(struct ssh *ssh) if ((r = kex_setup(ssh, myproposal)) != 0) fatal_r(r, "kex_setup"); kex_set_server_sig_algs(ssh, options.pubkey_accepted_algos); + kex_set_warn_weak_crypto(ssh, options.warn_weak_crypto); kex = ssh->kex; #ifdef WITH_OPENSSL diff --git a/sshd_config.5 b/sshd_config.5 index bfd208901..f446f094e 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.406 2026/09/16 00:25:50 djm Exp $ +.\" $OpenBSD: sshd_config.5,v 1.407 2026/09/16 00:29:44 djm Exp $ .Dd $Mdocdate: September 16 2026 $ .Dt SSHD_CONFIG 5 .Os @@ -2147,6 +2147,20 @@ Optionally specifies additional text to append to the SSH protocol banner sent by the server upon connection. The default is .Cm none . +.It Cm WarnWeakCrypto +Controls whether warnings are logged 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 about connections that don't use a post-quantum 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 X11DisplayOffset Specifies the first display number available for .Xr sshd 8 Ns 's