/* ****************************************************************************** * SOURCE FILE * * Document no: @(#) $Name: VXWORKS_ITER40_2019102207 $ $RCSfile: ipssh_keyex_dh.c,v $ $Revision: 1.62.10.5 $ * $Source: /home/interpeak/CVSRoot/ipssh/src/ipssh_keyex_dh.c,v $ * $Author: yli10 $ $Date: 2018-06-20 02:32:55 $ * $State: Exp $ $Locker: $ * * INTERPEAK_COPYRIGHT_STRING * Design and implementation by Roger Boden ****************************************************************************** */ /* **************************************************************************** * 1 DESCRIPTION **************************************************************************** */ /* **************************************************************************** * 2 CONFIGURATION **************************************************************************** */ /* **************************************************************************** * 3 INCLUDE FILES **************************************************************************** */ #include "ipssh_h.h" #include "ipssh_transport.h" #include "ipssh_buffer.h" #include "ipssh_keyex_dh.h" #include #include #include #include #include #include #include #include #include #include /* **************************************************************************** * 4 EXTERN PROTOTYPES **************************************************************************** */ /* **************************************************************************** * 5 DEFINES **************************************************************************** */ /* * Define if you want debug printouts of the buffers that are hashed as part * of the handshake. * * #define IPSSH_HASH_DBG */ /* **************************************************************************** * 6 TYPES **************************************************************************** */ /* **************************************************************************** * 7 LOCAL PROTOTYPES **************************************************************************** */ IP_GLOBAL Ip_err ipssh_dss_sign(Ipssh_conn* ssh_conn, Ip_u8* data, Ip_u32 data_len, Ipcom_buffer* buf); IP_GLOBAL Ip_err ipssh_send_kex_dh_reply(Ipssh_conn* ssh_conn); IP_GLOBAL Ip_err ipssh_send_kex_dh_init(Ipssh_conn* ssh_conn); IP_GLOBAL Ip_err ipssh_msg_kexdh_reply(Ipssh_conn* ssh_conn); /* **************************************************************************** * 8 DATA **************************************************************************** */ IP_STATIC const char *ipssh_dh_g_gr1 = "2"; IP_STATIC const char *ipssh_dh_p_gr1 = "FFFFFFFF" "FFFFFFFF" "C90FDAA2" "2168C234" "C4C6628B" "80DC1CD1" "29024E08" "8A67CC74" "020BBEA6" "3B139B22" "514A0879" "8E3404DD" "EF9519B3" "CD3A431B" "302B0A6D" "F25F1437" "4FE1356D" "6D51C245" "E485B576" "625E7EC6" "F44C42E9" "A637ED6B" "0BFF5CB6" "F406B7ED" "EE386BFB" "5A899FA5" "AE9F2411" "7C4B1FE6" "49286651" "ECE65381" "FFFFFFFF" "FFFFFFFF"; IP_STATIC const char *ipssh_dh_g_gr14 = "2"; IP_STATIC const char *ipssh_dh_p_gr14 = "FFFFFFFF" "FFFFFFFF" "C90FDAA2" "2168C234" "C4C6628B" "80DC1CD1" "29024E08" "8A67CC74" "020BBEA6" "3B139B22" "514A0879" "8E3404DD" "EF9519B3" "CD3A431B" "302B0A6D" "F25F1437" "4FE1356D" "6D51C245" "E485B576" "625E7EC6" "F44C42E9" "A637ED6B" "0BFF5CB6" "F406B7ED" "EE386BFB" "5A899FA5" "AE9F2411" "7C4B1FE6" "49286651" "ECE45B3D" "C2007CB8" "A163BF05" "98DA4836" "1C55D39A" "69163FA8" "FD24CF5F" "83655D23" "DCA3AD96" "1C62F356" "208552BB" "9ED52907" "7096966D" "670C354E" "4ABC9804" "F1746C08" "CA18217C" "32905E46" "2E36CE3B" "E39E772C" "180E8603" "9B2783A2" "EC07A28F" "B5C55DF0" "6F4C52C9" "DE2BCBF6" "95581718" "3995497C" "EA956AE5" "15D22618" "98FA0510" "15728E5A" "8AACAA68" "FFFFFFFF" "FFFFFFFF"; /* **************************************************************************** * 9 LOCAL FUNCTIONS **************************************************************************** */ /* *=========================================================================== * ipssh_create_dh_group *=========================================================================== * Description: * Parameters: * Returns: * */ IP_GLOBAL DH* ipssh_create_dh_group(Ip_u8 dh_id) { DH* dh; dh = DH_new(); if(!dh) return IP_NULL; switch (dh_id) { case IPSSH_KEYEX_DH_GR1_SHA1_ID: if(BN_hex2bn(&(dh->p), ipssh_dh_p_gr1) == 0 || BN_hex2bn(&(dh->g), ipssh_dh_g_gr1) == 0) { IPCOM_LOG0(ERR, "ipssh_create_dh_group :: BN_hex2bn() failed for group 1"); DH_free(dh); return IP_NULL; } break; case IPSSH_KEYEX_DH_GR14_SHA1_ID: if(BN_hex2bn(&(dh->p), ipssh_dh_p_gr14) == 0 || BN_hex2bn(&(dh->g), ipssh_dh_g_gr14) == 0) { IPCOM_LOG0(ERR, "ipssh_create_dh_group :: BN_hex2bn() failed for group 14"); DH_free(dh); return IP_NULL; } break; default: IP_PANIC(); DH_free(dh); return IP_NULL; } return dh; } /* **************************************************************************** * 10 PUBLIC FUNCTIONS **************************************************************************** */ /* *=========================================================================== * ipssh_dh_priv_key_set_length *=========================================================================== * Description: This function sets the length of the DH private key. * If not set, DH_generate_key will generate a private key * of the same size as the DH group. Especially for large groups * this is unnecessarily big, which will give long connection * time, in particular on weak CPUs. According to key length * recommendations, a DH private key length of 256 bits will be * sufficient until the year 2040. To be on the safe side, we * use twice that length, i.e. 512 bits. * Parameters: * Returns: */ IP_GLOBAL Ip_err ipssh_dh_priv_key_set_length(Ipssh_conn* ssh_conn) { if (BN_num_bits(ssh_conn->dh_data->dh->p) > 512) { ssh_conn->dh_data->dh->length = 512; } return IPCOM_SUCCESS; } /* *=========================================================================== * ipssh_calc_hash *=========================================================================== * Description: * Parameters: * Returns: * TODO: change to one argument: Ipssh_conn*? */ IP_GLOBAL Ip_err ipssh_calc_hash(Ipssh_msg_list* msgs, const EVP_MD* (*hash_func)(void), int hash_comps, Ip_u8** hash_value, Ip_u32* hash_len) { Ipcom_list *list, *node; Ipssh_msg_list* msg; Ip_s32 i, rc = IPCOM_SUCCESS; Ipcom_buffer* buf; EVP_MD_CTX* md_ctx; Ip_size_t buf_len = 0; /* Calculate needed buffer length */ list = (Ipcom_list*) msgs; for(node = ipcom_list_first(list); node; node = ipcom_list_next(node)) { msg = (Ipssh_msg_list*) node; buf_len += msg->msg_len; switch(msg->type) { case IPSSH_STR: buf_len += 4; break; case IPSSH_MPINT: buf_len += 5; break; } } buf = ipcom_buffer_new(buf_len+16); md_ctx = EVP_MD_CTX_create(); if(!buf || !md_ctx) { IPCOM_LOG0(ERR, "ipssh_calc_hash :: out of memory"); rc = IPCOM_ERR_FAILED; goto cleanup; } (void)EVP_DigestInit(md_ctx, hash_func()); list = (Ipcom_list*) msgs; for(i=0; ihash_pos == i) { if(msg->type == IPSSH_STR) { (void)ipssh_buffer_put_sshbuf(buf, msg->value, msg->msg_len); } else if(msg->type == IPSSH_MPINT) { (void)ipssh_buffer_put_ssh_mpint(buf, msg->value, msg->msg_len); } else if(msg->type == IPSSH_RAW || msg->type == IPSSH_INT) { (void)ipcom_buffer_put(buf, msg->value, msg->msg_len); } #if defined(IPSSH_DEBUG) && defined(IPSSH_HASH_DBG) ipcom_debug_printf("dumping hash comp %d\n", i); ipcom_buffer_dump(buf); ipcom_debug_printf("\n"); #endif (void)EVP_DigestUpdate(md_ctx, ipcom_buffer_start_ptr(buf),ipcom_buffer_len(buf)); found = 1; ipcom_buffer_reset(buf); break; } } if(!found) { IPCOM_LOG1(ERR, "ipssh_calc_hash :: message no %d not found", i); rc = IPCOM_ERR_FAILED; goto cleanup; } } *hash_len = md_ctx->digest->md_size; *hash_value = ipcom_malloc(*hash_len); if(!*hash_value) { IPCOM_LOG0(ERR, "ipssh_calc_hash :: out of memory"); rc = IPCOM_ERR_FAILED; goto cleanup; } (void)EVP_DigestFinal(md_ctx, *hash_value, IP_NULL); cleanup: if(buf) ipcom_buffer_free(buf); if(md_ctx) (void)EVP_MD_CTX_destroy(md_ctx); return rc; } /* *=========================================================================== * ipssh_verify_signature *=========================================================================== * Description: * Parameters: * Returns: * */ IP_GLOBAL Ip_err ipssh_verify_signature(EVP_PKEY* srv_key, Ip_u8* data, Ip_u32 data_len, const char* sig_type, Ip_u8* signature, Ip_u32 sig_len) { DSA_SIG* dsa_sig = IP_NULL; EVP_MD_CTX md; Ip_err rc = IPCOM_ERR_FAILED; EVP_MD_CTX_init(&md); switch(srv_key->type) { case NID_dsa: if(ipcom_strcmp(sig_type, IPSSH_DSA_STR) != 0 || sig_len != 2*IPSSH_SIG_LEN) return IPCOM_ERR_INVALID_ARG; dsa_sig = DSA_SIG_new(); if(!dsa_sig) return IPCOM_ERR_NO_MEMORY; dsa_sig->r = BN_new(); dsa_sig->s = BN_new(); if(!dsa_sig->r || !dsa_sig->s) { rc = IPCOM_ERR_NO_MEMORY; goto cleanup; } (void)BN_bin2bn(signature, IPSSH_SIG_LEN, dsa_sig->r); (void)BN_bin2bn(signature+IPSSH_SIG_LEN, IPSSH_SIG_LEN, dsa_sig->s); if (!EVP_VerifyInit(&md, EVP_dss1()) || !EVP_VerifyUpdate(&md, data, data_len) || !EVP_VerifyFinal(&md, signature, sig_len, srv_key)) { rc = IPCOM_ERR_FAILED; goto cleanup; } rc = IPCOM_SUCCESS; break; #ifdef IPSSH_USE_RSA case NID_rsaEncryption: if(ipcom_strcmp(sig_type, IPSSH_RSA_STR) != 0) return IPCOM_ERR_INVALID_ARG; if (!EVP_VerifyInit(&md, EVP_sha1()) || !EVP_VerifyUpdate(&md, data, data_len) || !EVP_VerifyFinal(&md, signature, sig_len, srv_key)) { rc = IPCOM_ERR_FAILED; goto cleanup; } rc = IPCOM_SUCCESS; break; #endif default: IP_PANIC(); } cleanup: if(dsa_sig) DSA_SIG_free(dsa_sig); EVP_MD_CTX_cleanup(&md); return rc; } /* *=========================================================================== * ipssh_dss_sign *=========================================================================== * Description: * Parameters: * Returns: * */ IP_GLOBAL Ip_err ipssh_dss_sign(Ipssh_conn* ssh_conn, Ip_u8* data, Ip_u32 data_len, Ipcom_buffer* buf) { Ip_u8 *raw_sig; Ip_u8 *tmp_raw_sig; DSA_SIG *sig = IP_NULL; const EVP_MD *evp_md = EVP_dss1(); EVP_MD_CTX md; Ip_s32 r_len, s_len, rc=0; Ip_u32 len; Ip_size_t blob_len_pos; int pad, i; char* len_ptr; EVP_PKEY* pkey = IP_NULL; unsigned int sig_size; IPCOM_LOG0(DEBUG2, "ipssh_dss_sign"); if(ipcom_buffer_capacity(buf) < 60) { IPCOM_LOG0(ERR, "ipssh_dss_sign :: too little buffer space"); return IPCOM_ERR_FAILED; } pkey = EVP_PKEY_new(); if (!pkey) { IPCOM_LOG0(ERR, "ipssh_dss_sign :: EVP_PKEY_new() failed"); return IPCOM_ERR_FAILED; } EVP_PKEY_set1_DSA(pkey, ssh_conn->srv_dsa_key); sig_size = EVP_PKEY_size(pkey); raw_sig = ipcom_malloc(sig_size); if (!raw_sig) { IPCOM_LOG0(ERR, "ipssh_dss_sign :: out of memory"); return IPCOM_ERR_FAILED; } if (!EVP_SignInit(&md, evp_md) || !EVP_SignUpdate(&md, data, data_len) || !EVP_SignFinal(&md, raw_sig, &sig_size, pkey)) { IPCOM_LOG0(ERR, "ipssh_dss_sign :: EVP_Sign*() failed"); rc = IPCOM_ERR_FAILED; goto cleanup; } tmp_raw_sig = raw_sig; sig = d2i_DSA_SIG(IP_NULL, &tmp_raw_sig, sig_size); if (sig == IP_NULL) { IPCOM_LOG0(ERR, "ipssh_dss_sign :: d2i_DSA_SIG() failed"); rc = IPCOM_ERR_FAILED; goto cleanup; } r_len = BN_num_bytes(sig->r); s_len = BN_num_bytes(sig->s); if (r_len > IPSSH_MAX_INT_LEN || s_len > IPSSH_MAX_INT_LEN) { IPCOM_LOG2(ERR, "ipssh_dss_sign :: bad sig size %d %d", r_len, s_len); rc = IPCOM_ERR_FAILED; goto cleanup; } len = (Ip_u32)ipcom_buffer_len(buf); blob_len_pos = ipcom_buffer_len(buf); (void)ipcom_buffer_put_int(buf, 0); (void)ipssh_buffer_put_sshstr(buf, IPSSH_DSA_STR); IPCOM_LOG2(DEBUG2, "ipssh_dss_sign :: r_len: %d, s_len: %d", r_len, s_len); (void)ipcom_buffer_put_int(buf, ip_htonl(2*IPSSH_SIG_LEN)); for(i=0,pad=IPSSH_SIG_LEN-r_len; ir, ipcom_buffer_end_ptr(buf)); (void)ipcom_buffer_decr_cap(buf, r_len); for(i=0,pad=IPSSH_SIG_LEN-s_len; is, ipcom_buffer_end_ptr(buf)); (void)ipcom_buffer_decr_cap(buf, s_len); len_ptr = ipcom_buffer_ptr2pos(buf, blob_len_pos); IP_SET_32ON8(len_ptr, ip_htonl(((Ip_u32)ipcom_buffer_len(buf)-len-4))); /*lint !e666*/ cleanup: EVP_PKEY_free(pkey); (void)EVP_MD_CTX_cleanup(&md); if(raw_sig) ipcom_free(raw_sig); if(sig) DSA_SIG_free(sig); return rc; } #ifdef IPSSH_USE_RSA /* *=========================================================================== * ipssh_rsa_sign *=========================================================================== * Description: * Parameters: * Returns: * */ IP_GLOBAL Ip_err ipssh_rsa_sign(Ipssh_conn* ssh_conn, Ip_u8* data, Ip_u32 data_len, Ipcom_buffer* buf) { char* len_ptr; Ip_u32 len; Ip_size_t len2; int rsa_blob_len; unsigned int sig_len; EVP_MD_CTX md; EVP_PKEY* pkey = IP_NULL; IPCOM_LOG0(DEBUG2, "ipssh_rsa_sign"); /* Length of whole signature */ len = (Ip_u32)ipcom_buffer_len(buf); (void)ipcom_buffer_put_int(buf, 0); (void)ipssh_buffer_put_sshstr(buf, IPSSH_RSA_STR); /* Length of the RSA signature blob */ len2 = ipcom_buffer_len(buf); (void)ipcom_buffer_put_int(buf, 0); if((int)ipcom_buffer_capacity(buf) < RSA_size(ssh_conn->srv_rsa_key)) { IPCOM_LOG0(ERR, "ipssh_rsa_sign :: too little buffer space"); return IPCOM_ERR_FAILED; } rsa_blob_len = RSA_size(ssh_conn->srv_rsa_key); pkey = EVP_PKEY_new(); if (!pkey) { IPCOM_LOG0(ERR, "ipssh_rsa_sign :: EVP_PKEY_new() failed"); return IPCOM_ERR_FAILED; } EVP_PKEY_set1_RSA(pkey, ssh_conn->srv_rsa_key); if (!EVP_SignInit(&md, EVP_sha1()) || !EVP_SignUpdate(&md, data, data_len) || !EVP_SignFinal(&md, ipcom_buffer_end_ptr(buf), &sig_len, pkey)) { IPCOM_LOG0(ERR, "ipssh_rsa_sign :: EVP_Sign failed"); EVP_PKEY_free(pkey); return IPCOM_ERR_FAILED; } EVP_PKEY_free(pkey); EVP_MD_CTX_cleanup(&md); (void)ipcom_buffer_decr_cap(buf, sig_len); /* Set length of RSA signature blob */ len_ptr = ipcom_buffer_ptr2pos(buf, len2); IP_SET_32ON8(len_ptr, ip_htonl(rsa_blob_len)); /*lint !e666*/ /* Set lenght of whole signature */ len_ptr = ipcom_buffer_ptr2pos(buf, len); IP_SET_32ON8(len_ptr, ip_htonl(((Ip_u32)ipcom_buffer_len(buf)-len-4))); /*lint !e666*/ return IPCOM_SUCCESS; } #endif /* IPSSH_USE_RSA */ /* *=========================================================================== * ipssh_sign_hash *=========================================================================== * Description: * Parameters: * Returns: * */ IP_GLOBAL Ip_err ipssh_sign_hash(Ipssh_conn* ssh_conn, Ipcom_buffer* pkt) { /* Sign hash with server key */ switch (ssh_conn->conn_prop_pending.srv_host_key_alg[0]) { case IPSSH_DSA_ID: if(ipssh_dss_sign(ssh_conn, ssh_conn->hash, ssh_conn->hash_len, pkt)) { IPCOM_LOG0(ERR, "ipssh_sign_hash :: ipssh_dss_sign() failed"); return IPCOM_ERR_FAILED; } break; #ifdef IPSSH_USE_RSA case IPSSH_RSA_ID: if(ipssh_rsa_sign(ssh_conn, ssh_conn->hash, ssh_conn->hash_len, pkt)) { IPCOM_LOG0(ERR, "ipssh_sign_hash :: ipssh_rsa_sign() failed"); return IPCOM_ERR_FAILED; } break; #endif default: IP_PANIC(); return IPCOM_ERR_FAILED; } return IPCOM_SUCCESS; } /* *=========================================================================== * ipssh_send_kex_dh_reply *=========================================================================== * Description: * Parameters: * Returns: * */ IP_GLOBAL Ip_err ipssh_send_kex_dh_reply(Ipssh_conn* ssh_conn) { Ip_size_t len; Ip_size_t f_pos; Ip_size_t host_key_pos; Ipcom_buffer* pkt; Ip_err rc; IPCOM_LOG0(DEBUG, "ipssh_send_kex_dh_reply()"); pkt = ipssh_buffer_new_v2_pkt(2048, IPSSH_MSG_KEXDH_REPLY); if(!pkt) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_reply :: out of memory"); return IPCOM_ERR_FAILED; } /* Add server key */ len = ipcom_buffer_len(pkt); host_key_pos = ipcom_buffer_len(pkt); switch (ssh_conn->conn_prop_pending.srv_host_key_alg[0]) { case IPSSH_DSA_ID: if(ipssh_buffer_put_dsa_key(pkt, ssh_conn->srv_dsa_key)) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_reply :: ipssh_buffer_put_dsa_key() failed"); goto failed; } break; #ifdef IPSSH_USE_RSA case IPSSH_RSA_ID: if(ipssh_buffer_put_rsa_key(pkt, ssh_conn->srv_rsa_key)) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_reply :: ipssh_buffer_put_rsa_key() failed"); goto failed; } break; #endif default: IP_PANIC(); goto failed; } if(ipssh_msg2list((Ipssh_msg_list*)&(ssh_conn->handshake_msgs), IPSSH_RAW, (Ip_u8*)ipcom_buffer_ptr2pos(pkt, host_key_pos), ipcom_buffer_len(pkt) - host_key_pos, 4)) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_reply :: ipssh_msg2list() failed for DSA key"); goto failed; } /* Add f */ f_pos = ipcom_buffer_len(pkt); rc = ipssh_buffer_put_bn(pkt, ssh_conn->dh_data->dh->pub_key); if (rc < 0) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_reply :: ipssh_buffer_put_bn() failed"); goto failed; } if(ipssh_msg2list((Ipssh_msg_list*)&(ssh_conn->handshake_msgs), IPSSH_MPINT, (Ip_u8*)IPSSH_GET_MPINT_VALUE(ipcom_buffer_ptr2pos(pkt, f_pos)), ipcom_buffer_len(pkt) - f_pos - 4, 6)) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_reply :: ipssh_msg2list() failed for f"); goto failed; } /* Calculate hash */ if (ssh_conn->hash) ipcom_free(ssh_conn->hash); if(ipssh_calc_hash((Ipssh_msg_list*)&(ssh_conn->handshake_msgs), ssh_conn->hash_func, 8, &(ssh_conn->hash), &(ssh_conn->hash_len))) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_reply :: ipssh_calc_hash() failed"); goto failed; } if (ssh_conn->sess_id_len == 0) { /* The session ID is only updated in the inital key exchange */ ssh_conn->sess_id_len = ssh_conn->hash_len; ssh_conn->sess_id = ipcom_malloc(ssh_conn->hash_len); if(!ssh_conn->sess_id) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_reply :: out of memory"); goto failed; } ipcom_memcpy(ssh_conn->sess_id, ssh_conn->hash, ssh_conn->sess_id_len); } /* Sign hash with server key */ switch (ssh_conn->conn_prop_pending.srv_host_key_alg[0]) { case IPSSH_DSA_ID: if(ipssh_dss_sign(ssh_conn, ssh_conn->hash, ssh_conn->hash_len, pkt)) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_reply :: ipssh_dss_sign() failed"); goto failed; } break; #ifdef IPSSH_USE_RSA case IPSSH_RSA_ID: if(ipssh_rsa_sign(ssh_conn, ssh_conn->hash, ssh_conn->hash_len, pkt)) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_reply :: ipssh_rsa_sign() failed"); goto failed; } break; #endif default: IP_PANIC(); goto failed; } if(ipssh_send_pkt(ssh_conn, pkt, IP_TRUE)) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_reply :: ipssh_send_pkt() failed"); return IPCOM_ERR_FAILED; } if(ipssh_compute_keys(ssh_conn)) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_reply :: ipssh_compute_keys() failed"); return IPCOM_ERR_FAILED; } if(ipssh_send_new_key_msg(ssh_conn)) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_reply :: ipssh_send_new_key() failed"); return IPCOM_ERR_FAILED; } ssh_conn->conn_state = IPSSH_CONN_STATE_SRV_NEW_KEY; return IPCOM_SUCCESS; failed: ipcom_buffer_free(pkt); return IPCOM_ERR_FAILED; } /* *=========================================================================== * ipssh_send_kex_dh_init *=========================================================================== * Description: * Parameters: * Returns: * */ IP_GLOBAL Ip_err ipssh_send_kex_dh_init(Ipssh_conn* ssh_conn) { Ip_err rc = IPCOM_ERR_FAILED; Ipcom_buffer* pkt; Ip_size_t pos; Ip_size_t len; IPCOM_LOG1(DEBUG, "ipssh_send_kex_dh_init :: ssh_conn: %x", ssh_conn); pkt = ipssh_buffer_new_v2_pkt(1024, IPSSH_MSG_KEXDH_INIT); if(!pkt) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_init :: out of memory"); return IPCOM_ERR_FAILED; } ssh_conn->dh_data->dh = ipssh_create_dh_group(ssh_conn->conn_prop_pending.kex_alg[0]); if(!ssh_conn->dh_data->dh) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_init :: ipssh_create_dh_group() failed"); goto cleanup; } /* Reduce the size of the DH private key for large DH groups. It is unnecessarily big by * default, which increases the connection time */ if (ipssh_dh_priv_key_set_length(ssh_conn) != IPCOM_SUCCESS) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_init :: ipssh_dh_priv_key_set_length() failed"); goto cleanup; } /* Generate random number y and create e = g^x mod p */ if(!DH_generate_key(ssh_conn->dh_data->dh)) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_init :: DH_generate_key() failed"); goto cleanup; } pos = ipcom_buffer_len(pkt); if(ipssh_buffer_put_bn(pkt, ssh_conn->dh_data->dh->pub_key) < 0) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_init :: ipssh_buffer_put_bn() failed"); goto cleanup; } pos += 4; /* proceed past length field */ len = ipcom_buffer_len(pkt) - pos ; if(ipssh_msg2list((Ipssh_msg_list*)&(ssh_conn->handshake_msgs), IPSSH_MPINT, (Ip_u8*)ipcom_buffer_ptr2pos(pkt, pos), len, 5)) { IPCOM_LOG0(ERR, "ipssh_send_kex_dh_reply :: ipssh_msg2list() failed for f"); goto cleanup; } if(ipssh_send_pkt(ssh_conn, pkt, IP_FALSE) == IPCOM_SUCCESS) { rc = IPCOM_SUCCESS; } cleanup: if(pkt) ipcom_buffer_free(pkt); return rc; } /* *=========================================================================== * ipssh_msg_kex_dh_init *=========================================================================== * Description: * Parameters: * Returns: * */ IP_GLOBAL Ip_err ipssh_msg_kex_dh_init(Ipssh_conn* ssh_conn) { Ip_u8* mp_int_value; Ip_s32 len; #ifdef IP_ASSERT int secr_len; #endif IPCOM_LOG0(DEBUG, "ipssh_msg_kex_dh_init()"); if(ssh_conn->skip_guessed_kex_pkt) { IPCOM_LOG0(DEBUG, "ipssh_msg_kex_dh_init :: skipping guessed packet"); ssh_conn->skip_guessed_kex_pkt = 0; (void)ipssh_consume_pkt(ssh_conn->recv_plain_buf); return IPCOM_SUCCESS; } if (ssh_conn->dh_data->dh) DH_free(ssh_conn->dh_data->dh); ssh_conn->dh_data->dh = ipssh_create_dh_group(ssh_conn->conn_prop_pending.kex_alg[0]); if(!ssh_conn->dh_data->dh) { IPCOM_LOG0(ERR, "ipssh_msg_kex_dh_init :: ipssh_create_dh_group() failed"); return IPCOM_ERR_FAILED; } /* Reduce the size of the DH private key for large DH groups. It is unnecessarily big by * default, which increases the connection time */ if (ipssh_dh_priv_key_set_length(ssh_conn) != IPCOM_SUCCESS) { IPCOM_LOG0(ERR, "ipssh_msg_kex_dh_init :: ipssh_dh_priv_key_set_length() failed"); return IPCOM_ERR_FAILED; } /* Generate random number y and create f = g^y mod p */ if(!DH_generate_key(ssh_conn->dh_data->dh)) { IPCOM_LOG0(ERR, "ipssh_msg_kex_dh_init :: DH_generate_key() failed"); return IPCOM_ERR_FAILED; } /* Get e from KEY_DH_INIT message */ if (ipcom_buffer_consume(ssh_conn->recv_plain_buf, 6) || ipssh_buffer_get_int(ssh_conn->recv_plain_buf, &len)) { IPCOM_LOG0(ERR, "ipssh_msg_kex_dh_init :: Failed to unpack e length"); return IPCOM_ERR_FAILED; } if (len == 0 || len != (Ip_s32)ipcom_buffer_len(ssh_conn->recv_plain_buf)) { IPCOM_LOG1(ERR, "ipssh_msg_kex_dh_init :: Invalid length of e: %d", len); return IPCOM_ERR_FAILED; } mp_int_value = ipcom_buffer_start_ptr(ssh_conn->recv_plain_buf); if (ssh_conn->dh_data->e) BN_free(ssh_conn->dh_data->e); ssh_conn->dh_data->e = BN_bin2bn(mp_int_value, len, IP_NULL); if(!ssh_conn->dh_data->e) { IPCOM_LOG0(ERR, "ipssh_msg_kex_dh_init :: BN_bin2bn() failed for e"); return IPCOM_ERR_FAILED; } (void)ipcom_buffer_consume(ssh_conn->recv_plain_buf, len); if(ipssh_msg2list((Ipssh_msg_list*)&(ssh_conn->handshake_msgs), /* Store e for later hash calculation */ IPSSH_MPINT, mp_int_value, len, 5)) { IPCOM_LOG0(ERR, "ipssh_msg_kex_dh_init :: ipssh_msg2list() failed"); return IPCOM_ERR_FAILED; } /* Calculate the shared secret */ ssh_conn->secr_len = DH_size(ssh_conn->dh_data->dh); #ifdef IP_ASSERT secr_len = ssh_conn->secr_len; #endif if (ssh_conn->shared_secret) ipcom_free(ssh_conn->shared_secret); /* Free old shared secret, re-keying */ ssh_conn->shared_secret = ipcom_malloc(ssh_conn->secr_len+1); if(!ssh_conn->shared_secret) { IPCOM_LOG0(ERR, "ipssh_msg_kex_dh_init :: out of memory"); return IPCOM_ERR_FAILED; } ssh_conn->secr_len = DH_compute_key(ssh_conn->shared_secret, ssh_conn->dh_data->e, ssh_conn->dh_data->dh); if(ssh_conn->secr_len < 0) { IPCOM_LOG0(ERR, "ipssh_msg_kex_dh_init :: DH_compute_key() failed"); return IPCOM_ERR_FAILED; } ip_assert(ssh_conn->secr_len <= secr_len); /* If the shared secret is a negative number, we must add a zero byte */ if (ssh_conn->shared_secret[0] & 0x80) { ipcom_memmove(ssh_conn->shared_secret+1, ssh_conn->shared_secret, ssh_conn->secr_len); ssh_conn->shared_secret[0] = 0; ssh_conn->secr_len++; } if(ipssh_msg2list((Ipssh_msg_list*)&(ssh_conn->handshake_msgs), /* Store shared secret for later hash calculation */ IPSSH_MPINT, ssh_conn->shared_secret, ssh_conn->secr_len, 7)) { IPCOM_LOG0(ERR, "ipssh_msg_kex_dh_init :: ipssh_msg2list() failed"); return IPCOM_ERR_FAILED; } /* Send SSH_MSG_KEXDH_REPLY */ return ipssh_send_kex_dh_reply(ssh_conn); } /* *=========================================================================== * ipssh_msg_kexdh_reply *=========================================================================== * Description: * Parameters: * Returns: * */ IP_GLOBAL Ip_err ipssh_msg_kexdh_reply(Ipssh_conn* ssh_conn) { Ip_u8 pkt_type; EVP_PKEY* srv_pub_key; char* sig_type = IP_NULL; Ip_u8* signature = IP_NULL; Ip_u32 sign_len; Ip_err rc = IPCOM_ERR_FAILED; Ip_u8* ptr; Ip_ptrdiff_t len; union Ip_sockaddr_union peer_addr; Ip_socklen_t peer_len; pkt_type = *ipcom_buffer_ptr2pos(ssh_conn->recv_plain_buf, 5); if(pkt_type != IPSSH_MSG_KEXDH_REPLY) { IPCOM_LOG1(ERR, "ipssh_msg_kexdh_reply :: unexpected pkt type: %d", pkt_type); IP_PANIC(); return IPCOM_ERR_FAILED; } /* Unpack message */ (void)ipcom_buffer_consume(ssh_conn->recv_plain_buf, 6); ptr = ipcom_buffer_start_ptr(ssh_conn->recv_plain_buf); if((srv_pub_key=ipssh_buffer_get_pub_key(ssh_conn->recv_plain_buf)) == IP_NULL) { IPCOM_LOG0(ERR, "ipssh_msg_kexdh_reply :: failed to unpack srv pub key"); goto cleanup; } len = ipcom_buffer_start_ptr(ssh_conn->recv_plain_buf) - ptr; if(ipssh_msg2list((Ipssh_msg_list*)&(ssh_conn->handshake_msgs), IPSSH_RAW, ptr, len, 4)) { IPCOM_LOG0(ERR, "ipssh_msg_kexdh_reply :: ipssh_msg2list() failed"); goto cleanup; } ptr = ipcom_buffer_start_ptr(ssh_conn->recv_plain_buf); if (ssh_conn->dh_data->f) BN_free(ssh_conn->dh_data->f); if(ipssh_buffer_get_ssh_mpint2(ssh_conn->recv_plain_buf, &(ssh_conn->dh_data->f)) != IPCOM_SUCCESS) { IPCOM_LOG0(ERR, "ipssh_msg_kexdh_reply :: failed to unpack f"); goto cleanup; } ptr += 4; /* Skip length field */ len = ipcom_buffer_start_ptr(ssh_conn->recv_plain_buf) - ptr; if(ipssh_msg2list((Ipssh_msg_list*)&(ssh_conn->handshake_msgs), IPSSH_MPINT, ptr, len, 6)) { IPCOM_LOG0(ERR, "ipssh_msg_kexdh_reply :: ipssh_msg2list() failed for f"); goto cleanup; } /* Calculate the shared secret */ ssh_conn->secr_len = DH_size(ssh_conn->dh_data->dh); ssh_conn->shared_secret = ipcom_malloc(ssh_conn->secr_len+1); if(!ssh_conn->shared_secret) { IPCOM_LOG0(ERR, "ipssh_msg_kexdh_reply :: out of memory"); return IPCOM_ERR_FAILED; } ssh_conn->secr_len = DH_compute_key(ssh_conn->shared_secret, ssh_conn->dh_data->f, ssh_conn->dh_data->dh); if(ssh_conn->secr_len < 0) { IPCOM_LOG0(ERR, "ipssh_msg_kexdh_reply :: DH_compute_key() failed"); return IPCOM_ERR_FAILED; } /* If the shared secret is a negative number, we must add a zero byte */ if (ssh_conn->shared_secret[0] & 0x80) { ipcom_memmove(ssh_conn->shared_secret+1, ssh_conn->shared_secret, ssh_conn->secr_len); ssh_conn->shared_secret[0] = 0; ssh_conn->secr_len++; } if(ipssh_msg2list((Ipssh_msg_list*)&(ssh_conn->handshake_msgs), /* Store shared secret for later hash calculation */ IPSSH_MPINT, ssh_conn->shared_secret, ssh_conn->secr_len, 7)) { IPCOM_LOG0(ERR, "ipssh_msg_kexdh_reply :: ipssh_msg2list() failed"); return IPCOM_ERR_FAILED; } if(ipcom_buffer_consume(ssh_conn->recv_plain_buf, 4)) /* Skip blob len field */ return IPCOM_ERR_FAILED; sig_type=ipssh_buffer_get_sshstr(ssh_conn->recv_plain_buf); signature=ipssh_buffer_get_sshbuf(ssh_conn->recv_plain_buf, &sign_len); if(!sig_type || !signature) { IPCOM_LOG0(ERR, "ipssh_msg_kexdh_reply :: failed to unpack signature"); goto cleanup; } /* Calculate hash */ if (ssh_conn->hash) ipcom_free(ssh_conn->hash); if (ipssh_calc_hash((Ipssh_msg_list*)&(ssh_conn->handshake_msgs), ssh_conn->hash_func, 8, &(ssh_conn->hash), &(ssh_conn->hash_len))) { IPCOM_LOG0(ERR, "ipssh_msg_kexdh_reply :: ipssh_calc_hash() failed"); goto cleanup; } /* Verify server key */ ipcom_memset(&peer_addr, 0, sizeof(peer_addr)); peer_len = sizeof(peer_addr); if(ipcom_getpeername(ssh_conn->sock, &peer_addr.sa, &peer_len) != IPCOM_SUCCESS) { IPCOM_LOG0(ERR, "ipssh_msg_kexdh_reply :: ipcom_getpeername() failed"); goto cleanup; } if(ipssh_validate_srv_public_key_cb(srv_pub_key, &peer_addr) != IPCOM_SUCCESS) { IPCOM_LOG0(ERR, "ipssh_msg_kexdh_reply :: ipssh_validate_srv_public_key_cb() failed"); goto cleanup; } /* Verify signature */ if(ipssh_verify_signature(srv_pub_key, ssh_conn->hash, ssh_conn->hash_len, sig_type, signature, sign_len) != IPCOM_SUCCESS) { IPCOM_LOG0(ERR, "ipssh_msg_kexdh_reply :: ipssh_verify_signature() failed"); goto cleanup; } if(ssh_conn->sess_id_len == 0) { ssh_conn->sess_id = ipcom_malloc(ssh_conn->hash_len); if(!ssh_conn->sess_id) { IPCOM_LOG0(ERR, "ipssh_msg_kexdh_reply :: out of memory"); goto cleanup; } ssh_conn->sess_id_len = ssh_conn->hash_len; ipcom_memcpy(ssh_conn->sess_id, ssh_conn->hash, ssh_conn->sess_id_len); } if(ipssh_compute_keys(ssh_conn)) { IPCOM_LOG0(ERR, "ipssh_msg_kexdh_reply :: ipssh_compute_keys() failed"); goto cleanup; } if(ipssh_send_new_key_msg(ssh_conn)) { IPCOM_LOG0(ERR, "ipssh_msg_kexdh_reply :: ipssh_send_new_key() failed"); goto cleanup; } rc = IPCOM_SUCCESS; cleanup: if(srv_pub_key) EVP_PKEY_free(srv_pub_key); if(sig_type) ipcom_free(sig_type); if(signature) ipcom_free(signature); return rc; } /* *=========================================================================== * ipssh_do_keyex_dh_clt *=========================================================================== * Description: * Parameters: * Returns: * */ IP_GLOBAL Ip_err ipssh_do_keyex_dh_clt(Ipssh_conn* ssh_conn) { Ip_err rc = IPCOM_ERR_FAILED; if(ipssh_send_kex_dh_init(ssh_conn) != IPCOM_SUCCESS) { IPCOM_LOG0(ERR, "ipssh_do_keyex_dh_clt :: ipssh_send_kex_dh_init() failed"); } if(ipssh_read_v2_pkt(ssh_conn) != IPCOM_SUCCESS) goto cleanup; if(ipssh_msg_kexdh_reply(ssh_conn) != IPCOM_SUCCESS) goto cleanup; if(ipssh_read_v2_pkt(ssh_conn) != IPCOM_SUCCESS) goto cleanup; if(ipssh_msg_new_key(ssh_conn) != IPCOM_SUCCESS) goto cleanup; rc = IPCOM_SUCCESS; cleanup: return rc; } /* **************************************************************************** * END OF FILE **************************************************************************** */