diff options
Diffstat (limited to 'libssh/dh.c')
-rw-r--r-- | libssh/dh.c | 412 |
1 files changed, 412 insertions, 0 deletions
diff --git a/libssh/dh.c b/libssh/dh.c new file mode 100644 index 00000000..25bf5ae6 --- /dev/null +++ b/libssh/dh.c @@ -0,0 +1,412 @@ +/* dh.c */ +/* this file contains usefull stuff for Diffie helman algorithm against SSH 2 */ +/* +Copyright 2003 Aris Adamantiadis + +This file is part of the SSH Library + +The SSH Library is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or (at your +option) any later version. + +The SSH Library is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with the SSH Library; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, +MA 02111-1307, USA. */ + +/* Let us resume the dh protocol. */ +/* Each side computes a private prime number, x at client side, y at server side. */ +/* g and n are two numbers common to every ssh software. */ +/* client's public key (e) is calculated by doing */ +/* e = g^x mod p */ +/* client sents e to the server . */ +/* the server computes his own public key, f */ +/* f = g^y mod p */ +/* it sents it to the client */ +/* the common key K is calculated by the client by doing */ +/* k = f^x mod p */ +/* the server does the same with the client public key e */ +/* k' = e^y mod p */ +/* if everything went correctly, k and k' are equal */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <netdb.h> +#include "libssh/priv.h" + +#include <openssl/rand.h> +#include <openssl/evp.h> +#include <openssl/err.h> +#include <string.h> +#include "libssh/crypto.h" +static unsigned char p_value[] = { + 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}; +#define P_LEN 128 /* Size in bytes of the p number */ + +static unsigned long g_int = 2 ; /* G is defined as 2 by the ssh2 standards */ +static bignum g; +static bignum p; + +/* maybe it might be enhanced .... */ +/* XXX Do it. */ +void ssh_get_random(void *where, int len){ + static int rndfd=0; + if(!rndfd){ + rndfd=open("/dev/urandom",O_RDONLY); + if(rndfd<0){ + fprintf(stderr,"Can't open /dev/urandom\n"); + exit(-1); + } + } + read(rndfd,where,len); +} + +/* it inits the values g and p which are used for DH key agreement */ +void ssh_crypto_init(){ + static int init=0; + if(!init){ + g=bignum_new(); + bignum_set_word(g,g_int); + p=bignum_new(); + bignum_bin2bn(p_value,P_LEN,p); + init++; + } +} + +/* prints the bignum on stderr */ +void ssh_print_bignum(char *which,bignum num){ + char *hex; + fprintf(stderr,"%s value: ",which); + hex=bignum_bn2hex(num); + fprintf(stderr,"%s\n",hex); + free(hex); +} + +void ssh_print_hexa(char *descr,unsigned char *what, int len){ + int i; + printf("%s : ",descr); + for(i=0;i<len-1;i++) + printf("%.2hhx:",what[i]); + printf("%.2hhx\n",what[i]); +} + +void dh_generate_x(SSH_SESSION *session){ + session->next_crypto->x=bignum_new(); + bignum_rand(session->next_crypto->x,128,0,-1); + /* not harder than this */ +#ifdef DEBUG_CRYPTO + ssh_print_bignum("x",session->next_crypto->x); +#endif +} + +void dh_generate_e(SSH_SESSION *session){ + bignum_CTX ctx=bignum_ctx_new(); + session->next_crypto->e=bignum_new(); + bignum_mod_exp(session->next_crypto->e,g,session->next_crypto->x,p,ctx); +#ifdef DEBUG_CRYPTO + ssh_print_bignum("e",session->next_crypto->e); +#endif + bignum_ctx_free(ctx); +} + + +STRING *make_bignum_string(bignum num){ + STRING *ptr; + int pad=0; + int len=bignum_num_bytes(num); + int bits=bignum_num_bits(num); + int finallen; + /* remember if the fist bit is set, it is considered as a negative number. so 0's must be appended */ + if(!(bits%8) && bignum_is_bit_set(num,bits-1)) + pad++; + ssh_say(3,"%d bits, %d bytes, %d padding\n",bits,len,pad); + ptr=malloc(4 + len + pad); + ptr->size=htonl(len+pad); + if(pad) + ptr->string[0]=0; + finallen=bignum_bn2bin(num,ptr->string+pad); + return ptr; +} + +bignum make_string_bn(STRING *string){ + int len=ntohl(string->size); + ssh_say(3,"Importing a %d bits,%d bytes object ...\n",len*8,len); + return bignum_bin2bn(string->string,len,NULL); +} + +STRING *dh_get_e(SSH_SESSION *session){ + return make_bignum_string(session->next_crypto->e); +} + +void dh_import_pubkey(SSH_SESSION *session,STRING *pubkey_string){ + session->next_crypto->server_pubkey=pubkey_string; +} + +void dh_import_f(SSH_SESSION *session,STRING *f_string){ + session->next_crypto->f=make_string_bn(f_string); +#ifdef DEBUG_CRYPTO + ssh_print_bignum("f",session->next_crypto->f); +#endif +} + +void dh_build_k(SSH_SESSION *session){ + bignum_CTX ctx=bignum_ctx_new(); + session->next_crypto->k=bignum_new(); + bignum_mod_exp(session->next_crypto->k,session->next_crypto->f,session->next_crypto->x,p,ctx); +#ifdef DEBUG_CRYPTO + ssh_print_bignum("shared secret key",session->next_crypto->k); +#endif + bignum_ctx_free(ctx); +} + +static void sha_add(STRING *str,SHACTX *ctx){ + sha1_update(ctx,str,string_len(str)+4); +} + +void make_sessionid(SSH_SESSION *session){ + SHACTX *ctx; + STRING *num,*str; + int len; + ctx=sha1_init(); + + str=string_from_char(session->clientbanner); + sha_add(str,ctx); + free(str); + + str=string_from_char(session->serverbanner); + sha_add(str,ctx); + free(str); + + buffer_add_u32(session->in_hashbuf,0); + buffer_add_u8(session->in_hashbuf,0); + buffer_add_u32(session->out_hashbuf,0); + buffer_add_u8(session->out_hashbuf,0); + + len=ntohl(buffer_get_len(session->out_hashbuf)); + sha1_update(ctx,&len,4); + + sha1_update(ctx,buffer_get(session->out_hashbuf),buffer_get_len(session->out_hashbuf)); + buffer_free(session->out_hashbuf); + session->out_hashbuf=NULL; + + len=ntohl(buffer_get_len(session->in_hashbuf)); + sha1_update(ctx,&len,4); + + sha1_update(ctx,buffer_get(session->in_hashbuf),buffer_get_len(session->in_hashbuf)); + buffer_free(session->in_hashbuf); + session->in_hashbuf=NULL; + sha1_update(ctx,session->next_crypto->server_pubkey,len=(string_len(session->next_crypto->server_pubkey)+4)); + num=make_bignum_string(session->next_crypto->e); + sha1_update(ctx,num,len=(string_len(num)+4)); + free(num); + num=make_bignum_string(session->next_crypto->f); + sha1_update(ctx,num,len=(string_len(num)+4)); + free(num); + num=make_bignum_string(session->next_crypto->k); + sha1_update(ctx,num,len=(string_len(num)+4)); + free(num); + sha1_final(session->next_crypto->session_id,ctx); + +#ifdef DEBUG_CRYPTO + printf("Session hash : "); + ssh_print_hexa("session id",session->next_crypto->session_id,SHA_DIGEST_LENGTH); +#endif +} + +void hashbufout_add_cookie(SSH_SESSION *session){ + session->out_hashbuf=buffer_new(); + buffer_add_u8(session->out_hashbuf,20); + buffer_add_data(session->out_hashbuf,session->client_kex.cookie,16); +} + + +void hashbufin_add_cookie(SSH_SESSION *session,unsigned char *cookie){ + session->in_hashbuf=buffer_new(); + buffer_add_u8(session->in_hashbuf,20); + buffer_add_data(session->in_hashbuf,cookie,16); +} + +static void generate_one_key(STRING *k,char session_id[SHA_DIGEST_LENGTH],char output[SHA_DIGEST_LENGTH],char letter){ + SHACTX *ctx=sha1_init(); + sha1_update(ctx,k,string_len(k)+4); + sha1_update(ctx,session_id,SHA_DIGEST_LENGTH); + sha1_update(ctx,&letter,1); + sha1_update(ctx,session_id,SHA_DIGEST_LENGTH); + sha1_final(output,ctx); +} + +void generate_session_keys(SSH_SESSION *session){ + STRING *k_string; + SHACTX *ctx; + k_string=make_bignum_string(session->next_crypto->k); + + /* IV */ + generate_one_key(k_string,session->next_crypto->session_id,session->next_crypto->encryptIV,'A'); + generate_one_key(k_string,session->next_crypto->session_id,session->next_crypto->decryptIV,'B'); + + generate_one_key(k_string,session->next_crypto->session_id,session->next_crypto->encryptkey,'C'); + + /* some ciphers need more than 20 bytes of input key */ + if(session->next_crypto->out_cipher->keylen > SHA_DIGEST_LENGTH*8){ + ctx=sha1_init(); + sha1_update(ctx,k_string,string_len(k_string)+4); + sha1_update(ctx,session->next_crypto->session_id,SHA_DIGEST_LENGTH); + sha1_update(ctx,session->next_crypto->encryptkey,SHA_DIGEST_LENGTH); + sha1_final(session->next_crypto->encryptkey+SHA_DIGEST_LEN,ctx); + } + + generate_one_key(k_string,session->next_crypto->session_id,session->next_crypto->decryptkey,'D'); + + if(session->next_crypto->in_cipher->keylen > SHA_DIGEST_LENGTH*8){ + ctx=sha1_init(); + sha1_update(ctx,k_string,string_len(k_string)+4); + sha1_update(ctx,session->next_crypto->session_id,SHA_DIGEST_LENGTH); + sha1_update(ctx,session->next_crypto->decryptkey,SHA_DIGEST_LENGTH); + sha1_final(session->next_crypto->decryptkey+SHA_DIGEST_LEN,ctx); + } + + generate_one_key(k_string,session->next_crypto->session_id,session->next_crypto->encryptMAC,'E'); + generate_one_key(k_string,session->next_crypto->session_id,session->next_crypto->decryptMAC,'F'); + +#ifdef DEBUG_CRYPTO + ssh_print_hexa("client->server IV",session->next_crypto->encryptIV,SHA_DIGEST_LENGTH); + ssh_print_hexa("server->client IV",session->next_crypto->decryptIV,SHA_DIGEST_LENGTH); + ssh_print_hexa("encryption key",session->next_crypto->encryptkey,16); + ssh_print_hexa("decryption key",session->next_crypto->decryptkey,16); + ssh_print_hexa("Encryption MAC",session->next_crypto->encryptMAC,SHA_DIGEST_LENGTH); + ssh_print_hexa("Decryption MAC",session->next_crypto->decryptMAC,20); +#endif + free(k_string); +} + +int ssh_get_pubkey_hash(SSH_SESSION *session,char hash[MD5_DIGEST_LEN]){ + STRING *pubkey=session->current_crypto->server_pubkey; + MD5CTX *ctx; + int len=string_len(pubkey); + + ctx=md5_init(); + md5_update(ctx,pubkey->string,len); + md5_final(hash,ctx); + return MD5_DIGEST_LEN; +} + +int pubkey_get_hash(SSH_SESSION *session, char hash[MD5_DIGEST_LEN]){ + return ssh_get_pubkey_hash(session,hash); +} + +STRING *ssh_get_pubkey(SSH_SESSION *session){ + return string_copy(session->current_crypto->server_pubkey); +} + +/* XXX i doubt it is still needed, or may need some fix */ +static int match(char *group,char *object){ + char *ptr,*saved; + char *end; + ptr=strdup(group); + saved=ptr; + while(1){ + end=strchr(ptr,','); + if(end) + *end=0; + if(!strcmp(ptr,object)){ + free(saved); + return 0; + } + if(end) + ptr=end+1; + else{ + free(saved); + return -1; + } + } + /* not reached */ + return 1; +} + +static int sig_verify(SSH_SESSION *session, PUBLIC_KEY *pubkey, SIGNATURE *signature, + char *digest){ + int valid=0; + char hash[SHA_DIGEST_LENGTH]; + sha1(digest,SHA_DIGEST_LENGTH,hash); + switch(pubkey->type){ + case TYPE_DSS: + valid=DSA_do_verify(hash,SHA_DIGEST_LENGTH,signature->dsa_sign, + pubkey->dsa_pub); + if(valid==1) + return 0; + if(valid==-1){ + ssh_set_error(session,SSH_FATAL,"DSA error : %s",ERR_error_string(ERR_get_error(),NULL)); + return -1; + } + ssh_set_error(session,SSH_FATAL,"Invalid DSA signature"); + return -1; + case TYPE_RSA: + case TYPE_RSA1: + valid=RSA_verify(NID_sha1,hash,SHA_DIGEST_LENGTH, + signature->rsa_sign->string,string_len(signature->rsa_sign),pubkey->rsa_pub); + if(valid==1) + return 0; + if(valid==-1){ + ssh_set_error(session,SSH_FATAL,"RSA error : %s",ERR_error_string(ERR_get_error(),NULL)); + return -1; + } + ssh_set_error(session,SSH_FATAL,"Invalid RSA signature"); + return -1; + default: + ssh_set_error(session,SSH_FATAL,"Unknown public key type"); + return -1; + } +return -1; +} + + +int signature_verify(SSH_SESSION *session,STRING *signature){ + PUBLIC_KEY *pubkey; + SIGNATURE *sign; + int err; + if(session->options->dont_verify_hostkey){ + ssh_say(1,"Host key wasn't verified\n"); + return 0; + } + pubkey=publickey_from_string(session->next_crypto->server_pubkey); + if(!pubkey) + return -1; + if(session->options->wanted_methods[SSH_HOSTKEYS]){ + if(match(session->options->wanted_methods[SSH_HOSTKEYS],pubkey->type_c)){ + ssh_set_error(session,SSH_FATAL,"Public key from server (%s) doesn't match user preference (%s)", + pubkey->type,session->options->wanted_methods[SSH_HOSTKEYS]); + publickey_free(pubkey); + return -1; + } + } + sign=signature_from_string(signature,pubkey,pubkey->type); + if(!sign){ + ssh_set_error(session,SSH_FATAL,"Invalid signature blob"); + publickey_free(pubkey); + return -1; + } + ssh_say(1,"Going to verify a %s type signature\n",pubkey->type_c); + err=sig_verify(session,pubkey,sign,session->next_crypto->session_id); + signature_free(sign); + session->next_crypto->server_pubkey_type=pubkey->type_c; + publickey_free(pubkey); + return err; +} |