HardenedBSD/crypto/openssh/kex.c
Brooks Davis 8998619212 Add support for dynamically adjusted buffers to allow the full use of
the bandwidth of long fat pipes (i.e. 100Mbps+ trans-oceanic or
trans-continental links).  Bandwidth-delay products up to 64MB are
supported.

Also add support (not compiled by default) for the None cypher.  The
None cypher can only be enabled on non-interactive sessions (those
without a pty where -T was not used) and must be enabled in both
the client and server configuration files and on the client command
line.  Additionally, the None cypher will only be activated after
authentication is complete.  To enable the None cypher you must add
-DNONE_CIPHER_ENABLED to CFLAGS via the make command line or in
/etc/make.conf.

This code is a style(9) compliant version of these features extracted
from the patches published at:

http://www.psc.edu/networking/projects/hpn-ssh/

Merging this patch has been a collaboration between me and Bjoern.

Reviewed by:	bz
Approved by:	re (kib), des (maintainer)
2011-08-03 19:14:22 +00:00

635 lines
16 KiB
C

/* $OpenBSD: kex.c,v 1.86 2010/09/22 05:01:29 djm Exp $ */
/* $FreeBSD$ */
/*
* Copyright (c) 2000, 2001 Markus Friedl. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "includes.h"
#include <sys/param.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/crypto.h>
#include "xmalloc.h"
#include "ssh2.h"
#include "buffer.h"
#include "packet.h"
#include "compat.h"
#include "cipher.h"
#include "key.h"
#include "kex.h"
#include "log.h"
#include "mac.h"
#include "match.h"
#include "dispatch.h"
#include "monitor.h"
#include "roaming.h"
#if OPENSSL_VERSION_NUMBER >= 0x00907000L
# if defined(HAVE_EVP_SHA256)
# define evp_ssh_sha256 EVP_sha256
# else
extern const EVP_MD *evp_ssh_sha256(void);
# endif
#endif
/* prototype */
static void kex_kexinit_finish(Kex *);
static void kex_choose_conf(Kex *);
/* Validate KEX method name list */
int
kex_names_valid(const char *names)
{
char *s, *cp, *p;
if (names == NULL || strcmp(names, "") == 0)
return 0;
s = cp = xstrdup(names);
for ((p = strsep(&cp, ",")); p && *p != '\0';
(p = strsep(&cp, ","))) {
if (strcmp(p, KEX_DHGEX_SHA256) != 0 &&
strcmp(p, KEX_DHGEX_SHA1) != 0 &&
strcmp(p, KEX_DH14) != 0 &&
strcmp(p, KEX_DH1) != 0 &&
(strncmp(p, KEX_ECDH_SHA2_STEM,
sizeof(KEX_ECDH_SHA2_STEM) - 1) != 0 ||
kex_ecdh_name_to_nid(p) == -1)) {
error("Unsupported KEX algorithm \"%.100s\"", p);
xfree(s);
return 0;
}
}
debug3("kex names ok: [%s]", names);
xfree(s);
return 1;
}
/* Put algorithm proposal into buffer. */
#ifndef NONE_CIPHER_ENABLED
static void
#else
/* Also used in sshconnect2.c. */
void
#endif
kex_prop2buf(Buffer *b, char *proposal[PROPOSAL_MAX])
{
u_int i;
buffer_clear(b);
/*
* add a dummy cookie, the cookie will be overwritten by
* kex_send_kexinit(), each time a kexinit is set
*/
for (i = 0; i < KEX_COOKIE_LEN; i++)
buffer_put_char(b, 0);
for (i = 0; i < PROPOSAL_MAX; i++)
buffer_put_cstring(b, proposal[i]);
buffer_put_char(b, 0); /* first_kex_packet_follows */
buffer_put_int(b, 0); /* uint32 reserved */
}
/* parse buffer and return algorithm proposal */
static char **
kex_buf2prop(Buffer *raw, int *first_kex_follows)
{
Buffer b;
u_int i;
char **proposal;
proposal = xcalloc(PROPOSAL_MAX, sizeof(char *));
buffer_init(&b);
buffer_append(&b, buffer_ptr(raw), buffer_len(raw));
/* skip cookie */
for (i = 0; i < KEX_COOKIE_LEN; i++)
buffer_get_char(&b);
/* extract kex init proposal strings */
for (i = 0; i < PROPOSAL_MAX; i++) {
proposal[i] = buffer_get_cstring(&b,NULL);
debug2("kex_parse_kexinit: %s", proposal[i]);
}
/* first kex follows / reserved */
i = buffer_get_char(&b);
if (first_kex_follows != NULL)
*first_kex_follows = i;
debug2("kex_parse_kexinit: first_kex_follows %d ", i);
i = buffer_get_int(&b);
debug2("kex_parse_kexinit: reserved %u ", i);
buffer_free(&b);
return proposal;
}
static void
kex_prop_free(char **proposal)
{
u_int i;
for (i = 0; i < PROPOSAL_MAX; i++)
xfree(proposal[i]);
xfree(proposal);
}
/* ARGSUSED */
static void
kex_protocol_error(int type, u_int32_t seq, void *ctxt)
{
error("Hm, kex protocol error: type %d seq %u", type, seq);
}
static void
kex_reset_dispatch(void)
{
dispatch_range(SSH2_MSG_TRANSPORT_MIN,
SSH2_MSG_TRANSPORT_MAX, &kex_protocol_error);
dispatch_set(SSH2_MSG_KEXINIT, &kex_input_kexinit);
}
void
kex_finish(Kex *kex)
{
kex_reset_dispatch();
packet_start(SSH2_MSG_NEWKEYS);
packet_send();
/* packet_write_wait(); */
debug("SSH2_MSG_NEWKEYS sent");
debug("expecting SSH2_MSG_NEWKEYS");
packet_read_expect(SSH2_MSG_NEWKEYS);
packet_check_eom();
debug("SSH2_MSG_NEWKEYS received");
kex->done = 1;
buffer_clear(&kex->peer);
/* buffer_clear(&kex->my); */
kex->flags &= ~KEX_INIT_SENT;
xfree(kex->name);
kex->name = NULL;
}
void
kex_send_kexinit(Kex *kex)
{
u_int32_t rnd = 0;
u_char *cookie;
u_int i;
if (kex == NULL) {
error("kex_send_kexinit: no kex, cannot rekey");
return;
}
if (kex->flags & KEX_INIT_SENT) {
debug("KEX_INIT_SENT");
return;
}
kex->done = 0;
/* generate a random cookie */
if (buffer_len(&kex->my) < KEX_COOKIE_LEN)
fatal("kex_send_kexinit: kex proposal too short");
cookie = buffer_ptr(&kex->my);
for (i = 0; i < KEX_COOKIE_LEN; i++) {
if (i % 4 == 0)
rnd = arc4random();
cookie[i] = rnd;
rnd >>= 8;
}
packet_start(SSH2_MSG_KEXINIT);
packet_put_raw(buffer_ptr(&kex->my), buffer_len(&kex->my));
packet_send();
debug("SSH2_MSG_KEXINIT sent");
kex->flags |= KEX_INIT_SENT;
}
/* ARGSUSED */
void
kex_input_kexinit(int type, u_int32_t seq, void *ctxt)
{
char *ptr;
u_int i, dlen;
Kex *kex = (Kex *)ctxt;
debug("SSH2_MSG_KEXINIT received");
if (kex == NULL)
fatal("kex_input_kexinit: no kex, cannot rekey");
ptr = packet_get_raw(&dlen);
buffer_append(&kex->peer, ptr, dlen);
/* discard packet */
for (i = 0; i < KEX_COOKIE_LEN; i++)
packet_get_char();
for (i = 0; i < PROPOSAL_MAX; i++)
xfree(packet_get_string(NULL));
(void) packet_get_char();
(void) packet_get_int();
packet_check_eom();
kex_kexinit_finish(kex);
}
Kex *
kex_setup(char *proposal[PROPOSAL_MAX])
{
Kex *kex;
kex = xcalloc(1, sizeof(*kex));
buffer_init(&kex->peer);
buffer_init(&kex->my);
kex_prop2buf(&kex->my, proposal);
kex->done = 0;
kex_send_kexinit(kex); /* we start */
kex_reset_dispatch();
return kex;
}
static void
kex_kexinit_finish(Kex *kex)
{
if (!(kex->flags & KEX_INIT_SENT))
kex_send_kexinit(kex);
kex_choose_conf(kex);
if (kex->kex_type >= 0 && kex->kex_type < KEX_MAX &&
kex->kex[kex->kex_type] != NULL) {
(kex->kex[kex->kex_type])(kex);
} else {
fatal("Unsupported key exchange %d", kex->kex_type);
}
}
static void
choose_enc(Enc *enc, char *client, char *server)
{
char *name = match_list(client, server, NULL);
if (name == NULL)
fatal("no matching cipher found: client %s server %s",
client, server);
if ((enc->cipher = cipher_by_name(name)) == NULL)
fatal("matching cipher is not supported: %s", name);
enc->name = name;
enc->enabled = 0;
enc->iv = NULL;
enc->key = NULL;
enc->key_len = cipher_keylen(enc->cipher);
enc->block_size = cipher_blocksize(enc->cipher);
}
static void
choose_mac(Mac *mac, char *client, char *server)
{
char *name = match_list(client, server, NULL);
if (name == NULL)
fatal("no matching mac found: client %s server %s",
client, server);
if (mac_setup(mac, name) < 0)
fatal("unsupported mac %s", name);
/* truncate the key */
if (datafellows & SSH_BUG_HMAC)
mac->key_len = 16;
mac->name = name;
mac->key = NULL;
mac->enabled = 0;
}
static void
choose_comp(Comp *comp, char *client, char *server)
{
char *name = match_list(client, server, NULL);
if (name == NULL)
fatal("no matching comp found: client %s server %s", client, server);
if (strcmp(name, "zlib@openssh.com") == 0) {
comp->type = COMP_DELAYED;
} else if (strcmp(name, "zlib") == 0) {
comp->type = COMP_ZLIB;
} else if (strcmp(name, "none") == 0) {
comp->type = COMP_NONE;
} else {
fatal("unsupported comp %s", name);
}
comp->name = name;
}
static void
choose_kex(Kex *k, char *client, char *server)
{
k->name = match_list(client, server, NULL);
if (k->name == NULL)
fatal("Unable to negotiate a key exchange method");
if (strcmp(k->name, KEX_DH1) == 0) {
k->kex_type = KEX_DH_GRP1_SHA1;
k->evp_md = EVP_sha1();
} else if (strcmp(k->name, KEX_DH14) == 0) {
k->kex_type = KEX_DH_GRP14_SHA1;
k->evp_md = EVP_sha1();
} else if (strcmp(k->name, KEX_DHGEX_SHA1) == 0) {
k->kex_type = KEX_DH_GEX_SHA1;
k->evp_md = EVP_sha1();
#if OPENSSL_VERSION_NUMBER >= 0x00907000L
} else if (strcmp(k->name, KEX_DHGEX_SHA256) == 0) {
k->kex_type = KEX_DH_GEX_SHA256;
k->evp_md = evp_ssh_sha256();
} else if (strncmp(k->name, KEX_ECDH_SHA2_STEM,
sizeof(KEX_ECDH_SHA2_STEM) - 1) == 0) {
k->kex_type = KEX_ECDH_SHA2;
k->evp_md = kex_ecdh_name_to_evpmd(k->name);
#endif
} else
fatal("bad kex alg %s", k->name);
}
static void
choose_hostkeyalg(Kex *k, char *client, char *server)
{
char *hostkeyalg = match_list(client, server, NULL);
if (hostkeyalg == NULL)
fatal("no hostkey alg");
k->hostkey_type = key_type_from_name(hostkeyalg);
if (k->hostkey_type == KEY_UNSPEC)
fatal("bad hostkey alg '%s'", hostkeyalg);
xfree(hostkeyalg);
}
static int
proposals_match(char *my[PROPOSAL_MAX], char *peer[PROPOSAL_MAX])
{
static int check[] = {
PROPOSAL_KEX_ALGS, PROPOSAL_SERVER_HOST_KEY_ALGS, -1
};
int *idx;
char *p;
for (idx = &check[0]; *idx != -1; idx++) {
if ((p = strchr(my[*idx], ',')) != NULL)
*p = '\0';
if ((p = strchr(peer[*idx], ',')) != NULL)
*p = '\0';
if (strcmp(my[*idx], peer[*idx]) != 0) {
debug2("proposal mismatch: my %s peer %s",
my[*idx], peer[*idx]);
return (0);
}
}
debug2("proposals match");
return (1);
}
static void
kex_choose_conf(Kex *kex)
{
Newkeys *newkeys;
char **my, **peer;
char **cprop, **sprop;
int nenc, nmac, ncomp;
u_int mode, ctos, need;
int first_kex_follows, type;
#ifdef NONE_CIPHER_ENABLED
int auth_flag;
#endif
my = kex_buf2prop(&kex->my, NULL);
peer = kex_buf2prop(&kex->peer, &first_kex_follows);
if (kex->server) {
cprop=peer;
sprop=my;
} else {
cprop=my;
sprop=peer;
}
/* Check whether server offers roaming */
if (!kex->server) {
char *roaming;
roaming = match_list(KEX_RESUME, peer[PROPOSAL_KEX_ALGS], NULL);
if (roaming) {
kex->roaming = 1;
xfree(roaming);
}
}
/* Algorithm Negotiation */
#ifdef NONE_CIPHER_ENABLED
auth_flag = packet_get_authentication_state();
debug ("AUTH STATE is %d", auth_flag);
#endif
for (mode = 0; mode < MODE_MAX; mode++) {
newkeys = xcalloc(1, sizeof(*newkeys));
kex->newkeys[mode] = newkeys;
ctos = (!kex->server && mode == MODE_OUT) ||
(kex->server && mode == MODE_IN);
nenc = ctos ? PROPOSAL_ENC_ALGS_CTOS : PROPOSAL_ENC_ALGS_STOC;
nmac = ctos ? PROPOSAL_MAC_ALGS_CTOS : PROPOSAL_MAC_ALGS_STOC;
ncomp = ctos ? PROPOSAL_COMP_ALGS_CTOS : PROPOSAL_COMP_ALGS_STOC;
choose_enc (&newkeys->enc, cprop[nenc], sprop[nenc]);
choose_mac (&newkeys->mac, cprop[nmac], sprop[nmac]);
choose_comp(&newkeys->comp, cprop[ncomp], sprop[ncomp]);
#ifdef NONE_CIPHER_ENABLED
debug("REQUESTED ENC.NAME is '%s'", newkeys->enc.name);
if (strcmp(newkeys->enc.name, "none") == 0) {
debug("Requesting NONE. Authflag is %d", auth_flag);
if (auth_flag == 1)
debug("None requested post authentication.");
else
fatal("Pre-authentication none cipher requests "
"are not allowed.");
}
#endif
debug("kex: %s %s %s %s",
ctos ? "client->server" : "server->client",
newkeys->enc.name,
newkeys->mac.name,
newkeys->comp.name);
}
choose_kex(kex, cprop[PROPOSAL_KEX_ALGS], sprop[PROPOSAL_KEX_ALGS]);
choose_hostkeyalg(kex, cprop[PROPOSAL_SERVER_HOST_KEY_ALGS],
sprop[PROPOSAL_SERVER_HOST_KEY_ALGS]);
need = 0;
for (mode = 0; mode < MODE_MAX; mode++) {
newkeys = kex->newkeys[mode];
if (need < newkeys->enc.key_len)
need = newkeys->enc.key_len;
if (need < newkeys->enc.block_size)
need = newkeys->enc.block_size;
if (need < newkeys->mac.key_len)
need = newkeys->mac.key_len;
}
/* XXX need runden? */
kex->we_need = need;
/* ignore the next message if the proposals do not match */
if (first_kex_follows && !proposals_match(my, peer) &&
!(datafellows & SSH_BUG_FIRSTKEX)) {
type = packet_read();
debug2("skipping next packet (type %u)", type);
}
kex_prop_free(my);
kex_prop_free(peer);
}
static u_char *
derive_key(Kex *kex, int id, u_int need, u_char *hash, u_int hashlen,
BIGNUM *shared_secret)
{
Buffer b;
EVP_MD_CTX md;
char c = id;
u_int have;
int mdsz;
u_char *digest;
if ((mdsz = EVP_MD_size(kex->evp_md)) <= 0)
fatal("bad kex md size %d", mdsz);
digest = xmalloc(roundup(need, mdsz));
buffer_init(&b);
buffer_put_bignum2(&b, shared_secret);
/* K1 = HASH(K || H || "A" || session_id) */
EVP_DigestInit(&md, kex->evp_md);
if (!(datafellows & SSH_BUG_DERIVEKEY))
EVP_DigestUpdate(&md, buffer_ptr(&b), buffer_len(&b));
EVP_DigestUpdate(&md, hash, hashlen);
EVP_DigestUpdate(&md, &c, 1);
EVP_DigestUpdate(&md, kex->session_id, kex->session_id_len);
EVP_DigestFinal(&md, digest, NULL);
/*
* expand key:
* Kn = HASH(K || H || K1 || K2 || ... || Kn-1)
* Key = K1 || K2 || ... || Kn
*/
for (have = mdsz; need > have; have += mdsz) {
EVP_DigestInit(&md, kex->evp_md);
if (!(datafellows & SSH_BUG_DERIVEKEY))
EVP_DigestUpdate(&md, buffer_ptr(&b), buffer_len(&b));
EVP_DigestUpdate(&md, hash, hashlen);
EVP_DigestUpdate(&md, digest, have);
EVP_DigestFinal(&md, digest + have, NULL);
}
buffer_free(&b);
#ifdef DEBUG_KEX
fprintf(stderr, "key '%c'== ", c);
dump_digest("key", digest, need);
#endif
return digest;
}
Newkeys *current_keys[MODE_MAX];
#define NKEYS 6
void
kex_derive_keys(Kex *kex, u_char *hash, u_int hashlen, BIGNUM *shared_secret)
{
u_char *keys[NKEYS];
u_int i, mode, ctos;
for (i = 0; i < NKEYS; i++) {
keys[i] = derive_key(kex, 'A'+i, kex->we_need, hash, hashlen,
shared_secret);
}
debug2("kex_derive_keys");
for (mode = 0; mode < MODE_MAX; mode++) {
current_keys[mode] = kex->newkeys[mode];
kex->newkeys[mode] = NULL;
ctos = (!kex->server && mode == MODE_OUT) ||
(kex->server && mode == MODE_IN);
current_keys[mode]->enc.iv = keys[ctos ? 0 : 1];
current_keys[mode]->enc.key = keys[ctos ? 2 : 3];
current_keys[mode]->mac.key = keys[ctos ? 4 : 5];
}
}
Newkeys *
kex_get_newkeys(int mode)
{
Newkeys *ret;
ret = current_keys[mode];
current_keys[mode] = NULL;
return ret;
}
void
derive_ssh1_session_id(BIGNUM *host_modulus, BIGNUM *server_modulus,
u_int8_t cookie[8], u_int8_t id[16])
{
const EVP_MD *evp_md = EVP_md5();
EVP_MD_CTX md;
u_int8_t nbuf[2048], obuf[EVP_MAX_MD_SIZE];
int len;
EVP_DigestInit(&md, evp_md);
len = BN_num_bytes(host_modulus);
if (len < (512 / 8) || (u_int)len > sizeof(nbuf))
fatal("%s: bad host modulus (len %d)", __func__, len);
BN_bn2bin(host_modulus, nbuf);
EVP_DigestUpdate(&md, nbuf, len);
len = BN_num_bytes(server_modulus);
if (len < (512 / 8) || (u_int)len > sizeof(nbuf))
fatal("%s: bad server modulus (len %d)", __func__, len);
BN_bn2bin(server_modulus, nbuf);
EVP_DigestUpdate(&md, nbuf, len);
EVP_DigestUpdate(&md, cookie, 8);
EVP_DigestFinal(&md, obuf, NULL);
memcpy(id, obuf, 16);
memset(nbuf, 0, sizeof(nbuf));
memset(obuf, 0, sizeof(obuf));
memset(&md, 0, sizeof(md));
}
#if defined(DEBUG_KEX) || defined(DEBUG_KEXDH) || defined(DEBUG_KEXECDH)
void
dump_digest(char *msg, u_char *digest, int len)
{
int i;
fprintf(stderr, "%s\n", msg);
for (i = 0; i < len; i++) {
fprintf(stderr, "%02x", digest[i]);
if (i%32 == 31)
fprintf(stderr, "\n");
else if (i%8 == 7)
fprintf(stderr, " ");
}
fprintf(stderr, "\n");
}
#endif