From 43a4f86b6e14a907b3a298d10d5cd7efb59f6a09 Mon Sep 17 00:00:00 2001 From: Aris Adamantiadis Date: Sat, 27 Oct 2018 23:54:56 +0200 Subject: dh: move unrelated functions out of dh.c Signed-off-by: Aris Adamantiadis Reviewed-by: Jakub Jelen Reviewed-by: Andreas Schneider --- include/libssh/dh.h | 6 - include/libssh/kex.h | 5 + src/dh.c | 708 --------------------------------------------------- src/kex.c | 490 +++++++++++++++++++++++++++++++++++ src/misc.c | 57 +++++ src/session.c | 234 +++++++++++++++++ 6 files changed, 786 insertions(+), 714 deletions(-) diff --git a/include/libssh/dh.h b/include/libssh/dh.h index 3d7312c5..adc8c48f 100644 --- a/include/libssh/dh.h +++ b/include/libssh/dh.h @@ -51,12 +51,6 @@ ssh_key ssh_dh_get_next_server_publickey(ssh_session session); int ssh_dh_get_next_server_publickey_blob(ssh_session session, ssh_string *pubkey_blob); -int ssh_make_sessionid(ssh_session session); -/* add data for the final cookie */ -int ssh_hashbufin_add_cookie(ssh_session session, unsigned char *cookie); -int ssh_hashbufout_add_cookie(ssh_session session); -int ssh_generate_session_keys(ssh_session session); - #ifdef WITH_SERVER void ssh_server_dh_init(ssh_session session); #endif /* WITH_SERVER */ diff --git a/include/libssh/kex.h b/include/libssh/kex.h index 644a3956..19a67c81 100644 --- a/include/libssh/kex.h +++ b/include/libssh/kex.h @@ -48,5 +48,10 @@ const char *ssh_kex_get_description(uint32_t algo); char *ssh_client_select_hostkeys(ssh_session session); int ssh_send_rekex(ssh_session session); int server_set_kex(ssh_session session); +int ssh_make_sessionid(ssh_session session); +/* add data for the final cookie */ +int ssh_hashbufin_add_cookie(ssh_session session, unsigned char *cookie); +int ssh_hashbufout_add_cookie(ssh_session session); +int ssh_generate_session_keys(ssh_session session); #endif /* KEX_H_ */ diff --git a/src/dh.c b/src/dh.c index 6158b465..3fb7e6c4 100644 --- a/src/dh.c +++ b/src/dh.c @@ -921,531 +921,12 @@ error: #endif /* WITH_SERVER */ -int ssh_make_sessionid(ssh_session session) { - ssh_string num = NULL; - ssh_buffer server_hash = NULL; - ssh_buffer client_hash = NULL; - ssh_buffer buf = NULL; - ssh_string server_pubkey_blob = NULL; - int rc = SSH_ERROR; - - buf = ssh_buffer_new(); - if (buf == NULL) { - return rc; - } - - rc = ssh_buffer_pack(buf, - "ss", - session->clientbanner, - session->serverbanner); - if (rc == SSH_ERROR) { - goto error; - } - - if (session->client) { - server_hash = session->in_hashbuf; - client_hash = session->out_hashbuf; - } else { - server_hash = session->out_hashbuf; - client_hash = session->in_hashbuf; - } - - /* - * Handle the two final fields for the KEXINIT message (RFC 4253 7.1): - * - * boolean first_kex_packet_follows - * uint32 0 (reserved for future extension) - */ - rc = ssh_buffer_add_u8(server_hash, 0); - if (rc < 0) { - goto error; - } - rc = ssh_buffer_add_u32(server_hash, 0); - if (rc < 0) { - goto error; - } - - /* These fields are handled for the server case in ssh_packet_kexinit. */ - if (session->client) { - rc = ssh_buffer_add_u8(client_hash, 0); - if (rc < 0) { - goto error; - } - rc = ssh_buffer_add_u32(client_hash, 0); - if (rc < 0) { - goto error; - } - } - - rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); - if (rc != SSH_OK) { - goto error; - } - - rc = ssh_buffer_pack(buf, - "dPdPS", - ssh_buffer_get_len(client_hash), - ssh_buffer_get_len(client_hash), - ssh_buffer_get(client_hash), - ssh_buffer_get_len(server_hash), - ssh_buffer_get_len(server_hash), - ssh_buffer_get(server_hash), - server_pubkey_blob); - ssh_string_free(server_pubkey_blob); - if(rc != SSH_OK){ - goto error; - } - - switch(session->next_crypto->kex_type) { - case SSH_KEX_DH_GROUP1_SHA1: - case SSH_KEX_DH_GROUP14_SHA1: - case SSH_KEX_DH_GROUP16_SHA512: - case SSH_KEX_DH_GROUP18_SHA512: - rc = ssh_buffer_pack(buf, - "BB", - session->next_crypto->e, - session->next_crypto->f); - if (rc != SSH_OK) { - goto error; - } - break; -#ifdef HAVE_ECDH - case SSH_KEX_ECDH_SHA2_NISTP256: - case SSH_KEX_ECDH_SHA2_NISTP384: - case SSH_KEX_ECDH_SHA2_NISTP521: - if (session->next_crypto->ecdh_client_pubkey == NULL || - session->next_crypto->ecdh_server_pubkey == NULL) { - SSH_LOG(SSH_LOG_WARNING, "ECDH parameted missing"); - goto error; - } - rc = ssh_buffer_pack(buf, - "SS", - session->next_crypto->ecdh_client_pubkey, - session->next_crypto->ecdh_server_pubkey); - if (rc != SSH_OK) { - goto error; - } - break; -#endif -#ifdef HAVE_CURVE25519 - case SSH_KEX_CURVE25519_SHA256: - case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: - rc = ssh_buffer_pack(buf, - "dPdP", - CURVE25519_PUBKEY_SIZE, - (size_t)CURVE25519_PUBKEY_SIZE, session->next_crypto->curve25519_client_pubkey, - CURVE25519_PUBKEY_SIZE, - (size_t)CURVE25519_PUBKEY_SIZE, session->next_crypto->curve25519_server_pubkey); - - if (rc != SSH_OK) { - goto error; - } - break; -#endif - } - rc = ssh_buffer_pack(buf, "B", session->next_crypto->k); - if (rc != SSH_OK) { - goto error; - } - -#ifdef DEBUG_CRYPTO - ssh_print_hexa("hash buffer", ssh_buffer_get(buf), ssh_buffer_get_len(buf)); -#endif - - switch (session->next_crypto->kex_type) { - case SSH_KEX_DH_GROUP1_SHA1: - case SSH_KEX_DH_GROUP14_SHA1: - session->next_crypto->digest_len = SHA_DIGEST_LENGTH; - session->next_crypto->mac_type = SSH_MAC_SHA1; - session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); - if (session->next_crypto->secret_hash == NULL) { - ssh_set_error_oom(session); - goto error; - } - sha1(ssh_buffer_get(buf), ssh_buffer_get_len(buf), - session->next_crypto->secret_hash); - break; - case SSH_KEX_ECDH_SHA2_NISTP256: - case SSH_KEX_CURVE25519_SHA256: - case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: - session->next_crypto->digest_len = SHA256_DIGEST_LENGTH; - session->next_crypto->mac_type = SSH_MAC_SHA256; - session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); - if (session->next_crypto->secret_hash == NULL) { - ssh_set_error_oom(session); - goto error; - } - sha256(ssh_buffer_get(buf), ssh_buffer_get_len(buf), - session->next_crypto->secret_hash); - break; - case SSH_KEX_ECDH_SHA2_NISTP384: - session->next_crypto->digest_len = SHA384_DIGEST_LENGTH; - session->next_crypto->mac_type = SSH_MAC_SHA384; - session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); - if (session->next_crypto->secret_hash == NULL) { - ssh_set_error_oom(session); - goto error; - } - sha384(ssh_buffer_get(buf), ssh_buffer_get_len(buf), - session->next_crypto->secret_hash); - break; - case SSH_KEX_DH_GROUP16_SHA512: - case SSH_KEX_DH_GROUP18_SHA512: - case SSH_KEX_ECDH_SHA2_NISTP521: - session->next_crypto->digest_len = SHA512_DIGEST_LENGTH; - session->next_crypto->mac_type = SSH_MAC_SHA512; - session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); - if (session->next_crypto->secret_hash == NULL) { - ssh_set_error_oom(session); - goto error; - } - sha512(ssh_buffer_get(buf), - ssh_buffer_get_len(buf), - session->next_crypto->secret_hash); - break; - } - /* During the first kex, secret hash and session ID are equal. However, after - * a key re-exchange, a new secret hash is calculated. This hash will not replace - * but complement existing session id. - */ - if (!session->next_crypto->session_id) { - session->next_crypto->session_id = malloc(session->next_crypto->digest_len); - if (session->next_crypto->session_id == NULL) { - ssh_set_error_oom(session); - goto error; - } - memcpy(session->next_crypto->session_id, session->next_crypto->secret_hash, - session->next_crypto->digest_len); - } -#ifdef DEBUG_CRYPTO - printf("Session hash: \n"); - ssh_print_hexa("secret hash", session->next_crypto->secret_hash, session->next_crypto->digest_len); - ssh_print_hexa("session id", session->next_crypto->session_id, session->next_crypto->digest_len); -#endif - - rc = SSH_OK; -error: - ssh_buffer_free(buf); - ssh_buffer_free(client_hash); - ssh_buffer_free(server_hash); - - session->in_hashbuf = NULL; - session->out_hashbuf = NULL; - - ssh_string_free(num); - - return rc; -} - -int ssh_hashbufout_add_cookie(ssh_session session) { - int rc; - - session->out_hashbuf = ssh_buffer_new(); - if (session->out_hashbuf == NULL) { - return -1; - } - - rc = ssh_buffer_allocate_size(session->out_hashbuf, - sizeof(uint8_t) + 16); - if (rc < 0) { - ssh_buffer_reinit(session->out_hashbuf); - return -1; - } - - if (ssh_buffer_add_u8(session->out_hashbuf, 20) < 0) { - ssh_buffer_reinit(session->out_hashbuf); - return -1; - } - - if (session->server) { - if (ssh_buffer_add_data(session->out_hashbuf, - session->next_crypto->server_kex.cookie, 16) < 0) { - ssh_buffer_reinit(session->out_hashbuf); - return -1; - } - } else { - if (ssh_buffer_add_data(session->out_hashbuf, - session->next_crypto->client_kex.cookie, 16) < 0) { - ssh_buffer_reinit(session->out_hashbuf); - return -1; - } - } - - return 0; -} - -int ssh_hashbufin_add_cookie(ssh_session session, unsigned char *cookie) { - int rc; - - session->in_hashbuf = ssh_buffer_new(); - if (session->in_hashbuf == NULL) { - return -1; - } - - rc = ssh_buffer_allocate_size(session->in_hashbuf, - sizeof(uint8_t) + 20 + 16); - if (rc < 0) { - ssh_buffer_reinit(session->in_hashbuf); - return -1; - } - - if (ssh_buffer_add_u8(session->in_hashbuf, 20) < 0) { - ssh_buffer_reinit(session->in_hashbuf); - return -1; - } - if (ssh_buffer_add_data(session->in_hashbuf,cookie, 16) < 0) { - ssh_buffer_reinit(session->in_hashbuf); - return -1; - } - - return 0; -} - -static int generate_one_key(ssh_string k, - struct ssh_crypto_struct *crypto, unsigned char **output, char letter, size_t requested_size) { - ssh_mac_ctx ctx; - unsigned char *tmp; - size_t size = crypto->digest_len; - ctx=ssh_mac_ctx_init(crypto->mac_type); - - if (ctx == NULL) { - return -1; - } - - ssh_mac_update(ctx, k, ssh_string_len(k) + 4); - ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); - ssh_mac_update(ctx, &letter, 1); - ssh_mac_update(ctx, crypto->session_id, crypto->digest_len); - ssh_mac_final(*output, ctx); - - while(requested_size > size) { - tmp = realloc(*output, size + crypto->digest_len); - if (tmp == NULL) { - return -1; - } - *output = tmp; - - ctx = ssh_mac_ctx_init(crypto->mac_type); - if (ctx == NULL) { - return -1; - } - ssh_mac_update(ctx, k, ssh_string_len(k) + 4); - ssh_mac_update(ctx, crypto->secret_hash, - crypto->digest_len); - ssh_mac_update(ctx, tmp, size); - ssh_mac_final(tmp + size, ctx); - size += crypto->digest_len; - } - - return 0; -} - -int ssh_generate_session_keys(ssh_session session) { - ssh_string k_string = NULL; - struct ssh_crypto_struct *crypto = session->next_crypto; - int rc = -1; - - k_string = ssh_make_bignum_string(crypto->k); - if (k_string == NULL) { - ssh_set_error_oom(session); - goto error; - } - - crypto->encryptIV = malloc(crypto->digest_len); - crypto->decryptIV = malloc(crypto->digest_len); - crypto->encryptkey = malloc(crypto->digest_len); - crypto->decryptkey = malloc(crypto->digest_len); - crypto->encryptMAC = malloc(crypto->digest_len); - crypto->decryptMAC = malloc(crypto->digest_len); - if(crypto->encryptIV == NULL || crypto->decryptIV == NULL || - crypto->encryptkey == NULL || crypto->decryptkey == NULL || - crypto->encryptMAC == NULL || crypto->decryptMAC == NULL){ - ssh_set_error_oom(session); - goto error; - } - - /* IV */ - if (session->client) { - rc = generate_one_key(k_string, crypto, &crypto->encryptIV, 'A', crypto->digest_len); - if (rc < 0) { - goto error; - } - rc = generate_one_key(k_string, crypto, &crypto->decryptIV, 'B', crypto->digest_len); - if (rc < 0) { - goto error; - } - } else { - rc = generate_one_key(k_string, crypto, &crypto->decryptIV, 'A', crypto->digest_len); - if (rc < 0) { - goto error; - } - rc = generate_one_key(k_string, crypto, &crypto->encryptIV, 'B', crypto->digest_len); - if (rc < 0) { - goto error; - } - } - if (session->client) { - rc = generate_one_key(k_string, crypto, &crypto->encryptkey, 'C', crypto->out_cipher->keysize / 8); - if (rc < 0) { - goto error; - } - rc = generate_one_key(k_string, crypto, &crypto->decryptkey, 'D', crypto->in_cipher->keysize / 8); - if (rc < 0) { - goto error; - } - } else { - rc = generate_one_key(k_string, crypto, &crypto->decryptkey, 'C', crypto->in_cipher->keysize / 8); - if (rc < 0) { - goto error; - } - rc = generate_one_key(k_string, crypto, &crypto->encryptkey, 'D', crypto->out_cipher->keysize / 8); - if (rc < 0) { - goto error; - } - } - - if(session->client) { - rc = generate_one_key(k_string, crypto, &crypto->encryptMAC, 'E', hmac_digest_len(crypto->out_hmac)); - if (rc < 0) { - goto error; - } - rc = generate_one_key(k_string, crypto, &crypto->decryptMAC, 'F', hmac_digest_len(crypto->in_hmac)); - if (rc < 0) { - goto error; - } - } else { - rc = generate_one_key(k_string, crypto, &crypto->decryptMAC, 'E', hmac_digest_len(crypto->in_hmac)); - if (rc < 0) { - goto error; - } - rc = generate_one_key(k_string, crypto, &crypto->encryptMAC, 'F', hmac_digest_len(crypto->out_hmac)); - if (rc < 0) { - goto error; - } - } - -#ifdef DEBUG_CRYPTO - ssh_print_hexa("Encrypt IV", crypto->encryptIV, crypto->digest_len); - ssh_print_hexa("Decrypt IV", crypto->decryptIV, crypto->digest_len); - ssh_print_hexa("Encryption key", crypto->encryptkey, crypto->out_cipher->keysize / 8); - ssh_print_hexa("Decryption key", crypto->decryptkey, crypto->in_cipher->keysize / 8); - ssh_print_hexa("Encryption MAC", crypto->encryptMAC, hmac_digest_len(crypto->out_hmac)); - ssh_print_hexa("Decryption MAC", crypto->decryptMAC, hmac_digest_len(crypto->in_hmac)); -#endif - - rc = 0; -error: - ssh_string_free(k_string); - - return rc; -} - /** * @addtogroup libssh_session * * @{ */ -/** - * @deprecated Use ssh_get_publickey_hash() - */ -int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) { - ssh_key pubkey = NULL; - ssh_string pubkey_blob = NULL; - MD5CTX ctx; - unsigned char *h; - int rc; - - if (session == NULL || hash == NULL) { - return SSH_ERROR; - } - *hash = NULL; - if (session->current_crypto == NULL || - session->current_crypto->server_pubkey == NULL) { - ssh_set_error(session,SSH_FATAL,"No current cryptographic context"); - return SSH_ERROR; - } - - h = calloc(MD5_DIGEST_LEN, sizeof(unsigned char)); - if (h == NULL) { - return SSH_ERROR; - } - - ctx = md5_init(); - if (ctx == NULL) { - SAFE_FREE(h); - return SSH_ERROR; - } - - rc = ssh_get_server_publickey(session, &pubkey); - if (rc != SSH_OK) { - md5_final(h, ctx); - SAFE_FREE(h); - return SSH_ERROR; - } - - rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); - ssh_key_free(pubkey); - if (rc != SSH_OK) { - md5_final(h, ctx); - SAFE_FREE(h); - return SSH_ERROR; - } - - md5_update(ctx, ssh_string_data(pubkey_blob), ssh_string_len(pubkey_blob)); - ssh_string_free(pubkey_blob); - md5_final(h, ctx); - - *hash = h; - - return MD5_DIGEST_LEN; -} - -/** - * @brief Deallocate the hash obtained by ssh_get_pubkey_hash. - * - * This is required under Microsoft platform as this library might use a - * different C library than your software, hence a different heap. - * - * @param[in] hash The buffer to deallocate. - * - * @see ssh_get_pubkey_hash() - */ -void ssh_clean_pubkey_hash(unsigned char **hash) { - SAFE_FREE(*hash); - *hash = NULL; -} - -/** - * @brief Get the server public key from a session. - * - * @param[in] session The session to get the key from. - * - * @param[out] key A pointer to store the allocated key. You need to free - * the key. - * - * @return SSH_OK on success, SSH_ERROR on errror. - * - * @see ssh_key_free() - */ -int ssh_get_server_publickey(ssh_session session, ssh_key *key) -{ - ssh_key pubkey = NULL; - - if (session == NULL || - session->current_crypto == NULL || - session->current_crypto->server_pubkey == NULL) { - return SSH_ERROR; - } - - pubkey = ssh_key_dup(session->current_crypto->server_pubkey); - if (pubkey == NULL) { - return SSH_ERROR; - } - - *key = pubkey; - return SSH_OK; -} ssh_key ssh_dh_get_current_server_publickey(ssh_session session) { @@ -1479,138 +960,6 @@ int ssh_dh_get_next_server_publickey_blob(ssh_session session, return ssh_pki_export_pubkey_blob(pubkey, pubkey_blob); } -/** - * @deprecated Use ssh_get_server_publickey() - */ -int ssh_get_publickey(ssh_session session, ssh_key *key) -{ - return ssh_get_server_publickey(session, key); -} - -/** - * @brief Allocates a buffer with the hash of the public key. - * - * This function allows you to get a hash of the public key. You can then - * print this hash in a human-readable form to the user so that he is able to - * verify it. Use ssh_get_hexa() or ssh_print_hexa() to display it. - * - * @param[in] key The public key to create the hash for. - * - * @param[in] type The type of the hash you want. - * - * @param[in] hash A pointer to store the allocated buffer. It can be - * freed using ssh_clean_pubkey_hash(). - * - * @param[in] hlen The length of the hash. - * - * @return 0 on success, -1 if an error occured. - * - * @warning It is very important that you verify at some moment that the hash - * matches a known server. If you don't do it, cryptography wont help - * you at making things secure. - * OpenSSH uses SHA1 to print public key digests. - * - * @see ssh_session_update_known_hosts() - * @see ssh_get_hexa() - * @see ssh_print_hexa() - * @see ssh_clean_pubkey_hash() - */ -int ssh_get_publickey_hash(const ssh_key key, - enum ssh_publickey_hash_type type, - unsigned char **hash, - size_t *hlen) -{ - ssh_string blob; - unsigned char *h; - int rc; - - rc = ssh_pki_export_pubkey_blob(key, &blob); - if (rc < 0) { - return rc; - } - - switch (type) { - case SSH_PUBLICKEY_HASH_SHA1: - { - SHACTX ctx; - - h = malloc(SHA_DIGEST_LEN); - if (h == NULL) { - rc = -1; - goto out; - } - - ctx = sha1_init(); - if (ctx == NULL) { - free(h); - rc = -1; - goto out; - } - - sha1_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); - sha1_final(h, ctx); - - *hlen = SHA_DIGEST_LEN; - } - break; - case SSH_PUBLICKEY_HASH_SHA256: - { - SHA256CTX ctx; - - h = malloc(SHA256_DIGEST_LEN); - if (h == NULL) { - rc = -1; - goto out; - } - - ctx = sha256_init(); - if (ctx == NULL) { - free(h); - rc = -1; - goto out; - } - - sha256_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); - sha256_final(h, ctx); - - *hlen = SHA256_DIGEST_LEN; - } - break; - case SSH_PUBLICKEY_HASH_MD5: - { - MD5CTX ctx; - - h = malloc(MD5_DIGEST_LEN); - if (h == NULL) { - rc = -1; - goto out; - } - - ctx = md5_init(); - if (ctx == NULL) { - free(h); - rc = -1; - goto out; - } - - md5_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); - md5_final(h, ctx); - - *hlen = MD5_DIGEST_LEN; - } - break; - default: - rc = -1; - goto out; - } - - *hash = h; - rc = 0; -out: - ssh_string_free(blob); - return rc; -} - /** * @internal * @@ -1643,43 +992,6 @@ static char *ssh_get_b64_unpadded(const unsigned char *hash, size_t len) return b64_unpadded; } -/** - * @brief Convert a buffer into a colon separated hex string. - * The caller has to free the memory. - * - * @param what What should be converted to a hex string. - * - * @param len Length of the buffer to convert. - * - * @return The hex string or NULL on error. - * - * @see ssh_string_free_char() - */ -char *ssh_get_hexa(const unsigned char *what, size_t len) { - const char h[] = "0123456789abcdef"; - char *hexa; - size_t i; - size_t hlen = len * 3; - - if (len > (UINT_MAX - 1) / 3) { - return NULL; - } - - hexa = malloc(hlen + 1); - if (hexa == NULL) { - return NULL; - } - - for (i = 0; i < len; i++) { - hexa[i * 3] = h[(what[i] >> 4) & 0xF]; - hexa[i * 3 + 1] = h[what[i] & 0xF]; - hexa[i * 3 + 2] = ':'; - } - hexa[hlen - 1] = '\0'; - - return hexa; -} - /** * @brief Get a hash as a human-readable hex- or base64-string. * @@ -1784,24 +1096,4 @@ void ssh_print_hash(enum ssh_publickey_hash_type type, SAFE_FREE(fingerprint); } -/** - * @brief Print a buffer as colon separated hex string. - * - * @param descr Description printed in front of the hex string. - * - * @param what What should be converted to a hex string. - * - * @param len Length of the buffer to convert. - */ -void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len) { - char *hexa = ssh_get_hexa(what, len); - - if (hexa == NULL) { - return; - } - fprintf(stderr, "%s: %s\n", descr, hexa); - - free(hexa); -} - /** @} */ diff --git a/src/kex.c b/src/kex.c index 03c843e0..dff5f47b 100644 --- a/src/kex.c +++ b/src/kex.c @@ -39,6 +39,7 @@ #include "libssh/knownhosts.h" #include "libssh/misc.h" #include "libssh/pki.h" +#include "libssh/bignum.h" #ifdef WITH_BLOWFISH_CIPHER # if defined(HAVE_OPENSSL_BLOWFISH_H) || defined(HAVE_LIBGCRYPT) || defined(HAVE_LIBMBEDCRYPTO) @@ -989,3 +990,492 @@ char *ssh_keep_known_algos(enum ssh_kex_types_e algo, const char *list) return ssh_find_all_matching(supported_methods[algo], list); } + +int ssh_make_sessionid(ssh_session session) +{ + ssh_string num = NULL; + ssh_buffer server_hash = NULL; + ssh_buffer client_hash = NULL; + ssh_buffer buf = NULL; + ssh_string server_pubkey_blob = NULL; + int rc = SSH_ERROR; + + buf = ssh_buffer_new(); + if (buf == NULL) { + return rc; + } + + rc = ssh_buffer_pack(buf, + "ss", + session->clientbanner, + session->serverbanner); + if (rc == SSH_ERROR) { + goto error; + } + + if (session->client) { + server_hash = session->in_hashbuf; + client_hash = session->out_hashbuf; + } else { + server_hash = session->out_hashbuf; + client_hash = session->in_hashbuf; + } + + /* + * Handle the two final fields for the KEXINIT message (RFC 4253 7.1): + * + * boolean first_kex_packet_follows + * uint32 0 (reserved for future extension) + */ + rc = ssh_buffer_add_u8(server_hash, 0); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u32(server_hash, 0); + if (rc < 0) { + goto error; + } + + /* These fields are handled for the server case in ssh_packet_kexinit. */ + if (session->client) { + rc = ssh_buffer_add_u8(client_hash, 0); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u32(client_hash, 0); + if (rc < 0) { + goto error; + } + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); + if (rc != SSH_OK) { + goto error; + } + + rc = ssh_buffer_pack(buf, + "dPdPS", + ssh_buffer_get_len(client_hash), + ssh_buffer_get_len(client_hash), + ssh_buffer_get(client_hash), + ssh_buffer_get_len(server_hash), + ssh_buffer_get_len(server_hash), + ssh_buffer_get(server_hash), + server_pubkey_blob); + ssh_string_free(server_pubkey_blob); + if(rc != SSH_OK){ + goto error; + } + + switch(session->next_crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_KEX_DH_GROUP18_SHA512: + rc = ssh_buffer_pack(buf, + "BB", + session->next_crypto->e, + session->next_crypto->f); + if (rc != SSH_OK) { + goto error; + } + break; +#ifdef HAVE_ECDH + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_ECDH_SHA2_NISTP384: + case SSH_KEX_ECDH_SHA2_NISTP521: + if (session->next_crypto->ecdh_client_pubkey == NULL || + session->next_crypto->ecdh_server_pubkey == NULL) { + SSH_LOG(SSH_LOG_WARNING, "ECDH parameted missing"); + goto error; + } + rc = ssh_buffer_pack(buf, + "SS", + session->next_crypto->ecdh_client_pubkey, + session->next_crypto->ecdh_server_pubkey); + if (rc != SSH_OK) { + goto error; + } + break; +#endif +#ifdef HAVE_CURVE25519 + case SSH_KEX_CURVE25519_SHA256: + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + rc = ssh_buffer_pack(buf, + "dPdP", + CURVE25519_PUBKEY_SIZE, + (size_t)CURVE25519_PUBKEY_SIZE, session->next_crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE, + (size_t)CURVE25519_PUBKEY_SIZE, session->next_crypto->curve25519_server_pubkey); + + if (rc != SSH_OK) { + goto error; + } + break; +#endif + } + rc = ssh_buffer_pack(buf, "B", session->next_crypto->k); + if (rc != SSH_OK) { + goto error; + } + +#ifdef DEBUG_CRYPTO + ssh_print_hexa("hash buffer", ssh_buffer_get(buf), ssh_buffer_get_len(buf)); +#endif + + switch (session->next_crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: + session->next_crypto->digest_len = SHA_DIGEST_LENGTH; + session->next_crypto->mac_type = SSH_MAC_SHA1; + session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); + if (session->next_crypto->secret_hash == NULL) { + ssh_set_error_oom(session); + goto error; + } + sha1(ssh_buffer_get(buf), ssh_buffer_get_len(buf), + session->next_crypto->secret_hash); + break; + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_CURVE25519_SHA256: + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + session->next_crypto->digest_len = SHA256_DIGEST_LENGTH; + session->next_crypto->mac_type = SSH_MAC_SHA256; + session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); + if (session->next_crypto->secret_hash == NULL) { + ssh_set_error_oom(session); + goto error; + } + sha256(ssh_buffer_get(buf), ssh_buffer_get_len(buf), + session->next_crypto->secret_hash); + break; + case SSH_KEX_ECDH_SHA2_NISTP384: + session->next_crypto->digest_len = SHA384_DIGEST_LENGTH; + session->next_crypto->mac_type = SSH_MAC_SHA384; + session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); + if (session->next_crypto->secret_hash == NULL) { + ssh_set_error_oom(session); + goto error; + } + sha384(ssh_buffer_get(buf), ssh_buffer_get_len(buf), + session->next_crypto->secret_hash); + break; + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_KEX_DH_GROUP18_SHA512: + case SSH_KEX_ECDH_SHA2_NISTP521: + session->next_crypto->digest_len = SHA512_DIGEST_LENGTH; + session->next_crypto->mac_type = SSH_MAC_SHA512; + session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); + if (session->next_crypto->secret_hash == NULL) { + ssh_set_error_oom(session); + goto error; + } + sha512(ssh_buffer_get(buf), + ssh_buffer_get_len(buf), + session->next_crypto->secret_hash); + break; + } + /* During the first kex, secret hash and session ID are equal. However, after + * a key re-exchange, a new secret hash is calculated. This hash will not replace + * but complement existing session id. + */ + if (!session->next_crypto->session_id) { + session->next_crypto->session_id = malloc(session->next_crypto->digest_len); + if (session->next_crypto->session_id == NULL) { + ssh_set_error_oom(session); + goto error; + } + memcpy(session->next_crypto->session_id, session->next_crypto->secret_hash, + session->next_crypto->digest_len); + } +#ifdef DEBUG_CRYPTO + printf("Session hash: \n"); + ssh_print_hexa("secret hash", session->next_crypto->secret_hash, session->next_crypto->digest_len); + ssh_print_hexa("session id", session->next_crypto->session_id, session->next_crypto->digest_len); +#endif + + rc = SSH_OK; +error: + ssh_buffer_free(buf); + ssh_buffer_free(client_hash); + ssh_buffer_free(server_hash); + + session->in_hashbuf = NULL; + session->out_hashbuf = NULL; + + ssh_string_free(num); + + return rc; +} + +int ssh_hashbufout_add_cookie(ssh_session session) +{ + int rc; + + session->out_hashbuf = ssh_buffer_new(); + if (session->out_hashbuf == NULL) { + return -1; + } + + rc = ssh_buffer_allocate_size(session->out_hashbuf, + sizeof(uint8_t) + 16); + if (rc < 0) { + ssh_buffer_reinit(session->out_hashbuf); + return -1; + } + + if (ssh_buffer_add_u8(session->out_hashbuf, 20) < 0) { + ssh_buffer_reinit(session->out_hashbuf); + return -1; + } + + if (session->server) { + if (ssh_buffer_add_data(session->out_hashbuf, + session->next_crypto->server_kex.cookie, 16) < 0) { + ssh_buffer_reinit(session->out_hashbuf); + return -1; + } + } else { + if (ssh_buffer_add_data(session->out_hashbuf, + session->next_crypto->client_kex.cookie, 16) < 0) { + ssh_buffer_reinit(session->out_hashbuf); + return -1; + } + } + + return 0; +} + +int ssh_hashbufin_add_cookie(ssh_session session, unsigned char *cookie) +{ + int rc; + + session->in_hashbuf = ssh_buffer_new(); + if (session->in_hashbuf == NULL) { + return -1; + } + + rc = ssh_buffer_allocate_size(session->in_hashbuf, + sizeof(uint8_t) + 20 + 16); + if (rc < 0) { + ssh_buffer_reinit(session->in_hashbuf); + return -1; + } + + if (ssh_buffer_add_u8(session->in_hashbuf, 20) < 0) { + ssh_buffer_reinit(session->in_hashbuf); + return -1; + } + if (ssh_buffer_add_data(session->in_hashbuf,cookie, 16) < 0) { + ssh_buffer_reinit(session->in_hashbuf); + return -1; + } + + return 0; +} + +static int generate_one_key(ssh_string k, + struct ssh_crypto_struct *crypto, + unsigned char **output, + char letter, + size_t requested_size) +{ + ssh_mac_ctx ctx; + unsigned char *tmp; + size_t size = crypto->digest_len; + ctx = ssh_mac_ctx_init(crypto->mac_type); + + if (ctx == NULL) { + return -1; + } + + ssh_mac_update(ctx, k, ssh_string_len(k) + 4); + ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); + ssh_mac_update(ctx, &letter, 1); + ssh_mac_update(ctx, crypto->session_id, crypto->digest_len); + ssh_mac_final(*output, ctx); + + while(requested_size > size) { + tmp = realloc(*output, size + crypto->digest_len); + if (tmp == NULL) { + return -1; + } + *output = tmp; + + ctx = ssh_mac_ctx_init(crypto->mac_type); + if (ctx == NULL) { + return -1; + } + ssh_mac_update(ctx, k, ssh_string_len(k) + 4); + ssh_mac_update(ctx, + crypto->secret_hash, + crypto->digest_len); + ssh_mac_update(ctx, tmp, size); + ssh_mac_final(tmp + size, ctx); + size += crypto->digest_len; + } + + return 0; +} + +int ssh_generate_session_keys(ssh_session session) +{ + ssh_string k_string = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + int rc = -1; + + k_string = ssh_make_bignum_string(crypto->k); + if (k_string == NULL) { + ssh_set_error_oom(session); + goto error; + } + + crypto->encryptIV = malloc(crypto->digest_len); + crypto->decryptIV = malloc(crypto->digest_len); + crypto->encryptkey = malloc(crypto->digest_len); + crypto->decryptkey = malloc(crypto->digest_len); + crypto->encryptMAC = malloc(crypto->digest_len); + crypto->decryptMAC = malloc(crypto->digest_len); + if (crypto->encryptIV == NULL || + crypto->decryptIV == NULL || + crypto->encryptkey == NULL || crypto->decryptkey == NULL || + crypto->encryptMAC == NULL || crypto->decryptMAC == NULL){ + ssh_set_error_oom(session); + goto error; + } + + /* IV */ + if (session->client) { + rc = generate_one_key(k_string, + crypto, + &crypto->encryptIV, + 'A', + crypto->digest_len); + if (rc < 0) { + goto error; + } + rc = generate_one_key(k_string, + crypto, + &crypto->decryptIV, + 'B', + crypto->digest_len); + if (rc < 0) { + goto error; + } + } else { + rc = generate_one_key(k_string, + crypto, + &crypto->decryptIV, + 'A', + crypto->digest_len); + if (rc < 0) { + goto error; + } + rc = generate_one_key(k_string, + crypto, + &crypto->encryptIV, + 'B', + crypto->digest_len); + if (rc < 0) { + goto error; + } + } + if (session->client) { + rc = generate_one_key(k_string, + crypto, + &crypto->encryptkey, + 'C', + crypto->out_cipher->keysize / 8); + if (rc < 0) { + goto error; + } + rc = generate_one_key(k_string, + crypto, + &crypto->decryptkey, + 'D', + crypto->in_cipher->keysize / 8); + if (rc < 0) { + goto error; + } + } else { + rc = generate_one_key(k_string, + crypto, + &crypto->decryptkey, + 'C', + crypto->in_cipher->keysize / 8); + if (rc < 0) { + goto error; + } + rc = generate_one_key(k_string, + crypto, + &crypto->encryptkey, + 'D', + crypto->out_cipher->keysize / 8); + if (rc < 0) { + goto error; + } + } + + if(session->client) { + rc = generate_one_key(k_string, + crypto, + &crypto->encryptMAC, + 'E', + hmac_digest_len(crypto->out_hmac)); + if (rc < 0) { + goto error; + } + rc = generate_one_key(k_string, + crypto, + &crypto->decryptMAC, + 'F', + hmac_digest_len(crypto->in_hmac)); + if (rc < 0) { + goto error; + } + } else { + rc = generate_one_key(k_string, + crypto, + &crypto->decryptMAC, + 'E', + hmac_digest_len(crypto->in_hmac)); + if (rc < 0) { + goto error; + } + rc = generate_one_key(k_string, + crypto, + &crypto->encryptMAC, + 'F', + hmac_digest_len(crypto->out_hmac)); + if (rc < 0) { + goto error; + } + } + +#ifdef DEBUG_CRYPTO + ssh_print_hexa("Encrypt IV", + crypto->encryptIV, + crypto->digest_len); + ssh_print_hexa("Decrypt IV", + crypto->decryptIV, + crypto->digest_len); + ssh_print_hexa("Encryption key", + crypto->encryptkey, + crypto->out_cipher->keysize / 8); + ssh_print_hexa("Decryption key", + crypto->decryptkey, + crypto->in_cipher->keysize / 8); + ssh_print_hexa("Encryption MAC", + crypto->encryptMAC, + hmac_digest_len(crypto->out_hmac)); + ssh_print_hexa("Decryption MAC", + crypto->decryptMAC, + hmac_digest_len(crypto->in_hmac)); +#endif + + rc = 0; +error: + ssh_string_free(k_string); + + return rc; +} diff --git a/src/misc.c b/src/misc.c index 782b7bc0..abb82425 100644 --- a/src/misc.c +++ b/src/misc.c @@ -332,6 +332,63 @@ char *ssh_hostport(const char *host, int port){ return dest; } +/** + * @brief Convert a buffer into a colon separated hex string. + * The caller has to free the memory. + * + * @param what What should be converted to a hex string. + * + * @param len Length of the buffer to convert. + * + * @return The hex string or NULL on error. + * + * @see ssh_string_free_char() + */ +char *ssh_get_hexa(const unsigned char *what, size_t len) { + const char h[] = "0123456789abcdef"; + char *hexa; + size_t i; + size_t hlen = len * 3; + + if (len > (UINT_MAX - 1) / 3) { + return NULL; + } + + hexa = malloc(hlen + 1); + if (hexa == NULL) { + return NULL; + } + + for (i = 0; i < len; i++) { + hexa[i * 3] = h[(what[i] >> 4) & 0xF]; + hexa[i * 3 + 1] = h[what[i] & 0xF]; + hexa[i * 3 + 2] = ':'; + } + hexa[hlen - 1] = '\0'; + + return hexa; +} + +/** + * @brief Print a buffer as colon separated hex string. + * + * @param descr Description printed in front of the hex string. + * + * @param what What should be converted to a hex string. + * + * @param len Length of the buffer to convert. + */ +void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len) { + char *hexa = ssh_get_hexa(what, len); + + if (hexa == NULL) { + return; + } + fprintf(stderr, "%s: %s\n", descr, hexa); + + free(hexa); +} + /** * @brief Check if libssh is the required version or get the version * string. diff --git a/src/session.c b/src/session.c index 01a96a32..b79f4548 100644 --- a/src/session.c +++ b/src/session.c @@ -38,6 +38,7 @@ #include "libssh/misc.h" #include "libssh/buffer.h" #include "libssh/poll.h" +#include "libssh/pki.h" #define FIRST_CHANNEL 42 // why not ? it helps to find bugs. @@ -949,4 +950,237 @@ void ssh_set_counters(ssh_session session, ssh_counter scounter, } } +/** + * @deprecated Use ssh_get_publickey_hash() + */ +int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) +{ + ssh_key pubkey = NULL; + ssh_string pubkey_blob = NULL; + MD5CTX ctx; + unsigned char *h; + int rc; + + if (session == NULL || hash == NULL) { + return SSH_ERROR; + } + *hash = NULL; + if (session->current_crypto == NULL || + session->current_crypto->server_pubkey == NULL) { + ssh_set_error(session,SSH_FATAL,"No current cryptographic context"); + return SSH_ERROR; + } + + h = calloc(MD5_DIGEST_LEN, sizeof(unsigned char)); + if (h == NULL) { + return SSH_ERROR; + } + + ctx = md5_init(); + if (ctx == NULL) { + SAFE_FREE(h); + return SSH_ERROR; + } + + rc = ssh_get_server_publickey(session, &pubkey); + if (rc != SSH_OK) { + md5_final(h, ctx); + SAFE_FREE(h); + return SSH_ERROR; + } + + rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); + ssh_key_free(pubkey); + if (rc != SSH_OK) { + md5_final(h, ctx); + SAFE_FREE(h); + return SSH_ERROR; + } + + md5_update(ctx, ssh_string_data(pubkey_blob), ssh_string_len(pubkey_blob)); + ssh_string_free(pubkey_blob); + md5_final(h, ctx); + + *hash = h; + + return MD5_DIGEST_LEN; +} + +/** + * @brief Deallocate the hash obtained by ssh_get_pubkey_hash. + * + * This is required under Microsoft platform as this library might use a + * different C library than your software, hence a different heap. + * + * @param[in] hash The buffer to deallocate. + * + * @see ssh_get_pubkey_hash() + */ +void ssh_clean_pubkey_hash(unsigned char **hash) { + SAFE_FREE(*hash); +} + +/** + * @brief Get the server public key from a session. + * + * @param[in] session The session to get the key from. + * + * @param[out] key A pointer to store the allocated key. You need to free + * the key. + * + * @return SSH_OK on success, SSH_ERROR on errror. + * + * @see ssh_key_free() + */ +int ssh_get_server_publickey(ssh_session session, ssh_key *key) +{ + ssh_key pubkey = NULL; + + if (session == NULL || + session->current_crypto == NULL || + session->current_crypto->server_pubkey == NULL) { + return SSH_ERROR; + } + + pubkey = ssh_key_dup(session->current_crypto->server_pubkey); + if (pubkey == NULL) { + return SSH_ERROR; + } + + *key = pubkey; + return SSH_OK; +} + +/** + * @deprecated Use ssh_get_server_publickey() + */ +int ssh_get_publickey(ssh_session session, ssh_key *key) +{ + return ssh_get_server_publickey(session, key); +} + +/** + * @brief Allocates a buffer with the hash of the public key. + * + * This function allows you to get a hash of the public key. You can then + * print this hash in a human-readable form to the user so that he is able to + * verify it. Use ssh_get_hexa() or ssh_print_hexa() to display it. + * + * @param[in] key The public key to create the hash for. + * + * @param[in] type The type of the hash you want. + * + * @param[in] hash A pointer to store the allocated buffer. It can be + * freed using ssh_clean_pubkey_hash(). + * + * @param[in] hlen The length of the hash. + * + * @return 0 on success, -1 if an error occured. + * + * @warning It is very important that you verify at some moment that the hash + * matches a known server. If you don't do it, cryptography wont help + * you at making things secure. + * OpenSSH uses SHA1 to print public key digests. + * + * @see ssh_session_update_known_hosts() + * @see ssh_get_hexa() + * @see ssh_print_hexa() + * @see ssh_clean_pubkey_hash() + */ +int ssh_get_publickey_hash(const ssh_key key, + enum ssh_publickey_hash_type type, + unsigned char **hash, + size_t *hlen) +{ + ssh_string blob; + unsigned char *h; + int rc; + + rc = ssh_pki_export_pubkey_blob(key, &blob); + if (rc < 0) { + return rc; + } + + switch (type) { + case SSH_PUBLICKEY_HASH_SHA1: + { + SHACTX ctx; + + h = malloc(SHA_DIGEST_LEN); + if (h == NULL) { + rc = -1; + goto out; + } + + ctx = sha1_init(); + if (ctx == NULL) { + free(h); + rc = -1; + goto out; + } + + sha1_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); + sha1_final(h, ctx); + + *hlen = SHA_DIGEST_LEN; + } + break; + case SSH_PUBLICKEY_HASH_SHA256: + { + SHA256CTX ctx; + + h = malloc(SHA256_DIGEST_LEN); + if (h == NULL) { + rc = -1; + goto out; + } + + ctx = sha256_init(); + if (ctx == NULL) { + free(h); + rc = -1; + goto out; + } + + sha256_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); + sha256_final(h, ctx); + + *hlen = SHA256_DIGEST_LEN; + } + break; + case SSH_PUBLICKEY_HASH_MD5: + { + MD5CTX ctx; + + h = malloc(MD5_DIGEST_LEN); + if (h == NULL) { + rc = -1; + goto out; + } + + ctx = md5_init(); + if (ctx == NULL) { + free(h); + rc = -1; + goto out; + } + + md5_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); + md5_final(h, ctx); + + *hlen = MD5_DIGEST_LEN; + } + break; + default: + rc = -1; + goto out; + } + + *hash = h; + rc = 0; +out: + ssh_string_free(blob); + return rc; +} + /** @} */ -- cgit v1.2.3