summaryrefslogtreecommitdiff
path: root/libs/libssh2/src/kex.c
diff options
context:
space:
mode:
Diffstat (limited to 'libs/libssh2/src/kex.c')
-rw-r--r--libs/libssh2/src/kex.c2794
1 files changed, 2794 insertions, 0 deletions
diff --git a/libs/libssh2/src/kex.c b/libs/libssh2/src/kex.c
new file mode 100644
index 0000000000..65b722f421
--- /dev/null
+++ b/libs/libssh2/src/kex.c
@@ -0,0 +1,2794 @@
+/* Copyright (c) 2004-2007, Sara Golemon <sarag@libssh2.org>
+ * Copyright (c) 2010, Daniel Stenberg <daniel@haxx.se>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms,
+ * with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ *
+ * 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.
+ *
+ * Neither the name of the copyright holder nor the names
+ * of any other contributors may be used to endorse or
+ * promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "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 COPYRIGHT OWNER OR
+ * CONTRIBUTORS 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 "libssh2_priv.h"
+
+#include "transport.h"
+#include "comp.h"
+#include "mac.h"
+
+/* TODO: Switch this to an inline and handle alloc() failures */
+/* Helper macro called from kex_method_diffie_hellman_group1_sha1_key_exchange */
+#define LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(value, reqlen, version) \
+ { \
+ libssh2_sha1_ctx hash; \
+ unsigned long len = 0; \
+ if (!(value)) { \
+ value = LIBSSH2_ALLOC(session, reqlen + SHA_DIGEST_LENGTH); \
+ } \
+ if (value) \
+ while (len < (unsigned long)reqlen) { \
+ libssh2_sha1_init(&hash); \
+ libssh2_sha1_update(hash, exchange_state->k_value, \
+ exchange_state->k_value_len); \
+ libssh2_sha1_update(hash, exchange_state->h_sig_comp, \
+ SHA_DIGEST_LENGTH); \
+ if (len > 0) { \
+ libssh2_sha1_update(hash, value, len); \
+ } else { \
+ libssh2_sha1_update(hash, (version), 1); \
+ libssh2_sha1_update(hash, session->session_id, \
+ session->session_id_len); \
+ } \
+ libssh2_sha1_final(hash, (value) + len); \
+ len += SHA_DIGEST_LENGTH; \
+ } \
+ }
+
+
+/* Helper macro called from kex_method_diffie_hellman_group1_sha256_key_exchange */
+#define LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA256_HASH(value, reqlen, version) \
+ { \
+ libssh2_sha256_ctx hash; \
+ unsigned long len = 0; \
+ if (!(value)) { \
+ value = LIBSSH2_ALLOC(session, reqlen + SHA256_DIGEST_LENGTH); \
+ } \
+ if (value) \
+ while (len < (unsigned long)reqlen) { \
+ libssh2_sha256_init(&hash); \
+ libssh2_sha256_update(hash, exchange_state->k_value, \
+ exchange_state->k_value_len); \
+ libssh2_sha256_update(hash, exchange_state->h_sig_comp, \
+ SHA256_DIGEST_LENGTH); \
+ if (len > 0) { \
+ libssh2_sha256_update(hash, value, len); \
+ } else { \
+ libssh2_sha256_update(hash, (version), 1); \
+ libssh2_sha256_update(hash, session->session_id, \
+ session->session_id_len); \
+ } \
+ libssh2_sha256_final(hash, (value) + len); \
+ len += SHA256_DIGEST_LENGTH; \
+ } \
+ }
+
+
+/*
+ * diffie_hellman_sha1
+ *
+ * Diffie Hellman Key Exchange, Group Agnostic
+ */
+static int diffie_hellman_sha1(LIBSSH2_SESSION *session,
+ _libssh2_bn *g,
+ _libssh2_bn *p,
+ int group_order,
+ unsigned char packet_type_init,
+ unsigned char packet_type_reply,
+ unsigned char *midhash,
+ unsigned long midhash_len,
+ kmdhgGPshakex_state_t *exchange_state)
+{
+ int ret = 0;
+ int rc;
+ libssh2_sha1_ctx exchange_hash_ctx;
+
+ if (exchange_state->state == libssh2_NB_state_idle) {
+ /* Setup initial values */
+ exchange_state->e_packet = NULL;
+ exchange_state->s_packet = NULL;
+ exchange_state->k_value = NULL;
+ exchange_state->ctx = _libssh2_bn_ctx_new();
+ exchange_state->x = _libssh2_bn_init(); /* Random from client */
+ exchange_state->e = _libssh2_bn_init(); /* g^x mod p */
+ exchange_state->f = _libssh2_bn_init_from_bin(); /* g^(Random from server) mod p */
+ exchange_state->k = _libssh2_bn_init(); /* The shared secret: f^x mod p */
+
+ /* Zero the whole thing out */
+ memset(&exchange_state->req_state, 0, sizeof(packet_require_state_t));
+
+ /* Generate x and e */
+ _libssh2_bn_rand(exchange_state->x, group_order * 8 - 1, 0, -1);
+ _libssh2_bn_mod_exp(exchange_state->e, g, exchange_state->x, p,
+ exchange_state->ctx);
+
+ /* Send KEX init */
+ /* packet_type(1) + String Length(4) + leading 0(1) */
+ exchange_state->e_packet_len =
+ _libssh2_bn_bytes(exchange_state->e) + 6;
+ if (_libssh2_bn_bits(exchange_state->e) % 8) {
+ /* Leading 00 not needed */
+ exchange_state->e_packet_len--;
+ }
+
+ exchange_state->e_packet =
+ LIBSSH2_ALLOC(session, exchange_state->e_packet_len);
+ if (!exchange_state->e_packet) {
+ ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Out of memory error");
+ goto clean_exit;
+ }
+ exchange_state->e_packet[0] = packet_type_init;
+ _libssh2_htonu32(exchange_state->e_packet + 1,
+ exchange_state->e_packet_len - 5);
+ if (_libssh2_bn_bits(exchange_state->e) % 8) {
+ _libssh2_bn_to_bin(exchange_state->e,
+ exchange_state->e_packet + 5);
+ } else {
+ exchange_state->e_packet[5] = 0;
+ _libssh2_bn_to_bin(exchange_state->e,
+ exchange_state->e_packet + 6);
+ }
+
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sending KEX packet %d",
+ (int) packet_type_init);
+ exchange_state->state = libssh2_NB_state_created;
+ }
+
+ if (exchange_state->state == libssh2_NB_state_created) {
+ rc = _libssh2_transport_send(session, exchange_state->e_packet,
+ exchange_state->e_packet_len,
+ NULL, 0);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ } else if (rc) {
+ ret = _libssh2_error(session, rc,
+ "Unable to send KEX init message");
+ goto clean_exit;
+ }
+ exchange_state->state = libssh2_NB_state_sent;
+ }
+
+ if (exchange_state->state == libssh2_NB_state_sent) {
+ if (session->burn_optimistic_kexinit) {
+ /* The first KEX packet to come along will be the guess initially
+ * sent by the server. That guess turned out to be wrong so we
+ * need to silently ignore it */
+ int burn_type;
+
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Waiting for badly guessed KEX packet (to be ignored)");
+ burn_type =
+ _libssh2_packet_burn(session, &exchange_state->burn_state);
+ if (burn_type == LIBSSH2_ERROR_EAGAIN) {
+ return burn_type;
+ } else if (burn_type <= 0) {
+ /* Failed to receive a packet */
+ ret = burn_type;
+ goto clean_exit;
+ }
+ session->burn_optimistic_kexinit = 0;
+
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Burnt packet of type: %02x",
+ (unsigned int) burn_type);
+ }
+
+ exchange_state->state = libssh2_NB_state_sent1;
+ }
+
+ if (exchange_state->state == libssh2_NB_state_sent1) {
+ /* Wait for KEX reply */
+ rc = _libssh2_packet_require(session, packet_type_reply,
+ &exchange_state->s_packet,
+ &exchange_state->s_packet_len, 0, NULL,
+ 0, &exchange_state->req_state);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ }
+ if (rc) {
+ ret = _libssh2_error(session, LIBSSH2_ERROR_TIMEOUT,
+ "Timed out waiting for KEX reply");
+ goto clean_exit;
+ }
+
+ /* Parse KEXDH_REPLY */
+ exchange_state->s = exchange_state->s_packet + 1;
+
+ session->server_hostkey_len = _libssh2_ntohu32(exchange_state->s);
+ exchange_state->s += 4;
+
+ if (session->server_hostkey)
+ LIBSSH2_FREE(session, session->server_hostkey);
+
+ session->server_hostkey =
+ LIBSSH2_ALLOC(session, session->server_hostkey_len);
+ if (!session->server_hostkey) {
+ ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate memory for a copy "
+ "of the host key");
+ goto clean_exit;
+ }
+ memcpy(session->server_hostkey, exchange_state->s,
+ session->server_hostkey_len);
+ exchange_state->s += session->server_hostkey_len;
+
+#if LIBSSH2_MD5
+ {
+ libssh2_md5_ctx fingerprint_ctx;
+
+ if (libssh2_md5_init(&fingerprint_ctx)) {
+ libssh2_md5_update(fingerprint_ctx, session->server_hostkey,
+ session->server_hostkey_len);
+ libssh2_md5_final(fingerprint_ctx,
+ session->server_hostkey_md5);
+ session->server_hostkey_md5_valid = TRUE;
+ }
+ else {
+ session->server_hostkey_md5_valid = FALSE;
+ }
+ }
+#ifdef LIBSSH2DEBUG
+ {
+ char fingerprint[50], *fprint = fingerprint;
+ int i;
+ for(i = 0; i < 16; i++, fprint += 3) {
+ snprintf(fprint, 4, "%02x:", session->server_hostkey_md5[i]);
+ }
+ *(--fprint) = '\0';
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Server's MD5 Fingerprint: %s", fingerprint);
+ }
+#endif /* LIBSSH2DEBUG */
+#endif /* ! LIBSSH2_MD5 */
+
+ {
+ libssh2_sha1_ctx fingerprint_ctx;
+
+ if (libssh2_sha1_init(&fingerprint_ctx)) {
+ libssh2_sha1_update(fingerprint_ctx, session->server_hostkey,
+ session->server_hostkey_len);
+ libssh2_sha1_final(fingerprint_ctx,
+ session->server_hostkey_sha1);
+ session->server_hostkey_sha1_valid = TRUE;
+ }
+ else {
+ session->server_hostkey_sha1_valid = FALSE;
+ }
+ }
+#ifdef LIBSSH2DEBUG
+ {
+ char fingerprint[64], *fprint = fingerprint;
+ int i;
+
+ for(i = 0; i < 20; i++, fprint += 3) {
+ snprintf(fprint, 4, "%02x:", session->server_hostkey_sha1[i]);
+ }
+ *(--fprint) = '\0';
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Server's SHA1 Fingerprint: %s", fingerprint);
+ }
+#endif /* LIBSSH2DEBUG */
+
+ if (session->hostkey->init(session, session->server_hostkey,
+ session->server_hostkey_len,
+ &session->server_hostkey_abstract)) {
+ ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_INIT,
+ "Unable to initialize hostkey importer");
+ goto clean_exit;
+ }
+
+ exchange_state->f_value_len = _libssh2_ntohu32(exchange_state->s);
+ exchange_state->s += 4;
+ exchange_state->f_value = exchange_state->s;
+ exchange_state->s += exchange_state->f_value_len;
+ _libssh2_bn_from_bin(exchange_state->f, exchange_state->f_value_len,
+ exchange_state->f_value);
+
+ exchange_state->h_sig_len = _libssh2_ntohu32(exchange_state->s);
+ exchange_state->s += 4;
+ exchange_state->h_sig = exchange_state->s;
+
+ /* Compute the shared secret */
+ _libssh2_bn_mod_exp(exchange_state->k, exchange_state->f,
+ exchange_state->x, p, exchange_state->ctx);
+ exchange_state->k_value_len = _libssh2_bn_bytes(exchange_state->k) + 5;
+ if (_libssh2_bn_bits(exchange_state->k) % 8) {
+ /* don't need leading 00 */
+ exchange_state->k_value_len--;
+ }
+ exchange_state->k_value =
+ LIBSSH2_ALLOC(session, exchange_state->k_value_len);
+ if (!exchange_state->k_value) {
+ ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate buffer for K");
+ goto clean_exit;
+ }
+ _libssh2_htonu32(exchange_state->k_value,
+ exchange_state->k_value_len - 4);
+ if (_libssh2_bn_bits(exchange_state->k) % 8) {
+ _libssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 4);
+ } else {
+ exchange_state->k_value[4] = 0;
+ _libssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 5);
+ }
+
+ exchange_state->exchange_hash = (void*)&exchange_hash_ctx;
+ libssh2_sha1_init(&exchange_hash_ctx);
+
+ if (session->local.banner) {
+ _libssh2_htonu32(exchange_state->h_sig_comp,
+ strlen((char *) session->local.banner) - 2);
+ libssh2_sha1_update(exchange_hash_ctx,
+ exchange_state->h_sig_comp, 4);
+ libssh2_sha1_update(exchange_hash_ctx,
+ (char *) session->local.banner,
+ strlen((char *) session->local.banner) - 2);
+ } else {
+ _libssh2_htonu32(exchange_state->h_sig_comp,
+ sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1);
+ libssh2_sha1_update(exchange_hash_ctx,
+ exchange_state->h_sig_comp, 4);
+ libssh2_sha1_update(exchange_hash_ctx,
+ LIBSSH2_SSH_DEFAULT_BANNER,
+ sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1);
+ }
+
+ _libssh2_htonu32(exchange_state->h_sig_comp,
+ strlen((char *) session->remote.banner));
+ libssh2_sha1_update(exchange_hash_ctx,
+ exchange_state->h_sig_comp, 4);
+ libssh2_sha1_update(exchange_hash_ctx,
+ session->remote.banner,
+ strlen((char *) session->remote.banner));
+
+ _libssh2_htonu32(exchange_state->h_sig_comp,
+ session->local.kexinit_len);
+ libssh2_sha1_update(exchange_hash_ctx,
+ exchange_state->h_sig_comp, 4);
+ libssh2_sha1_update(exchange_hash_ctx,
+ session->local.kexinit,
+ session->local.kexinit_len);
+
+ _libssh2_htonu32(exchange_state->h_sig_comp,
+ session->remote.kexinit_len);
+ libssh2_sha1_update(exchange_hash_ctx,
+ exchange_state->h_sig_comp, 4);
+ libssh2_sha1_update(exchange_hash_ctx,
+ session->remote.kexinit,
+ session->remote.kexinit_len);
+
+ _libssh2_htonu32(exchange_state->h_sig_comp,
+ session->server_hostkey_len);
+ libssh2_sha1_update(exchange_hash_ctx,
+ exchange_state->h_sig_comp, 4);
+ libssh2_sha1_update(exchange_hash_ctx,
+ session->server_hostkey,
+ session->server_hostkey_len);
+
+ if (packet_type_init == SSH_MSG_KEX_DH_GEX_INIT) {
+ /* diffie-hellman-group-exchange hashes additional fields */
+#ifdef LIBSSH2_DH_GEX_NEW
+ _libssh2_htonu32(exchange_state->h_sig_comp,
+ LIBSSH2_DH_GEX_MINGROUP);
+ _libssh2_htonu32(exchange_state->h_sig_comp + 4,
+ LIBSSH2_DH_GEX_OPTGROUP);
+ _libssh2_htonu32(exchange_state->h_sig_comp + 8,
+ LIBSSH2_DH_GEX_MAXGROUP);
+ libssh2_sha1_update(exchange_hash_ctx,
+ exchange_state->h_sig_comp, 12);
+#else
+ _libssh2_htonu32(exchange_state->h_sig_comp,
+ LIBSSH2_DH_GEX_OPTGROUP);
+ libssh2_sha1_update(exchange_hash_ctx,
+ exchange_state->h_sig_comp, 4);
+#endif
+ }
+
+ if (midhash) {
+ libssh2_sha1_update(exchange_hash_ctx, midhash,
+ midhash_len);
+ }
+
+ libssh2_sha1_update(exchange_hash_ctx,
+ exchange_state->e_packet + 1,
+ exchange_state->e_packet_len - 1);
+
+ _libssh2_htonu32(exchange_state->h_sig_comp,
+ exchange_state->f_value_len);
+ libssh2_sha1_update(exchange_hash_ctx,
+ exchange_state->h_sig_comp, 4);
+ libssh2_sha1_update(exchange_hash_ctx,
+ exchange_state->f_value,
+ exchange_state->f_value_len);
+
+ libssh2_sha1_update(exchange_hash_ctx,
+ exchange_state->k_value,
+ exchange_state->k_value_len);
+
+ libssh2_sha1_final(exchange_hash_ctx,
+ exchange_state->h_sig_comp);
+
+ if (session->hostkey->
+ sig_verify(session, exchange_state->h_sig,
+ exchange_state->h_sig_len, exchange_state->h_sig_comp,
+ 20, &session->server_hostkey_abstract)) {
+ ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_SIGN,
+ "Unable to verify hostkey signature");
+ goto clean_exit;
+ }
+
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sending NEWKEYS message");
+ exchange_state->c = SSH_MSG_NEWKEYS;
+
+ exchange_state->state = libssh2_NB_state_sent2;
+ }
+
+ if (exchange_state->state == libssh2_NB_state_sent2) {
+ rc = _libssh2_transport_send(session, &exchange_state->c, 1, NULL, 0);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ } else if (rc) {
+ ret = _libssh2_error(session, rc, "Unable to send NEWKEYS message");
+ goto clean_exit;
+ }
+
+ exchange_state->state = libssh2_NB_state_sent3;
+ }
+
+ if (exchange_state->state == libssh2_NB_state_sent3) {
+ rc = _libssh2_packet_require(session, SSH_MSG_NEWKEYS,
+ &exchange_state->tmp,
+ &exchange_state->tmp_len, 0, NULL, 0,
+ &exchange_state->req_state);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ } else if (rc) {
+ ret = _libssh2_error(session, rc, "Timed out waiting for NEWKEYS");
+ goto clean_exit;
+ }
+ /* The first key exchange has been performed,
+ switch to active crypt/comp/mac mode */
+ session->state |= LIBSSH2_STATE_NEWKEYS;
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Received NEWKEYS message");
+
+ /* This will actually end up being just packet_type(1)
+ for this packet type anyway */
+ LIBSSH2_FREE(session, exchange_state->tmp);
+
+ if (!session->session_id) {
+ session->session_id = LIBSSH2_ALLOC(session, SHA_DIGEST_LENGTH);
+ if (!session->session_id) {
+ ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate buffer for SHA digest");
+ goto clean_exit;
+ }
+ memcpy(session->session_id, exchange_state->h_sig_comp,
+ SHA_DIGEST_LENGTH);
+ session->session_id_len = SHA_DIGEST_LENGTH;
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "session_id calculated");
+ }
+
+ /* Cleanup any existing cipher */
+ if (session->local.crypt->dtor) {
+ session->local.crypt->dtor(session,
+ &session->local.crypt_abstract);
+ }
+
+ /* Calculate IV/Secret/Key for each direction */
+ if (session->local.crypt->init) {
+ unsigned char *iv = NULL, *secret = NULL;
+ int free_iv = 0, free_secret = 0;
+
+ LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(iv,
+ session->local.crypt->
+ iv_len, "A");
+ if (!iv) {
+ ret = -1;
+ goto clean_exit;
+ }
+ LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(secret,
+ session->local.crypt->
+ secret_len, "C");
+ if (!secret) {
+ LIBSSH2_FREE(session, iv);
+ ret = LIBSSH2_ERROR_KEX_FAILURE;
+ goto clean_exit;
+ }
+ if (session->local.crypt->
+ init(session, session->local.crypt, iv, &free_iv, secret,
+ &free_secret, 1, &session->local.crypt_abstract)) {
+ LIBSSH2_FREE(session, iv);
+ LIBSSH2_FREE(session, secret);
+ ret = LIBSSH2_ERROR_KEX_FAILURE;
+ goto clean_exit;
+ }
+
+ if (free_iv) {
+ memset(iv, 0, session->local.crypt->iv_len);
+ LIBSSH2_FREE(session, iv);
+ }
+
+ if (free_secret) {
+ memset(secret, 0, session->local.crypt->secret_len);
+ LIBSSH2_FREE(session, secret);
+ }
+ }
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Client to Server IV and Key calculated");
+
+ if (session->remote.crypt->dtor) {
+ /* Cleanup any existing cipher */
+ session->remote.crypt->dtor(session,
+ &session->remote.crypt_abstract);
+ }
+
+ if (session->remote.crypt->init) {
+ unsigned char *iv = NULL, *secret = NULL;
+ int free_iv = 0, free_secret = 0;
+
+ LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(iv,
+ session->remote.crypt->
+ iv_len, "B");
+ if (!iv) {
+ ret = LIBSSH2_ERROR_KEX_FAILURE;
+ goto clean_exit;
+ }
+ LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(secret,
+ session->remote.crypt->
+ secret_len, "D");
+ if (!secret) {
+ LIBSSH2_FREE(session, iv);
+ ret = LIBSSH2_ERROR_KEX_FAILURE;
+ goto clean_exit;
+ }
+ if (session->remote.crypt->
+ init(session, session->remote.crypt, iv, &free_iv, secret,
+ &free_secret, 0, &session->remote.crypt_abstract)) {
+ LIBSSH2_FREE(session, iv);
+ LIBSSH2_FREE(session, secret);
+ ret = LIBSSH2_ERROR_KEX_FAILURE;
+ goto clean_exit;
+ }
+
+ if (free_iv) {
+ memset(iv, 0, session->remote.crypt->iv_len);
+ LIBSSH2_FREE(session, iv);
+ }
+
+ if (free_secret) {
+ memset(secret, 0, session->remote.crypt->secret_len);
+ LIBSSH2_FREE(session, secret);
+ }
+ }
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Server to Client IV and Key calculated");
+
+ if (session->local.mac->dtor) {
+ session->local.mac->dtor(session, &session->local.mac_abstract);
+ }
+
+ if (session->local.mac->init) {
+ unsigned char *key = NULL;
+ int free_key = 0;
+
+ LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(key,
+ session->local.mac->
+ key_len, "E");
+ if (!key) {
+ ret = LIBSSH2_ERROR_KEX_FAILURE;
+ goto clean_exit;
+ }
+ session->local.mac->init(session, key, &free_key,
+ &session->local.mac_abstract);
+
+ if (free_key) {
+ memset(key, 0, session->local.mac->key_len);
+ LIBSSH2_FREE(session, key);
+ }
+ }
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Client to Server HMAC Key calculated");
+
+ if (session->remote.mac->dtor) {
+ session->remote.mac->dtor(session, &session->remote.mac_abstract);
+ }
+
+ if (session->remote.mac->init) {
+ unsigned char *key = NULL;
+ int free_key = 0;
+
+ LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA1_HASH(key,
+ session->remote.mac->
+ key_len, "F");
+ if (!key) {
+ ret = LIBSSH2_ERROR_KEX_FAILURE;
+ goto clean_exit;
+ }
+ session->remote.mac->init(session, key, &free_key,
+ &session->remote.mac_abstract);
+
+ if (free_key) {
+ memset(key, 0, session->remote.mac->key_len);
+ LIBSSH2_FREE(session, key);
+ }
+ }
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Server to Client HMAC Key calculated");
+
+ /* Initialize compression for each direction */
+
+ /* Cleanup any existing compression */
+ if (session->local.comp && session->local.comp->dtor) {
+ session->local.comp->dtor(session, 1,
+ &session->local.comp_abstract);
+ }
+
+ if (session->local.comp && session->local.comp->init) {
+ if (session->local.comp->init(session, 1,
+ &session->local.comp_abstract)) {
+ ret = LIBSSH2_ERROR_KEX_FAILURE;
+ goto clean_exit;
+ }
+ }
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Client to Server compression initialized");
+
+ if (session->remote.comp && session->remote.comp->dtor) {
+ session->remote.comp->dtor(session, 0,
+ &session->remote.comp_abstract);
+ }
+
+ if (session->remote.comp && session->remote.comp->init) {
+ if (session->remote.comp->init(session, 0,
+ &session->remote.comp_abstract)) {
+ ret = LIBSSH2_ERROR_KEX_FAILURE;
+ goto clean_exit;
+ }
+ }
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Server to Client compression initialized");
+
+ }
+
+ clean_exit:
+ _libssh2_bn_free(exchange_state->x);
+ exchange_state->x = NULL;
+ _libssh2_bn_free(exchange_state->e);
+ exchange_state->e = NULL;
+ _libssh2_bn_free(exchange_state->f);
+ exchange_state->f = NULL;
+ _libssh2_bn_free(exchange_state->k);
+ exchange_state->k = NULL;
+ _libssh2_bn_ctx_free(exchange_state->ctx);
+ exchange_state->ctx = NULL;
+
+ if (exchange_state->e_packet) {
+ LIBSSH2_FREE(session, exchange_state->e_packet);
+ exchange_state->e_packet = NULL;
+ }
+
+ if (exchange_state->s_packet) {
+ LIBSSH2_FREE(session, exchange_state->s_packet);
+ exchange_state->s_packet = NULL;
+ }
+
+ if (exchange_state->k_value) {
+ LIBSSH2_FREE(session, exchange_state->k_value);
+ exchange_state->k_value = NULL;
+ }
+
+ exchange_state->state = libssh2_NB_state_idle;
+
+ return ret;
+}
+
+
+/*
+ * diffie_hellman_sha256
+ *
+ * Diffie Hellman Key Exchange, Group Agnostic
+ */
+static int diffie_hellman_sha256(LIBSSH2_SESSION *session,
+ _libssh2_bn *g,
+ _libssh2_bn *p,
+ int group_order,
+ unsigned char packet_type_init,
+ unsigned char packet_type_reply,
+ unsigned char *midhash,
+ unsigned long midhash_len,
+ kmdhgGPshakex_state_t *exchange_state)
+{
+ int ret = 0;
+ int rc;
+ libssh2_sha256_ctx exchange_hash_ctx;
+
+ if (exchange_state->state == libssh2_NB_state_idle) {
+ /* Setup initial values */
+ exchange_state->e_packet = NULL;
+ exchange_state->s_packet = NULL;
+ exchange_state->k_value = NULL;
+ exchange_state->ctx = _libssh2_bn_ctx_new();
+ exchange_state->x = _libssh2_bn_init(); /* Random from client */
+ exchange_state->e = _libssh2_bn_init(); /* g^x mod p */
+ exchange_state->f = _libssh2_bn_init_from_bin(); /* g^(Random from server) mod p */
+ exchange_state->k = _libssh2_bn_init(); /* The shared secret: f^x mod p */
+
+ /* Zero the whole thing out */
+ memset(&exchange_state->req_state, 0, sizeof(packet_require_state_t));
+
+ /* Generate x and e */
+ _libssh2_bn_rand(exchange_state->x, group_order * 8 - 1, 0, -1);
+ _libssh2_bn_mod_exp(exchange_state->e, g, exchange_state->x, p,
+ exchange_state->ctx);
+
+ /* Send KEX init */
+ /* packet_type(1) + String Length(4) + leading 0(1) */
+ exchange_state->e_packet_len =
+ _libssh2_bn_bytes(exchange_state->e) + 6;
+ if (_libssh2_bn_bits(exchange_state->e) % 8) {
+ /* Leading 00 not needed */
+ exchange_state->e_packet_len--;
+ }
+
+ exchange_state->e_packet =
+ LIBSSH2_ALLOC(session, exchange_state->e_packet_len);
+ if (!exchange_state->e_packet) {
+ ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Out of memory error");
+ goto clean_exit;
+ }
+ exchange_state->e_packet[0] = packet_type_init;
+ _libssh2_htonu32(exchange_state->e_packet + 1,
+ exchange_state->e_packet_len - 5);
+ if (_libssh2_bn_bits(exchange_state->e) % 8) {
+ _libssh2_bn_to_bin(exchange_state->e,
+ exchange_state->e_packet + 5);
+ } else {
+ exchange_state->e_packet[5] = 0;
+ _libssh2_bn_to_bin(exchange_state->e,
+ exchange_state->e_packet + 6);
+ }
+
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sending KEX packet %d",
+ (int) packet_type_init);
+ exchange_state->state = libssh2_NB_state_created;
+ }
+
+ if (exchange_state->state == libssh2_NB_state_created) {
+ rc = _libssh2_transport_send(session, exchange_state->e_packet,
+ exchange_state->e_packet_len,
+ NULL, 0);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ } else if (rc) {
+ ret = _libssh2_error(session, rc,
+ "Unable to send KEX init message");
+ goto clean_exit;
+ }
+ exchange_state->state = libssh2_NB_state_sent;
+ }
+
+ if (exchange_state->state == libssh2_NB_state_sent) {
+ if (session->burn_optimistic_kexinit) {
+ /* The first KEX packet to come along will be the guess initially
+ * sent by the server. That guess turned out to be wrong so we
+ * need to silently ignore it */
+ int burn_type;
+
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Waiting for badly guessed KEX packet (to be ignored)");
+ burn_type =
+ _libssh2_packet_burn(session, &exchange_state->burn_state);
+ if (burn_type == LIBSSH2_ERROR_EAGAIN) {
+ return burn_type;
+ } else if (burn_type <= 0) {
+ /* Failed to receive a packet */
+ ret = burn_type;
+ goto clean_exit;
+ }
+ session->burn_optimistic_kexinit = 0;
+
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Burnt packet of type: %02x",
+ (unsigned int) burn_type);
+ }
+
+ exchange_state->state = libssh2_NB_state_sent1;
+ }
+
+ if (exchange_state->state == libssh2_NB_state_sent1) {
+ /* Wait for KEX reply */
+ rc = _libssh2_packet_require(session, packet_type_reply,
+ &exchange_state->s_packet,
+ &exchange_state->s_packet_len, 0, NULL,
+ 0, &exchange_state->req_state);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ }
+ if (rc) {
+ ret = _libssh2_error(session, LIBSSH2_ERROR_TIMEOUT,
+ "Timed out waiting for KEX reply");
+ goto clean_exit;
+ }
+
+ /* Parse KEXDH_REPLY */
+ exchange_state->s = exchange_state->s_packet + 1;
+
+ session->server_hostkey_len = _libssh2_ntohu32(exchange_state->s);
+ exchange_state->s += 4;
+
+ if (session->server_hostkey)
+ LIBSSH2_FREE(session, session->server_hostkey);
+
+ session->server_hostkey =
+ LIBSSH2_ALLOC(session, session->server_hostkey_len);
+ if (!session->server_hostkey) {
+ ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate memory for a copy "
+ "of the host key");
+ goto clean_exit;
+ }
+ memcpy(session->server_hostkey, exchange_state->s,
+ session->server_hostkey_len);
+ exchange_state->s += session->server_hostkey_len;
+
+#if LIBSSH2_MD5
+ {
+ libssh2_md5_ctx fingerprint_ctx;
+
+ if (libssh2_md5_init(&fingerprint_ctx)) {
+ libssh2_md5_update(fingerprint_ctx, session->server_hostkey,
+ session->server_hostkey_len);
+ libssh2_md5_final(fingerprint_ctx,
+ session->server_hostkey_md5);
+ session->server_hostkey_md5_valid = TRUE;
+ }
+ else {
+ session->server_hostkey_md5_valid = FALSE;
+ }
+ }
+#ifdef LIBSSH2DEBUG
+ {
+ char fingerprint[50], *fprint = fingerprint;
+ int i;
+ for(i = 0; i < 16; i++, fprint += 3) {
+ snprintf(fprint, 4, "%02x:", session->server_hostkey_md5[i]);
+ }
+ *(--fprint) = '\0';
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Server's MD5 Fingerprint: %s", fingerprint);
+ }
+#endif /* LIBSSH2DEBUG */
+#endif /* ! LIBSSH2_MD5 */
+
+ {
+ libssh2_sha1_ctx fingerprint_ctx;
+
+ if (libssh2_sha1_init(&fingerprint_ctx)) {
+ libssh2_sha1_update(fingerprint_ctx, session->server_hostkey,
+ session->server_hostkey_len);
+ libssh2_sha1_final(fingerprint_ctx,
+ session->server_hostkey_sha1);
+ session->server_hostkey_sha1_valid = TRUE;
+ }
+ else {
+ session->server_hostkey_sha1_valid = FALSE;
+ }
+ }
+#ifdef LIBSSH2DEBUG
+ {
+ char fingerprint[64], *fprint = fingerprint;
+ int i;
+
+ for(i = 0; i < 20; i++, fprint += 3) {
+ snprintf(fprint, 4, "%02x:", session->server_hostkey_sha1[i]);
+ }
+ *(--fprint) = '\0';
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Server's SHA1 Fingerprint: %s", fingerprint);
+ }
+#endif /* LIBSSH2DEBUG */
+
+ if (session->hostkey->init(session, session->server_hostkey,
+ session->server_hostkey_len,
+ &session->server_hostkey_abstract)) {
+ ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_INIT,
+ "Unable to initialize hostkey importer");
+ goto clean_exit;
+ }
+
+ exchange_state->f_value_len = _libssh2_ntohu32(exchange_state->s);
+ exchange_state->s += 4;
+ exchange_state->f_value = exchange_state->s;
+ exchange_state->s += exchange_state->f_value_len;
+ _libssh2_bn_from_bin(exchange_state->f, exchange_state->f_value_len,
+ exchange_state->f_value);
+
+ exchange_state->h_sig_len = _libssh2_ntohu32(exchange_state->s);
+ exchange_state->s += 4;
+ exchange_state->h_sig = exchange_state->s;
+
+ /* Compute the shared secret */
+ _libssh2_bn_mod_exp(exchange_state->k, exchange_state->f,
+ exchange_state->x, p, exchange_state->ctx);
+ exchange_state->k_value_len = _libssh2_bn_bytes(exchange_state->k) + 5;
+ if (_libssh2_bn_bits(exchange_state->k) % 8) {
+ /* don't need leading 00 */
+ exchange_state->k_value_len--;
+ }
+ exchange_state->k_value =
+ LIBSSH2_ALLOC(session, exchange_state->k_value_len);
+ if (!exchange_state->k_value) {
+ ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate buffer for K");
+ goto clean_exit;
+ }
+ _libssh2_htonu32(exchange_state->k_value,
+ exchange_state->k_value_len - 4);
+ if (_libssh2_bn_bits(exchange_state->k) % 8) {
+ _libssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 4);
+ } else {
+ exchange_state->k_value[4] = 0;
+ _libssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 5);
+ }
+
+ exchange_state->exchange_hash = (void*)&exchange_hash_ctx;
+ libssh2_sha256_init(&exchange_hash_ctx);
+
+ if (session->local.banner) {
+ _libssh2_htonu32(exchange_state->h_sig_comp,
+ strlen((char *) session->local.banner) - 2);
+ libssh2_sha256_update(exchange_hash_ctx,
+ exchange_state->h_sig_comp, 4);
+ libssh2_sha256_update(exchange_hash_ctx,
+ (char *) session->local.banner,
+ strlen((char *) session->local.banner) - 2);
+ } else {
+ _libssh2_htonu32(exchange_state->h_sig_comp,
+ sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1);
+ libssh2_sha256_update(exchange_hash_ctx,
+ exchange_state->h_sig_comp, 4);
+ libssh2_sha256_update(exchange_hash_ctx,
+ LIBSSH2_SSH_DEFAULT_BANNER,
+ sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1);
+ }
+
+ _libssh2_htonu32(exchange_state->h_sig_comp,
+ strlen((char *) session->remote.banner));
+ libssh2_sha256_update(exchange_hash_ctx,
+ exchange_state->h_sig_comp, 4);
+ libssh2_sha256_update(exchange_hash_ctx,
+ session->remote.banner,
+ strlen((char *) session->remote.banner));
+
+ _libssh2_htonu32(exchange_state->h_sig_comp,
+ session->local.kexinit_len);
+ libssh2_sha256_update(exchange_hash_ctx,
+ exchange_state->h_sig_comp, 4);
+ libssh2_sha256_update(exchange_hash_ctx,
+ session->local.kexinit,
+ session->local.kexinit_len);
+
+ _libssh2_htonu32(exchange_state->h_sig_comp,
+ session->remote.kexinit_len);
+ libssh2_sha256_update(exchange_hash_ctx,
+ exchange_state->h_sig_comp, 4);
+ libssh2_sha256_update(exchange_hash_ctx,
+ session->remote.kexinit,
+ session->remote.kexinit_len);
+
+ _libssh2_htonu32(exchange_state->h_sig_comp,
+ session->server_hostkey_len);
+ libssh2_sha256_update(exchange_hash_ctx,
+ exchange_state->h_sig_comp, 4);
+ libssh2_sha256_update(exchange_hash_ctx,
+ session->server_hostkey,
+ session->server_hostkey_len);
+
+ if (packet_type_init == SSH_MSG_KEX_DH_GEX_INIT) {
+ /* diffie-hellman-group-exchange hashes additional fields */
+#ifdef LIBSSH2_DH_GEX_NEW
+ _libssh2_htonu32(exchange_state->h_sig_comp,
+ LIBSSH2_DH_GEX_MINGROUP);
+ _libssh2_htonu32(exchange_state->h_sig_comp + 4,
+ LIBSSH2_DH_GEX_OPTGROUP);
+ _libssh2_htonu32(exchange_state->h_sig_comp + 8,
+ LIBSSH2_DH_GEX_MAXGROUP);
+ libssh2_sha256_update(exchange_hash_ctx,
+ exchange_state->h_sig_comp, 12);
+#else
+ _libssh2_htonu32(exchange_state->h_sig_comp,
+ LIBSSH2_DH_GEX_OPTGROUP);
+ libssh2_sha256_update(exchange_hash_ctx,
+ exchange_state->h_sig_comp, 4);
+#endif
+ }
+
+ if (midhash) {
+ libssh2_sha256_update(exchange_hash_ctx, midhash,
+ midhash_len);
+ }
+
+ libssh2_sha256_update(exchange_hash_ctx,
+ exchange_state->e_packet + 1,
+ exchange_state->e_packet_len - 1);
+
+ _libssh2_htonu32(exchange_state->h_sig_comp,
+ exchange_state->f_value_len);
+ libssh2_sha256_update(exchange_hash_ctx,
+ exchange_state->h_sig_comp, 4);
+ libssh2_sha256_update(exchange_hash_ctx,
+ exchange_state->f_value,
+ exchange_state->f_value_len);
+
+ libssh2_sha256_update(exchange_hash_ctx,
+ exchange_state->k_value,
+ exchange_state->k_value_len);
+
+ libssh2_sha256_final(exchange_hash_ctx,
+ exchange_state->h_sig_comp);
+
+ if (session->hostkey->
+ sig_verify(session, exchange_state->h_sig,
+ exchange_state->h_sig_len, exchange_state->h_sig_comp,
+ SHA256_DIGEST_LENGTH, &session->server_hostkey_abstract)) {
+ ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_SIGN,
+ "Unable to verify hostkey signature");
+ goto clean_exit;
+ }
+
+
+
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sending NEWKEYS message");
+ exchange_state->c = SSH_MSG_NEWKEYS;
+
+ exchange_state->state = libssh2_NB_state_sent2;
+ }
+
+ if (exchange_state->state == libssh2_NB_state_sent2) {
+ rc = _libssh2_transport_send(session, &exchange_state->c, 1, NULL, 0);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ } else if (rc) {
+ ret = _libssh2_error(session, rc, "Unable to send NEWKEYS message");
+ goto clean_exit;
+ }
+
+ exchange_state->state = libssh2_NB_state_sent3;
+ }
+
+ if (exchange_state->state == libssh2_NB_state_sent3) {
+ rc = _libssh2_packet_require(session, SSH_MSG_NEWKEYS,
+ &exchange_state->tmp,
+ &exchange_state->tmp_len, 0, NULL, 0,
+ &exchange_state->req_state);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ } else if (rc) {
+ ret = _libssh2_error(session, rc, "Timed out waiting for NEWKEYS");
+ goto clean_exit;
+ }
+ /* The first key exchange has been performed,
+ switch to active crypt/comp/mac mode */
+ session->state |= LIBSSH2_STATE_NEWKEYS;
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Received NEWKEYS message");
+
+ /* This will actually end up being just packet_type(1)
+ for this packet type anyway */
+ LIBSSH2_FREE(session, exchange_state->tmp);
+
+ if (!session->session_id) {
+ session->session_id = LIBSSH2_ALLOC(session, SHA256_DIGEST_LENGTH);
+ if (!session->session_id) {
+ ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate buffer for SHA digest");
+ goto clean_exit;
+ }
+ memcpy(session->session_id, exchange_state->h_sig_comp,
+ SHA256_DIGEST_LENGTH);
+ session->session_id_len = SHA256_DIGEST_LENGTH;
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "session_id calculated");
+ }
+
+ /* Cleanup any existing cipher */
+ if (session->local.crypt->dtor) {
+ session->local.crypt->dtor(session,
+ &session->local.crypt_abstract);
+ }
+
+ /* Calculate IV/Secret/Key for each direction */
+ if (session->local.crypt->init) {
+ unsigned char *iv = NULL, *secret = NULL;
+ int free_iv = 0, free_secret = 0;
+
+ LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA256_HASH(iv,
+ session->local.crypt->
+ iv_len, "A");
+ if (!iv) {
+ ret = -1;
+ goto clean_exit;
+ }
+ LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA256_HASH(secret,
+ session->local.crypt->
+ secret_len, "C");
+ if (!secret) {
+ LIBSSH2_FREE(session, iv);
+ ret = LIBSSH2_ERROR_KEX_FAILURE;
+ goto clean_exit;
+ }
+ if (session->local.crypt->
+ init(session, session->local.crypt, iv, &free_iv, secret,
+ &free_secret, 1, &session->local.crypt_abstract)) {
+ LIBSSH2_FREE(session, iv);
+ LIBSSH2_FREE(session, secret);
+ ret = LIBSSH2_ERROR_KEX_FAILURE;
+ goto clean_exit;
+ }
+
+ if (free_iv) {
+ memset(iv, 0, session->local.crypt->iv_len);
+ LIBSSH2_FREE(session, iv);
+ }
+
+ if (free_secret) {
+ memset(secret, 0, session->local.crypt->secret_len);
+ LIBSSH2_FREE(session, secret);
+ }
+ }
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Client to Server IV and Key calculated");
+
+ if (session->remote.crypt->dtor) {
+ /* Cleanup any existing cipher */
+ session->remote.crypt->dtor(session,
+ &session->remote.crypt_abstract);
+ }
+
+ if (session->remote.crypt->init) {
+ unsigned char *iv = NULL, *secret = NULL;
+ int free_iv = 0, free_secret = 0;
+
+ LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA256_HASH(iv,
+ session->remote.crypt->
+ iv_len, "B");
+ if (!iv) {
+ ret = LIBSSH2_ERROR_KEX_FAILURE;
+ goto clean_exit;
+ }
+ LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA256_HASH(secret,
+ session->remote.crypt->
+ secret_len, "D");
+ if (!secret) {
+ LIBSSH2_FREE(session, iv);
+ ret = LIBSSH2_ERROR_KEX_FAILURE;
+ goto clean_exit;
+ }
+ if (session->remote.crypt->
+ init(session, session->remote.crypt, iv, &free_iv, secret,
+ &free_secret, 0, &session->remote.crypt_abstract)) {
+ LIBSSH2_FREE(session, iv);
+ LIBSSH2_FREE(session, secret);
+ ret = LIBSSH2_ERROR_KEX_FAILURE;
+ goto clean_exit;
+ }
+
+ if (free_iv) {
+ memset(iv, 0, session->remote.crypt->iv_len);
+ LIBSSH2_FREE(session, iv);
+ }
+
+ if (free_secret) {
+ memset(secret, 0, session->remote.crypt->secret_len);
+ LIBSSH2_FREE(session, secret);
+ }
+ }
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Server to Client IV and Key calculated");
+
+ if (session->local.mac->dtor) {
+ session->local.mac->dtor(session, &session->local.mac_abstract);
+ }
+
+ if (session->local.mac->init) {
+ unsigned char *key = NULL;
+ int free_key = 0;
+
+ LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA256_HASH(key,
+ session->local.mac->
+ key_len, "E");
+ if (!key) {
+ ret = LIBSSH2_ERROR_KEX_FAILURE;
+ goto clean_exit;
+ }
+ session->local.mac->init(session, key, &free_key,
+ &session->local.mac_abstract);
+
+ if (free_key) {
+ memset(key, 0, session->local.mac->key_len);
+ LIBSSH2_FREE(session, key);
+ }
+ }
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Client to Server HMAC Key calculated");
+
+ if (session->remote.mac->dtor) {
+ session->remote.mac->dtor(session, &session->remote.mac_abstract);
+ }
+
+ if (session->remote.mac->init) {
+ unsigned char *key = NULL;
+ int free_key = 0;
+
+ LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA256_HASH(key,
+ session->remote.mac->
+ key_len, "F");
+ if (!key) {
+ ret = LIBSSH2_ERROR_KEX_FAILURE;
+ goto clean_exit;
+ }
+ session->remote.mac->init(session, key, &free_key,
+ &session->remote.mac_abstract);
+
+ if (free_key) {
+ memset(key, 0, session->remote.mac->key_len);
+ LIBSSH2_FREE(session, key);
+ }
+ }
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Server to Client HMAC Key calculated");
+
+ /* Initialize compression for each direction */
+
+ /* Cleanup any existing compression */
+ if (session->local.comp && session->local.comp->dtor) {
+ session->local.comp->dtor(session, 1,
+ &session->local.comp_abstract);
+ }
+
+ if (session->local.comp && session->local.comp->init) {
+ if (session->local.comp->init(session, 1,
+ &session->local.comp_abstract)) {
+ ret = LIBSSH2_ERROR_KEX_FAILURE;
+ goto clean_exit;
+ }
+ }
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Client to Server compression initialized");
+
+ if (session->remote.comp && session->remote.comp->dtor) {
+ session->remote.comp->dtor(session, 0,
+ &session->remote.comp_abstract);
+ }
+
+ if (session->remote.comp && session->remote.comp->init) {
+ if (session->remote.comp->init(session, 0,
+ &session->remote.comp_abstract)) {
+ ret = LIBSSH2_ERROR_KEX_FAILURE;
+ goto clean_exit;
+ }
+ }
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Server to Client compression initialized");
+
+ }
+
+ clean_exit:
+ _libssh2_bn_free(exchange_state->x);
+ exchange_state->x = NULL;
+ _libssh2_bn_free(exchange_state->e);
+ exchange_state->e = NULL;
+ _libssh2_bn_free(exchange_state->f);
+ exchange_state->f = NULL;
+ _libssh2_bn_free(exchange_state->k);
+ exchange_state->k = NULL;
+ _libssh2_bn_ctx_free(exchange_state->ctx);
+ exchange_state->ctx = NULL;
+
+ if (exchange_state->e_packet) {
+ LIBSSH2_FREE(session, exchange_state->e_packet);
+ exchange_state->e_packet = NULL;
+ }
+
+ if (exchange_state->s_packet) {
+ LIBSSH2_FREE(session, exchange_state->s_packet);
+ exchange_state->s_packet = NULL;
+ }
+
+ if (exchange_state->k_value) {
+ LIBSSH2_FREE(session, exchange_state->k_value);
+ exchange_state->k_value = NULL;
+ }
+
+ exchange_state->state = libssh2_NB_state_idle;
+
+ return ret;
+}
+
+
+
+/* kex_method_diffie_hellman_group1_sha1_key_exchange
+ * Diffie-Hellman Group1 (Actually Group2) Key Exchange using SHA1
+ */
+static int
+kex_method_diffie_hellman_group1_sha1_key_exchange(LIBSSH2_SESSION *session,
+ key_exchange_state_low_t
+ * key_state)
+{
+ static const unsigned char p_value[128] = {
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34,
+ 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1,
+ 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74,
+ 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22,
+ 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
+ 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B,
+ 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37,
+ 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45,
+ 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6,
+ 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B,
+ 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED,
+ 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5,
+ 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6,
+ 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+ };
+
+ int ret;
+
+ if (key_state->state == libssh2_NB_state_idle) {
+ /* g == 2 */
+ key_state->p = _libssh2_bn_init_from_bin(); /* SSH2 defined value (p_value) */
+ key_state->g = _libssh2_bn_init(); /* SSH2 defined value (2) */
+
+ /* Initialize P and G */
+ _libssh2_bn_set_word(key_state->g, 2);
+ _libssh2_bn_from_bin(key_state->p, 128, p_value);
+
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Initiating Diffie-Hellman Group1 Key Exchange");
+
+ key_state->state = libssh2_NB_state_created;
+ }
+ ret = diffie_hellman_sha1(session, key_state->g, key_state->p, 128,
+ SSH_MSG_KEXDH_INIT, SSH_MSG_KEXDH_REPLY,
+ NULL, 0, &key_state->exchange_state);
+ if (ret == LIBSSH2_ERROR_EAGAIN) {
+ return ret;
+ }
+
+ _libssh2_bn_free(key_state->p);
+ key_state->p = NULL;
+ _libssh2_bn_free(key_state->g);
+ key_state->g = NULL;
+ key_state->state = libssh2_NB_state_idle;
+
+ return ret;
+}
+
+
+
+/* kex_method_diffie_hellman_group14_sha1_key_exchange
+ * Diffie-Hellman Group14 Key Exchange using SHA1
+ */
+static int
+kex_method_diffie_hellman_group14_sha1_key_exchange(LIBSSH2_SESSION *session,
+ key_exchange_state_low_t
+ * key_state)
+{
+ static const unsigned char p_value[256] = {
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34,
+ 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1,
+ 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74,
+ 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22,
+ 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
+ 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B,
+ 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37,
+ 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45,
+ 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6,
+ 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B,
+ 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED,
+ 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5,
+ 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6,
+ 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D,
+ 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05,
+ 0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A,
+ 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F,
+ 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96,
+ 0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB,
+ 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D,
+ 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04,
+ 0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C,
+ 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B,
+ 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03,
+ 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F,
+ 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9,
+ 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18,
+ 0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5,
+ 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10,
+ 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+ };
+ int ret;
+
+ if (key_state->state == libssh2_NB_state_idle) {
+ key_state->p = _libssh2_bn_init_from_bin(); /* SSH2 defined value (p_value) */
+ key_state->g = _libssh2_bn_init(); /* SSH2 defined value (2) */
+
+ /* g == 2 */
+ /* Initialize P and G */
+ _libssh2_bn_set_word(key_state->g, 2);
+ _libssh2_bn_from_bin(key_state->p, 256, p_value);
+
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Initiating Diffie-Hellman Group14 Key Exchange");
+
+ key_state->state = libssh2_NB_state_created;
+ }
+ ret = diffie_hellman_sha1(session, key_state->g, key_state->p,
+ 256, SSH_MSG_KEXDH_INIT, SSH_MSG_KEXDH_REPLY,
+ NULL, 0, &key_state->exchange_state);
+ if (ret == LIBSSH2_ERROR_EAGAIN) {
+ return ret;
+ }
+
+ key_state->state = libssh2_NB_state_idle;
+ _libssh2_bn_free(key_state->p);
+ key_state->p = NULL;
+ _libssh2_bn_free(key_state->g);
+ key_state->g = NULL;
+
+ return ret;
+}
+
+
+
+/* kex_method_diffie_hellman_group_exchange_sha1_key_exchange
+ * Diffie-Hellman Group Exchange Key Exchange using SHA1
+ * Negotiates random(ish) group for secret derivation
+ */
+static int
+kex_method_diffie_hellman_group_exchange_sha1_key_exchange
+(LIBSSH2_SESSION * session, key_exchange_state_low_t * key_state)
+{
+ unsigned long p_len, g_len;
+ int ret = 0;
+ int rc;
+
+ if (key_state->state == libssh2_NB_state_idle) {
+ key_state->p = _libssh2_bn_init_from_bin();
+ key_state->g = _libssh2_bn_init_from_bin();
+ /* Ask for a P and G pair */
+#ifdef LIBSSH2_DH_GEX_NEW
+ key_state->request[0] = SSH_MSG_KEX_DH_GEX_REQUEST;
+ _libssh2_htonu32(key_state->request + 1, LIBSSH2_DH_GEX_MINGROUP);
+ _libssh2_htonu32(key_state->request + 5, LIBSSH2_DH_GEX_OPTGROUP);
+ _libssh2_htonu32(key_state->request + 9, LIBSSH2_DH_GEX_MAXGROUP);
+ key_state->request_len = 13;
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Initiating Diffie-Hellman Group-Exchange (New Method)");
+#else
+ key_state->request[0] = SSH_MSG_KEX_DH_GEX_REQUEST_OLD;
+ _libssh2_htonu32(key_state->request + 1, LIBSSH2_DH_GEX_OPTGROUP);
+ key_state->request_len = 5;
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Initiating Diffie-Hellman Group-Exchange (Old Method)");
+#endif
+
+ key_state->state = libssh2_NB_state_created;
+ }
+
+ if (key_state->state == libssh2_NB_state_created) {
+ rc = _libssh2_transport_send(session, key_state->request,
+ key_state->request_len, NULL, 0);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ } else if (rc) {
+ ret = _libssh2_error(session, rc,
+ "Unable to send Group Exchange Request");
+ goto dh_gex_clean_exit;
+ }
+
+ key_state->state = libssh2_NB_state_sent;
+ }
+
+ if (key_state->state == libssh2_NB_state_sent) {
+ rc = _libssh2_packet_require(session, SSH_MSG_KEX_DH_GEX_GROUP,
+ &key_state->data, &key_state->data_len,
+ 0, NULL, 0, &key_state->req_state);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ } else if (rc) {
+ ret = _libssh2_error(session, rc,
+ "Timeout waiting for GEX_GROUP reply");
+ goto dh_gex_clean_exit;
+ }
+
+ key_state->state = libssh2_NB_state_sent1;
+ }
+
+ if (key_state->state == libssh2_NB_state_sent1) {
+ unsigned char *s = key_state->data + 1;
+ p_len = _libssh2_ntohu32(s);
+ s += 4;
+ _libssh2_bn_from_bin(key_state->p, p_len, s);
+ s += p_len;
+
+ g_len = _libssh2_ntohu32(s);
+ s += 4;
+ _libssh2_bn_from_bin(key_state->g, g_len, s);
+
+ ret = diffie_hellman_sha1(session, key_state->g, key_state->p, p_len,
+ SSH_MSG_KEX_DH_GEX_INIT,
+ SSH_MSG_KEX_DH_GEX_REPLY,
+ key_state->data + 1,
+ key_state->data_len - 1,
+ &key_state->exchange_state);
+ if (ret == LIBSSH2_ERROR_EAGAIN) {
+ return ret;
+ }
+
+ LIBSSH2_FREE(session, key_state->data);
+ }
+
+ dh_gex_clean_exit:
+ key_state->state = libssh2_NB_state_idle;
+ _libssh2_bn_free(key_state->g);
+ key_state->g = NULL;
+ _libssh2_bn_free(key_state->p);
+ key_state->p = NULL;
+
+ return ret;
+}
+
+
+
+/* kex_method_diffie_hellman_group_exchange_sha256_key_exchange
+ * Diffie-Hellman Group Exchange Key Exchange using SHA256
+ * Negotiates random(ish) group for secret derivation
+ */
+static int
+kex_method_diffie_hellman_group_exchange_sha256_key_exchange
+(LIBSSH2_SESSION * session, key_exchange_state_low_t * key_state)
+{
+ unsigned long p_len, g_len;
+ int ret = 0;
+ int rc;
+
+ if (key_state->state == libssh2_NB_state_idle) {
+ key_state->p = _libssh2_bn_init();
+ key_state->g = _libssh2_bn_init();
+ /* Ask for a P and G pair */
+#ifdef LIBSSH2_DH_GEX_NEW
+ key_state->request[0] = SSH_MSG_KEX_DH_GEX_REQUEST;
+ _libssh2_htonu32(key_state->request + 1, LIBSSH2_DH_GEX_MINGROUP);
+ _libssh2_htonu32(key_state->request + 5, LIBSSH2_DH_GEX_OPTGROUP);
+ _libssh2_htonu32(key_state->request + 9, LIBSSH2_DH_GEX_MAXGROUP);
+ key_state->request_len = 13;
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Initiating Diffie-Hellman Group-Exchange (New Method SHA256)");
+#else
+ key_state->request[0] = SSH_MSG_KEX_DH_GEX_REQUEST_OLD;
+ _libssh2_htonu32(key_state->request + 1, LIBSSH2_DH_GEX_OPTGROUP);
+ key_state->request_len = 5;
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX,
+ "Initiating Diffie-Hellman Group-Exchange (Old Method SHA256)");
+#endif
+
+ key_state->state = libssh2_NB_state_created;
+ }
+
+ if (key_state->state == libssh2_NB_state_created) {
+ rc = _libssh2_transport_send(session, key_state->request,
+ key_state->request_len, NULL, 0);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ } else if (rc) {
+ ret = _libssh2_error(session, rc,
+ "Unable to send Group Exchange Request SHA256");
+ goto dh_gex_clean_exit;
+ }
+
+ key_state->state = libssh2_NB_state_sent;
+ }
+
+ if (key_state->state == libssh2_NB_state_sent) {
+ rc = _libssh2_packet_require(session, SSH_MSG_KEX_DH_GEX_GROUP,
+ &key_state->data, &key_state->data_len,
+ 0, NULL, 0, &key_state->req_state);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ } else if (rc) {
+ ret = _libssh2_error(session, rc,
+ "Timeout waiting for GEX_GROUP reply SHA256");
+ goto dh_gex_clean_exit;
+ }
+
+ key_state->state = libssh2_NB_state_sent1;
+ }
+
+ if (key_state->state == libssh2_NB_state_sent1) {
+ unsigned char *s = key_state->data + 1;
+ p_len = _libssh2_ntohu32(s);
+ s += 4;
+ _libssh2_bn_from_bin(key_state->p, p_len, s);
+ s += p_len;
+
+ g_len = _libssh2_ntohu32(s);
+ s += 4;
+ _libssh2_bn_from_bin(key_state->g, g_len, s);
+
+ ret = diffie_hellman_sha256(session, key_state->g, key_state->p, p_len,
+ SSH_MSG_KEX_DH_GEX_INIT,
+ SSH_MSG_KEX_DH_GEX_REPLY,
+ key_state->data + 1,
+ key_state->data_len - 1,
+ &key_state->exchange_state);
+ if (ret == LIBSSH2_ERROR_EAGAIN) {
+ return ret;
+ }
+
+ LIBSSH2_FREE(session, key_state->data);
+ }
+
+ dh_gex_clean_exit:
+ key_state->state = libssh2_NB_state_idle;
+ _libssh2_bn_free(key_state->g);
+ key_state->g = NULL;
+ _libssh2_bn_free(key_state->p);
+ key_state->p = NULL;
+
+ return ret;
+}
+
+
+#define LIBSSH2_KEX_METHOD_FLAG_REQ_ENC_HOSTKEY 0x0001
+#define LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY 0x0002
+
+static const LIBSSH2_KEX_METHOD kex_method_diffie_helman_group1_sha1 = {
+ "diffie-hellman-group1-sha1",
+ kex_method_diffie_hellman_group1_sha1_key_exchange,
+ LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY,
+};
+
+static const LIBSSH2_KEX_METHOD kex_method_diffie_helman_group14_sha1 = {
+ "diffie-hellman-group14-sha1",
+ kex_method_diffie_hellman_group14_sha1_key_exchange,
+ LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY,
+};
+
+static const LIBSSH2_KEX_METHOD
+kex_method_diffie_helman_group_exchange_sha1 = {
+ "diffie-hellman-group-exchange-sha1",
+ kex_method_diffie_hellman_group_exchange_sha1_key_exchange,
+ LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY,
+};
+
+static const LIBSSH2_KEX_METHOD
+kex_method_diffie_helman_group_exchange_sha256 = {
+ "diffie-hellman-group-exchange-sha256",
+ kex_method_diffie_hellman_group_exchange_sha256_key_exchange,
+ LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY,
+};
+
+static const LIBSSH2_KEX_METHOD *libssh2_kex_methods[] = {
+ &kex_method_diffie_helman_group_exchange_sha256,
+ &kex_method_diffie_helman_group_exchange_sha1,
+ &kex_method_diffie_helman_group14_sha1,
+ &kex_method_diffie_helman_group1_sha1,
+ NULL
+};
+
+typedef struct _LIBSSH2_COMMON_METHOD
+{
+ const char *name;
+} LIBSSH2_COMMON_METHOD;
+
+/* kex_method_strlen
+ * Calculate the length of a particular method list's resulting string
+ * Includes SUM(strlen() of each individual method plus 1 (for coma)) - 1 (because the last coma isn't used)
+ * Another sign of bad coding practices gone mad. Pretend you don't see this.
+ */
+static size_t
+kex_method_strlen(LIBSSH2_COMMON_METHOD ** method)
+{
+ size_t len = 0;
+
+ if (!method || !*method) {
+ return 0;
+ }
+
+ while (*method && (*method)->name) {
+ len += strlen((*method)->name) + 1;
+ method++;
+ }
+
+ return len - 1;
+}
+
+
+
+/* kex_method_list
+ * Generate formatted preference list in buf
+ */
+static size_t
+kex_method_list(unsigned char *buf, size_t list_strlen,
+ LIBSSH2_COMMON_METHOD ** method)
+{
+ _libssh2_htonu32(buf, list_strlen);
+ buf += 4;
+
+ if (!method || !*method) {
+ return 4;
+ }
+
+ while (*method && (*method)->name) {
+ int mlen = strlen((*method)->name);
+ memcpy(buf, (*method)->name, mlen);
+ buf += mlen;
+ *(buf++) = ',';
+ method++;
+ }
+
+ return list_strlen + 4;
+}
+
+
+
+#define LIBSSH2_METHOD_PREFS_LEN(prefvar, defaultvar) \
+ ((prefvar) ? strlen(prefvar) : \
+ kex_method_strlen((LIBSSH2_COMMON_METHOD**)(defaultvar)))
+
+#define LIBSSH2_METHOD_PREFS_STR(buf, prefvarlen, prefvar, defaultvar) \
+ if (prefvar) { \
+ _libssh2_htonu32((buf), (prefvarlen)); \
+ buf += 4; \
+ memcpy((buf), (prefvar), (prefvarlen)); \
+ buf += (prefvarlen); \
+ } else { \
+ buf += kex_method_list((buf), (prefvarlen), \
+ (LIBSSH2_COMMON_METHOD**)(defaultvar)); \
+ }
+
+/* kexinit
+ * Send SSH_MSG_KEXINIT packet
+ */
+static int kexinit(LIBSSH2_SESSION * session)
+{
+ /* 62 = packet_type(1) + cookie(16) + first_packet_follows(1) +
+ reserved(4) + length longs(40) */
+ size_t data_len = 62;
+ size_t kex_len, hostkey_len = 0;
+ size_t crypt_cs_len, crypt_sc_len;
+ size_t comp_cs_len, comp_sc_len;
+ size_t mac_cs_len, mac_sc_len;
+ size_t lang_cs_len, lang_sc_len;
+ unsigned char *data, *s;
+ int rc;
+
+ if (session->kexinit_state == libssh2_NB_state_idle) {
+ kex_len =
+ LIBSSH2_METHOD_PREFS_LEN(session->kex_prefs, libssh2_kex_methods);
+ hostkey_len =
+ LIBSSH2_METHOD_PREFS_LEN(session->hostkey_prefs,
+ libssh2_hostkey_methods());
+ crypt_cs_len =
+ LIBSSH2_METHOD_PREFS_LEN(session->local.crypt_prefs,
+ libssh2_crypt_methods());
+ crypt_sc_len =
+ LIBSSH2_METHOD_PREFS_LEN(session->remote.crypt_prefs,
+ libssh2_crypt_methods());
+ mac_cs_len =
+ LIBSSH2_METHOD_PREFS_LEN(session->local.mac_prefs,
+ _libssh2_mac_methods());
+ mac_sc_len =
+ LIBSSH2_METHOD_PREFS_LEN(session->remote.mac_prefs,
+ _libssh2_mac_methods());
+ comp_cs_len =
+ LIBSSH2_METHOD_PREFS_LEN(session->local.comp_prefs,
+ _libssh2_comp_methods(session));
+ comp_sc_len =
+ LIBSSH2_METHOD_PREFS_LEN(session->remote.comp_prefs,
+ _libssh2_comp_methods(session));
+ lang_cs_len =
+ LIBSSH2_METHOD_PREFS_LEN(session->local.lang_prefs, NULL);
+ lang_sc_len =
+ LIBSSH2_METHOD_PREFS_LEN(session->remote.lang_prefs, NULL);
+
+ data_len += kex_len + hostkey_len + crypt_cs_len + crypt_sc_len +
+ comp_cs_len + comp_sc_len + mac_cs_len + mac_sc_len +
+ lang_cs_len + lang_sc_len;
+
+ s = data = LIBSSH2_ALLOC(session, data_len);
+ if (!data) {
+ return _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate memory");
+ }
+
+ *(s++) = SSH_MSG_KEXINIT;
+
+ _libssh2_random(s, 16);
+ s += 16;
+
+ /* Ennumerating through these lists twice is probably (certainly?)
+ inefficient from a CPU standpoint, but it saves multiple
+ malloc/realloc calls */
+ LIBSSH2_METHOD_PREFS_STR(s, kex_len, session->kex_prefs,
+ libssh2_kex_methods);
+ LIBSSH2_METHOD_PREFS_STR(s, hostkey_len, session->hostkey_prefs,
+ libssh2_hostkey_methods());
+ LIBSSH2_METHOD_PREFS_STR(s, crypt_cs_len, session->local.crypt_prefs,
+ libssh2_crypt_methods());
+ LIBSSH2_METHOD_PREFS_STR(s, crypt_sc_len, session->remote.crypt_prefs,
+ libssh2_crypt_methods());
+ LIBSSH2_METHOD_PREFS_STR(s, mac_cs_len, session->local.mac_prefs,
+ _libssh2_mac_methods());
+ LIBSSH2_METHOD_PREFS_STR(s, mac_sc_len, session->remote.mac_prefs,
+ _libssh2_mac_methods());
+ LIBSSH2_METHOD_PREFS_STR(s, comp_cs_len, session->local.comp_prefs,
+ _libssh2_comp_methods(session));
+ LIBSSH2_METHOD_PREFS_STR(s, comp_sc_len, session->remote.comp_prefs,
+ _libssh2_comp_methods(session));
+ LIBSSH2_METHOD_PREFS_STR(s, lang_cs_len, session->local.lang_prefs,
+ NULL);
+ LIBSSH2_METHOD_PREFS_STR(s, lang_sc_len, session->remote.lang_prefs,
+ NULL);
+
+ /* No optimistic KEX packet follows */
+ /* Deal with optimistic packets
+ * session->flags |= KEXINIT_OPTIMISTIC
+ * session->flags |= KEXINIT_METHODSMATCH
+ */
+ *(s++) = 0;
+
+ /* Reserved == 0 */
+ _libssh2_htonu32(s, 0);
+
+#ifdef LIBSSH2DEBUG
+ {
+ /* Funnily enough, they'll all "appear" to be '\0' terminated */
+ unsigned char *p = data + 21; /* type(1) + cookie(16) + len(4) */
+
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent KEX: %s", p);
+ p += kex_len + 4;
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent HOSTKEY: %s", p);
+ p += hostkey_len + 4;
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent CRYPT_CS: %s", p);
+ p += crypt_cs_len + 4;
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent CRYPT_SC: %s", p);
+ p += crypt_sc_len + 4;
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent MAC_CS: %s", p);
+ p += mac_cs_len + 4;
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent MAC_SC: %s", p);
+ p += mac_sc_len + 4;
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent COMP_CS: %s", p);
+ p += comp_cs_len + 4;
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent COMP_SC: %s", p);
+ p += comp_sc_len + 4;
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent LANG_CS: %s", p);
+ p += lang_cs_len + 4;
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Sent LANG_SC: %s", p);
+ p += lang_sc_len + 4;
+ }
+#endif /* LIBSSH2DEBUG */
+
+ session->kexinit_state = libssh2_NB_state_created;
+ } else {
+ data = session->kexinit_data;
+ data_len = session->kexinit_data_len;
+ /* zap the variables to ensure there is NOT a double free later */
+ session->kexinit_data = NULL;
+ session->kexinit_data_len = 0;
+ }
+
+ rc = _libssh2_transport_send(session, data, data_len, NULL, 0);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ session->kexinit_data = data;
+ session->kexinit_data_len = data_len;
+ return rc;
+ }
+ else if (rc) {
+ LIBSSH2_FREE(session, data);
+ session->kexinit_state = libssh2_NB_state_idle;
+ return _libssh2_error(session, rc,
+ "Unable to send KEXINIT packet to remote host");
+
+ }
+
+ if (session->local.kexinit) {
+ LIBSSH2_FREE(session, session->local.kexinit);
+ }
+
+ session->local.kexinit = data;
+ session->local.kexinit_len = data_len;
+
+ session->kexinit_state = libssh2_NB_state_idle;
+
+ return 0;
+}
+
+/* kex_agree_instr
+ * Kex specific variant of strstr()
+ * Needle must be preceed by BOL or ',', and followed by ',' or EOL
+ */
+static unsigned char *
+kex_agree_instr(unsigned char *haystack, unsigned long haystack_len,
+ const unsigned char *needle, unsigned long needle_len)
+{
+ unsigned char *s;
+
+ /* Haystack too short to bother trying */
+ if (haystack_len < needle_len) {
+ return NULL;
+ }
+
+ /* Needle at start of haystack */
+ if ((strncmp((char *) haystack, (char *) needle, needle_len) == 0) &&
+ (needle_len == haystack_len || haystack[needle_len] == ',')) {
+ return haystack;
+ }
+
+ s = haystack;
+ /* Search until we run out of comas or we run out of haystack,
+ whichever comes first */
+ while ((s = (unsigned char *) strchr((char *) s, ','))
+ && ((haystack_len - (s - haystack)) > needle_len)) {
+ s++;
+ /* Needle at X position */
+ if ((strncmp((char *) s, (char *) needle, needle_len) == 0) &&
+ (((s - haystack) + needle_len) == haystack_len
+ || s[needle_len] == ',')) {
+ return s;
+ }
+ }
+
+ return NULL;
+}
+
+
+
+/* kex_get_method_by_name
+ */
+static const LIBSSH2_COMMON_METHOD *
+kex_get_method_by_name(const char *name, size_t name_len,
+ const LIBSSH2_COMMON_METHOD ** methodlist)
+{
+ while (*methodlist) {
+ if ((strlen((*methodlist)->name) == name_len) &&
+ (strncmp((*methodlist)->name, name, name_len) == 0)) {
+ return *methodlist;
+ }
+ methodlist++;
+ }
+ return NULL;
+}
+
+
+
+/* kex_agree_hostkey
+ * Agree on a Hostkey which works with this kex
+ */
+static int kex_agree_hostkey(LIBSSH2_SESSION * session,
+ unsigned long kex_flags,
+ unsigned char *hostkey, unsigned long hostkey_len)
+{
+ const LIBSSH2_HOSTKEY_METHOD **hostkeyp = libssh2_hostkey_methods();
+ unsigned char *s;
+
+ if (session->hostkey_prefs) {
+ s = (unsigned char *) session->hostkey_prefs;
+
+ while (s && *s) {
+ unsigned char *p = (unsigned char *) strchr((char *) s, ',');
+ size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s));
+ if (kex_agree_instr(hostkey, hostkey_len, s, method_len)) {
+ const LIBSSH2_HOSTKEY_METHOD *method =
+ (const LIBSSH2_HOSTKEY_METHOD *)
+ kex_get_method_by_name((char *) s, method_len,
+ (const LIBSSH2_COMMON_METHOD **)
+ hostkeyp);
+
+ if (!method) {
+ /* Invalid method -- Should never be reached */
+ return -1;
+ }
+
+ /* So far so good, but does it suit our purposes? (Encrypting
+ vs Signing) */
+ if (((kex_flags & LIBSSH2_KEX_METHOD_FLAG_REQ_ENC_HOSTKEY) ==
+ 0) || (method->encrypt)) {
+ /* Either this hostkey can do encryption or this kex just
+ doesn't require it */
+ if (((kex_flags & LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY)
+ == 0) || (method->sig_verify)) {
+ /* Either this hostkey can do signing or this kex just
+ doesn't require it */
+ session->hostkey = method;
+ return 0;
+ }
+ }
+ }
+
+ s = p ? p + 1 : NULL;
+ }
+ return -1;
+ }
+
+ while (hostkeyp && (*hostkeyp) && (*hostkeyp)->name) {
+ s = kex_agree_instr(hostkey, hostkey_len,
+ (unsigned char *) (*hostkeyp)->name,
+ strlen((*hostkeyp)->name));
+ if (s) {
+ /* So far so good, but does it suit our purposes? (Encrypting vs
+ Signing) */
+ if (((kex_flags & LIBSSH2_KEX_METHOD_FLAG_REQ_ENC_HOSTKEY) == 0) ||
+ ((*hostkeyp)->encrypt)) {
+ /* Either this hostkey can do encryption or this kex just
+ doesn't require it */
+ if (((kex_flags & LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY) ==
+ 0) || ((*hostkeyp)->sig_verify)) {
+ /* Either this hostkey can do signing or this kex just
+ doesn't require it */
+ session->hostkey = *hostkeyp;
+ return 0;
+ }
+ }
+ }
+ hostkeyp++;
+ }
+
+ return -1;
+}
+
+
+
+/* kex_agree_kex_hostkey
+ * Agree on a Key Exchange method and a hostkey encoding type
+ */
+static int kex_agree_kex_hostkey(LIBSSH2_SESSION * session, unsigned char *kex,
+ unsigned long kex_len, unsigned char *hostkey,
+ unsigned long hostkey_len)
+{
+ const LIBSSH2_KEX_METHOD **kexp = libssh2_kex_methods;
+ unsigned char *s;
+
+ if (session->kex_prefs) {
+ s = (unsigned char *) session->kex_prefs;
+
+ while (s && *s) {
+ unsigned char *q, *p = (unsigned char *) strchr((char *) s, ',');
+ size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s));
+ if ((q = kex_agree_instr(kex, kex_len, s, method_len))) {
+ const LIBSSH2_KEX_METHOD *method = (const LIBSSH2_KEX_METHOD *)
+ kex_get_method_by_name((char *) s, method_len,
+ (const LIBSSH2_COMMON_METHOD **)
+ kexp);
+
+ if (!method) {
+ /* Invalid method -- Should never be reached */
+ return -1;
+ }
+
+ /* We've agreed on a key exchange method,
+ * Can we agree on a hostkey that works with this kex?
+ */
+ if (kex_agree_hostkey(session, method->flags, hostkey,
+ hostkey_len) == 0) {
+ session->kex = method;
+ if (session->burn_optimistic_kexinit && (kex == q)) {
+ /* Server sent an optimistic packet,
+ * and client agrees with preference
+ * cancel burning the first KEX_INIT packet that comes in */
+ session->burn_optimistic_kexinit = 0;
+ }
+ return 0;
+ }
+ }
+
+ s = p ? p + 1 : NULL;
+ }
+ return -1;
+ }
+
+ while (*kexp && (*kexp)->name) {
+ s = kex_agree_instr(kex, kex_len,
+ (unsigned char *) (*kexp)->name,
+ strlen((*kexp)->name));
+ if (s) {
+ /* We've agreed on a key exchange method,
+ * Can we agree on a hostkey that works with this kex?
+ */
+ if (kex_agree_hostkey(session, (*kexp)->flags, hostkey,
+ hostkey_len) == 0) {
+ session->kex = *kexp;
+ if (session->burn_optimistic_kexinit && (kex == s)) {
+ /* Server sent an optimistic packet,
+ * and client agrees with preference
+ * cancel burning the first KEX_INIT packet that comes in */
+ session->burn_optimistic_kexinit = 0;
+ }
+ return 0;
+ }
+ }
+ kexp++;
+ }
+ return -1;
+}
+
+
+
+/* kex_agree_crypt
+ * Agree on a cipher algo
+ */
+static int kex_agree_crypt(LIBSSH2_SESSION * session,
+ libssh2_endpoint_data *endpoint,
+ unsigned char *crypt,
+ unsigned long crypt_len)
+{
+ const LIBSSH2_CRYPT_METHOD **cryptp = libssh2_crypt_methods();
+ unsigned char *s;
+
+ (void) session;
+
+ if (endpoint->crypt_prefs) {
+ s = (unsigned char *) endpoint->crypt_prefs;
+
+ while (s && *s) {
+ unsigned char *p = (unsigned char *) strchr((char *) s, ',');
+ size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s));
+
+ if (kex_agree_instr(crypt, crypt_len, s, method_len)) {
+ const LIBSSH2_CRYPT_METHOD *method =
+ (const LIBSSH2_CRYPT_METHOD *)
+ kex_get_method_by_name((char *) s, method_len,
+ (const LIBSSH2_COMMON_METHOD **)
+ cryptp);
+
+ if (!method) {
+ /* Invalid method -- Should never be reached */
+ return -1;
+ }
+
+ endpoint->crypt = method;
+ return 0;
+ }
+
+ s = p ? p + 1 : NULL;
+ }
+ return -1;
+ }
+
+ while (*cryptp && (*cryptp)->name) {
+ s = kex_agree_instr(crypt, crypt_len,
+ (unsigned char *) (*cryptp)->name,
+ strlen((*cryptp)->name));
+ if (s) {
+ endpoint->crypt = *cryptp;
+ return 0;
+ }
+ cryptp++;
+ }
+
+ return -1;
+}
+
+
+
+/* kex_agree_mac
+ * Agree on a message authentication hash
+ */
+static int kex_agree_mac(LIBSSH2_SESSION * session,
+ libssh2_endpoint_data * endpoint, unsigned char *mac,
+ unsigned long mac_len)
+{
+ const LIBSSH2_MAC_METHOD **macp = _libssh2_mac_methods();
+ unsigned char *s;
+ (void) session;
+
+ if (endpoint->mac_prefs) {
+ s = (unsigned char *) endpoint->mac_prefs;
+
+ while (s && *s) {
+ unsigned char *p = (unsigned char *) strchr((char *) s, ',');
+ size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s));
+
+ if (kex_agree_instr(mac, mac_len, s, method_len)) {
+ const LIBSSH2_MAC_METHOD *method = (const LIBSSH2_MAC_METHOD *)
+ kex_get_method_by_name((char *) s, method_len,
+ (const LIBSSH2_COMMON_METHOD **)
+ macp);
+
+ if (!method) {
+ /* Invalid method -- Should never be reached */
+ return -1;
+ }
+
+ endpoint->mac = method;
+ return 0;
+ }
+
+ s = p ? p + 1 : NULL;
+ }
+ return -1;
+ }
+
+ while (*macp && (*macp)->name) {
+ s = kex_agree_instr(mac, mac_len, (unsigned char *) (*macp)->name,
+ strlen((*macp)->name));
+ if (s) {
+ endpoint->mac = *macp;
+ return 0;
+ }
+ macp++;
+ }
+
+ return -1;
+}
+
+
+
+/* kex_agree_comp
+ * Agree on a compression scheme
+ */
+static int kex_agree_comp(LIBSSH2_SESSION *session,
+ libssh2_endpoint_data *endpoint, unsigned char *comp,
+ unsigned long comp_len)
+{
+ const LIBSSH2_COMP_METHOD **compp = _libssh2_comp_methods(session);
+ unsigned char *s;
+ (void) session;
+
+ if (endpoint->comp_prefs) {
+ s = (unsigned char *) endpoint->comp_prefs;
+
+ while (s && *s) {
+ unsigned char *p = (unsigned char *) strchr((char *) s, ',');
+ size_t method_len = (p ? (size_t)(p - s) : strlen((char *) s));
+
+ if (kex_agree_instr(comp, comp_len, s, method_len)) {
+ const LIBSSH2_COMP_METHOD *method =
+ (const LIBSSH2_COMP_METHOD *)
+ kex_get_method_by_name((char *) s, method_len,
+ (const LIBSSH2_COMMON_METHOD **)
+ compp);
+
+ if (!method) {
+ /* Invalid method -- Should never be reached */
+ return -1;
+ }
+
+ endpoint->comp = method;
+ return 0;
+ }
+
+ s = p ? p + 1 : NULL;
+ }
+ return -1;
+ }
+
+ while (*compp && (*compp)->name) {
+ s = kex_agree_instr(comp, comp_len, (unsigned char *) (*compp)->name,
+ strlen((*compp)->name));
+ if (s) {
+ endpoint->comp = *compp;
+ return 0;
+ }
+ compp++;
+ }
+
+ return -1;
+}
+
+
+
+/* TODO: When in server mode we need to turn this logic on its head
+ * The Client gets to make the final call on "agreed methods"
+ */
+
+/*
+ * kex_string_pair() extracts a string from the packet and makes sure it fits
+ * within the given packet.
+ */
+static int kex_string_pair(unsigned char **sp, /* parsing position */
+ unsigned char *data, /* start pointer to packet */
+ size_t data_len, /* size of total packet */
+ size_t *lenp, /* length of the string */
+ unsigned char **strp) /* pointer to string start */
+{
+ unsigned char *s = *sp;
+ *lenp = _libssh2_ntohu32(s);
+
+ /* the length of the string must fit within the current pointer and the
+ end of the packet */
+ if (*lenp > (data_len - (s - data) -4))
+ return 1;
+ *strp = s + 4;
+ s += 4 + *lenp;
+
+ *sp = s;
+ return 0;
+}
+
+/* kex_agree_methods
+ * Decide which specific method to use of the methods offered by each party
+ */
+static int kex_agree_methods(LIBSSH2_SESSION * session, unsigned char *data,
+ unsigned data_len)
+{
+ unsigned char *kex, *hostkey, *crypt_cs, *crypt_sc, *comp_cs, *comp_sc,
+ *mac_cs, *mac_sc;
+ size_t kex_len, hostkey_len, crypt_cs_len, crypt_sc_len, comp_cs_len;
+ size_t comp_sc_len, mac_cs_len, mac_sc_len;
+ unsigned char *s = data;
+
+ /* Skip packet_type, we know it already */
+ s++;
+
+ /* Skip cookie, don't worry, it's preserved in the kexinit field */
+ s += 16;
+
+ /* Locate each string */
+ if(kex_string_pair(&s, data, data_len, &kex_len, &kex))
+ return -1;
+ if(kex_string_pair(&s, data, data_len, &hostkey_len, &hostkey))
+ return -1;
+ if(kex_string_pair(&s, data, data_len, &crypt_cs_len, &crypt_cs))
+ return -1;
+ if(kex_string_pair(&s, data, data_len, &crypt_sc_len, &crypt_sc))
+ return -1;
+ if(kex_string_pair(&s, data, data_len, &mac_cs_len, &mac_cs))
+ return -1;
+ if(kex_string_pair(&s, data, data_len, &mac_sc_len, &mac_sc))
+ return -1;
+ if(kex_string_pair(&s, data, data_len, &comp_cs_len, &comp_cs))
+ return -1;
+ if(kex_string_pair(&s, data, data_len, &comp_sc_len, &comp_sc))
+ return -1;
+
+ /* If the server sent an optimistic packet, assume that it guessed wrong.
+ * If the guess is determined to be right (by kex_agree_kex_hostkey)
+ * This flag will be reset to zero so that it's not ignored */
+ session->burn_optimistic_kexinit = *(s++);
+ /* Next uint32 in packet is all zeros (reserved) */
+
+ if (data_len < (unsigned) (s - data))
+ return -1; /* short packet */
+
+ if (kex_agree_kex_hostkey(session, kex, kex_len, hostkey, hostkey_len)) {
+ return -1;
+ }
+
+ if (kex_agree_crypt(session, &session->local, crypt_cs, crypt_cs_len)
+ || kex_agree_crypt(session, &session->remote, crypt_sc, crypt_sc_len)) {
+ return -1;
+ }
+
+ if (kex_agree_mac(session, &session->local, mac_cs, mac_cs_len) ||
+ kex_agree_mac(session, &session->remote, mac_sc, mac_sc_len)) {
+ return -1;
+ }
+
+ if (kex_agree_comp(session, &session->local, comp_cs, comp_cs_len) ||
+ kex_agree_comp(session, &session->remote, comp_sc, comp_sc_len)) {
+ return -1;
+ }
+
+#if 0
+ if (libssh2_kex_agree_lang(session, &session->local, lang_cs, lang_cs_len)
+ || libssh2_kex_agree_lang(session, &session->remote, lang_sc,
+ lang_sc_len)) {
+ return -1;
+ }
+#endif
+
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on KEX method: %s",
+ session->kex->name);
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on HOSTKEY method: %s",
+ session->hostkey->name);
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on CRYPT_CS method: %s",
+ session->local.crypt->name);
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on CRYPT_SC method: %s",
+ session->remote.crypt->name);
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on MAC_CS method: %s",
+ session->local.mac->name);
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on MAC_SC method: %s",
+ session->remote.mac->name);
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on COMP_CS method: %s",
+ session->local.comp->name);
+ _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Agreed on COMP_SC method: %s",
+ session->remote.comp->name);
+
+ return 0;
+}
+
+
+
+/* _libssh2_kex_exchange
+ * Exchange keys
+ * Returns 0 on success, non-zero on failure
+ *
+ * Returns some errors without _libssh2_error()
+ */
+int
+_libssh2_kex_exchange(LIBSSH2_SESSION * session, int reexchange,
+ key_exchange_state_t * key_state)
+{
+ int rc = 0;
+ int retcode;
+
+ session->state |= LIBSSH2_STATE_KEX_ACTIVE;
+
+ if (key_state->state == libssh2_NB_state_idle) {
+ /* Prevent loop in packet_add() */
+ session->state |= LIBSSH2_STATE_EXCHANGING_KEYS;
+
+ if (reexchange) {
+ session->kex = NULL;
+
+ if (session->hostkey && session->hostkey->dtor) {
+ session->hostkey->dtor(session,
+ &session->server_hostkey_abstract);
+ }
+ session->hostkey = NULL;
+ }
+
+ key_state->state = libssh2_NB_state_created;
+ }
+
+ if (!session->kex || !session->hostkey) {
+ if (key_state->state == libssh2_NB_state_created) {
+ /* Preserve in case of failure */
+ key_state->oldlocal = session->local.kexinit;
+ key_state->oldlocal_len = session->local.kexinit_len;
+
+ session->local.kexinit = NULL;
+
+ key_state->state = libssh2_NB_state_sent;
+ }
+
+ if (key_state->state == libssh2_NB_state_sent) {
+ retcode = kexinit(session);
+ if (retcode == LIBSSH2_ERROR_EAGAIN) {
+ session->state &= ~LIBSSH2_STATE_KEX_ACTIVE;
+ return retcode;
+ } else if (retcode) {
+ session->local.kexinit = key_state->oldlocal;
+ session->local.kexinit_len = key_state->oldlocal_len;
+ key_state->state = libssh2_NB_state_idle;
+ session->state &= ~LIBSSH2_STATE_KEX_ACTIVE;
+ session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS;
+ return -1;
+ }
+
+ key_state->state = libssh2_NB_state_sent1;
+ }
+
+ if (key_state->state == libssh2_NB_state_sent1) {
+ retcode =
+ _libssh2_packet_require(session, SSH_MSG_KEXINIT,
+ &key_state->data,
+ &key_state->data_len, 0, NULL, 0,
+ &key_state->req_state);
+ if (retcode == LIBSSH2_ERROR_EAGAIN) {
+ session->state &= ~LIBSSH2_STATE_KEX_ACTIVE;
+ return retcode;
+ }
+ else if (retcode) {
+ if (session->local.kexinit) {
+ LIBSSH2_FREE(session, session->local.kexinit);
+ }
+ session->local.kexinit = key_state->oldlocal;
+ session->local.kexinit_len = key_state->oldlocal_len;
+ key_state->state = libssh2_NB_state_idle;
+ session->state &= ~LIBSSH2_STATE_KEX_ACTIVE;
+ session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS;
+ return -1;
+ }
+
+ if (session->remote.kexinit) {
+ LIBSSH2_FREE(session, session->remote.kexinit);
+ }
+ session->remote.kexinit = key_state->data;
+ session->remote.kexinit_len = key_state->data_len;
+
+ if (kex_agree_methods(session, key_state->data,
+ key_state->data_len))
+ rc = LIBSSH2_ERROR_KEX_FAILURE;
+
+ key_state->state = libssh2_NB_state_sent2;
+ }
+ } else {
+ key_state->state = libssh2_NB_state_sent2;
+ }
+
+ if (rc == 0 && session->kex) {
+ if (key_state->state == libssh2_NB_state_sent2) {
+ retcode = session->kex->exchange_keys(session,
+ &key_state->key_state_low);
+ if (retcode == LIBSSH2_ERROR_EAGAIN) {
+ session->state &= ~LIBSSH2_STATE_KEX_ACTIVE;
+ return retcode;
+ } else if (retcode) {
+ rc = _libssh2_error(session, LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE,
+ "Unrecoverable error exchanging keys");
+ }
+ }
+ }
+
+ /* Done with kexinit buffers */
+ if (session->local.kexinit) {
+ LIBSSH2_FREE(session, session->local.kexinit);
+ session->local.kexinit = NULL;
+ }
+ if (session->remote.kexinit) {
+ LIBSSH2_FREE(session, session->remote.kexinit);
+ session->remote.kexinit = NULL;
+ }
+
+ session->state &= ~LIBSSH2_STATE_KEX_ACTIVE;
+ session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS;
+
+ key_state->state = libssh2_NB_state_idle;
+
+ return rc;
+}
+
+
+
+/* libssh2_session_method_pref
+ * Set preferred method
+ */
+LIBSSH2_API int
+libssh2_session_method_pref(LIBSSH2_SESSION * session, int method_type,
+ const char *prefs)
+{
+ char **prefvar, *s, *newprefs;
+ int prefs_len = strlen(prefs);
+ const LIBSSH2_COMMON_METHOD **mlist;
+
+ switch (method_type) {
+ case LIBSSH2_METHOD_KEX:
+ prefvar = &session->kex_prefs;
+ mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_kex_methods;
+ break;
+
+ case LIBSSH2_METHOD_HOSTKEY:
+ prefvar = &session->hostkey_prefs;
+ mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_hostkey_methods();
+ break;
+
+ case LIBSSH2_METHOD_CRYPT_CS:
+ prefvar = &session->local.crypt_prefs;
+ mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_crypt_methods();
+ break;
+
+ case LIBSSH2_METHOD_CRYPT_SC:
+ prefvar = &session->remote.crypt_prefs;
+ mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_crypt_methods();
+ break;
+
+ case LIBSSH2_METHOD_MAC_CS:
+ prefvar = &session->local.mac_prefs;
+ mlist = (const LIBSSH2_COMMON_METHOD **) _libssh2_mac_methods();
+ break;
+
+ case LIBSSH2_METHOD_MAC_SC:
+ prefvar = &session->remote.mac_prefs;
+ mlist = (const LIBSSH2_COMMON_METHOD **) _libssh2_mac_methods();
+ break;
+
+ case LIBSSH2_METHOD_COMP_CS:
+ prefvar = &session->local.comp_prefs;
+ mlist = (const LIBSSH2_COMMON_METHOD **)
+ _libssh2_comp_methods(session);
+ break;
+
+ case LIBSSH2_METHOD_COMP_SC:
+ prefvar = &session->remote.comp_prefs;
+ mlist = (const LIBSSH2_COMMON_METHOD **)
+ _libssh2_comp_methods(session);
+ break;
+
+ case LIBSSH2_METHOD_LANG_CS:
+ prefvar = &session->local.lang_prefs;
+ mlist = NULL;
+ break;
+
+ case LIBSSH2_METHOD_LANG_SC:
+ prefvar = &session->remote.lang_prefs;
+ mlist = NULL;
+ break;
+
+ default:
+ return _libssh2_error(session, LIBSSH2_ERROR_INVAL,
+ "Invalid parameter specified for method_type");
+ }
+
+ s = newprefs = LIBSSH2_ALLOC(session, prefs_len + 1);
+ if (!newprefs) {
+ return _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Error allocated space for method preferences");
+ }
+ memcpy(s, prefs, prefs_len + 1);
+
+ while (s && *s && mlist) {
+ char *p = strchr(s, ',');
+ int method_len = p ? (p - s) : (int) strlen(s);
+
+ if (!kex_get_method_by_name(s, method_len, mlist)) {
+ /* Strip out unsupported method */
+ if (p) {
+ memcpy(s, p + 1, strlen(s) - method_len);
+ } else {
+ if (s > newprefs) {
+ *(--s) = '\0';
+ } else {
+ *s = '\0';
+ }
+ }
+ }
+
+ s = p ? (p + 1) : NULL;
+ }
+
+ if (strlen(newprefs) == 0) {
+ LIBSSH2_FREE(session, newprefs);
+ return _libssh2_error(session, LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
+ "The requested method(s) are not currently "
+ "supported");
+ }
+
+ if (*prefvar) {
+ LIBSSH2_FREE(session, *prefvar);
+ }
+ *prefvar = newprefs;
+
+ return 0;
+}
+
+/*
+ * libssh2_session_supported_algs()
+ * returns a number of returned algorithms (a positive number) on success,
+ * a negative number on failure
+ */
+
+LIBSSH2_API int libssh2_session_supported_algs(LIBSSH2_SESSION* session,
+ int method_type,
+ const char*** algs)
+{
+ unsigned int i;
+ unsigned int j;
+ unsigned int ialg;
+ const LIBSSH2_COMMON_METHOD **mlist;
+
+ /* to prevent coredumps due to dereferencing of NULL */
+ if (NULL == algs)
+ return _libssh2_error(session, LIBSSH2_ERROR_BAD_USE,
+ "algs must not be NULL");
+
+ switch (method_type) {
+ case LIBSSH2_METHOD_KEX:
+ mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_kex_methods;
+ break;
+
+ case LIBSSH2_METHOD_HOSTKEY:
+ mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_hostkey_methods();
+ break;
+
+ case LIBSSH2_METHOD_CRYPT_CS:
+ case LIBSSH2_METHOD_CRYPT_SC:
+ mlist = (const LIBSSH2_COMMON_METHOD **) libssh2_crypt_methods();
+ break;
+
+ case LIBSSH2_METHOD_MAC_CS:
+ case LIBSSH2_METHOD_MAC_SC:
+ mlist = (const LIBSSH2_COMMON_METHOD **) _libssh2_mac_methods();
+ break;
+
+ case LIBSSH2_METHOD_COMP_CS:
+ case LIBSSH2_METHOD_COMP_SC:
+ mlist = (const LIBSSH2_COMMON_METHOD **) _libssh2_comp_methods(session);
+ break;
+
+ default:
+ return _libssh2_error(session, LIBSSH2_ERROR_METHOD_NOT_SUPPORTED,
+ "Unknown method type");
+ } /* switch */
+
+ /* weird situation */
+ if (NULL==mlist)
+ return _libssh2_error(session, LIBSSH2_ERROR_INVAL,
+ "No algorithm found");
+
+ /*
+ mlist is looped through twice. The first time to find the number od
+ supported algorithms (needed to allocate the proper size of array) and
+ the second time to actually copy the pointers. Typically this function
+ will not be called often (typically at the beginning of a session) and
+ the number of algorithms (i.e. niumber of iterations in one loop) will
+ not be high (typically it will not exceed 20) for quite a long time.
+
+ So double looping really shouldn't be an issue and it is definitely a
+ better solution than reallocation several times.
+ */
+
+ /* count the number of supported algorithms */
+ for ( i=0, ialg=0; NULL!=mlist[i]; i++) {
+ /* do not count fields with NULL name */
+ if (mlist[i]->name)
+ ialg++;
+ }
+
+ /* weird situation, no algorithm found */
+ if (0==ialg)
+ return _libssh2_error(session, LIBSSH2_ERROR_INVAL,
+ "No algorithm found");
+
+ /* allocate buffer */
+ *algs = (const char**) LIBSSH2_ALLOC(session, ialg*sizeof(const char*));
+ if ( NULL==*algs ) {
+ return _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Memory allocation failed");
+ }
+ /* Past this point *algs must be deallocated in case of an error!! */
+
+ /* copy non-NULL pointers only */
+ for ( i=0, j=0; NULL!=mlist[i] && j<ialg; i++ ) {
+ if ( NULL==mlist[i]->name ){
+ /* maybe a weird situation but if it occurs, do not include NULL
+ pointers */
+ continue;
+ }
+
+ /* note that [] has higher priority than * (dereferencing) */
+ (*algs)[j++] = mlist[i]->name;
+ }
+
+ /* correct number of pointers copied? (test the code above) */
+ if ( j!=ialg ) {
+ /* deallocate buffer */
+ LIBSSH2_FREE(session, (void *)*algs);
+ *algs = NULL;
+
+ return _libssh2_error(session, LIBSSH2_ERROR_BAD_USE,
+ "Internal error");
+ }
+
+ return ialg;
+}