-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
upstream: implement "strict key exchange" in ssh and sshd
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. with markus@ OpenBSD-Commit-ID: 2a66ac962f0a630d7945fee54004ed9e9c439f14
- Loading branch information
Showing
6 changed files
with
148 additions
and
85 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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]" | ||
|
@@ -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 $ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
/* $OpenBSD: kex.c,v 1.182 2023/10/11 04:46:29 djm Exp $ */ | ||
/* $OpenBSD: kex.c,v 1.183 2023/12/18 14:45:17 djm Exp $ */ | ||
/* | ||
* Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. | ||
* | ||
|
@@ -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] = { | ||
|
@@ -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') | ||
|
@@ -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); | ||
|
@@ -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: | ||
|
@@ -363,6 +381,7 @@ kex_proposal_populate_entries(struct ssh *ssh, char *prop[PROPOSAL_MAX], | |
prop[i] = xstrdup(defprop[i]); | ||
} | ||
} | ||
free(cp); | ||
} | ||
|
||
void | ||
|
@@ -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) | ||
|
@@ -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) | ||
|
@@ -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; | ||
|
@@ -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) | ||
|
@@ -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; | ||
|
@@ -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 */ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
1edb00c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi Damien Miller, I was trying to apply this patch to openssh8.3p1, but some of the code is not available to patch in our source code. Is there patch available for openssh8.3p1?