de-fatal kex-ecdh

This commit is contained in:
Markus Friedl
2012-01-15 20:46:07 +01:00
parent b618c7766b
commit d8b01951d3
4 changed files with 228 additions and 152 deletions

View File

@@ -164,10 +164,10 @@ int
kexgex_hash(const EVP_MD *, char *, char *, char *, size_t, char *,
size_t, u_char *, size_t, int, int, int, BIGNUM *, BIGNUM *, BIGNUM *,
BIGNUM *, BIGNUM *, u_char **, size_t *);
void
kex_ecdh_hash(const EVP_MD *, const EC_GROUP *, char *, char *, char *, int,
char *, int, u_char *, int, const EC_POINT *, const EC_POINT *,
const BIGNUM *, u_char **, u_int *);
int
kex_ecdh_hash(const EVP_MD *, const EC_GROUP *, char *, char *, char *, size_t,
char *, size_t, u_char *, size_t, const EC_POINT *, const EC_POINT *,
const BIGNUM *, u_char **, size_t *);
int kex_ecdh_name_to_nid(const char *);
const EVP_MD *kex_ecdh_name_to_evpmd(const char *);

View File

@@ -40,12 +40,13 @@
#include "cipher.h"
#include "kex.h"
#include "log.h"
#include "err.h"
int
kex_ecdh_name_to_nid(const char *kexname)
{
if (strlen(kexname) < sizeof(KEX_ECDH_SHA2_STEM) - 1)
fatal("%s: kexname too short \"%s\"", __func__, kexname);
return -1;
return sshkey_curve_name_to_nid(kexname +
sizeof(KEX_ECDH_SHA2_STEM) - 1);
}
@@ -60,54 +61,57 @@ kex_ecdh_name_to_evpmd(const char *kexname)
return sshkey_ec_nid_to_evpmd(nid);
}
void
int
kex_ecdh_hash(
const EVP_MD *evp_md,
const EC_GROUP *ec_group,
char *client_version_string,
char *server_version_string,
char *ckexinit, int ckexinitlen,
char *skexinit, int skexinitlen,
u_char *serverhostkeyblob, int sbloblen,
char *ckexinit, size_t ckexinitlen,
char *skexinit, size_t skexinitlen,
u_char *serverhostkeyblob, size_t sbloblen,
const EC_POINT *client_dh_pub,
const EC_POINT *server_dh_pub,
const BIGNUM *shared_secret,
u_char **hash, u_int *hashlen)
u_char **hash, size_t *hashlen)
{
Buffer b;
struct sshbuf *b;
EVP_MD_CTX md;
static u_char digest[EVP_MAX_MD_SIZE];
int r;
buffer_init(&b);
buffer_put_cstring(&b, client_version_string);
buffer_put_cstring(&b, server_version_string);
/* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */
buffer_put_int(&b, ckexinitlen+1);
buffer_put_char(&b, SSH2_MSG_KEXINIT);
buffer_append(&b, ckexinit, ckexinitlen);
buffer_put_int(&b, skexinitlen+1);
buffer_put_char(&b, SSH2_MSG_KEXINIT);
buffer_append(&b, skexinit, skexinitlen);
buffer_put_string(&b, serverhostkeyblob, sbloblen);
buffer_put_ecpoint(&b, ec_group, client_dh_pub);
buffer_put_ecpoint(&b, ec_group, server_dh_pub);
buffer_put_bignum2(&b, shared_secret);
if ((b = sshbuf_new()) == NULL)
return SSH_ERR_ALLOC_FAIL;
if ((r = sshbuf_put_cstring(b, client_version_string)) != 0 ||
(r = sshbuf_put_cstring(b, server_version_string)) != 0 ||
/* kexinit messages: fake header: len+SSH2_MSG_KEXINIT */
(r = sshbuf_put_u32(b, ckexinitlen+1)) != 0 ||
(r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) != 0 ||
(r = sshbuf_put(b, ckexinit, ckexinitlen)) != 0 ||
(r = sshbuf_put_u32(b, skexinitlen+1)) != 0 ||
(r = sshbuf_put_u8(b, SSH2_MSG_KEXINIT)) != 0 ||
(r = sshbuf_put(b, skexinit, skexinitlen)) != 0 ||
(r = sshbuf_put_string(b, serverhostkeyblob, sbloblen)) != 0 ||
(r = sshbuf_put_ec(b, client_dh_pub, ec_group)) != 0 ||
(r = sshbuf_put_ec(b, server_dh_pub, ec_group)) != 0 ||
(r = sshbuf_put_bignum2(b, shared_secret)) != 0) {
sshbuf_free(b);
return r;
}
#ifdef DEBUG_KEX
buffer_dump(&b);
#endif
EVP_DigestInit(&md, evp_md);
EVP_DigestUpdate(&md, buffer_ptr(&b), buffer_len(&b));
EVP_DigestFinal(&md, digest, NULL);
buffer_free(&b);
if (EVP_DigestInit(&md, evp_md) != 1 ||
EVP_DigestUpdate(&md, sshbuf_ptr(b), sshbuf_len(b)) != 1 ||
EVP_DigestFinal(&md, digest, NULL) != 1) {
sshbuf_free(b);
return SSH_ERR_LIBCRYPTO_ERROR;
}
sshbuf_free(b);
#ifdef DEBUG_KEX
dump_digest("hash", digest, EVP_MD_size(evp_md));
#endif
*hash = digest;
*hashlen = EVP_MD_size(evp_md);
return 0;
}

View File

@@ -51,33 +51,46 @@ void
kexecdh_client(struct ssh *ssh)
{
Kex *kex = ssh->kex;
EC_KEY *client_key;
EC_KEY *client_key = NULL;
const EC_GROUP *group;
int curve_nid;
const EC_POINT *public_key;
int r, curve_nid;
if ((curve_nid = kex_ecdh_name_to_nid(kex->name)) == -1)
fatal("%s: unsupported ECDH curve \"%s\"", __func__, kex->name);
if ((client_key = EC_KEY_new_by_curve_name(curve_nid)) == NULL)
fatal("%s: EC_KEY_new_by_curve_name failed", __func__);
if (EC_KEY_generate_key(client_key) != 1)
fatal("%s: EC_KEY_generate_key failed", __func__);
if ((curve_nid = kex_ecdh_name_to_nid(kex->name)) == -1) {
r = SSH_ERR_INVALID_ARGUMENT;
goto out;
}
if ((client_key = EC_KEY_new_by_curve_name(curve_nid)) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if (EC_KEY_generate_key(client_key) != 1) {
r = SSH_ERR_LIBCRYPTO_ERROR;
goto out;
}
group = EC_KEY_get0_group(client_key);
public_key = EC_KEY_get0_public_key(client_key);
ssh_packet_start(ssh, SSH2_MSG_KEX_ECDH_INIT);
ssh_packet_put_ecpoint(ssh, group, EC_KEY_get0_public_key(client_key));
ssh_packet_send(ssh);
if ((r = sshpkt_start(ssh, SSH2_MSG_KEX_ECDH_INIT)) != 0 ||
(r = sshpkt_put_ec(ssh, public_key, group)) != 0 ||
(r = sshpkt_send(ssh)) != 0)
goto out;
debug("sending SSH2_MSG_KEX_ECDH_INIT");
#ifdef DEBUG_KEXECDH
fputs("client private key:\n", stderr);
sshkey_dump_ec_key(client_key);
#endif
kex->ec_client_key = client_key;
kex->ec_group = group;
debug("expecting SSH2_MSG_KEX_ECDH_REPLY");
ssh_dispatch_set(ssh, SSH2_MSG_KEX_ECDH_REPLY, &input_kex_ecdh_reply);
return;
out:
if (client_key)
EC_KEY_free(client_key);
fatal("%s: %s", __func__, ssh_err(r));
}
static int
@@ -85,68 +98,77 @@ input_kex_ecdh_reply(int type, u_int32_t seq, struct ssh *ssh)
{
Kex *kex = ssh->kex;
const EC_GROUP *group;
EC_POINT *server_public;
EC_POINT *server_public = NULL;
EC_KEY *client_key;
BIGNUM *shared_secret;
struct sshkey *server_host_key;
BIGNUM *shared_secret = NULL;
struct sshkey *server_host_key = NULL;
u_char *server_host_key_blob = NULL, *signature = NULL;
u_char *kbuf, *hash;
u_int klen, slen, sbloblen, hashlen;
u_char *kbuf = NULL, *hash;
size_t slen, sbloblen;
size_t klen = 0, hashlen;
int r;
if (kex->verify_host_key == NULL) {
r = SSH_ERR_INVALID_ARGUMENT;
goto out;
}
group = kex->ec_group;
client_key = kex->ec_client_key;
/* hostkey */
server_host_key_blob = ssh_packet_get_string(ssh, &sbloblen);
if ((r = sshkey_from_blob(server_host_key_blob, sbloblen,
if ((r = sshpkt_get_string(ssh, &server_host_key_blob,
&sbloblen)) != 0 ||
(r = sshkey_from_blob(server_host_key_blob, sbloblen,
&server_host_key)) != 0)
fatal("%s: could not parse server host key: %s",
__func__, ssh_err(r));
if (server_host_key == NULL)
fatal("cannot decode server_host_key_blob");
if (server_host_key->type != kex->hostkey_type)
fatal("type mismatch for decoded server_host_key_blob");
if (kex->verify_host_key == NULL)
fatal("cannot verify server_host_key");
if (kex->verify_host_key(server_host_key, ssh) == -1)
fatal("server_host_key verification failed");
goto out;
if (server_host_key->type != kex->hostkey_type) {
r = SSH_ERR_KEY_TYPE_MISMATCH;
goto out;
}
if (kex->verify_host_key(server_host_key, ssh) == -1) {
r = SSH_ERR_SIGNATURE_INVALID;
goto out;
}
/* Q_S, server public key */
if ((server_public = EC_POINT_new(group)) == NULL)
fatal("%s: EC_POINT_new failed", __func__);
ssh_packet_get_ecpoint(ssh, group, server_public);
if (sshkey_ec_validate_public(group, server_public) != 0)
fatal("%s: invalid server public key", __func__);
/* signed H */
if ((server_public = EC_POINT_new(group)) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if ((r = sshpkt_get_ec(ssh, server_public, group)) != 0 ||
(r = sshpkt_get_string(ssh, &signature, &slen)) != 0 ||
(r = sshpkt_get_end(ssh)) != 0)
goto out;
#ifdef DEBUG_KEXECDH
fputs("server public key:\n", stderr);
sshkey_dump_ec_point(group, server_public);
#endif
/* signed H */
signature = ssh_packet_get_string(ssh, &slen);
ssh_packet_check_eom(ssh);
if (sshkey_ec_validate_public(group, server_public) != 0) {
sshpkt_disconnect(ssh, "invalid server public key");
r = SSH_ERR_MESSAGE_INCOMPLETE;
goto out;
}
klen = (EC_GROUP_get_degree(group) + 7) / 8;
kbuf = xmalloc(klen);
if ((kbuf = malloc(klen)) == NULL ||
(shared_secret = BN_new()) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if (ECDH_compute_key(kbuf, klen, server_public,
client_key, NULL) != (int)klen)
fatal("%s: ECDH_compute_key failed", __func__);
client_key, NULL) != (int)klen ||
BN_bin2bn(kbuf, klen, shared_secret) == NULL) {
r = SSH_ERR_LIBCRYPTO_ERROR;
goto out;
}
#ifdef DEBUG_KEXECDH
dump_digest("shared secret", kbuf, klen);
#endif
if ((shared_secret = BN_new()) == NULL)
fatal("%s: BN_new failed", __func__);
if (BN_bin2bn(kbuf, klen, shared_secret) == NULL)
fatal("%s: BN_bin2bn failed", __func__);
memset(kbuf, 0, klen);
xfree(kbuf);
/* calc and verify H */
kex_ecdh_hash(
if ((r = kex_ecdh_hash(
kex->evp_md,
group,
kex->client_version_string,
@@ -157,28 +179,46 @@ input_kex_ecdh_reply(int type, u_int32_t seq, struct ssh *ssh)
EC_KEY_get0_public_key(client_key),
server_public,
shared_secret,
&hash, &hashlen
);
xfree(server_host_key_blob);
EC_POINT_clear_free(server_public);
EC_KEY_free(kex->ec_client_key);
kex->ec_client_key = NULL;
&hash, &hashlen)) != 0)
goto out;
if ((r = sshkey_verify(server_host_key, signature, slen, hash,
hashlen, datafellows)) != 0)
fatal("key_verify failed for server_host_key: %s", ssh_err(r));
sshkey_free(server_host_key);
xfree(signature);
/* save session id */
if (kex->session_id == NULL) {
kex->session_id_len = hashlen;
kex->session_id = xmalloc(kex->session_id_len);
kex->session_id = malloc(kex->session_id_len);
if (kex->session_id == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
memcpy(kex->session_id, hash, kex->session_id_len);
}
/* XXX check error */
kex_derive_keys(ssh, hash, hashlen, shared_secret);
BN_clear_free(shared_secret);
kex_finish(ssh);
return 0;
r = 0;
out:
if (kex->ec_client_key) {
EC_KEY_free(kex->ec_client_key);
kex->ec_client_key = NULL;
}
if (server_host_key_blob)
free(server_host_key_blob);
if (server_host_key)
sshkey_free(server_host_key);
if (server_public)
EC_POINT_clear_free(server_public);
if (kbuf) {
bzero(kbuf, klen);
free(kbuf);
}
if (shared_secret)
BN_clear_free(shared_secret);
if (signature)
free(signature);
return r;
}

View File

@@ -61,21 +61,29 @@ input_kex_ecdh_init(int type, u_int32_t seq, struct ssh *ssh)
{
Kex *kex = ssh->kex;
EC_POINT *client_public;
EC_KEY *server_key;
EC_KEY *server_key = NULL;
const EC_GROUP *group;
BIGNUM *shared_secret;
const EC_POINT *public_key;
BIGNUM *shared_secret = NULL;
struct sshkey *server_host_private, *server_host_public;
u_char *server_host_key_blob = NULL, *signature = NULL;
u_char *kbuf, *hash;
u_int klen, slen, sbloblen, hashlen;
u_char *kbuf = NULL, *hash;
u_int slen, sbloblen;
size_t klen = 0, hashlen;
int curve_nid, r;
if ((curve_nid = kex_ecdh_name_to_nid(kex->name)) == -1)
fatal("%s: unsupported ECDH curve \"%s\"", __func__, kex->name);
if ((server_key = EC_KEY_new_by_curve_name(curve_nid)) == NULL)
fatal("%s: EC_KEY_new_by_curve_name failed", __func__);
if (EC_KEY_generate_key(server_key) != 1)
fatal("%s: EC_KEY_generate_key failed", __func__);
if ((curve_nid = kex_ecdh_name_to_nid(kex->name)) == -1) {
r = SSH_ERR_INVALID_ARGUMENT;
goto out;
}
if ((server_key = EC_KEY_new_by_curve_name(curve_nid)) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if (EC_KEY_generate_key(server_key) != 1) {
r = SSH_ERR_LIBCRYPTO_ERROR;
goto out;
}
group = EC_KEY_get0_group(server_key);
#ifdef DEBUG_KEXECDH
@@ -84,51 +92,57 @@ input_kex_ecdh_init(int type, u_int32_t seq, struct ssh *ssh)
#endif
if (kex->load_host_public_key == NULL ||
kex->load_host_private_key == NULL)
fatal("Cannot load hostkey");
server_host_public = kex->load_host_public_key(kex->hostkey_type, ssh);
if (server_host_public == NULL)
fatal("Unsupported hostkey type %d", kex->hostkey_type);
server_host_private = kex->load_host_private_key(kex->hostkey_type, ssh);
if (server_host_private == NULL)
fatal("Missing private key for hostkey type %d",
kex->hostkey_type);
if ((client_public = EC_POINT_new(group)) == NULL)
fatal("%s: EC_POINT_new failed", __func__);
ssh_packet_get_ecpoint(ssh, group, client_public);
ssh_packet_check_eom(ssh);
if (sshkey_ec_validate_public(group, client_public) != 0)
fatal("%s: invalid client public key", __func__);
kex->load_host_private_key == NULL) {
r = SSH_ERR_INVALID_ARGUMENT;
goto out;
}
if ((server_host_public = kex->load_host_public_key(kex->hostkey_type,
ssh)) == NULL ||
(server_host_private = kex->load_host_private_key(kex->hostkey_type,
ssh)) == NULL) {
r = SSH_ERR_KEY_TYPE_MISMATCH; /* XXX */
goto out;
}
if ((client_public = EC_POINT_new(group)) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if ((r = sshpkt_get_ec(ssh, client_public, group)) != 0 ||
(r = sshpkt_get_end(ssh)) != 0)
goto out;
#ifdef DEBUG_KEXECDH
fputs("client public key:\n", stderr);
sshkey_dump_ec_point(group, client_public);
#endif
if (sshkey_ec_validate_public(group, client_public) != 0) {
sshpkt_disconnect(ssh, "invalid client public key");
r = SSH_ERR_MESSAGE_INCOMPLETE;
goto out;
}
/* Calculate shared_secret */
klen = (EC_GROUP_get_degree(group) + 7) / 8;
kbuf = xmalloc(klen);
if ((kbuf = malloc(klen)) == NULL ||
(shared_secret = BN_new()) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if (ECDH_compute_key(kbuf, klen, client_public,
server_key, NULL) != (int)klen)
fatal("%s: ECDH_compute_key failed", __func__);
server_key, NULL) != (int)klen ||
BN_bin2bn(kbuf, klen, shared_secret) == NULL) {
r = SSH_ERR_LIBCRYPTO_ERROR;
goto out;
}
#ifdef DEBUG_KEXDH
#ifdef DEBUG_KEXECDH
dump_digest("shared secret", kbuf, klen);
#endif
if ((shared_secret = BN_new()) == NULL)
fatal("%s: BN_new failed", __func__);
if (BN_bin2bn(kbuf, klen, shared_secret) == NULL)
fatal("%s: BN_bin2bn failed", __func__);
memset(kbuf, 0, klen);
xfree(kbuf);
/* calc H */
if ((r = sshkey_to_blob(server_host_public, &server_host_key_blob,
&sbloblen)) != 0)
fatal("%s: sshkey_to_blob: %s", __func__, ssh_err(r));
kex_ecdh_hash(
goto out;
if ((r = kex_ecdh_hash(
kex->evp_md,
group,
kex->client_version_string,
@@ -139,14 +153,17 @@ input_kex_ecdh_init(int type, u_int32_t seq, struct ssh *ssh)
client_public,
EC_KEY_get0_public_key(server_key),
shared_secret,
&hash, &hashlen
);
EC_POINT_clear_free(client_public);
&hash, &hashlen)) != 0)
goto out;
/* save session id := H */
if (kex->session_id == NULL) {
kex->session_id_len = hashlen;
kex->session_id = xmalloc(kex->session_id_len);
kex->session_id = malloc(kex->session_id_len);
if (kex->session_id == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
memcpy(kex->session_id, hash, kex->session_id_len);
}
@@ -157,20 +174,35 @@ input_kex_ecdh_init(int type, u_int32_t seq, struct ssh *ssh)
/* destroy_sensitive_data(); */
public_key = EC_KEY_get0_public_key(server_key);
/* send server hostkey, ECDH pubkey 'Q_S' and signed H */
ssh_packet_start(ssh, SSH2_MSG_KEX_ECDH_REPLY);
ssh_packet_put_string(ssh, server_host_key_blob, sbloblen);
ssh_packet_put_ecpoint(ssh, group, EC_KEY_get0_public_key(server_key));
ssh_packet_put_string(ssh, signature, slen);
ssh_packet_send(ssh);
xfree(signature);
xfree(server_host_key_blob);
/* have keys, free server key */
EC_KEY_free(server_key);
if ((r = sshpkt_start(ssh, SSH2_MSG_KEX_ECDH_REPLY)) != 0 ||
(r = sshpkt_put_string(ssh, server_host_key_blob, sbloblen)) != 0 ||
(r = sshpkt_put_ec(ssh, public_key, group)) != 0 ||
(r = sshpkt_put_string(ssh, signature, slen)) != 0 ||
(r = sshpkt_send(ssh)) != 0)
goto out;
/* have keys */
kex_derive_keys(ssh, hash, hashlen, shared_secret);
BN_clear_free(shared_secret);
kex_finish(ssh);
return 0;
r = 0;
out:
if (kex->ec_client_key) {
EC_KEY_free(kex->ec_client_key);
kex->ec_client_key = NULL;
}
if (server_host_key_blob)
free(server_host_key_blob);
if (server_key)
EC_KEY_free(server_key);
if (kbuf) {
bzero(kbuf, klen);
free(kbuf);
}
if (shared_secret)
BN_clear_free(shared_secret);
if (signature)
free(signature);
return r;
}