/
niceSOFT
/
libssh2
Обзор
Документация
Войти
/
niceSOFT
/
libssh2
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/kex.c
4 007 строк
147 KB
Viktor Szakats
src: add support for chacha20-poly130-encrypted OpenSSH key files
03 авг 2026, 06:23
03 авг 2026, 06:23
7abbac4
Код
Авторство
О чём код?
/* Copyright (C) Sara Golemon <sarag@libssh2.org> * Copyright (C) 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: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * 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 HOLDER 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. * * SPDX-License-Identifier: BSD-3-Clause */ #include "libssh2_priv.h" #include "transport.h" #include "comp.h" #include "mac.h" #include <assert.h> static void kex_value_hash(ssh2_hash_alg hash_alg, size_t digest_len, LIBSSH2_SESSION *session, struct kmdhgGPshakex_state *exchange_state, unsigned char **data, size_t data_len, const void *version) { if(!digest_len || digest_len > MAX_SHA_DIGEST_LEN) { *data = NULL; return; } if(!*data) *data = SSH2_ALLOC(session, data_len + digest_len); if(*data) { size_t len = 0; while(len < data_len) { ssh2_hash_ctx ctx; int hok = ssh2_hash_init(&ctx, hash_alg); if(hok) { hok &= ssh2_hash_update(&ctx, exchange_state->k_value, exchange_state->k_value_len); hok &= ssh2_hash_update(&ctx, exchange_state->h_sig_comp, digest_len); if(len) hok &= ssh2_hash_update(&ctx, *data, len); else { hok &= ssh2_hash_update(&ctx, version, 1); hok &= ssh2_hash_update(&ctx, session->session_id, session->session_id_len); } hok &= ssh2_hash_final(&ctx, *data + len, digest_len); } if(!hok) { SSH2_SAFEFREE(session, *data); return; } len += digest_len; } } } static int kex_proc_hostkey(LIBSSH2_SESSION *session, struct string_buf *buf, const struct kmdhgGPshakex_state *exchange_state, unsigned char *data, size_t data_len) { size_t host_key_len; /* Parse reply */ if(data) { if(data_len < 5) return ssh2_err(session, LIBSSH2_ERROR_PROTO, "Unexpected packet length"); buf->data = data; buf->len = data_len; } else { if(exchange_state->s_packet_len < 5) return ssh2_err(session, LIBSSH2_ERROR_PROTO, "Unexpected packet length"); buf->data = exchange_state->s_packet; buf->len = exchange_state->s_packet_len; } buf->dataptr = buf->data; buf->dataptr++; /* advance past type */ if(session->server_hostkey) { SSH2_SAFEFREE(session, session->server_hostkey); session->server_hostkey_len = 0; } if(ssh2_copy_string(session, buf, &session->server_hostkey, &host_key_len)) return ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Could not copy host key"); session->server_hostkey_len = (uint32_t)host_key_len; #if LIBSSH2_MD5 session->server_hostkey_md5_valid = ssh2_hash(SSH2_MD5_ALG, session->server_hostkey, session->server_hostkey_len, session->server_hostkey_md5, sizeof(session->server_hostkey_md5)); #ifdef LIBSSH2DEBUG { char fingerprint[SSH2_MD5_DIG_LEN * 3 + 1]; char *fprint = fingerprint; int i; for(i = 0; i < SSH2_MD5_DIG_LEN; i++, fprint += 3) ssh2_snprintf(fprint, 4, "%02x:", session->server_hostkey_md5[i]); *(--fprint) = '\0'; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Server's MD5 Fingerprint: %s", fingerprint)); } #endif /* LIBSSH2DEBUG */ #endif /* !LIBSSH2_MD5 */ session->server_hostkey_sha1_valid = ssh2_hash(SSH2_SHA1_ALG, session->server_hostkey, session->server_hostkey_len, session->server_hostkey_sha1, sizeof(session->server_hostkey_sha1)); #ifdef LIBSSH2DEBUG { char fingerprint[SSH2_SHA1_DIG_LEN * 3 + 1]; char *fprint = fingerprint; int i; for(i = 0; i < SSH2_SHA1_DIG_LEN; i++, fprint += 3) ssh2_snprintf(fprint, 4, "%02x:", session->server_hostkey_sha1[i]); *(--fprint) = '\0'; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Server's SHA1 Fingerprint: %s", fingerprint)); } #endif /* LIBSSH2DEBUG */ session->server_hostkey_sha256_valid = ssh2_hash(SSH2_SHA256_ALG, session->server_hostkey, session->server_hostkey_len, session->server_hostkey_sha256, sizeof(session->server_hostkey_sha256)); #ifdef LIBSSH2DEBUG { char *base64Fingerprint = NULL; ssh2_base64_encode(session, (const char *)session->server_hostkey_sha256, SSH2_SHA256_DIG_LEN, &base64Fingerprint); if(base64Fingerprint) { ssh2_deb((session, LIBSSH2_TRACE_KEX, "Server's SHA256 Fingerprint: %s", base64Fingerprint)); SSH2_FREE(session, base64Fingerprint); } } #endif /* LIBSSH2DEBUG */ if(!session->hostkey) return ssh2_err(session, LIBSSH2_ERROR_PROTO, "hostkey is NULL"); if(session->hostkey->init(session, session->server_hostkey, session->server_hostkey_len, &session->server_hostkey_abstract)) return ssh2_err(session, LIBSSH2_ERROR_HOSTKEY_INIT, "Unable to initialize hostkey importer"); return 0; } static int kex_finish(LIBSSH2_SESSION *session, struct kmdhgGPshakex_state *exchange_state, ssh2_hash_alg hash_alg, size_t digest_len) { int rc; rc = ssh2_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; if(rc) return ssh2_err(session, rc, "Timed out waiting for NEWKEYS"); /* The first key exchange has been performed, switch to active crypt/comp/mac mode */ session->state |= SSH2_STATE_NEWKEYS; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Received NEWKEYS message")); /* This actually ends up being packet_type(1) for this packet type anyway */ SSH2_FREE(session, exchange_state->tmp); if(!session->session_id) { session->session_id = SSH2_ALLOC(session, digest_len); if(!session->session_id) return ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate buffer for SHA digest"); memcpy(session->session_id, exchange_state->h_sig_comp, digest_len); session->session_id_len = (uint32_t)digest_len; ssh2_deb((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; kex_value_hash(hash_alg, digest_len, session, exchange_state, &iv, session->local.crypt->iv_len, "A"); if(!iv) return ssh2_err(session, LIBSSH2_ERROR_KEX_FAILURE, "Unable to generate IV for key exchange"); kex_value_hash(hash_alg, digest_len, session, exchange_state, &secret, session->local.crypt->secret_len, "C"); if(!secret) { SSH2_FREE(session, iv); return ssh2_err(session, LIBSSH2_ERROR_KEX_FAILURE, "Unable to derive secret for key exchange"); } if(session->local.crypt->init(session, session->local.crypt, iv, &free_iv, secret, &free_secret, 1, 0, &session->local.crypt_abstract)) { SSH2_FREE(session, iv); SSH2_FREE(session, secret); return ssh2_err(session, LIBSSH2_ERROR_KEX_FAILURE, "Unable to initialize local encryption context"); } if(free_iv) { ssh2_explicit_zero(iv, session->local.crypt->iv_len); SSH2_FREE(session, iv); } if(free_secret) { ssh2_explicit_zero(secret, session->local.crypt->secret_len); SSH2_FREE(session, secret); } } ssh2_deb((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; kex_value_hash(hash_alg, digest_len, session, exchange_state, &iv, session->remote.crypt->iv_len, "B"); if(!iv) return ssh2_err(session, LIBSSH2_ERROR_KEX_FAILURE, "Failed to derive remote IV during key exchange"); kex_value_hash(hash_alg, digest_len, session, exchange_state, &secret, session->remote.crypt->secret_len, "D"); if(!secret) { SSH2_FREE(session, iv); return ssh2_err(session, LIBSSH2_ERROR_KEX_FAILURE, "Failed to derive remote encryption secret during " "key exchange"); } if(session->remote.crypt->init(session, session->remote.crypt, iv, &free_iv, secret, &free_secret, 0, 0, &session->remote.crypt_abstract)) { SSH2_FREE(session, iv); SSH2_FREE(session, secret); return ssh2_err(session, LIBSSH2_ERROR_KEX_FAILURE, "Failed to initialize remote encryption context"); } if(free_iv) { ssh2_explicit_zero(iv, session->remote.crypt->iv_len); SSH2_FREE(session, iv); } if(free_secret) { ssh2_explicit_zero(secret, session->remote.crypt->secret_len); SSH2_FREE(session, secret); } } ssh2_deb((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; kex_value_hash(hash_alg, digest_len, session, exchange_state, &key, session->local.mac->key_len, "E"); if(!key) return ssh2_err(session, LIBSSH2_ERROR_KEX_FAILURE, "Unable to derive client-to-server MAC key"); session->local.mac->init(session, key, &free_key, &session->local.mac_abstract); if(free_key) { ssh2_explicit_zero(key, session->local.mac->key_len); SSH2_FREE(session, key); } } ssh2_deb((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; kex_value_hash(hash_alg, digest_len, session, exchange_state, &key, session->remote.mac->key_len, "F"); if(!key) return ssh2_err(session, LIBSSH2_ERROR_KEX_FAILURE, "Unable to derive server-to-client MAC key"); session->remote.mac->init(session, key, &free_key, &session->remote.mac_abstract); if(free_key) { ssh2_explicit_zero(key, session->remote.mac->key_len); SSH2_FREE(session, key); } } ssh2_deb((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)) return LIBSSH2_ERROR_KEX_FAILURE; } ssh2_deb((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)) return LIBSSH2_ERROR_KEX_FAILURE; } ssh2_deb((session, LIBSSH2_TRACE_KEX, "Server to Client compression initialized")); return 0; } static void kex_diffie_hellman_state_cleanup( LIBSSH2_SESSION *session, struct kmdhgGPshakex_state *exchange_state) { ssh2_dh_dtor(&exchange_state->x); ssh2_bn_free(exchange_state->e); exchange_state->e = NULL; ssh2_bn_free(exchange_state->f); exchange_state->f = NULL; ssh2_bn_free(exchange_state->k); exchange_state->k = NULL; ssh2_bn_ctx_free(exchange_state->ctx); exchange_state->ctx = NULL; if(exchange_state->e_packet) SSH2_SAFEFREE(session, exchange_state->e_packet); if(exchange_state->s_packet) SSH2_SAFEFREE(session, exchange_state->s_packet); if(exchange_state->k_value) SSH2_SAFEFREE(session, exchange_state->k_value); exchange_state->state = ssh2_NB_state_idle; } static void kex_diffie_hellman_cleanup( LIBSSH2_SESSION *session, struct key_exchange_state_low *key_state) { if(key_state->state != ssh2_NB_state_idle) { ssh2_bn_free(key_state->p); key_state->p = NULL; ssh2_bn_free(key_state->g); key_state->g = NULL; if(key_state->data) SSH2_SAFEFREE(session, key_state->data); key_state->state = ssh2_NB_state_idle; } if(key_state->exchange_state.state != ssh2_NB_state_idle) kex_diffie_hellman_state_cleanup(session, &key_state->exchange_state); } /* * @abstract Diffie Hellman Key Exchange, Group Agnostic, * SHA Algorithm Agnostic * @result 0 on success, error code on failure */ static int kex_diffie_hellman_sha(LIBSSH2_SESSION *session, ssh2_bn *g, ssh2_bn *p, int group_order, ssh2_hash_alg hash_alg, size_t digest_len, unsigned char packet_type_init, unsigned char packet_type_reply, unsigned char *midhash, size_t midhash_len, struct kmdhgGPshakex_state *exchange_state) { int ret = 0; int rc; if(exchange_state->state == ssh2_NB_state_idle) { /* Setup initial values */ exchange_state->e_packet = NULL; exchange_state->s_packet = NULL; exchange_state->k_value = NULL; exchange_state->ctx = ssh2_bn_ctx_new(); ssh2_dh_init(&exchange_state->x); exchange_state->e = ssh2_bn_init(); /* g^x mod p */ exchange_state->f = ssh2_bn_init_from_bin(); /* g^(Random from server) mod p */ exchange_state->k = ssh2_bn_init(); /* The shared secret: f^x mod p */ /* Zero the whole thing out */ memset(&exchange_state->req_state, 0, sizeof(exchange_state->req_state)); /* Generate x and e */ if(ssh2_bn_bits(p) > SSH2_DH_MAX_MODULUS_BITS) { ret = ssh2_err(session, LIBSSH2_ERROR_INVAL, "DH modulus value is too large"); goto clean_exit; } rc = ssh2_dh_key_pair(&exchange_state->x, exchange_state->e, g, p, group_order, exchange_state->ctx); if(rc) { ret = ssh2_err(session, LIBSSH2_ERROR_KEX_FAILURE, "DH key pair generation failed"); goto clean_exit; } /* Send KEX init */ /* packet_type(1) + String Length(4) + leading 0(1) */ exchange_state->e_packet_len = ssh2_bn_bytes(exchange_state->e) + 6; if(ssh2_bn_bits(exchange_state->e) % 8) exchange_state->e_packet_len--; /* Leading 00 not needed */ exchange_state->e_packet = SSH2_ALLOC(session, exchange_state->e_packet_len); if(!exchange_state->e_packet) { ret = ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Out of memory error"); goto clean_exit; } exchange_state->e_packet[0] = packet_type_init; ssh2_htonu32(exchange_state->e_packet + 1, (uint32_t)(exchange_state->e_packet_len - 5)); if(ssh2_bn_bits(exchange_state->e) % 8) { if(ssh2_bn_to_bin(exchange_state->e, exchange_state->e_packet + 5)) { ret = ssh2_err(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, "Cannot write exchange_state->e"); goto clean_exit; } } else { exchange_state->e_packet[5] = 0; if(ssh2_bn_to_bin(exchange_state->e, exchange_state->e_packet + 6)) { ret = ssh2_err(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, "Cannot write exchange_state->e"); goto clean_exit; } } ssh2_deb((session, LIBSSH2_TRACE_KEX, "Sending KEX packet %u", (unsigned int)packet_type_init)); exchange_state->state = ssh2_NB_state_created; } if(exchange_state->state == ssh2_NB_state_created) { rc = ssh2_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 = ssh2_err(session, rc, "Unable to send KEX init message"); goto clean_exit; } exchange_state->state = ssh2_NB_state_sent; } if(exchange_state->state == ssh2_NB_state_sent) { if(session->burn_optimistic_kexinit) { /* The first KEX packet to come along is the guess initially * sent by the server. That guess turned out to be wrong so we * need to silently ignore it */ int burn_type; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Waiting for badly guessed KEX packet (to be ignored)")); burn_type = ssh2_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; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Burnt packet of type: %02x", (unsigned int)burn_type)); } exchange_state->state = ssh2_NB_state_sent1; } if(exchange_state->state == ssh2_NB_state_sent1) { /* Wait for KEX reply */ struct string_buf buf; ssh2_hash_ctx ctx; int err; int hok; rc = ssh2_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 = ssh2_err(session, LIBSSH2_ERROR_TIMEOUT, "Timed out waiting for KEX reply"); goto clean_exit; } ret = kex_proc_hostkey(session, &buf, exchange_state, NULL, 0); if(ret) goto clean_exit; if(ssh2_get_string(&buf, &exchange_state->f_value, &exchange_state->f_value_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_HOSTKEY_INIT, "Unable to get DH-SHA f value"); goto clean_exit; } if(ssh2_bn_from_bin(exchange_state->f, exchange_state->f_value, exchange_state->f_value_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_HOSTKEY_INIT, "Invalid DH-SHA f value"); goto clean_exit; } if(ssh2_get_string(&buf, &exchange_state->h_sig, &exchange_state->h_sig_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_HOSTKEY_INIT, "Unable to get DH-SHA h sig"); goto clean_exit; } /* Compute the shared secret */ if(ssh2_dh_secret(&exchange_state->x, exchange_state->k, exchange_state->f, p, exchange_state->ctx)) { ret = ssh2_err(session, LIBSSH2_ERROR_HOSTKEY_INIT, "Invalid DH secret parameters"); goto clean_exit; } exchange_state->k_value_len = ssh2_bn_bytes(exchange_state->k) + 5; if(ssh2_bn_bits(exchange_state->k) % 8) exchange_state->k_value_len--; /* do not need leading 00 */ exchange_state->k_value = SSH2_ALLOC(session, exchange_state->k_value_len); if(!exchange_state->k_value) { ret = ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate buffer for DH-SHA K"); goto clean_exit; } ssh2_htonu32(exchange_state->k_value, (uint32_t)(exchange_state->k_value_len - 4)); if(ssh2_bn_bits(exchange_state->k) % 8) { if(ssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 4)) { ret = ssh2_err(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, "Cannot write exchange_state->k"); goto clean_exit; } } else { exchange_state->k_value[4] = 0; if(ssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 5)) { ret = ssh2_err(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, "Cannot write exchange_state->k"); goto clean_exit; } } hok = ssh2_hash_init(&ctx, hash_alg); if(!hok) { ret = ssh2_err(session, LIBSSH2_ERROR_HASH_INIT, "Unable to initialize hash context DH-SHA"); goto clean_exit; } if(session->local.banner) { ssh2_htonu32(exchange_state->h_sig_comp, (uint32_t)(strlen(session->local.banner) - 2)); hok &= ssh2_hash_update(&ctx, exchange_state->h_sig_comp, 4); hok &= ssh2_hash_update(&ctx, session->local.banner, strlen(session->local.banner) - 2); } else { ssh2_htonu32(exchange_state->h_sig_comp, sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1); hok &= ssh2_hash_update(&ctx, exchange_state->h_sig_comp, 4); hok &= ssh2_hash_update(&ctx, LIBSSH2_SSH_DEFAULT_BANNER, sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1); } ssh2_htonu32(exchange_state->h_sig_comp, (uint32_t)strlen(session->remote.banner)); hok &= ssh2_hash_update(&ctx, exchange_state->h_sig_comp, 4); hok &= ssh2_hash_update(&ctx, session->remote.banner, strlen(session->remote.banner)); ssh2_htonu32(exchange_state->h_sig_comp, (uint32_t)session->local.kexinit_len); hok &= ssh2_hash_update(&ctx, exchange_state->h_sig_comp, 4); hok &= ssh2_hash_update(&ctx, session->local.kexinit, session->local.kexinit_len); ssh2_htonu32(exchange_state->h_sig_comp, (uint32_t)session->remote.kexinit_len); hok &= ssh2_hash_update(&ctx, exchange_state->h_sig_comp, 4); hok &= ssh2_hash_update(&ctx, session->remote.kexinit, session->remote.kexinit_len); ssh2_htonu32(exchange_state->h_sig_comp, session->server_hostkey_len); hok &= ssh2_hash_update(&ctx, exchange_state->h_sig_comp, 4); hok &= ssh2_hash_update(&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 */ ssh2_htonu32(exchange_state->h_sig_comp, SSH2_DH_GEX_MINGROUP); ssh2_htonu32(exchange_state->h_sig_comp + 4, SSH2_DH_GEX_OPTGROUP); ssh2_htonu32(exchange_state->h_sig_comp + 8, SSH2_DH_GEX_MAXGROUP); hok &= ssh2_hash_update(&ctx, exchange_state->h_sig_comp, 12); } if(midhash) hok &= ssh2_hash_update(&ctx, midhash, midhash_len); hok &= ssh2_hash_update(&ctx, exchange_state->e_packet + 1, exchange_state->e_packet_len - 1); ssh2_htonu32(exchange_state->h_sig_comp, (uint32_t)exchange_state->f_value_len); hok &= ssh2_hash_update(&ctx, exchange_state->h_sig_comp, 4); hok &= ssh2_hash_update(&ctx, exchange_state->f_value, exchange_state->f_value_len); hok &= ssh2_hash_update(&ctx, exchange_state->k_value, exchange_state->k_value_len); hok &= ssh2_hash_final(&ctx, exchange_state->h_sig_comp, sizeof(exchange_state->h_sig_comp)); if(!hok) { ret = ssh2_err(session, LIBSSH2_ERROR_HASH_CALC, "Failed to calculate hash DH-SHA"); goto clean_exit; } err = session->hostkey->sig_verify(session, exchange_state->h_sig, exchange_state->h_sig_len, exchange_state->h_sig_comp, digest_len, &session->server_hostkey_abstract); if(err) { ssh2_deb((session, LIBSSH2_TRACE_KEX, "Failed hostkey sig_verify(): %s: %d", session->hostkey->name, err)); ret = ssh2_err(session, LIBSSH2_ERROR_HOSTKEY_SIGN, "Unable to verify hostkey signature DH-SHA"); goto clean_exit; } ssh2_deb((session, LIBSSH2_TRACE_KEX, "Sending NEWKEYS message")); exchange_state->c = SSH_MSG_NEWKEYS; exchange_state->state = ssh2_NB_state_sent2; } if(exchange_state->state == ssh2_NB_state_sent2) { rc = ssh2_transport_send(session, &exchange_state->c, 1, NULL, 0); if(rc == LIBSSH2_ERROR_EAGAIN) return rc; else if(rc) { ret = ssh2_err(session, rc, "Unable to send NEWKEYS message DH-SHA"); goto clean_exit; } exchange_state->state = ssh2_NB_state_sent3; } if(exchange_state->state == ssh2_NB_state_sent3) { ret = kex_finish(session, exchange_state, hash_alg, digest_len); if(ret == LIBSSH2_ERROR_EAGAIN) return ret; } clean_exit: kex_diffie_hellman_state_cleanup(session, exchange_state); return ret; } #ifdef LIBSSH2_KEX_SHA1_ENABLE /* * Diffie-Hellman Group1 (Actually Group2) Key Exchange using SHA1 */ static int kex_method_diffie_hellman_group1_sha1_key_exchange( LIBSSH2_SESSION *session, struct key_exchange_state_low *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 == ssh2_NB_state_idle) { /* g == 2 */ key_state->p = ssh2_bn_init_from_bin(); /* SSH2 defined value (p_value) */ key_state->g = ssh2_bn_init(); /* SSH2 defined value (2) */ /* Initialize P and G */ if(!key_state->g || ssh2_bn_set_word(key_state->g, 2)) { ret = ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Failed to allocate key state g."); goto clean_exit; } if(!key_state->p || ssh2_bn_from_bin(key_state->p, p_value, 128)) { ret = ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Failed to allocate key state p."); goto clean_exit; } ssh2_deb((session, LIBSSH2_TRACE_KEX, "Initiating Diffie-Hellman Group1 Key Exchange")); key_state->state = ssh2_NB_state_created; } ret = kex_diffie_hellman_sha(session, key_state->g, key_state->p, 128, SSH2_SHA1_ALG, SSH2_SHA1_DIG_LEN, SSH_MSG_KEXDH_INIT, SSH_MSG_KEXDH_REPLY, NULL, 0, &key_state->exchange_state); if(ret == LIBSSH2_ERROR_EAGAIN) return ret; clean_exit: kex_diffie_hellman_cleanup(session, key_state); return ret; } #endif /* * Diffie-Hellman Group14 Key Exchange with hash function callback */ typedef int (*diffie_hellman_hash_func_t)( LIBSSH2_SESSION *session, ssh2_bn *g, ssh2_bn *p, int group_order, ssh2_hash_alg hash_alg, size_t digest_len, unsigned char packet_type_init, unsigned char packet_type_reply, unsigned char *midhash, size_t midhash_len, struct kmdhgGPshakex_state *exchange_state); static int kex_method_diffie_hellman_group14_key_exchange( LIBSSH2_SESSION *session, struct key_exchange_state_low *key_state, ssh2_hash_alg hash_alg, size_t digest_len, diffie_hellman_hash_func_t hashfunc) { 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 == ssh2_NB_state_idle) { key_state->p = ssh2_bn_init_from_bin(); /* SSH2 defined value (p_value) */ key_state->g = ssh2_bn_init(); /* SSH2 defined value (2) */ /* g == 2 */ /* Initialize P and G */ if(!key_state->g || ssh2_bn_set_word(key_state->g, 2)) { ret = ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Failed to allocate key state g."); goto clean_exit; } else if(!key_state->p || ssh2_bn_from_bin(key_state->p, p_value, 256)) { ret = ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Failed to allocate key state p."); goto clean_exit; } ssh2_deb((session, LIBSSH2_TRACE_KEX, "Initiating Diffie-Hellman Group14 Key Exchange")); key_state->state = ssh2_NB_state_created; } ret = hashfunc(session, key_state->g, key_state->p, 256, hash_alg, digest_len, SSH_MSG_KEXDH_INIT, SSH_MSG_KEXDH_REPLY, NULL, 0, &key_state->exchange_state); if(ret == LIBSSH2_ERROR_EAGAIN) return ret; clean_exit: kex_diffie_hellman_cleanup(session, key_state); return ret; } #ifdef LIBSSH2_KEX_SHA1_ENABLE /* * Diffie-Hellman Group14 Key Exchange using SHA1 */ static int kex_method_diffie_hellman_group14_sha1_key_exchange( LIBSSH2_SESSION *session, struct key_exchange_state_low *key_state) { return kex_method_diffie_hellman_group14_key_exchange(session, key_state, SSH2_SHA1_ALG, SSH2_SHA1_DIG_LEN, kex_diffie_hellman_sha); } #endif /* * Diffie-Hellman Group14 Key Exchange using SHA256 */ static int kex_method_diffie_hellman_group14_sha256_key_exchange( LIBSSH2_SESSION *session, struct key_exchange_state_low *key_state) { return kex_method_diffie_hellman_group14_key_exchange(session, key_state, SSH2_SHA256_ALG, SSH2_SHA256_DIG_LEN, kex_diffie_hellman_sha); } /* * Diffie-Hellman Group16 Key Exchange using SHA512 */ static int kex_method_diffie_hellman_group16_sha512_key_exchange( LIBSSH2_SESSION *session, struct key_exchange_state_low *key_state) { static const unsigned char p_value[512] = { 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, 0xAA, 0xC4, 0x2D, 0xAD, 0x33, 0x17, 0x0D, 0x04, 0x50, 0x7A, 0x33, 0xA8, 0x55, 0x21, 0xAB, 0xDF, 0x1C, 0xBA, 0x64, 0xEC, 0xFB, 0x85, 0x04, 0x58, 0xDB, 0xEF, 0x0A, 0x8A, 0xEA, 0x71, 0x57, 0x5D, 0x06, 0x0C, 0x7D, 0xB3, 0x97, 0x0F, 0x85, 0xA6, 0xE1, 0xE4, 0xC7, 0xAB, 0xF5, 0xAE, 0x8C, 0xDB, 0x09, 0x33, 0xD7, 0x1E, 0x8C, 0x94, 0xE0, 0x4A, 0x25, 0x61, 0x9D, 0xCE, 0xE3, 0xD2, 0x26, 0x1A, 0xD2, 0xEE, 0x6B, 0xF1, 0x2F, 0xFA, 0x06, 0xD9, 0x8A, 0x08, 0x64, 0xD8, 0x76, 0x02, 0x73, 0x3E, 0xC8, 0x6A, 0x64, 0x52, 0x1F, 0x2B, 0x18, 0x17, 0x7B, 0x20, 0x0C, 0xBB, 0xE1, 0x17, 0x57, 0x7A, 0x61, 0x5D, 0x6C, 0x77, 0x09, 0x88, 0xC0, 0xBA, 0xD9, 0x46, 0xE2, 0x08, 0xE2, 0x4F, 0xA0, 0x74, 0xE5, 0xAB, 0x31, 0x43, 0xDB, 0x5B, 0xFC, 0xE0, 0xFD, 0x10, 0x8E, 0x4B, 0x82, 0xD1, 0x20, 0xA9, 0x21, 0x08, 0x01, 0x1A, 0x72, 0x3C, 0x12, 0xA7, 0x87, 0xE6, 0xD7, 0x88, 0x71, 0x9A, 0x10, 0xBD, 0xBA, 0x5B, 0x26, 0x99, 0xC3, 0x27, 0x18, 0x6A, 0xF4, 0xE2, 0x3C, 0x1A, 0x94, 0x68, 0x34, 0xB6, 0x15, 0x0B, 0xDA, 0x25, 0x83, 0xE9, 0xCA, 0x2A, 0xD4, 0x4C, 0xE8, 0xDB, 0xBB, 0xC2, 0xDB, 0x04, 0xDE, 0x8E, 0xF9, 0x2E, 0x8E, 0xFC, 0x14, 0x1F, 0xBE, 0xCA, 0xA6, 0x28, 0x7C, 0x59, 0x47, 0x4E, 0x6B, 0xC0, 0x5D, 0x99, 0xB2, 0x96, 0x4F, 0xA0, 0x90, 0xC3, 0xA2, 0x23, 0x3B, 0xA1, 0x86, 0x51, 0x5B, 0xE7, 0xED, 0x1F, 0x61, 0x29, 0x70, 0xCE, 0xE2, 0xD7, 0xAF, 0xB8, 0x1B, 0xDD, 0x76, 0x21, 0x70, 0x48, 0x1C, 0xD0, 0x06, 0x91, 0x27, 0xD5, 0xB0, 0x5A, 0xA9, 0x93, 0xB4, 0xEA, 0x98, 0x8D, 0x8F, 0xDD, 0xC1, 0x86, 0xFF, 0xB7, 0xDC, 0x90, 0xA6, 0xC0, 0x8F, 0x4D, 0xF4, 0x35, 0xC9, 0x34, 0x06, 0x31, 0x99, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; int ret; if(key_state->state == ssh2_NB_state_idle) { key_state->p = ssh2_bn_init_from_bin(); /* SSH2 defined value (p_value) */ key_state->g = ssh2_bn_init(); /* SSH2 defined value (2) */ /* g == 2 */ /* Initialize P and G */ if(!key_state->g || ssh2_bn_set_word(key_state->g, 2)) { ret = ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Failed to allocate key state g."); goto clean_exit; } if(!key_state->p || ssh2_bn_from_bin(key_state->p, p_value, 512)) { ret = ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Failed to allocate key state p."); goto clean_exit; } ssh2_deb((session, LIBSSH2_TRACE_KEX, "Initiating Diffie-Hellman Group16 Key Exchange")); key_state->state = ssh2_NB_state_created; } ret = kex_diffie_hellman_sha(session, key_state->g, key_state->p, 512, SSH2_SHA512_ALG, SSH2_SHA512_DIG_LEN, SSH_MSG_KEXDH_INIT, SSH_MSG_KEXDH_REPLY, NULL, 0, &key_state->exchange_state); if(ret == LIBSSH2_ERROR_EAGAIN) return ret; clean_exit: kex_diffie_hellman_cleanup(session, key_state); return ret; } /* * Diffie-Hellman Group18 Key Exchange using SHA512 */ static int kex_method_diffie_hellman_group18_sha512_key_exchange( LIBSSH2_SESSION *session, struct key_exchange_state_low *key_state) { static const unsigned char p_value[1024] = { 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, 0xAA, 0xC4, 0x2D, 0xAD, 0x33, 0x17, 0x0D, 0x04, 0x50, 0x7A, 0x33, 0xA8, 0x55, 0x21, 0xAB, 0xDF, 0x1C, 0xBA, 0x64, 0xEC, 0xFB, 0x85, 0x04, 0x58, 0xDB, 0xEF, 0x0A, 0x8A, 0xEA, 0x71, 0x57, 0x5D, 0x06, 0x0C, 0x7D, 0xB3, 0x97, 0x0F, 0x85, 0xA6, 0xE1, 0xE4, 0xC7, 0xAB, 0xF5, 0xAE, 0x8C, 0xDB, 0x09, 0x33, 0xD7, 0x1E, 0x8C, 0x94, 0xE0, 0x4A, 0x25, 0x61, 0x9D, 0xCE, 0xE3, 0xD2, 0x26, 0x1A, 0xD2, 0xEE, 0x6B, 0xF1, 0x2F, 0xFA, 0x06, 0xD9, 0x8A, 0x08, 0x64, 0xD8, 0x76, 0x02, 0x73, 0x3E, 0xC8, 0x6A, 0x64, 0x52, 0x1F, 0x2B, 0x18, 0x17, 0x7B, 0x20, 0x0C, 0xBB, 0xE1, 0x17, 0x57, 0x7A, 0x61, 0x5D, 0x6C, 0x77, 0x09, 0x88, 0xC0, 0xBA, 0xD9, 0x46, 0xE2, 0x08, 0xE2, 0x4F, 0xA0, 0x74, 0xE5, 0xAB, 0x31, 0x43, 0xDB, 0x5B, 0xFC, 0xE0, 0xFD, 0x10, 0x8E, 0x4B, 0x82, 0xD1, 0x20, 0xA9, 0x21, 0x08, 0x01, 0x1A, 0x72, 0x3C, 0x12, 0xA7, 0x87, 0xE6, 0xD7, 0x88, 0x71, 0x9A, 0x10, 0xBD, 0xBA, 0x5B, 0x26, 0x99, 0xC3, 0x27, 0x18, 0x6A, 0xF4, 0xE2, 0x3C, 0x1A, 0x94, 0x68, 0x34, 0xB6, 0x15, 0x0B, 0xDA, 0x25, 0x83, 0xE9, 0xCA, 0x2A, 0xD4, 0x4C, 0xE8, 0xDB, 0xBB, 0xC2, 0xDB, 0x04, 0xDE, 0x8E, 0xF9, 0x2E, 0x8E, 0xFC, 0x14, 0x1F, 0xBE, 0xCA, 0xA6, 0x28, 0x7C, 0x59, 0x47, 0x4E, 0x6B, 0xC0, 0x5D, 0x99, 0xB2, 0x96, 0x4F, 0xA0, 0x90, 0xC3, 0xA2, 0x23, 0x3B, 0xA1, 0x86, 0x51, 0x5B, 0xE7, 0xED, 0x1F, 0x61, 0x29, 0x70, 0xCE, 0xE2, 0xD7, 0xAF, 0xB8, 0x1B, 0xDD, 0x76, 0x21, 0x70, 0x48, 0x1C, 0xD0, 0x06, 0x91, 0x27, 0xD5, 0xB0, 0x5A, 0xA9, 0x93, 0xB4, 0xEA, 0x98, 0x8D, 0x8F, 0xDD, 0xC1, 0x86, 0xFF, 0xB7, 0xDC, 0x90, 0xA6, 0xC0, 0x8F, 0x4D, 0xF4, 0x35, 0xC9, 0x34, 0x02, 0x84, 0x92, 0x36, 0xC3, 0xFA, 0xB4, 0xD2, 0x7C, 0x70, 0x26, 0xC1, 0xD4, 0xDC, 0xB2, 0x60, 0x26, 0x46, 0xDE, 0xC9, 0x75, 0x1E, 0x76, 0x3D, 0xBA, 0x37, 0xBD, 0xF8, 0xFF, 0x94, 0x06, 0xAD, 0x9E, 0x53, 0x0E, 0xE5, 0xDB, 0x38, 0x2F, 0x41, 0x30, 0x01, 0xAE, 0xB0, 0x6A, 0x53, 0xED, 0x90, 0x27, 0xD8, 0x31, 0x17, 0x97, 0x27, 0xB0, 0x86, 0x5A, 0x89, 0x18, 0xDA, 0x3E, 0xDB, 0xEB, 0xCF, 0x9B, 0x14, 0xED, 0x44, 0xCE, 0x6C, 0xBA, 0xCE, 0xD4, 0xBB, 0x1B, 0xDB, 0x7F, 0x14, 0x47, 0xE6, 0xCC, 0x25, 0x4B, 0x33, 0x20, 0x51, 0x51, 0x2B, 0xD7, 0xAF, 0x42, 0x6F, 0xB8, 0xF4, 0x01, 0x37, 0x8C, 0xD2, 0xBF, 0x59, 0x83, 0xCA, 0x01, 0xC6, 0x4B, 0x92, 0xEC, 0xF0, 0x32, 0xEA, 0x15, 0xD1, 0x72, 0x1D, 0x03, 0xF4, 0x82, 0xD7, 0xCE, 0x6E, 0x74, 0xFE, 0xF6, 0xD5, 0x5E, 0x70, 0x2F, 0x46, 0x98, 0x0C, 0x82, 0xB5, 0xA8, 0x40, 0x31, 0x90, 0x0B, 0x1C, 0x9E, 0x59, 0xE7, 0xC9, 0x7F, 0xBE, 0xC7, 0xE8, 0xF3, 0x23, 0xA9, 0x7A, 0x7E, 0x36, 0xCC, 0x88, 0xBE, 0x0F, 0x1D, 0x45, 0xB7, 0xFF, 0x58, 0x5A, 0xC5, 0x4B, 0xD4, 0x07, 0xB2, 0x2B, 0x41, 0x54, 0xAA, 0xCC, 0x8F, 0x6D, 0x7E, 0xBF, 0x48, 0xE1, 0xD8, 0x14, 0xCC, 0x5E, 0xD2, 0x0F, 0x80, 0x37, 0xE0, 0xA7, 0x97, 0x15, 0xEE, 0xF2, 0x9B, 0xE3, 0x28, 0x06, 0xA1, 0xD5, 0x8B, 0xB7, 0xC5, 0xDA, 0x76, 0xF5, 0x50, 0xAA, 0x3D, 0x8A, 0x1F, 0xBF, 0xF0, 0xEB, 0x19, 0xCC, 0xB1, 0xA3, 0x13, 0xD5, 0x5C, 0xDA, 0x56, 0xC9, 0xEC, 0x2E, 0xF2, 0x96, 0x32, 0x38, 0x7F, 0xE8, 0xD7, 0x6E, 0x3C, 0x04, 0x68, 0x04, 0x3E, 0x8F, 0x66, 0x3F, 0x48, 0x60, 0xEE, 0x12, 0xBF, 0x2D, 0x5B, 0x0B, 0x74, 0x74, 0xD6, 0xE6, 0x94, 0xF9, 0x1E, 0x6D, 0xBE, 0x11, 0x59, 0x74, 0xA3, 0x92, 0x6F, 0x12, 0xFE, 0xE5, 0xE4, 0x38, 0x77, 0x7C, 0xB6, 0xA9, 0x32, 0xDF, 0x8C, 0xD8, 0xBE, 0xC4, 0xD0, 0x73, 0xB9, 0x31, 0xBA, 0x3B, 0xC8, 0x32, 0xB6, 0x8D, 0x9D, 0xD3, 0x00, 0x74, 0x1F, 0xA7, 0xBF, 0x8A, 0xFC, 0x47, 0xED, 0x25, 0x76, 0xF6, 0x93, 0x6B, 0xA4, 0x24, 0x66, 0x3A, 0xAB, 0x63, 0x9C, 0x5A, 0xE4, 0xF5, 0x68, 0x34, 0x23, 0xB4, 0x74, 0x2B, 0xF1, 0xC9, 0x78, 0x23, 0x8F, 0x16, 0xCB, 0xE3, 0x9D, 0x65, 0x2D, 0xE3, 0xFD, 0xB8, 0xBE, 0xFC, 0x84, 0x8A, 0xD9, 0x22, 0x22, 0x2E, 0x04, 0xA4, 0x03, 0x7C, 0x07, 0x13, 0xEB, 0x57, 0xA8, 0x1A, 0x23, 0xF0, 0xC7, 0x34, 0x73, 0xFC, 0x64, 0x6C, 0xEA, 0x30, 0x6B, 0x4B, 0xCB, 0xC8, 0x86, 0x2F, 0x83, 0x85, 0xDD, 0xFA, 0x9D, 0x4B, 0x7F, 0xA2, 0xC0, 0x87, 0xE8, 0x79, 0x68, 0x33, 0x03, 0xED, 0x5B, 0xDD, 0x3A, 0x06, 0x2B, 0x3C, 0xF5, 0xB3, 0xA2, 0x78, 0xA6, 0x6D, 0x2A, 0x13, 0xF8, 0x3F, 0x44, 0xF8, 0x2D, 0xDF, 0x31, 0x0E, 0xE0, 0x74, 0xAB, 0x6A, 0x36, 0x45, 0x97, 0xE8, 0x99, 0xA0, 0x25, 0x5D, 0xC1, 0x64, 0xF3, 0x1C, 0xC5, 0x08, 0x46, 0x85, 0x1D, 0xF9, 0xAB, 0x48, 0x19, 0x5D, 0xED, 0x7E, 0xA1, 0xB1, 0xD5, 0x10, 0xBD, 0x7E, 0xE7, 0x4D, 0x73, 0xFA, 0xF3, 0x6B, 0xC3, 0x1E, 0xCF, 0xA2, 0x68, 0x35, 0x90, 0x46, 0xF4, 0xEB, 0x87, 0x9F, 0x92, 0x40, 0x09, 0x43, 0x8B, 0x48, 0x1C, 0x6C, 0xD7, 0x88, 0x9A, 0x00, 0x2E, 0xD5, 0xEE, 0x38, 0x2B, 0xC9, 0x19, 0x0D, 0xA6, 0xFC, 0x02, 0x6E, 0x47, 0x95, 0x58, 0xE4, 0x47, 0x56, 0x77, 0xE9, 0xAA, 0x9E, 0x30, 0x50, 0xE2, 0x76, 0x56, 0x94, 0xDF, 0xC8, 0x1F, 0x56, 0xE8, 0x80, 0xB9, 0x6E, 0x71, 0x60, 0xC9, 0x80, 0xDD, 0x98, 0xED, 0xD3, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; int ret; if(key_state->state == ssh2_NB_state_idle) { key_state->p = ssh2_bn_init_from_bin(); /* SSH2 defined value (p_value) */ key_state->g = ssh2_bn_init(); /* SSH2 defined value (2) */ /* g == 2 */ /* Initialize P and G */ if(!key_state->g || ssh2_bn_set_word(key_state->g, 2)) { ret = ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Failed to allocate key state g."); goto clean_exit; } else if(!key_state->p || ssh2_bn_from_bin(key_state->p, p_value, 1024)) { ret = ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Failed to allocate key state p."); goto clean_exit; } ssh2_deb((session, LIBSSH2_TRACE_KEX, "Initiating Diffie-Hellman Group18 Key Exchange")); key_state->state = ssh2_NB_state_created; } ret = kex_diffie_hellman_sha(session, key_state->g, key_state->p, 1024, SSH2_SHA512_ALG, SSH2_SHA512_DIG_LEN, SSH_MSG_KEXDH_INIT, SSH_MSG_KEXDH_REPLY, NULL, 0, &key_state->exchange_state); if(ret == LIBSSH2_ERROR_EAGAIN) return ret; clean_exit: kex_diffie_hellman_cleanup(session, key_state); return ret; } #ifdef LIBSSH2_KEX_SHA1_ENABLE /* * 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, struct key_exchange_state_low *key_state) { int ret = 0; int rc; if(key_state->state == ssh2_NB_state_idle) { key_state->p = ssh2_bn_init_from_bin(); key_state->g = ssh2_bn_init_from_bin(); /* Ask for a P and G pair */ key_state->request[0] = SSH_MSG_KEX_DH_GEX_REQUEST; ssh2_htonu32(key_state->request + 1, SSH2_DH_GEX_MINGROUP); ssh2_htonu32(key_state->request + 5, SSH2_DH_GEX_OPTGROUP); ssh2_htonu32(key_state->request + 9, SSH2_DH_GEX_MAXGROUP); key_state->request_len = 13; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Initiating Diffie-Hellman Group-Exchange SHA1")); key_state->state = ssh2_NB_state_created; } if(key_state->state == ssh2_NB_state_created) { rc = ssh2_transport_send(session, key_state->request, key_state->request_len, NULL, 0); if(rc == LIBSSH2_ERROR_EAGAIN) return rc; else if(rc) { ret = ssh2_err(session, rc, "Unable to send Group Exchange Request"); goto dh_gex_clean_exit; } key_state->state = ssh2_NB_state_sent; } if(key_state->state == ssh2_NB_state_sent) { rc = ssh2_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 = ssh2_err(session, rc, "Timeout waiting for GEX_GROUP reply"); goto dh_gex_clean_exit; } key_state->state = ssh2_NB_state_sent1; } if(key_state->state == ssh2_NB_state_sent1) { size_t p_len, g_len; unsigned char *p, *g; struct string_buf buf; int bits; if(key_state->data_len < 9) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "Unexpected key length DH-SHA1"); goto dh_gex_clean_exit; } buf.data = key_state->data; buf.dataptr = buf.data; buf.len = key_state->data_len; buf.dataptr++; /* increment to big num */ if(ssh2_get_bignum_bytes(&buf, &p, &p_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "Unexpected value DH-SHA1 p"); goto dh_gex_clean_exit; } if(ssh2_get_bignum_bytes(&buf, &g, &g_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "Unexpected value DH-SHA1 g"); goto dh_gex_clean_exit; } if(ssh2_bn_from_bin(key_state->p, p, p_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "Invalid DH-SHA1 p"); goto dh_gex_clean_exit; } bits = (int)ssh2_bn_bits(key_state->p); if(bits < SSH2_DH_GEX_MINGROUP || bits > SSH2_DH_GEX_MAXGROUP) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "DH-SHA1 p out of range"); goto dh_gex_clean_exit; } if(ssh2_bn_from_bin(key_state->g, g, g_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "Invalid DH-SHA1 g"); goto dh_gex_clean_exit; } ret = kex_diffie_hellman_sha(session, key_state->g, key_state->p, (int)p_len, SSH2_SHA1_ALG, SSH2_SHA1_DIG_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; } dh_gex_clean_exit: kex_diffie_hellman_cleanup(session, key_state); return ret; } #endif /* * 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, struct key_exchange_state_low *key_state) { int ret = 0; int rc; if(key_state->state == ssh2_NB_state_idle) { key_state->p = ssh2_bn_init_from_bin(); key_state->g = ssh2_bn_init_from_bin(); /* Ask for a P and G pair */ key_state->request[0] = SSH_MSG_KEX_DH_GEX_REQUEST; ssh2_htonu32(key_state->request + 1, SSH2_DH_GEX_MINGROUP); ssh2_htonu32(key_state->request + 5, SSH2_DH_GEX_OPTGROUP); ssh2_htonu32(key_state->request + 9, SSH2_DH_GEX_MAXGROUP); key_state->request_len = 13; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Initiating Diffie-Hellman Group-Exchange SHA256")); key_state->state = ssh2_NB_state_created; } if(key_state->state == ssh2_NB_state_created) { rc = ssh2_transport_send(session, key_state->request, key_state->request_len, NULL, 0); if(rc == LIBSSH2_ERROR_EAGAIN) return rc; else if(rc) { ret = ssh2_err(session, rc, "Unable to send Group Exchange Request SHA256"); goto dh_gex_clean_exit; } key_state->state = ssh2_NB_state_sent; } if(key_state->state == ssh2_NB_state_sent) { rc = ssh2_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 = ssh2_err(session, rc, "Timeout waiting for GEX_GROUP reply SHA256"); goto dh_gex_clean_exit; } key_state->state = ssh2_NB_state_sent1; } if(key_state->state == ssh2_NB_state_sent1) { unsigned char *p, *g; size_t p_len, g_len; struct string_buf buf; int bits; if(key_state->data_len < 9) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "Unexpected key length DH-SHA256"); goto dh_gex_clean_exit; } buf.data = key_state->data; buf.dataptr = buf.data; buf.len = key_state->data_len; buf.dataptr++; /* increment to big num */ if(ssh2_get_bignum_bytes(&buf, &p, &p_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "Unexpected value DH-SHA256 p"); goto dh_gex_clean_exit; } if(ssh2_get_bignum_bytes(&buf, &g, &g_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "Unexpected value DH-SHA256 g"); goto dh_gex_clean_exit; } if(ssh2_bn_from_bin(key_state->p, p, p_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "Invalid DH-SHA256 p"); goto dh_gex_clean_exit; } bits = (int)ssh2_bn_bits(key_state->p); if(bits < SSH2_DH_GEX_MINGROUP || bits > SSH2_DH_GEX_MAXGROUP) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "DH-SHA256 p out of range"); goto dh_gex_clean_exit; } if(ssh2_bn_from_bin(key_state->g, g, g_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "Invalid DH-SHA256 g"); goto dh_gex_clean_exit; } ret = kex_diffie_hellman_sha(session, key_state->g, key_state->p, (int)p_len, SSH2_SHA256_ALG, SSH2_SHA256_DIG_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; } dh_gex_clean_exit: kex_diffie_hellman_cleanup(session, key_state); return ret; } #if LIBSSH2_ECDSA || LIBSSH2_ED25519 /* * Create and verify EC[+PQ hybrid] SHA hash with a given digest size * * Payload format: * * string V_C, client's identification string (CR and LF excluded) * string V_S, server's identification string (CR and LF excluded) * string I_C, payload of the client's SSH_MSG_KEXINIT * string I_S, payload of the server's SSH_MSG_KEXINIT * string K_S, server's public host key * string Q_C, client's ephemeral public key octet string * string Q_S, server's ephemeral public key octet string * mpint K, shared secret */ static int kex_method_ec_sha_hash_create_verify( LIBSSH2_SESSION *session, struct kmdhgGPshakex_state *exchange_state, unsigned char *public_key, size_t public_key_len, unsigned char *public_pq_key, size_t public_pq_key_len, unsigned char *server_public_key, size_t server_public_key_len, ssh2_hash_alg hash_alg, size_t digest_len, const char *signerr) { ssh2_hash_ctx ctx; int err; int hok; hok = ssh2_hash_init(&ctx, hash_alg); if(!hok) return ssh2_err(session, LIBSSH2_ERROR_HASH_INIT, "Unable to initialize hash context EC/ED"); if(session->local.banner) { #ifdef LIBSSH2_DEBUG_MLKEM ssh2_deb((session, LIBSSH2_TRACE_KEX, "kex_method_ec_sha_hash_create_verify: %s " "session->local.banner=|%s|", session->hostkey->name, session->local.banner)); #endif ssh2_htonu32(exchange_state->h_sig_comp, (uint32_t)(strlen(session->local.banner) - 2)); hok &= ssh2_hash_update(&ctx, exchange_state->h_sig_comp, 4); hok &= ssh2_hash_update(&ctx, session->local.banner, strlen(session->local.banner) - 2); } else { #ifdef LIBSSH2_DEBUG_MLKEM ssh2_deb((session, LIBSSH2_TRACE_KEX, "kex_method_ec_sha_hash_create_verify: %s " "default_banner=|%s|", session->hostkey->name, LIBSSH2_SSH_DEFAULT_BANNER)); #endif ssh2_htonu32(exchange_state->h_sig_comp, sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1); hok &= ssh2_hash_update(&ctx, exchange_state->h_sig_comp, 4); hok &= ssh2_hash_update(&ctx, LIBSSH2_SSH_DEFAULT_BANNER, sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1); } #ifdef LIBSSH2_DEBUG_MLKEM ssh2_deb((session, LIBSSH2_TRACE_KEX, "kex_method_ec_sha_hash_create_verify: %s " "session->remote.banner=|%s|", session->hostkey->name, session->remote.banner)); #endif ssh2_htonu32(exchange_state->h_sig_comp, (uint32_t)strlen(session->remote.banner)); hok &= ssh2_hash_update(&ctx, exchange_state->h_sig_comp, 4); hok &= ssh2_hash_update(&ctx, session->remote.banner, strlen(session->remote.banner)); #ifdef LIBSSH2_DEBUG_MLKEM ssh2_deb((session, LIBSSH2_TRACE_KEX, "kex_method_ec_sha_hash_create_verify: %s " "session->local.kexinit_len=|%lu| " "session->remote.kexinit_len=|%lu| " "session->server_hostkey_len=|%lu| " "public_pq_key_len=|%lu| " "public_key_len=|%lu| " "server_public_key_len=|%lu| " "exchange_state->k_value_len=|%lu|", session->hostkey->name, (unsigned long)session->local.kexinit_len, (unsigned long)session->remote.kexinit_len, (unsigned long)session->server_hostkey_len, (unsigned long)public_pq_key_len, (unsigned long)public_key_len, (unsigned long)server_public_key_len, (unsigned long)exchange_state->k_value_len)); #endif ssh2_htonu32(exchange_state->h_sig_comp, (uint32_t)session->local.kexinit_len); hok &= ssh2_hash_update(&ctx, exchange_state->h_sig_comp, 4); hok &= ssh2_hash_update(&ctx, session->local.kexinit, session->local.kexinit_len); ssh2_htonu32(exchange_state->h_sig_comp, (uint32_t)session->remote.kexinit_len); hok &= ssh2_hash_update(&ctx, exchange_state->h_sig_comp, 4); hok &= ssh2_hash_update(&ctx, session->remote.kexinit, session->remote.kexinit_len); ssh2_htonu32(exchange_state->h_sig_comp, session->server_hostkey_len); hok &= ssh2_hash_update(&ctx, exchange_state->h_sig_comp, 4); hok &= ssh2_hash_update(&ctx, session->server_hostkey, session->server_hostkey_len); ssh2_htonu32(exchange_state->h_sig_comp, (uint32_t)(public_pq_key_len + public_key_len)); hok &= ssh2_hash_update(&ctx, exchange_state->h_sig_comp, 4); if(public_pq_key && public_pq_key_len) hok &= ssh2_hash_update(&ctx, public_pq_key, public_pq_key_len); hok &= ssh2_hash_update(&ctx, public_key, public_key_len); ssh2_htonu32(exchange_state->h_sig_comp, (uint32_t)server_public_key_len); hok &= ssh2_hash_update(&ctx, exchange_state->h_sig_comp, 4); hok &= ssh2_hash_update(&ctx, server_public_key, server_public_key_len); hok &= ssh2_hash_update(&ctx, exchange_state->k_value, exchange_state->k_value_len); hok &= ssh2_hash_final(&ctx, exchange_state->h_sig_comp, sizeof(exchange_state->h_sig_comp)); if(!hok) return ssh2_err(session, LIBSSH2_ERROR_HASH_CALC, "Failed to calculate hash EC/ED"); err = session->hostkey->sig_verify(session, exchange_state->h_sig, exchange_state->h_sig_len, exchange_state->h_sig_comp, digest_len, &session->server_hostkey_abstract); if(err) { ssh2_deb((session, LIBSSH2_TRACE_KEX, "Failed hostkey sig_verify() EC/ED: %s: %d", session->hostkey->name, err)); return ssh2_err(session, LIBSSH2_ERROR_HOSTKEY_SIGN, signerr); } return LIBSSH2_ERROR_NONE; } #endif /* LIBSSH2_ECDSA || LIBSSH2_ED25519 */ #if LIBSSH2_ECDSA static void kex_ecdh_exchange_state_cleanup( LIBSSH2_SESSION *session, struct kmdhgGPshakex_state *exchange_state) { ssh2_bn_free(exchange_state->k); exchange_state->k = NULL; if(exchange_state->k_value) SSH2_SAFEFREE(session, exchange_state->k_value); exchange_state->state = ssh2_NB_state_idle; } static void kex_method_ecdh_cleanup(LIBSSH2_SESSION *session, struct key_exchange_state_low *key_state) { if(key_state->public_key_oct) SSH2_SAFEFREE(session, key_state->public_key_oct); if(key_state->private_key) { ssh2_ecdsa_free(key_state->private_key); key_state->private_key = NULL; } if(key_state->data) SSH2_SAFEFREE(session, key_state->data); key_state->state = ssh2_NB_state_idle; if(key_state->exchange_state.state != ssh2_NB_state_idle) kex_ecdh_exchange_state_cleanup(session, &key_state->exchange_state); } /* * Elliptic Curve Diffie Hellman Key Exchange */ static int kex_ecdh_sha2_nistp(LIBSSH2_SESSION *session, ssh2_curve_type curve, unsigned char *data, size_t data_len, unsigned char *public_key, size_t public_key_len, ssh2_ec_key *private_key, struct kmdhgGPshakex_state *exchange_state) { int ret = 0; int rc; ssh2_hash_alg hash_alg; size_t digest_len = 0; if(curve == SSH2_EC_CURVE_NISTP256) { hash_alg = SSH2_SHA256_ALG; digest_len = SSH2_SHA256_DIG_LEN; } else if(curve == SSH2_EC_CURVE_NISTP384) { hash_alg = SSH2_SHA384_ALG; digest_len = SSH2_SHA384_DIG_LEN; } else if(curve == SSH2_EC_CURVE_NISTP521) { hash_alg = SSH2_SHA512_ALG; digest_len = SSH2_SHA512_DIG_LEN; } else { ret = ssh2_err(session, LIBSSH2_ERROR_KEX_FAILURE, "Unexpected ecdh-sha2-nistp curve type"); goto clean_exit; } if(exchange_state->state == ssh2_NB_state_idle) { /* Setup initial values */ exchange_state->k = ssh2_bn_init(); exchange_state->state = ssh2_NB_state_created; } if(exchange_state->state == ssh2_NB_state_created) { /* parse INIT reply data */ /* host key K_S */ unsigned char *server_public_key; size_t server_public_key_len; struct string_buf buf; if(!data) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "Missing host key data"); goto clean_exit; } ret = kex_proc_hostkey(session, &buf, exchange_state, data, data_len); if(ret) goto clean_exit; /* server public key Q_S */ if(ssh2_get_string(&buf, &server_public_key, &server_public_key_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "Unexpected ECDH server public key"); goto clean_exit; } /* server signature */ if(ssh2_get_string(&buf, &exchange_state->h_sig, &exchange_state->h_sig_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_HOSTKEY_INIT, "Unexpected ECDH server sig length"); goto clean_exit; } /* Compute the shared secret K */ rc = ssh2_ecdh_gen_k(&exchange_state->k, private_key, server_public_key, server_public_key_len); if(rc) { ret = ssh2_err(session, LIBSSH2_ERROR_KEX_FAILURE, "Unable to create ECDH shared secret"); goto clean_exit; } exchange_state->k_value_len = ssh2_bn_bytes(exchange_state->k) + 5; if(ssh2_bn_bits(exchange_state->k) % 8) exchange_state->k_value_len--; /* do not need leading 00 */ exchange_state->k_value = SSH2_ALLOC(session, exchange_state->k_value_len); if(!exchange_state->k_value) { ret = ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate buffer for ECDH K"); goto clean_exit; } ssh2_htonu32(exchange_state->k_value, (uint32_t)(exchange_state->k_value_len - 4)); if(ssh2_bn_bits(exchange_state->k) % 8) { if(ssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 4)) { ret = ssh2_err(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, "Cannot write exchange_state->k"); goto clean_exit; } } else { exchange_state->k_value[4] = 0; if(ssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 5)) { ret = ssh2_err(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, "Cannot write exchange_state->k"); goto clean_exit; } } /* verify hash */ ret = kex_method_ec_sha_hash_create_verify(session, exchange_state, public_key, public_key_len, NULL, 0, server_public_key, server_public_key_len, hash_alg, digest_len, "Unable to verify hostkey signature ECDH"); if(ret) goto clean_exit; exchange_state->c = SSH_MSG_NEWKEYS; exchange_state->state = ssh2_NB_state_sent; } if(exchange_state->state == ssh2_NB_state_sent) { rc = ssh2_transport_send(session, &exchange_state->c, 1, NULL, 0); if(rc == LIBSSH2_ERROR_EAGAIN) return rc; else if(rc) { ret = ssh2_err(session, rc, "Unable to send NEWKEYS message ECDH"); goto clean_exit; } exchange_state->state = ssh2_NB_state_sent2; } if(exchange_state->state == ssh2_NB_state_sent2) { ret = kex_finish(session, exchange_state, hash_alg, digest_len); if(ret == LIBSSH2_ERROR_EAGAIN) return ret; } clean_exit: kex_ecdh_exchange_state_cleanup(session, exchange_state); return ret; } /* * returns the EC curve type by name used in key exchange */ static int kex_session_curve_type(const char *name, ssh2_curve_type *out_curve) { ssh2_curve_type curve; if(!name) return -1; if(!strcmp(name, "mlkem768nistp256-sha256") || !strcmp(name, "ecdh-sha2-nistp256")) curve = SSH2_EC_CURVE_NISTP256; else if(!strcmp(name, "mlkem1024nistp384-sha384") || !strcmp(name, "ecdh-sha2-nistp384")) curve = SSH2_EC_CURVE_NISTP384; else if(!strcmp(name, "ecdh-sha2-nistp521")) curve = SSH2_EC_CURVE_NISTP521; else return -1; if(out_curve) *out_curve = curve; return 0; } /* * Elliptic Curve Diffie Hellman Key Exchange * supports SHA256/384/512 hashes based on negotiated ecdh method */ static int kex_method_ecdh_key_exchange( LIBSSH2_SESSION *session, struct key_exchange_state_low *key_state) { int ret = 0; int rc = 0; unsigned char *s; ssh2_curve_type curve; if(key_state->state == ssh2_NB_state_idle) { key_state->public_key_oct = NULL; key_state->state = ssh2_NB_state_created; } if(key_state->state == ssh2_NB_state_created) { rc = kex_session_curve_type(session->kex->name, &curve); if(rc) { ret = ssh2_err(session, -1, "Unrecognized KEX nistp curve type"); goto ecdh_clean_exit; } rc = ssh2_ecdsa_create_key(&key_state->private_key, session, &key_state->public_key_oct, &key_state->public_key_oct_len, curve); if(rc) { ret = ssh2_err(session, rc, "Unable to create private key"); goto ecdh_clean_exit; } key_state->request[0] = SSH2_MSG_KEX_ECDH_INIT; s = key_state->request + 1; ssh2_store_str(&s, key_state->public_key_oct, key_state->public_key_oct_len); key_state->request_len = key_state->public_key_oct_len + 5; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Initiating ECDH SHA2 NISTP256")); key_state->state = ssh2_NB_state_sent; } if(key_state->state == ssh2_NB_state_sent) { rc = ssh2_transport_send(session, key_state->request, key_state->request_len, NULL, 0); if(rc == LIBSSH2_ERROR_EAGAIN) return rc; else if(rc) { ret = ssh2_err(session, rc, "Unable to send ECDH_INIT"); goto ecdh_clean_exit; } key_state->state = ssh2_NB_state_sent1; } if(key_state->state == ssh2_NB_state_sent1) { rc = ssh2_packet_require(session, SSH2_MSG_KEX_ECDH_REPLY, &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 = ssh2_err(session, rc, "Timeout waiting for ECDH_REPLY reply"); goto ecdh_clean_exit; } key_state->state = ssh2_NB_state_sent2; } if(key_state->state == ssh2_NB_state_sent2) { rc = kex_session_curve_type(session->kex->name, &curve); if(rc) { ret = ssh2_err(session, -1, "Unrecognized KEX nistp curve type"); goto ecdh_clean_exit; } ret = kex_ecdh_sha2_nistp(session, curve, key_state->data, key_state->data_len, (unsigned char *)key_state->public_key_oct, key_state->public_key_oct_len, key_state->private_key, &key_state->exchange_state); if(ret == LIBSSH2_ERROR_EAGAIN) return ret; } ecdh_clean_exit: kex_method_ecdh_cleanup(session, key_state); return ret; } #if LIBSSH2_MLKEM static void kex_mlkem_nistp_exchange_state_cleanup( LIBSSH2_SESSION *session, struct kmdhgGPshakex_state *exchange_state) { ssh2_bn_free(exchange_state->k); exchange_state->k = NULL; if(exchange_state->k_value) SSH2_SAFEFREE(session, exchange_state->k_value); exchange_state->state = ssh2_NB_state_idle; } static void kex_method_mlkem_nistp_cleanup( LIBSSH2_SESSION *session, struct key_exchange_state_low *key_state) { if(key_state->public_key_oct) SSH2_SAFEFREE(session, key_state->public_key_oct); if(key_state->private_key) { ssh2_ecdsa_free(key_state->private_key); key_state->private_key = NULL; } if(key_state->mlkem_public_key) { ssh2_explicit_zero(key_state->mlkem_public_key, key_state->mlkem_public_key_len); SSH2_SAFEFREE(session, key_state->mlkem_public_key); key_state->mlkem_public_key_len = 0; } if(key_state->mlkem_private_key) { ssh2_explicit_zero(key_state->mlkem_private_key, key_state->mlkem_private_key_len); SSH2_SAFEFREE(session, key_state->mlkem_private_key); key_state->mlkem_private_key_len = 0; } if(key_state->data) SSH2_SAFEFREE(session, key_state->data); key_state->state = ssh2_NB_state_idle; if(key_state->exchange_state.state != ssh2_NB_state_idle) kex_ecdh_exchange_state_cleanup(session, &key_state->exchange_state); } static int kex_mlkem_nistp(LIBSSH2_SESSION *session, unsigned char *data, size_t data_len, unsigned char *public_t_key, size_t public_t_key_len, ssh2_ec_key *private_t_key, unsigned char *public_pq_key, unsigned char *private_pq_key, struct kmdhgGPshakex_state *exchange_state) { int ret = 0; int rc, mlkem_size; ssh2_hash_alg hash_alg; ssh2_curve_type curve; size_t digest_len, mlkem_cipher_len, public_pq_key_len; unsigned char *shared_secret = NULL; if(kex_session_curve_type(session->kex->name, &curve)) { ret = ssh2_err(session, -1, "Unrecognized KEX hybrid nistp curve type"); goto clean_exit; } switch(curve) { case SSH2_EC_CURVE_NISTP256: hash_alg = SSH2_SHA256_ALG; digest_len = SSH2_SHA256_DIG_LEN; mlkem_cipher_len = SSH2_MLKEM_768_CIPHERTEXT; mlkem_size = 768; public_pq_key_len = SSH2_MLKEM_768_PUBLIC_KEY_LEN; break; case SSH2_EC_CURVE_NISTP384: hash_alg = SSH2_SHA384_ALG; digest_len = SSH2_SHA384_DIG_LEN; mlkem_cipher_len = SSH2_MLKEM_1024_CIPHERTEXT; mlkem_size = 1024; public_pq_key_len = SSH2_MLKEM_1024_PUBLIC_KEY_LEN; break; default: ret = ssh2_err(session, -1, "Unexpected KEX hybrid nistp curve type"); goto clean_exit; } if(exchange_state->state == ssh2_NB_state_idle) { /* Setup initial values */ exchange_state->k = ssh2_bn_init(); exchange_state->state = ssh2_NB_state_created; } if(exchange_state->state == ssh2_NB_state_created) { /* parse INIT reply data */ unsigned char *server_public_key; size_t shared_secret_len; size_t server_public_key_len; struct string_buf buf; if(!data) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "Missing host key data"); goto clean_exit; } ret = kex_proc_hostkey(session, &buf, exchange_state, data, data_len); if(ret) goto clean_exit; /* server public key Q_S */ if(ssh2_get_string(&buf, &server_public_key, &server_public_key_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "Unexpected mlkemnistp server public key"); goto clean_exit; } if(server_public_key_len <= mlkem_cipher_len) { ret = ssh2_err(session, LIBSSH2_ERROR_HOSTKEY_INIT, "Unexpected mlkemnistp server public key length"); goto clean_exit; } /* server signature */ if(ssh2_get_string(&buf, &exchange_state->h_sig, &exchange_state->h_sig_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_HOSTKEY_INIT, "Unexpected mlkemnistp server sig length"); goto clean_exit; } exchange_state->k_value_len = digest_len + 4; exchange_state->k_value = SSH2_ALLOC(session, exchange_state->k_value_len); if(!exchange_state->k_value) { ret = ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate buffer for K"); goto clean_exit; } ssh2_htonu32(exchange_state->k_value, (uint32_t)digest_len); /* Compute the ecdh shared secret K */ rc = ssh2_ecdh_gen_k(&exchange_state->k, private_t_key, server_public_key + mlkem_cipher_len, server_public_key_len - mlkem_cipher_len); if(rc) { ret = ssh2_err(session, LIBSSH2_ERROR_KEX_FAILURE, "Unable to create ECDH shared secret"); goto clean_exit; } shared_secret_len = SSH2_MLKEM_SHARED_SECRET_LEN + ssh2_bn_bytes(exchange_state->k); shared_secret = SSH2_ALLOC(session, shared_secret_len); if(!shared_secret) { ret = ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate buffer for " "mlkemnistp shared secret"); goto clean_exit; } /* Compute the ML-KEM shared secret */ rc = ssh2_mlkem_get_sk(shared_secret, mlkem_size, private_pq_key, server_public_key); if(rc) { ret = ssh2_err(session, LIBSSH2_ERROR_KEX_FAILURE, "Unable to create mlkem shared secret"); goto clean_exit; } if(ssh2_bn_to_bin(exchange_state->k, shared_secret + SSH2_MLKEM_SHARED_SECRET_LEN)) { ret = ssh2_err(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, "Cannot write shared secret"); goto clean_exit; } /* verify hash */ if(!ssh2_hash(hash_alg, shared_secret, shared_secret_len, exchange_state->k_value + 4, digest_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_HASH_CALC, "kex: failed to calculate hash"); goto clean_exit; } ret = kex_method_ec_sha_hash_create_verify(session, exchange_state, public_t_key, public_t_key_len, public_pq_key, public_pq_key_len, server_public_key, server_public_key_len, hash_alg, digest_len, "Unable to verify hostkey signature mlkemnistp"); if(ret) goto clean_exit; exchange_state->c = SSH_MSG_NEWKEYS; exchange_state->state = ssh2_NB_state_sent; } if(exchange_state->state == ssh2_NB_state_sent) { rc = ssh2_transport_send(session, &exchange_state->c, 1, NULL, 0); if(rc == LIBSSH2_ERROR_EAGAIN) return rc; else if(rc) { ret = ssh2_err(session, rc, "Unable to send NEWKEYS message mlkemnistp"); goto clean_exit; } exchange_state->state = ssh2_NB_state_sent2; } if(exchange_state->state == ssh2_NB_state_sent2) { ret = kex_finish(session, exchange_state, hash_alg, digest_len); if(ret == LIBSSH2_ERROR_EAGAIN) goto clean_free; } clean_exit: kex_mlkem_nistp_exchange_state_cleanup(session, exchange_state); clean_free: if(shared_secret) SSH2_FREE(session, shared_secret); return ret; } static int kex_method_mlkem_nistp_key_exchange( LIBSSH2_SESSION *session, struct key_exchange_state_low *key_state) { int ret = 0; int rc = 0; if(key_state->state == ssh2_NB_state_idle) { key_state->public_key_oct = NULL; key_state->state = ssh2_NB_state_created; } if(key_state->state == ssh2_NB_state_created) { ssh2_curve_type curve; int mlkem_size; size_t mlkem_public_key_len; size_t mlkem_private_key_len; unsigned char *s = NULL; if(kex_session_curve_type(session->kex->name, &curve)) { ret = ssh2_err(session, -1, "Unrecognized KEX hybrid nistp curve type"); goto clean_exit; } switch(curve) { case SSH2_EC_CURVE_NISTP256: mlkem_size = 768; mlkem_public_key_len = SSH2_MLKEM_768_PUBLIC_KEY_LEN; mlkem_private_key_len = SSH2_MLKEM_768_PRIVATE_KEY_LEN; break; case SSH2_EC_CURVE_NISTP384: mlkem_size = 1024; mlkem_public_key_len = SSH2_MLKEM_1024_PUBLIC_KEY_LEN; mlkem_private_key_len = SSH2_MLKEM_1024_PRIVATE_KEY_LEN; break; default: ret = ssh2_err(session, -1, "Unexpected KEX hybrid nistp curve type"); goto clean_exit; } rc = ssh2_ecdsa_create_key(&key_state->private_key, session, &key_state->public_key_oct, &key_state->public_key_oct_len, curve); if(rc) { ret = ssh2_err(session, rc, "Unable to create ecdh private key"); goto clean_exit; } rc = ssh2_mlkem_new(session, mlkem_size, &key_state->mlkem_public_key, &key_state->mlkem_private_key); if(rc) { ret = ssh2_err(session, rc, "Unable to create mlkem private key"); goto clean_exit; } key_state->mlkem_public_key_len = mlkem_public_key_len; key_state->mlkem_private_key_len = mlkem_private_key_len; key_state->request[0] = SSH2_MSG_KEX_ECDH_INIT; s = key_state->request + 1; ssh2_store_hybrid_str(&s, key_state->mlkem_public_key, mlkem_public_key_len, key_state->public_key_oct, key_state->public_key_oct_len); key_state->request_len = mlkem_public_key_len + key_state->public_key_oct_len + 5; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Initiating mlkem-nistp hybrid SHA2")); key_state->state = ssh2_NB_state_sent; } if(key_state->state == ssh2_NB_state_sent) { rc = ssh2_transport_send(session, key_state->request, key_state->request_len, NULL, 0); if(rc == LIBSSH2_ERROR_EAGAIN) return rc; if(rc) { ret = ssh2_err(session, rc, "Unable to send ECDH_INIT"); goto clean_exit; } key_state->state = ssh2_NB_state_sent1; } if(key_state->state == ssh2_NB_state_sent1) { rc = ssh2_packet_require(session, SSH2_MSG_KEX_ECDH_REPLY, &key_state->data, &key_state->data_len, 0, NULL, 0, &key_state->req_state); if(rc == LIBSSH2_ERROR_EAGAIN) return rc; if(rc) { ret = ssh2_err(session, rc, "Timeout waiting for ECDH_REPLY reply"); goto clean_exit; } key_state->state = ssh2_NB_state_sent2; } if(key_state->state == ssh2_NB_state_sent2) { ret = kex_mlkem_nistp(session, key_state->data, key_state->data_len, (unsigned char *)key_state->public_key_oct, key_state->public_key_oct_len, key_state->private_key, key_state->mlkem_public_key, key_state->mlkem_private_key, &key_state->exchange_state); if(ret == LIBSSH2_ERROR_EAGAIN) return ret; } clean_exit: kex_method_mlkem_nistp_cleanup(session, key_state); return ret; } #endif /* LIBSSH2_MLKEM */ #endif /* LIBSSH2_ECDSA */ #if LIBSSH2_ED25519 static void kex_curve25519_exchange_state_cleanup( LIBSSH2_SESSION *session, struct kmdhgGPshakex_state *exchange_state) { ssh2_bn_free(exchange_state->k); exchange_state->k = NULL; if(exchange_state->k_value) SSH2_SAFEFREE(session, exchange_state->k_value); exchange_state->state = ssh2_NB_state_idle; } static void kex_method_curve25519_cleanup( LIBSSH2_SESSION *session, struct key_exchange_state_low *key_state) { if(key_state->curve25519_public_key) { ssh2_explicit_zero(key_state->curve25519_public_key, SSH2_ED25519_KEY_LEN); SSH2_SAFEFREE(session, key_state->curve25519_public_key); } if(key_state->curve25519_private_key) { ssh2_explicit_zero(key_state->curve25519_private_key, SSH2_ED25519_KEY_LEN); SSH2_SAFEFREE(session, key_state->curve25519_private_key); } if(key_state->data) SSH2_SAFEFREE(session, key_state->data); key_state->state = ssh2_NB_state_idle; if(key_state->exchange_state.state != ssh2_NB_state_idle) kex_curve25519_exchange_state_cleanup(session, &key_state->exchange_state); } /* * Elliptic Curve Key Exchange */ static int kex_curve25519_sha256( LIBSSH2_SESSION *session, unsigned char *data, size_t data_len, unsigned char public_key[SSH2_ED25519_KEY_LEN], unsigned char private_key[SSH2_ED25519_KEY_LEN], struct kmdhgGPshakex_state *exchange_state) { int ret = 0; int rc; if(exchange_state->state == ssh2_NB_state_idle) { /* Setup initial values */ exchange_state->k = ssh2_bn_init(); exchange_state->state = ssh2_NB_state_created; } if(exchange_state->state == ssh2_NB_state_created) { /* parse INIT reply data */ unsigned char *server_public_key; size_t server_public_key_len; size_t public_key_len = SSH2_ED25519_KEY_LEN; struct string_buf buf; if(!data) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "Missing host key data"); goto clean_exit; } ret = kex_proc_hostkey(session, &buf, exchange_state, data, data_len); if(ret) goto clean_exit; /* server public key Q_S */ if(ssh2_get_string(&buf, &server_public_key, &server_public_key_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "Unexpected curve25519 server public key"); goto clean_exit; } if(server_public_key_len != public_key_len) { ret = ssh2_err(session, LIBSSH2_ERROR_HOSTKEY_INIT, "Unexpected curve25519 server public key length"); goto clean_exit; } /* server signature */ if(ssh2_get_string(&buf, &exchange_state->h_sig, &exchange_state->h_sig_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_HOSTKEY_INIT, "Unexpected curve25519 server sig length"); goto clean_exit; } /* Compute the shared secret K */ rc = ssh2_curve25519_gen_k(&exchange_state->k, private_key, server_public_key); if(rc) { ret = ssh2_err(session, LIBSSH2_ERROR_KEX_FAILURE, "Unable to create curve25519 shared secret"); goto clean_exit; } exchange_state->k_value_len = ssh2_bn_bytes(exchange_state->k) + 5; if(ssh2_bn_bits(exchange_state->k) % 8) exchange_state->k_value_len--; /* do not need leading 00 */ exchange_state->k_value = SSH2_ALLOC(session, exchange_state->k_value_len); if(!exchange_state->k_value) { ret = ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate buffer for K"); goto clean_exit; } ssh2_htonu32(exchange_state->k_value, (uint32_t)(exchange_state->k_value_len - 4)); if(ssh2_bn_bits(exchange_state->k) % 8) { if(ssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 4)) { ret = ssh2_err(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, "Cannot write exchange_state->k"); goto clean_exit; } } else { exchange_state->k_value[4] = 0; if(ssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 5)) { ret = ssh2_err(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, "Cannot write exchange_state->k"); goto clean_exit; } } /* verify hash */ ret = kex_method_ec_sha_hash_create_verify(session, exchange_state, public_key, public_key_len, NULL, 0, server_public_key, server_public_key_len, SSH2_SHA256_ALG, SSH2_SHA256_DIG_LEN, "Unable to verify hostkey signature curve25519"); if(ret) goto clean_exit; exchange_state->c = SSH_MSG_NEWKEYS; exchange_state->state = ssh2_NB_state_sent; } if(exchange_state->state == ssh2_NB_state_sent) { rc = ssh2_transport_send(session, &exchange_state->c, 1, NULL, 0); if(rc == LIBSSH2_ERROR_EAGAIN) return rc; else if(rc) { ret = ssh2_err(session, rc, "Unable to send NEWKEYS message curve25519"); goto clean_exit; } exchange_state->state = ssh2_NB_state_sent2; } if(exchange_state->state == ssh2_NB_state_sent2) { ret = kex_finish(session, exchange_state, SSH2_SHA256_ALG, SSH2_SHA256_DIG_LEN); if(ret == LIBSSH2_ERROR_EAGAIN) return ret; } clean_exit: kex_curve25519_exchange_state_cleanup(session, exchange_state); return ret; } /* * Elliptic Curve X25519 Key Exchange with SHA256 hash */ static int kex_method_curve25519_key_exchange( LIBSSH2_SESSION *session, struct key_exchange_state_low *key_state) { int ret = 0; int rc = 0; if(key_state->state == ssh2_NB_state_idle) { key_state->public_key_oct = NULL; key_state->state = ssh2_NB_state_created; } if(key_state->state == ssh2_NB_state_created) { unsigned char *s = NULL; rc = strcmp(session->kex->name, "curve25519-sha256@libssh.org"); if(rc) rc = strcmp(session->kex->name, "curve25519-sha256"); if(rc) { ret = ssh2_err(session, -1, "Unrecognized KEX curve25519 curve type"); goto clean_exit; } rc = ssh2_curve25519_new(session, &key_state->curve25519_public_key, &key_state->curve25519_private_key); if(rc) { ret = ssh2_err(session, rc, "Unable to create private key"); goto clean_exit; } key_state->request[0] = SSH2_MSG_KEX_ECDH_INIT; s = key_state->request + 1; ssh2_store_str(&s, key_state->curve25519_public_key, SSH2_ED25519_KEY_LEN); key_state->request_len = SSH2_ED25519_KEY_LEN + 5; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Initiating curve25519 SHA2")); key_state->state = ssh2_NB_state_sent; } if(key_state->state == ssh2_NB_state_sent) { rc = ssh2_transport_send(session, key_state->request, key_state->request_len, NULL, 0); if(rc == LIBSSH2_ERROR_EAGAIN) return rc; else if(rc) { ret = ssh2_err(session, rc, "Unable to send ECDH_INIT"); goto clean_exit; } key_state->state = ssh2_NB_state_sent1; } if(key_state->state == ssh2_NB_state_sent1) { rc = ssh2_packet_require(session, SSH2_MSG_KEX_ECDH_REPLY, &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 = ssh2_err(session, rc, "Timeout waiting for ECDH_REPLY reply"); goto clean_exit; } key_state->state = ssh2_NB_state_sent2; } if(key_state->state == ssh2_NB_state_sent2) { ret = kex_curve25519_sha256(session, key_state->data, key_state->data_len, key_state->curve25519_public_key, key_state->curve25519_private_key, &key_state->exchange_state); if(ret == LIBSSH2_ERROR_EAGAIN) return ret; } clean_exit: kex_method_curve25519_cleanup(session, key_state); return ret; } #if LIBSSH2_MLKEM static void kex_mlkem768x25519_exchange_state_cleanup( LIBSSH2_SESSION *session, struct kmdhgGPshakex_state *exchange_state) { ssh2_bn_free(exchange_state->k); exchange_state->k = NULL; if(exchange_state->k_value) SSH2_SAFEFREE(session, exchange_state->k_value); exchange_state->state = ssh2_NB_state_idle; } static void kex_method_mlkem768x25519_cleanup( LIBSSH2_SESSION *session, struct key_exchange_state_low *key_state) { if(key_state->curve25519_public_key) { ssh2_explicit_zero(key_state->curve25519_public_key, SSH2_ED25519_KEY_LEN); SSH2_SAFEFREE(session, key_state->curve25519_public_key); } if(key_state->curve25519_private_key) { ssh2_explicit_zero(key_state->curve25519_private_key, SSH2_ED25519_KEY_LEN); SSH2_SAFEFREE(session, key_state->curve25519_private_key); } if(key_state->mlkem_public_key) { ssh2_explicit_zero(key_state->mlkem_public_key, key_state->mlkem_public_key_len); SSH2_SAFEFREE(session, key_state->mlkem_public_key); key_state->mlkem_public_key_len = 0; } if(key_state->mlkem_private_key) { ssh2_explicit_zero(key_state->mlkem_private_key, key_state->mlkem_private_key_len); SSH2_SAFEFREE(session, key_state->mlkem_private_key); key_state->mlkem_private_key_len = 0; } if(key_state->data) SSH2_SAFEFREE(session, key_state->data); key_state->state = ssh2_NB_state_idle; if(key_state->exchange_state.state != ssh2_NB_state_idle) kex_curve25519_exchange_state_cleanup(session, &key_state->exchange_state); } static int kex_mlkem768x25519_sha256( LIBSSH2_SESSION *session, unsigned char *data, size_t data_len, unsigned char public_t_key[SSH2_ED25519_KEY_LEN], unsigned char private_t_key[SSH2_ED25519_KEY_LEN], unsigned char public_pq_key[SSH2_MLKEM_768_PUBLIC_KEY_LEN], unsigned char private_pq_key[SSH2_MLKEM_768_PRIVATE_KEY_LEN], struct kmdhgGPshakex_state *exchange_state) { int ret = 0; int rc; if(exchange_state->state == ssh2_NB_state_idle) { /* Setup initial values */ exchange_state->k = ssh2_bn_init(); exchange_state->state = ssh2_NB_state_created; } if(exchange_state->state == ssh2_NB_state_created) { /* parse INIT reply data */ unsigned char *server_public_key; size_t public_t_key_len = SSH2_ED25519_KEY_LEN; size_t public_pq_key_len = SSH2_MLKEM_768_PUBLIC_KEY_LEN; unsigned char shared_secret[SSH2_MLKEM_SHARED_SECRET_LEN + SSH2_ED25519_KEY_LEN]; size_t server_public_key_len; struct string_buf buf; if(!data) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "Missing host key data"); goto clean_exit; } ret = kex_proc_hostkey(session, &buf, exchange_state, data, data_len); if(ret) goto clean_exit; /* server public key Q_S */ if(ssh2_get_string(&buf, &server_public_key, &server_public_key_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_PROTO, "Unexpected mlkem768x25519 server public key"); goto clean_exit; } if(server_public_key_len != SSH2_MLKEM_768_CIPHERTEXT + SSH2_ED25519_KEY_LEN) { ret = ssh2_err(session, LIBSSH2_ERROR_HOSTKEY_INIT, "Unexpected mlkem768x25519 server " "public key length"); goto clean_exit; } /* server signature */ if(ssh2_get_string(&buf, &exchange_state->h_sig, &exchange_state->h_sig_len)) { ret = ssh2_err(session, LIBSSH2_ERROR_HOSTKEY_INIT, "Unexpected mlkem768x25519 server sig length"); goto clean_exit; } exchange_state->k_value_len = SSH2_SHA256_DIG_LEN + 4; exchange_state->k_value = SSH2_ALLOC(session, exchange_state->k_value_len); if(!exchange_state->k_value) { ret = ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate buffer for K"); goto clean_exit; } ssh2_htonu32(exchange_state->k_value, SSH2_SHA256_DIG_LEN); /* Compute the ML-KEM shared secret */ rc = ssh2_mlkem_get_sk(shared_secret, 768, private_pq_key, server_public_key); if(rc) { ret = ssh2_err(session, LIBSSH2_ERROR_KEX_FAILURE, "Unable to create mlkem shared secret"); goto clean_exit; } /* Compute the x25519 shared secret K */ rc = ssh2_curve25519_gen_k(&exchange_state->k, private_t_key, server_public_key + SSH2_MLKEM_768_CIPHERTEXT); if(rc) { ret = ssh2_err(session, LIBSSH2_ERROR_KEX_FAILURE, "Unable to create curve25519 shared secret"); goto clean_exit; } if(ssh2_bn_to_bin(exchange_state->k, shared_secret + SSH2_MLKEM_SHARED_SECRET_LEN)) { ret = ssh2_err(session, LIBSSH2_ERROR_OUT_OF_BOUNDARY, "Cannot write shared secret"); goto clean_exit; } /* verify hash */ if(!ssh2_hash(SSH2_SHA256_ALG, shared_secret, SSH2_MLKEM_SHARED_SECRET_LEN + SSH2_ED25519_KEY_LEN, exchange_state->k_value + 4, SSH2_SHA256_DIG_LEN)) { ret = ssh2_err(session, LIBSSH2_ERROR_HASH_CALC, "kex: failed to calculate hash"); goto clean_exit; } ret = kex_method_ec_sha_hash_create_verify(session, exchange_state, public_t_key, public_t_key_len, public_pq_key, public_pq_key_len, server_public_key, server_public_key_len, SSH2_SHA256_ALG, SSH2_SHA256_DIG_LEN, "Unable to verify hostkey signature mlkem768x25519"); if(ret) goto clean_exit; exchange_state->c = SSH_MSG_NEWKEYS; exchange_state->state = ssh2_NB_state_sent; } if(exchange_state->state == ssh2_NB_state_sent) { rc = ssh2_transport_send(session, &exchange_state->c, 1, NULL, 0); if(rc == LIBSSH2_ERROR_EAGAIN) return rc; else if(rc) { ret = ssh2_err(session, rc, "Unable to send NEWKEYS message mlkem768x25519"); goto clean_exit; } exchange_state->state = ssh2_NB_state_sent2; } if(exchange_state->state == ssh2_NB_state_sent2) { ret = kex_finish(session, exchange_state, SSH2_SHA256_ALG, SSH2_SHA256_DIG_LEN); if(ret == LIBSSH2_ERROR_EAGAIN) return ret; } clean_exit: kex_mlkem768x25519_exchange_state_cleanup(session, exchange_state); return ret; } static int kex_method_mlkem768x25519_key_exchange( LIBSSH2_SESSION *session, struct key_exchange_state_low *key_state) { int ret = 0; int rc = 0; if(key_state->state == ssh2_NB_state_idle) { key_state->public_key_oct = NULL; key_state->state = ssh2_NB_state_created; } if(key_state->state == ssh2_NB_state_created) { int mlkem_size = 768; size_t mlkem_public_key_len = SSH2_MLKEM_768_PUBLIC_KEY_LEN; size_t mlkem_private_key_len = SSH2_MLKEM_768_PRIVATE_KEY_LEN; unsigned char *s = NULL; rc = ssh2_curve25519_new(session, &key_state->curve25519_public_key, &key_state->curve25519_private_key); if(rc) { ret = ssh2_err(session, rc, "Unable to create x25519 private key"); goto clean_exit; } rc = ssh2_mlkem_new(session, mlkem_size, &key_state->mlkem_public_key, &key_state->mlkem_private_key); if(rc) { ret = ssh2_err(session, rc, "Unable to create mlkem private key"); goto clean_exit; } key_state->mlkem_public_key_len = mlkem_public_key_len; key_state->mlkem_private_key_len = mlkem_private_key_len; key_state->request[0] = SSH2_MSG_KEX_ECDH_INIT; s = key_state->request + 1; ssh2_store_hybrid_str(&s, key_state->mlkem_public_key, mlkem_public_key_len, key_state->curve25519_public_key, SSH2_ED25519_KEY_LEN); key_state->request_len = mlkem_public_key_len + SSH2_ED25519_KEY_LEN + 5; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Initiating mlkem768x25519 SHA2")); key_state->state = ssh2_NB_state_sent; } if(key_state->state == ssh2_NB_state_sent) { rc = ssh2_transport_send(session, key_state->request, key_state->request_len, NULL, 0); if(rc == LIBSSH2_ERROR_EAGAIN) return rc; if(rc) { ret = ssh2_err(session, rc, "Unable to send ECDH_INIT"); goto clean_exit; } key_state->state = ssh2_NB_state_sent1; } if(key_state->state == ssh2_NB_state_sent1) { rc = ssh2_packet_require(session, SSH2_MSG_KEX_ECDH_REPLY, &key_state->data, &key_state->data_len, 0, NULL, 0, &key_state->req_state); if(rc == LIBSSH2_ERROR_EAGAIN) return rc; if(rc) { ret = ssh2_err(session, rc, "Timeout waiting for ECDH_REPLY reply"); goto clean_exit; } key_state->state = ssh2_NB_state_sent2; } if(key_state->state == ssh2_NB_state_sent2) { ret = kex_mlkem768x25519_sha256(session, key_state->data, key_state->data_len, key_state->curve25519_public_key, key_state->curve25519_private_key, key_state->mlkem_public_key, key_state->mlkem_private_key, &key_state->exchange_state); if(ret == LIBSSH2_ERROR_EAGAIN) return ret; } clean_exit: kex_method_mlkem768x25519_cleanup(session, key_state); return ret; } #endif /* LIBSSH2_MLKEM */ #endif /* LIBSSH2_ED25519 */ #define KEX_METHOD_FLAG_REQ_ENC_HOSTKEY 0x0001 #define KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY 0x0002 #ifdef LIBSSH2_KEX_SHA1_ENABLE static const struct kex_method kex_method_diffie_hellman_group1_sha1 = { "diffie-hellman-group1-sha1", kex_method_diffie_hellman_group1_sha1_key_exchange, kex_diffie_hellman_cleanup, KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; static const struct kex_method kex_method_diffie_hellman_group14_sha1 = { "diffie-hellman-group14-sha1", kex_method_diffie_hellman_group14_sha1_key_exchange, kex_diffie_hellman_cleanup, KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; #endif static const struct kex_method kex_method_diffie_hellman_group14_sha256 = { "diffie-hellman-group14-sha256", kex_method_diffie_hellman_group14_sha256_key_exchange, kex_diffie_hellman_cleanup, KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; static const struct kex_method kex_method_diffie_hellman_group16_sha512 = { "diffie-hellman-group16-sha512", kex_method_diffie_hellman_group16_sha512_key_exchange, kex_diffie_hellman_cleanup, KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; static const struct kex_method kex_method_diffie_hellman_group18_sha512 = { "diffie-hellman-group18-sha512", kex_method_diffie_hellman_group18_sha512_key_exchange, kex_diffie_hellman_cleanup, KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; #ifdef LIBSSH2_KEX_SHA1_ENABLE static const struct kex_method kex_method_diffie_hellman_group_exchange_sha1 = { "diffie-hellman-group-exchange-sha1", kex_method_diffie_hellman_group_exchange_sha1_key_exchange, kex_diffie_hellman_cleanup, KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; #endif static const struct kex_method kex_method_diffie_hellman_group_exchange_sha256 = { "diffie-hellman-group-exchange-sha256", kex_method_diffie_hellman_group_exchange_sha256_key_exchange, kex_diffie_hellman_cleanup, KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; #if LIBSSH2_ECDSA static const struct kex_method kex_method_ecdh_sha2_nistp256 = { "ecdh-sha2-nistp256", kex_method_ecdh_key_exchange, kex_method_ecdh_cleanup, KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; static const struct kex_method kex_method_ecdh_sha2_nistp384 = { "ecdh-sha2-nistp384", kex_method_ecdh_key_exchange, kex_method_ecdh_cleanup, KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; static const struct kex_method kex_method_ecdh_sha2_nistp521 = { "ecdh-sha2-nistp521", kex_method_ecdh_key_exchange, kex_method_ecdh_cleanup, KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; #if LIBSSH2_MLKEM static const struct kex_method kex_method_ssh_mlkem768_nistp256_sha256 = { "mlkem768nistp256-sha256", kex_method_mlkem_nistp_key_exchange, kex_method_mlkem_nistp_cleanup, KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; static const struct kex_method kex_method_ssh_mlkem1024_nistp384_sha384 = { "mlkem1024nistp384-sha384", kex_method_mlkem_nistp_key_exchange, kex_method_mlkem_nistp_cleanup, KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; #endif /* LIBSSH2_MLKEM */ #endif /* LIBSSH2_ECDSA */ #if LIBSSH2_ED25519 static const struct kex_method kex_method_ssh_curve25519_sha256_libssh = { "curve25519-sha256@libssh.org", kex_method_curve25519_key_exchange, kex_method_curve25519_cleanup, KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; static const struct kex_method kex_method_ssh_curve25519_sha256 = { "curve25519-sha256", kex_method_curve25519_key_exchange, kex_method_curve25519_cleanup, KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; #if LIBSSH2_MLKEM static const struct kex_method kex_method_ssh_mlkem768_x25519_sha256 = { "mlkem768x25519-sha256", kex_method_mlkem768x25519_key_exchange, kex_method_mlkem768x25519_cleanup, KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; #endif /* LIBSSH2_MLKEM */ #endif /* LIBSSH2_ED25519 */ /* this kex method signals that client can receive extensions * as described in https://datatracker.ietf.org/doc/html/rfc8308 */ static const struct kex_method kex_method_extension_negotiation = { "ext-info-c", NULL, NULL, 0, }; static const struct kex_method kex_method_strict_client_extension = { "kex-strict-c-v00@openssh.com", NULL, NULL, 0, }; static const struct kex_method *kex_methods[] = { #if LIBSSH2_MLKEM #if LIBSSH2_ED25519 &kex_method_ssh_mlkem768_x25519_sha256, #endif #if LIBSSH2_ECDSA &kex_method_ssh_mlkem768_nistp256_sha256, &kex_method_ssh_mlkem1024_nistp384_sha384, #endif #endif /* LIBSSH2_MLKEM */ #if LIBSSH2_ED25519 &kex_method_ssh_curve25519_sha256, &kex_method_ssh_curve25519_sha256_libssh, #endif #if LIBSSH2_ECDSA &kex_method_ecdh_sha2_nistp256, &kex_method_ecdh_sha2_nistp384, &kex_method_ecdh_sha2_nistp521, #endif &kex_method_diffie_hellman_group_exchange_sha256, &kex_method_diffie_hellman_group16_sha512, &kex_method_diffie_hellman_group18_sha512, &kex_method_diffie_hellman_group14_sha256, #ifdef LIBSSH2_KEX_SHA1_ENABLE &kex_method_diffie_hellman_group14_sha1, &kex_method_diffie_hellman_group1_sha1, &kex_method_diffie_hellman_group_exchange_sha1, #endif &kex_method_extension_negotiation, &kex_method_strict_client_extension, NULL }; struct common_method { const char *name; }; /* * Calculate the length of a particular method list's resulting string * Includes SUM(strlen() of each individual method plus 1 (for comma)) - 1 * (because the last comma is not used) * Another sign of bad coding practices gone mad. Pretend you do not see this. */ static size_t kex_method_strlen(const struct 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; } /* * Generate formatted preference list in buf */ static uint32_t kex_method_list(unsigned char *buf, uint32_t list_strlen, const struct common_method **method) { ssh2_htonu32(buf, list_strlen); buf += 4; if(!method || !*method) return 4; while(*method && (*method)->name) { uint32_t mlen = (uint32_t)strlen((*method)->name); memcpy(buf, (*method)->name, mlen); buf += mlen; *(buf++) = ','; method++; } return list_strlen + 4; } #define KEX_METHOD_PREFS_LEN(prefvar, defaultvar) \ (uint32_t)((prefvar) ? strlen(prefvar) : \ kex_method_strlen((const struct common_method **)(defaultvar))) #define KEX_METHOD_PREFS_STR(buf, prefvarlen, prefvar, defaultvar) \ do { \ if(prefvar) { \ ssh2_htonu32(buf, prefvarlen); \ (buf) += 4; \ memcpy(buf, prefvar, prefvarlen); \ (buf) += (prefvarlen); \ } \ else \ (buf) += kex_method_list(buf, prefvarlen, \ (const struct common_method **)(defaultvar)); \ } while(0) /* * Send SSH_MSG_KEXINIT packet */ static int kex_init(LIBSSH2_SESSION *session) { /* 62 = packet_type(1) + cookie(16) + first_packet_follows(1) + reserved(4) + length longs(40) */ size_t data_len = 62; unsigned char *data, *s; int rc; if(session->kexinit_state == ssh2_NB_state_idle) { uint32_t kex_len, hostkey_len; uint32_t crypt_cs_len, crypt_sc_len; uint32_t comp_cs_len, comp_sc_len; uint32_t mac_cs_len, mac_sc_len; uint32_t lang_cs_len, lang_sc_len; kex_len = KEX_METHOD_PREFS_LEN(session->kex_prefs, kex_methods); hostkey_len = KEX_METHOD_PREFS_LEN(session->hostkey_prefs, ssh2_hostkey_methods()); crypt_cs_len = KEX_METHOD_PREFS_LEN(session->local.crypt_prefs, ssh2_crypt_methods()); crypt_sc_len = KEX_METHOD_PREFS_LEN(session->remote.crypt_prefs, ssh2_crypt_methods()); mac_cs_len = KEX_METHOD_PREFS_LEN(session->local.mac_prefs, ssh2_mac_methods()); mac_sc_len = KEX_METHOD_PREFS_LEN(session->remote.mac_prefs, ssh2_mac_methods()); comp_cs_len = KEX_METHOD_PREFS_LEN(session->local.comp_prefs, ssh2_comp_methods(session)); comp_sc_len = KEX_METHOD_PREFS_LEN(session->remote.comp_prefs, ssh2_comp_methods(session)); lang_cs_len = KEX_METHOD_PREFS_LEN(session->local.lang_prefs, NULL); lang_sc_len = KEX_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 = SSH2_ALLOC(session, data_len); if(!data) return ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory"); *(s++) = SSH_MSG_KEXINIT; if(ssh2_random(s, 16)) { SSH2_FREE(session, data); return ssh2_err(session, LIBSSH2_ERROR_RANDGEN, "Unable to get random bytes for KEXINIT cookie"); } s += 16; /* Enumerating through these lists twice is probably (certainly?) inefficient from a CPU standpoint, but it saves multiple malloc/realloc calls */ KEX_METHOD_PREFS_STR(s, kex_len, session->kex_prefs, kex_methods); KEX_METHOD_PREFS_STR(s, hostkey_len, session->hostkey_prefs, ssh2_hostkey_methods()); KEX_METHOD_PREFS_STR(s, crypt_cs_len, session->local.crypt_prefs, ssh2_crypt_methods()); KEX_METHOD_PREFS_STR(s, crypt_sc_len, session->remote.crypt_prefs, ssh2_crypt_methods()); KEX_METHOD_PREFS_STR(s, mac_cs_len, session->local.mac_prefs, ssh2_mac_methods()); KEX_METHOD_PREFS_STR(s, mac_sc_len, session->remote.mac_prefs, ssh2_mac_methods()); KEX_METHOD_PREFS_STR(s, comp_cs_len, session->local.comp_prefs, ssh2_comp_methods(session)); KEX_METHOD_PREFS_STR(s, comp_sc_len, session->remote.comp_prefs, ssh2_comp_methods(session)); KEX_METHOD_PREFS_STR(s, lang_cs_len, session->local.lang_prefs, NULL); KEX_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 */ ssh2_htonu32(s, 0); #ifdef LIBSSH2DEBUG { unsigned char *p = data + 21; /* type(1) + cookie(16) + len(4) */ ssh2_deb((session, LIBSSH2_TRACE_KEX, "Sent KEX: %.*s", (int)kex_len, p)); p += kex_len + 4; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Sent HOSTKEY: %.*s", (int)hostkey_len, p)); p += hostkey_len + 4; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Sent CRYPT_CS: %.*s", (int)crypt_cs_len, p)); p += crypt_cs_len + 4; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Sent CRYPT_SC: %.*s", (int)crypt_sc_len, p)); p += crypt_sc_len + 4; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Sent MAC_CS: %.*s", (int)mac_cs_len, p)); p += mac_cs_len + 4; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Sent MAC_SC: %.*s", (int)mac_sc_len, p)); p += mac_sc_len + 4; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Sent COMP_CS: %.*s", (int)comp_cs_len, p)); p += comp_cs_len + 4; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Sent COMP_SC: %.*s", (int)comp_sc_len, p)); p += comp_sc_len + 4; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Sent LANG_CS: %.*s", (int)lang_cs_len, p)); p += lang_cs_len + 4; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Sent LANG_SC: %.*s", (int)lang_sc_len, p)); p += lang_sc_len; ssh2_deb((session, LIBSSH2_TRACE_KEX, "Sent first_kex_packet_follows: %02x", p[0])); } #endif /* LIBSSH2DEBUG */ session->kexinit_state = ssh2_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 = ssh2_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) { SSH2_FREE(session, data); session->kexinit_state = ssh2_NB_state_idle; return ssh2_err(session, rc, "Unable to send KEXINIT packet to remote host"); } if(session->local.kexinit) SSH2_FREE(session, session->local.kexinit); session->local.kexinit = data; session->local.kexinit_len = data_len; session->kexinit_state = ssh2_NB_state_idle; return 0; } /* * Kex specific variant of strstr() * Needle must be preceded by BOL or ',', and followed by ',' or EOL */ const char *ssh2_kex_agree_instr(const char *haystack, size_t haystack_len, const char *needle, size_t needle_len) { const char *s; const char *end_haystack; size_t left; if(!haystack || !needle) return NULL; /* Haystack too short to bother trying */ if(haystack_len < needle_len || needle_len == 0) return NULL; s = haystack; end_haystack = &haystack[haystack_len]; left = end_haystack - s; /* Needle at start of haystack */ if(!strncmp(haystack, needle, needle_len) && (needle_len == haystack_len || haystack[needle_len] == ',')) return haystack; /* Search until we run out of commas or we run out of haystack, whichever comes first */ /* !checksrc! disable EQUALSNULL 1 */ while((s = memchr(s, ',', left)) != NULL) { /* Advance buffer past comma if we can */ left = end_haystack - s; if(left >= 1 && left <= haystack_len && left > needle_len) { s++; left--; } else return NULL; /* Needle at X position */ if(!strncmp(s, needle, needle_len) && (((s - haystack) + needle_len) == haystack_len || s[needle_len] == ',')) return s; } return NULL; } static const struct common_method *kex_get_method_by_name( const char *name, size_t name_len, const struct common_method **methodlist) { while(*methodlist) { if(strlen((*methodlist)->name) == name_len && !strncmp((*methodlist)->name, name, name_len)) return *methodlist; methodlist++; } return NULL; } /* * Agree on a Hostkey which works with this kex */ static int kex_agree_hostkey(LIBSSH2_SESSION *session, size_t kex_flags, const char *hostkey, size_t hostkey_len) { const struct hostkey_method **hostkeyp = ssh2_hostkey_methods(); const char *s; if(session->hostkey_prefs) { s = session->hostkey_prefs; while(s && *s) { const char *p = strchr(s, ','); size_t method_len = p ? (size_t)(p - s) : strlen(s); if(ssh2_kex_agree_instr(hostkey, hostkey_len, s, method_len)) { const struct hostkey_method *method = (const struct hostkey_method *)kex_get_method_by_name( s, method_len, (const struct common_method **)hostkeyp); if(!method) return -1; /* Invalid method -- Should never be reached */ /* OK so far, but does it suit our purposes? (Encrypting vs Signing) */ if((kex_flags & KEX_METHOD_FLAG_REQ_ENC_HOSTKEY) == 0 || method->encrypt) { /* Either this hostkey can do encryption or this kex does not require it */ if((kex_flags & KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY) == 0 || method->sig_verify) { /* Either this hostkey can do signing or this kex does not require it */ session->hostkey = method; return 0; } } } s = p ? p + 1 : NULL; } return -1; } while(hostkeyp && *hostkeyp && (*hostkeyp)->name) { s = ssh2_kex_agree_instr(hostkey, hostkey_len, (*hostkeyp)->name, strlen((*hostkeyp)->name)); if(s) { /* OK so far, but does it suit our purposes? (Encrypting vs Signing) */ if((kex_flags & KEX_METHOD_FLAG_REQ_ENC_HOSTKEY) == 0 || (*hostkeyp)->encrypt) { /* Either this hostkey can do encryption or this kex does not require it */ if((kex_flags & KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY) == 0 || (*hostkeyp)->sig_verify) { /* Either this hostkey can do signing or this kex does not require it */ session->hostkey = *hostkeyp; return 0; } } } hostkeyp++; } return -1; } /* * Agree on a Key Exchange method and a hostkey encoding type */ static int kex_agree_kex_hostkey(LIBSSH2_SESSION *session, const char *kex, size_t kex_len, const char *hostkey, size_t hostkey_len) { static const char strict[] = "kex-strict-s-v00@openssh.com"; const struct kex_method **kexp = kex_methods; const char *s; if(ssh2_kex_agree_instr(kex, kex_len, strict, sizeof(strict) - 1)) session->kex_strict = 1; if(session->kex_prefs) { s = session->kex_prefs; while(s && *s) { const char *q, *p = strchr(s, ','); size_t method_len = p ? (size_t)(p - s) : strlen(s); q = ssh2_kex_agree_instr(kex, kex_len, s, method_len); if(q) { const struct kex_method *method = (const struct kex_method *)kex_get_method_by_name( s, method_len, (const struct common_method **)kexp); if(!method) return -1; /* Invalid method -- Should never be reached */ /* We have 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 = ssh2_kex_agree_instr(kex, kex_len, (*kexp)->name, strlen((*kexp)->name)); if(s) { /* We have 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; } /* * Agree on a cipher algo */ static int kex_agree_crypt(LIBSSH2_SESSION *session, struct endpoint_data *endpoint, const char *crypt, size_t crypt_len) { const struct crypt_method **cryptp = ssh2_crypt_methods(); const char *s; (void)session; if(endpoint->crypt_prefs) { s = endpoint->crypt_prefs; while(s && *s) { const char *p = strchr(s, ','); size_t method_len = p ? (size_t)(p - s) : strlen(s); if(ssh2_kex_agree_instr(crypt, crypt_len, s, method_len)) { const struct crypt_method *method = (const struct crypt_method *)kex_get_method_by_name( s, method_len, (const struct common_method **)cryptp); if(!method) return -1; /* Invalid method -- Should never be reached */ endpoint->crypt = method; return 0; } s = p ? p + 1 : NULL; } return -1; } while(*cryptp && (*cryptp)->name) { s = ssh2_kex_agree_instr(crypt, crypt_len, (*cryptp)->name, strlen((*cryptp)->name)); if(s) { endpoint->crypt = *cryptp; return 0; } cryptp++; } return -1; } /* * Agree on a message authentication hash */ static int kex_agree_mac(LIBSSH2_SESSION *session, struct endpoint_data *endpoint, const char *mac, size_t mac_len) { const struct mac_method **macp = ssh2_mac_methods(); const struct mac_method *override; const char *s; (void)session; override = ssh2_mac_override(endpoint->crypt); if(override) { /* This crypto method has its own hmac method built-in, so a separate * negotiation (and use) of a separate hmac method is unnecessary */ endpoint->mac = override; return 0; } if(endpoint->mac_prefs) { s = endpoint->mac_prefs; while(s && *s) { const char *p = strchr(s, ','); size_t method_len = p ? (size_t)(p - s) : strlen(s); if(ssh2_kex_agree_instr(mac, mac_len, s, method_len)) { const struct mac_method *method = (const struct mac_method *)kex_get_method_by_name( s, method_len, (const struct common_method **)macp); if(!method) return -1; /* Invalid method -- Should never be reached */ endpoint->mac = method; return 0; } s = p ? p + 1 : NULL; } return -1; } while(*macp && (*macp)->name) { s = ssh2_kex_agree_instr(mac, mac_len, (*macp)->name, strlen((*macp)->name)); if(s) { endpoint->mac = *macp; return 0; } macp++; } return -1; } /* * Agree on a compression scheme */ static int kex_agree_comp(LIBSSH2_SESSION *session, struct endpoint_data *endpoint, const char *comp, size_t comp_len) { const struct comp_method **compp = ssh2_comp_methods(session); const char *s; (void)session; if(endpoint->comp_prefs) { s = endpoint->comp_prefs; while(s && *s) { const char *p = strchr(s, ','); size_t method_len = p ? (size_t)(p - s) : strlen(s); if(ssh2_kex_agree_instr(comp, comp_len, s, method_len)) { const struct comp_method *method = (const struct comp_method *)kex_get_method_by_name( s, method_len, (const struct common_method **)compp); if(!method) return -1; /* Invalid method -- Should never be reached */ endpoint->comp = method; return 0; } s = p ? p + 1 : NULL; } return -1; } while(*compp && (*compp)->name) { s = ssh2_kex_agree_instr(comp, comp_len, (*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" */ /* * Decide which specific method to use of the methods offered by each party */ static int kex_agree_methods(LIBSSH2_SESSION *session, unsigned char *data, size_t data_len) { char *kex, *hostkey, *crypt_cs, *crypt_sc, *comp_cs, *comp_sc, *mac_cs, *mac_sc, *tmp; 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; struct string_buf buf; if(data_len < 17) return -1; buf.data = data; buf.len = data_len; buf.dataptr = buf.data; buf.dataptr++; /* advance past packet type */ /* Skip cookie, do not worry, it is preserved in the kexinit field */ buf.dataptr += 16; /* Locate each string */ if(ssh2_get_chars(&buf, &kex, &kex_len)) return -1; if(ssh2_get_chars(&buf, &hostkey, &hostkey_len)) return -1; if(ssh2_get_chars(&buf, &crypt_cs, &crypt_cs_len)) return -1; if(ssh2_get_chars(&buf, &crypt_sc, &crypt_sc_len)) return -1; if(ssh2_get_chars(&buf, &mac_cs, &mac_cs_len)) return -1; if(ssh2_get_chars(&buf, &mac_sc, &mac_sc_len)) return -1; if(ssh2_get_chars(&buf, &comp_cs, &comp_cs_len)) return -1; if(ssh2_get_chars(&buf, &comp_sc, &comp_sc_len)) return -1; /* read lang_cs and lang_sc but are unused */ if(ssh2_get_chars(&buf, &tmp, NULL)) return -1; if(ssh2_get_chars(&buf, &tmp, NULL)) 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 is reset to zero so that it is not ignored */ if(ssh2_check_length(&buf, 1)) session->burn_optimistic_kexinit = *(buf.dataptr++); else return -1; /* Next uint32 in packet is all zeros (reserved) */ 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; /* This must happen after kex_agree_crypt since some MACs depend on the negotiated crypto method */ 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(kex_agree_lang(session, &session->local, lang_cs, lang_cs_len) || kex_agree_lang(session, &session->remote, lang_sc, lang_sc_len)) return -1; #endif ssh2_deb((session, LIBSSH2_TRACE_KEX, "Agreed on KEX method: %s", session->kex->name)); ssh2_deb((session, LIBSSH2_TRACE_KEX, "Agreed on HOSTKEY method: %s", session->hostkey->name)); ssh2_deb((session, LIBSSH2_TRACE_KEX, "Agreed on CRYPT_CS method: %s", session->local.crypt->name)); ssh2_deb((session, LIBSSH2_TRACE_KEX, "Agreed on CRYPT_SC method: %s", session->remote.crypt->name)); ssh2_deb((session, LIBSSH2_TRACE_KEX, "Agreed on MAC_CS method: %s", session->local.mac->name)); ssh2_deb((session, LIBSSH2_TRACE_KEX, "Agreed on MAC_SC method: %s", session->remote.mac->name)); ssh2_deb((session, LIBSSH2_TRACE_KEX, "Agreed on COMP_CS method: %s", session->local.comp->name)); ssh2_deb((session, LIBSSH2_TRACE_KEX, "Agreed on COMP_SC method: %s", session->remote.comp->name)); return 0; } /* * Exchange keys * Returns 0 on success, non-zero on failure * * Returns some errors without ssh2_err() */ int ssh2_kex_exchange(LIBSSH2_SESSION *session, int reexchange, struct key_exchange_state *key_state) { int rc = 0; int retcode; session->state |= SSH2_STATE_KEX_ACTIVE; if(key_state->state == ssh2_NB_state_idle) { /* Prevent loop in packet_add() */ session->state |= SSH2_STATE_EXCHANGING_KEYS; if(reexchange) { if(session->kex && session->kex->cleanup) session->kex->cleanup(session, &key_state->key_state_low); session->kex = NULL; if(session->hostkey && session->hostkey->dtor) session->hostkey->dtor(session, &session->server_hostkey_abstract); session->hostkey = NULL; } key_state->state = ssh2_NB_state_created; } if(!session->kex || !session->hostkey) { if(key_state->state == ssh2_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 = ssh2_NB_state_sent; } if(key_state->state == ssh2_NB_state_sent) { retcode = kex_init(session); if(retcode == LIBSSH2_ERROR_EAGAIN) { session->state &= ~SSH2_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 = ssh2_NB_state_idle; session->state &= ~SSH2_STATE_INITIAL_KEX; session->state &= ~SSH2_STATE_KEX_ACTIVE; session->state &= ~SSH2_STATE_EXCHANGING_KEYS; return LIBSSH2_ERROR_KEX_FAILURE; } key_state->state = ssh2_NB_state_sent1; } if(key_state->state == ssh2_NB_state_sent1) { retcode = ssh2_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 &= ~SSH2_STATE_KEX_ACTIVE; return retcode; } else if(retcode) { if(session->local.kexinit) SSH2_FREE(session, session->local.kexinit); session->local.kexinit = key_state->oldlocal; session->local.kexinit_len = key_state->oldlocal_len; key_state->state = ssh2_NB_state_idle; session->state &= ~SSH2_STATE_INITIAL_KEX; session->state &= ~SSH2_STATE_KEX_ACTIVE; session->state &= ~SSH2_STATE_EXCHANGING_KEYS; return LIBSSH2_ERROR_KEX_FAILURE; } if(session->remote.kexinit) SSH2_FREE(session, session->remote.kexinit); session->remote.kexinit = key_state->data; session->remote.kexinit_len = key_state->data_len; key_state->data = NULL; if(kex_agree_methods(session, session->remote.kexinit, session->remote.kexinit_len)) rc = LIBSSH2_ERROR_KEX_FAILURE; key_state->state = ssh2_NB_state_sent2; } } else key_state->state = ssh2_NB_state_sent2; if(rc == 0 && session->kex && session->kex->exchange_keys) { if(key_state->state == ssh2_NB_state_sent2) { retcode = session->kex->exchange_keys(session, &key_state->key_state_low); if(retcode == LIBSSH2_ERROR_EAGAIN) { session->state &= ~SSH2_STATE_KEX_ACTIVE; return retcode; } else if(retcode) rc = ssh2_err(session, LIBSSH2_ERROR_KEY_EXCHANGE_FAILURE, "Unrecoverable error exchanging keys"); } } /* Done with kexinit buffers */ if(session->local.kexinit) SSH2_SAFEFREE(session, session->local.kexinit); if(session->remote.kexinit) SSH2_SAFEFREE(session, session->remote.kexinit); session->state &= ~SSH2_STATE_INITIAL_KEX; session->state &= ~SSH2_STATE_KEX_ACTIVE; session->state &= ~SSH2_STATE_EXCHANGING_KEYS; key_state->state = ssh2_NB_state_idle; return rc; } /* * Set preferred method */ int libssh2_session_method_pref(LIBSSH2_SESSION *session, int method_type, const char *prefs) { char **prefvar, *s, *newprefs; char *tmpprefs = NULL; size_t prefs_len; const struct common_method **mlist; if(!session || !prefs) return LIBSSH2_ERROR_BAD_USE; prefs_len = strlen(prefs); switch(method_type) { case LIBSSH2_METHOD_KEX: prefvar = &session->kex_prefs; mlist = (const struct common_method **)kex_methods; break; case LIBSSH2_METHOD_HOSTKEY: prefvar = &session->hostkey_prefs; mlist = (const struct common_method **)ssh2_hostkey_methods(); break; case LIBSSH2_METHOD_CRYPT_CS: prefvar = &session->local.crypt_prefs; mlist = (const struct common_method **)ssh2_crypt_methods(); break; case LIBSSH2_METHOD_CRYPT_SC: prefvar = &session->remote.crypt_prefs; mlist = (const struct common_method **)ssh2_crypt_methods(); break; case LIBSSH2_METHOD_MAC_CS: prefvar = &session->local.mac_prefs; mlist = (const struct common_method **)ssh2_mac_methods(); break; case LIBSSH2_METHOD_MAC_SC: prefvar = &session->remote.mac_prefs; mlist = (const struct common_method **)ssh2_mac_methods(); break; case LIBSSH2_METHOD_COMP_CS: prefvar = &session->local.comp_prefs; mlist = (const struct common_method **)ssh2_comp_methods(session); break; case LIBSSH2_METHOD_COMP_SC: prefvar = &session->remote.comp_prefs; mlist = (const struct common_method **)ssh2_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; case LIBSSH2_METHOD_SIGN_ALGO: prefvar = &session->sign_algo_prefs; mlist = NULL; break; default: return ssh2_err(session, LIBSSH2_ERROR_INVAL, "Invalid parameter specified for method_type"); } s = newprefs = SSH2_ALLOC(session, prefs_len + 1); if(!newprefs) { if(tmpprefs) SSH2_FREE(session, tmpprefs); return ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Error allocated space for method preferences"); } memcpy(s, prefs, prefs_len + 1); while(s && *s && mlist) { char *p = strchr(s, ','); size_t method_len = p ? (size_t)(p - s) : strlen(s); if(!kex_get_method_by_name(s, method_len, mlist)) { /* Strip out unsupported method */ if(p) memmove(s, p + 1, strlen(s) - method_len); else { if(s > newprefs) *(--s) = '\0'; else *s = '\0'; } } else s = p ? (p + 1) : NULL; } if(tmpprefs) SSH2_FREE(session, tmpprefs); if(!*newprefs) { SSH2_FREE(session, newprefs); return ssh2_err(session, LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, "The requested method(s) are not currently supported"); } /* add method kex extension to the start of the user list */ if(method_type == LIBSSH2_METHOD_KEX) { static const char kex_extensions[] = "ext-info-c,kex-strict-c-v00@openssh.com,"; size_t kex_extensions_len = sizeof(kex_extensions) - 1; size_t tmp_len = kex_extensions_len + strlen(newprefs); tmpprefs = SSH2_ALLOC(session, tmp_len + 1); if(!tmpprefs) return ssh2_err(session, LIBSSH2_ERROR_ALLOC, "Error allocated space for kex method" " preferences"); memcpy(tmpprefs, kex_extensions, kex_extensions_len); memcpy(tmpprefs + kex_extensions_len, newprefs, strlen(newprefs)); tmpprefs[tmp_len] = '\0'; SSH2_FREE(session, newprefs); newprefs = tmpprefs; } if(*prefvar) SSH2_FREE(session, *prefvar); *prefvar = newprefs; return 0; } /* * returns a number of returned algorithms (a positive number) on success, * a negative number on failure */ int libssh2_session_supported_algs(LIBSSH2_SESSION *session, int method_type, const char ***algs) { unsigned int i; unsigned int j; unsigned int ialg; const struct common_method **mlist; if(!session) return LIBSSH2_ERROR_BAD_USE; /* to prevent coredumps due to dereferencing of NULL */ if(!algs) return ssh2_err(session, LIBSSH2_ERROR_BAD_USE, "algs must not be NULL"); switch(method_type) { case LIBSSH2_METHOD_KEX: mlist = (const struct common_method **)kex_methods; break; case LIBSSH2_METHOD_HOSTKEY: mlist = (const struct common_method **)ssh2_hostkey_methods(); break; case LIBSSH2_METHOD_CRYPT_CS: case LIBSSH2_METHOD_CRYPT_SC: mlist = (const struct common_method **)ssh2_crypt_methods(); break; case LIBSSH2_METHOD_MAC_CS: case LIBSSH2_METHOD_MAC_SC: mlist = (const struct common_method **)ssh2_mac_methods(); break; case LIBSSH2_METHOD_COMP_CS: case LIBSSH2_METHOD_COMP_SC: mlist = (const struct common_method **)ssh2_comp_methods(session); break; case LIBSSH2_METHOD_SIGN_ALGO: /* no built-in supported list due to backend support */ mlist = NULL; break; default: return ssh2_err(session, LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, "Unrecognized method type"); } /* switch */ /* weird situation */ if(!mlist) return ssh2_err(session, LIBSSH2_ERROR_INVAL, "No algorithm found"); /* mlist is looped through twice. The first time to find the number of supported algorithms (needed to allocate the proper size of array) and the second time to actually copy the pointers. Typically this function it not called often (typically at the beginning of a session) and the number of algorithms (i.e. number of iterations in one loop) are not expected to become high (typically it does not exceed 20) for quite a long time. Thus double looping really should not 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; mlist[i]; i++) { /* do not count fields with NULL name */ if(mlist[i]->name) ialg++; } /* weird situation, no algorithm found */ if(ialg == 0) return ssh2_err(session, LIBSSH2_ERROR_INVAL, "No algorithm found"); /* allocate buffer */ *algs = SSH2_ALLOC(session, ialg * sizeof(const char *)); if(!*algs) return ssh2_err(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; mlist[i] && j < ialg; i++) { if(!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) { SSH2_FREE(session, SSH2_UNCONST(*algs)); /* deallocate buffer */ *algs = NULL; return ssh2_err(session, LIBSSH2_ERROR_BAD_USE, "Internal error"); } return ialg; }