Skip to content

Commit

Permalink
Implement "strict key exchange" in ssh and sshd.
Browse files Browse the repository at this point in the history
This adds a protocol extension to improve the integrity of the SSH
transport protocol, particular in and around the initial key exchange
(KEX) phase.

Full details of the extension are in the PROTOCOL file.

OpenBSD-Commit-ID: 2a66ac962f0a630d7945fee54004ed9e9c439f14

Approved by:	so (implicit)
Obtained from:	https://anongit.mindrot.org/openssh.git/patch/?id=1edb00c58f8a6875fad6a497aa2bacf37f9e6cd5
Security:	CVE-2023-48795
Security:	FreeBSD-SA-23:19.openssh

(cherry picked from commit 92f58c6)
(cherry picked from commit 673d1ead65c912ee3b52e507421d499b8104a810)
  • Loading branch information
tetlowgm authored and brooksdavis committed Dec 20, 2023
1 parent 0c943c4 commit 698d163
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 83 deletions.
28 changes: 27 additions & 1 deletion crypto/openssh/PROTOCOL
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,32 @@ than as a named global or channel request to allow pings with very
short packet lengths, which would not be possible with other
approaches.

1.9 transport: strict key exchange extension

OpenSSH supports a number of transport-layer hardening measures under
a "strict KEX" feature. This feature is signalled similarly to the
RFC8308 ext-info feature: by including a additional algorithm in the
initiial SSH2_MSG_KEXINIT kex_algorithms field. The client may append
"[email protected]" to its kex_algorithms and the server
may append "[email protected]". These pseudo-algorithms
are only valid in the initial SSH2_MSG_KEXINIT and MUST be ignored
if they are present in subsequent SSH2_MSG_KEXINIT packets.

When an endpoint that supports this extension observes this algorithm
name in a peer's KEXINIT packet, it MUST make the following changes to
the the protocol:

a) During initial KEX, terminate the connection if any unexpected or
out-of-sequence packet is received. This includes terminating the
connection if the first packet received is not SSH2_MSG_KEXINIT.
Unexpected packets for the purpose of strict KEX include messages
that are otherwise valid at any time during the connection such as
SSH2_MSG_DEBUG and SSH2_MSG_IGNORE.
b) After sending or receiving a SSH2_MSG_NEWKEYS message, reset the
packet sequence number to zero. This behaviour persists for the
duration of the connection (i.e. not just the first
SSH2_MSG_NEWKEYS).

2. Connection protocol changes

2.1. connection: Channel write close extension "[email protected]"
Expand Down Expand Up @@ -745,4 +771,4 @@ master instance and later clients.
OpenSSH extends the usual agent protocol. These changes are documented
in the PROTOCOL.agent file.

$OpenBSD: PROTOCOL,v 1.49 2023/08/28 03:28:43 djm Exp $
$OpenBSD: PROTOCOL,v 1.50 2023/12/18 14:45:17 djm Exp $
82 changes: 55 additions & 27 deletions crypto/openssh/kex.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
#include "xmalloc.h"

/* prototype */
static int kex_choose_conf(struct ssh *);
static int kex_choose_conf(struct ssh *, uint32_t seq);
static int kex_input_newkeys(int, u_int32_t, struct ssh *);

static const char * const proposal_names[PROPOSAL_MAX] = {
Expand Down Expand Up @@ -177,14 +177,26 @@ kex_names_valid(const char *names)
return 1;
}

/* returns non-zero if proposal contains any algorithm from algs */
static int
has_any_alg(const char *proposal, const char *algs)
{
char *cp;

if ((cp = match_list(proposal, algs, NULL)) == NULL)
return 0;
free(cp);
return 1;
}

/*
* Concatenate algorithm names, avoiding duplicates in the process.
* Caller must free returned string.
*/
char *
kex_names_cat(const char *a, const char *b)
{
char *ret = NULL, *tmp = NULL, *cp, *p, *m;
char *ret = NULL, *tmp = NULL, *cp, *p;
size_t len;

if (a == NULL || *a == '\0')
Expand All @@ -201,10 +213,8 @@ kex_names_cat(const char *a, const char *b)
}
strlcpy(ret, a, len);
for ((p = strsep(&cp, ",")); p && *p != '\0'; (p = strsep(&cp, ","))) {
if ((m = match_list(ret, p, NULL)) != NULL) {
free(m);
if (has_any_alg(ret, p))
continue; /* Algorithm already present */
}
if (strlcat(ret, ",", len) >= len ||
strlcat(ret, p, len) >= len) {
free(tmp);
Expand Down Expand Up @@ -334,15 +344,23 @@ kex_proposal_populate_entries(struct ssh *ssh, char *prop[PROPOSAL_MAX],
const char *defpropclient[PROPOSAL_MAX] = { KEX_CLIENT };
const char **defprop = ssh->kex->server ? defpropserver : defpropclient;
u_int i;
char *cp;

if (prop == NULL)
fatal_f("proposal missing");

/* Append EXT_INFO signalling to KexAlgorithms */
if (kexalgos == NULL)
kexalgos = defprop[PROPOSAL_KEX_ALGS];
if ((cp = kex_names_cat(kexalgos, ssh->kex->server ?
"[email protected]" :
"ext-info-c,[email protected]")) == NULL)
fatal_f("kex_names_cat");

for (i = 0; i < PROPOSAL_MAX; i++) {
switch(i) {
case PROPOSAL_KEX_ALGS:
prop[i] = compat_kex_proposal(ssh,
kexalgos ? kexalgos : defprop[i]);
prop[i] = compat_kex_proposal(ssh, cp);
break;
case PROPOSAL_ENC_ALGS_CTOS:
case PROPOSAL_ENC_ALGS_STOC:
Expand All @@ -363,6 +381,7 @@ kex_proposal_populate_entries(struct ssh *ssh, char *prop[PROPOSAL_MAX],
prop[i] = xstrdup(defprop[i]);
}
}
free(cp);
}

void
Expand Down Expand Up @@ -466,7 +485,12 @@ kex_protocol_error(int type, u_int32_t seq, struct ssh *ssh)
{
int r;

error("kex protocol error: type %d seq %u", type, seq);
/* If in strict mode, any unexpected message is an error */
if ((ssh->kex->flags & KEX_INITIAL) && ssh->kex->kex_strict) {
ssh_packet_disconnect(ssh, "strict KEX violation: "
"unexpected packet type %u (seqnr %u)", type, seq);
}
error_f("type %u seq %u", type, seq);
if ((r = sshpkt_start(ssh, SSH2_MSG_UNIMPLEMENTED)) != 0 ||
(r = sshpkt_put_u32(ssh, seq)) != 0 ||
(r = sshpkt_send(ssh)) != 0)
Expand Down Expand Up @@ -563,7 +587,7 @@ kex_input_ext_info(int type, u_int32_t seq, struct ssh *ssh)
if (ninfo >= 1024) {
error("SSH2_MSG_EXT_INFO with too many entries, expected "
"<=1024, received %u", ninfo);
return SSH_ERR_INVALID_FORMAT;
return dispatch_protocol_error(type, seq, ssh);
}
for (i = 0; i < ninfo; i++) {
if ((r = sshpkt_get_cstring(ssh, &name, NULL)) != 0)
Expand Down Expand Up @@ -681,7 +705,7 @@ kex_input_kexinit(int type, u_int32_t seq, struct ssh *ssh)
error_f("no kex");
return SSH_ERR_INTERNAL_ERROR;
}
ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, 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)
return r;
Expand Down Expand Up @@ -717,7 +741,7 @@ kex_input_kexinit(int type, u_int32_t seq, struct ssh *ssh)
if (!(kex->flags & KEX_INIT_SENT))
if ((r = kex_send_kexinit(ssh)) != 0)
return r;
if ((r = kex_choose_conf(ssh)) != 0)
if ((r = kex_choose_conf(ssh, seq)) != 0)
return r;

if (kex->kex_type < KEX_MAX && kex->kex[kex->kex_type] != NULL)
Expand Down Expand Up @@ -981,20 +1005,14 @@ proposals_match(char *my[PROPOSAL_MAX], char *peer[PROPOSAL_MAX])
return (1);
}

/* returns non-zero if proposal contains any algorithm from algs */
static int
has_any_alg(const char *proposal, const char *algs)
kexalgs_contains(char **peer, const char *ext)
{
char *cp;

if ((cp = match_list(proposal, algs, NULL)) == NULL)
return 0;
free(cp);
return 1;
return has_any_alg(peer[PROPOSAL_KEX_ALGS], ext);
}

static int
kex_choose_conf(struct ssh *ssh)
kex_choose_conf(struct ssh *ssh, uint32_t seq)
{
struct kex *kex = ssh->kex;
struct newkeys *newkeys;
Expand All @@ -1019,13 +1037,23 @@ kex_choose_conf(struct ssh *ssh)
sprop=peer;
}

/* Check whether client supports ext_info_c */
if (kex->server && (kex->flags & KEX_INITIAL)) {
char *ext;

ext = match_list("ext-info-c", peer[PROPOSAL_KEX_ALGS], NULL);
kex->ext_info_c = (ext != NULL);
free(ext);
/* Check whether peer supports ext_info/kex_strict */
if ((kex->flags & KEX_INITIAL) != 0) {
if (kex->server) {
kex->ext_info_c = kexalgs_contains(peer, "ext-info-c");
kex->kex_strict = kexalgs_contains(peer,
"[email protected]");
} else {
kex->kex_strict = kexalgs_contains(peer,
"[email protected]");
}
if (kex->kex_strict) {
debug3_f("will use strict KEX ordering");
if (seq != 0)
ssh_packet_disconnect(ssh,
"strict KEX violation: "
"KEXINIT was not the first packet");
}
}

/* Check whether client supports rsa-sha2 algorithms */
Expand Down
3 changes: 2 additions & 1 deletion crypto/openssh/kex.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* $OpenBSD: kex.h,v 1.119 2023/08/28 03:28:43 djm Exp $ */
/* $OpenBSD: kex.h,v 1.120 2023/12/18 14:45:17 djm Exp $ */

/*
* Copyright (c) 2000, 2001 Markus Friedl. All rights reserved.
Expand Down Expand Up @@ -149,6 +149,7 @@ struct kex {
u_int kex_type;
char *server_sig_algs;
int ext_info_c;
int kex_strict;
struct sshbuf *my;
struct sshbuf *peer;
struct sshbuf *client_version;
Expand Down
Loading

0 comments on commit 698d163

Please sign in to comment.