aboutsummaryrefslogtreecommitdiff
path: root/libssh/kex.c
diff options
context:
space:
mode:
Diffstat (limited to 'libssh/kex.c')
-rw-r--r--libssh/kex.c439
1 files changed, 439 insertions, 0 deletions
diff --git a/libssh/kex.c b/libssh/kex.c
new file mode 100644
index 00000000..4a8c30be
--- /dev/null
+++ b/libssh/kex.c
@@ -0,0 +1,439 @@
+/* kex.c is used well, in key exchange :-) */
+/*
+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. */
+
+#include <string.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include "libssh/priv.h"
+#include "libssh/ssh2.h"
+#include "libssh/ssh1.h"
+
+#ifdef HAVE_OPENSSL_BLOWFISH_H
+#define BLOWFISH "blowfish-cbc,"
+#else
+#define BLOWFISH ""
+#endif
+#ifdef HAVE_OPENSSL_AES_H
+#define AES "aes256-cbc,aes192-cbc,aes128-cbc,"
+#else
+#define AES ""
+#endif
+
+#define DES "3des-cbc,"
+#ifdef HAVE_LIBZ
+#define ZLIB "none,zlib"
+#else
+#define ZLIB "none"
+#endif
+char *default_methods[]={
+ "diffie-hellman-group1-sha1","ssh-dss,ssh-rsa",AES BLOWFISH DES,AES BLOWFISH
+ DES, "hmac-sha1","hmac-sha1","none","none","","",NULL };
+char *supported_methods[]={
+ "diffie-hellman-group1-sha1","ssh-dss,ssh-rsa",AES BLOWFISH DES,AES BLOWFISH
+ DES, "hmac-sha1","hmac-sha1",ZLIB,ZLIB,"","",NULL };
+/* descriptions of the key exchange packet */
+char *ssh_kex_nums[]={
+ "kex algos","server host key algo","encryption client->server","encryption server->client",
+ "mac algo client->server","mac algo server->client","compression algo client->server",
+ "compression algo server->client","languages client->server","languages server->client",NULL};
+
+/* tokenize will return a token of strings delimited by ",". the first element has to be freed */
+static char **tokenize(char *chain){
+ char **tokens;
+ int n=1;
+ int i=0;
+ char *ptr=chain=strdup(chain);
+ while(*ptr){
+ if(*ptr==','){
+ n++;
+ *ptr=0;
+ }
+ ptr++;
+ }
+ /* now n contains the number of tokens, the first possibly empty if the list was empty too e.g. "" */
+ tokens=malloc(sizeof(char *) * (n+1) ); /* +1 for the null */
+ ptr=chain;
+ for(i=0;i<n;i++){
+ tokens[i]=ptr;
+ while(*ptr)
+ ptr++; // find a zero
+ ptr++; // then go one step further
+ }
+ tokens[i]=NULL;
+ return tokens;
+}
+
+/* same as tokenize(), but with spaces instead of ',' */
+char **space_tokenize(char *chain){
+ char **tokens;
+ int n=1;
+ int i=0;
+ char *ptr=chain=strdup(chain);
+ while(*ptr==' ')
+ ++ptr; /* skip initial spaces */
+ while(*ptr){
+ if(*ptr==' '){
+ n++; /* count one token per word */
+ *ptr=0;
+ while(*(ptr+1)==' '){ /* don't count if the tokens have more than 2 spaces */
+ *(ptr++)=0;
+ }
+ }
+ ptr++;
+ }
+ /* now n contains the number of tokens, the first possibly empty if the list was empty too e.g. "" */
+ tokens=malloc(sizeof(char *) * (n+1) ); /* +1 for the null */
+ ptr=chain; /* we don't pass the initial spaces because the "chain" pointer is needed by the caller */
+ /* function to free the tokens. */
+ for(i=0;i<n;i++){
+ tokens[i]=ptr;
+ if(i!=n-1){
+ while(*ptr)
+ ptr++; // find a zero
+ while(!*(ptr+1))
+ ++ptr; /* if the zero is followed by other zeros, go through them */
+ ptr++; // then go one step further
+ }
+ }
+ tokens[i]=NULL;
+ return tokens;
+}
+
+/* find_matching gets 2 parameters : a list of available objects (in_d), separated by colons,*/
+/* and a list of prefered objects (what_d) */
+/* it will return a strduped pointer on the first prefered object found in the available objects list */
+
+static char *find_matching(char *in_d, char *what_d){
+ char ** tok_in, **tok_what;
+ int i_in, i_what;
+ char *ret;
+
+ if( ! (in_d && what_d))
+ return NULL; /* don't deal with null args */
+ ssh_say(3,"find_matching(\"%s\",\"%s\") = ",in_d,what_d);
+ tok_in=tokenize(in_d);
+ tok_what=tokenize(what_d);
+ for(i_in=0; tok_in[i_in]; ++i_in){
+ for(i_what=0; tok_what[i_what] ; ++i_what){
+ if(!strcmp(tok_in[i_in],tok_what[i_what])){
+ /* match */
+ ssh_say(3,"\"%s\"\n",tok_in[i_in]);
+ ret=strdup(tok_in[i_in]);
+ /* free the tokens */
+ free(tok_in[0]);
+ free(tok_what[0]);
+ free(tok_in);
+ free(tok_what);
+ return ret;
+ }
+ }
+ }
+ ssh_say(3,"NULL\n");
+ free(tok_in[0]);
+ free(tok_what[0]);
+ free(tok_in);
+ free(tok_what);
+ return NULL;
+}
+
+int ssh_get_kex(SSH_SESSION *session,int server_kex ){
+ STRING *str;
+ char *strings[10];
+ int i;
+ if(packet_wait(session,SSH2_MSG_KEXINIT,1))
+ return -1;
+ if(buffer_get_data(session->in_buffer,session->server_kex.cookie,16)!=16){
+ ssh_set_error(session,SSH_FATAL,"get_kex(): no cookie in packet");
+ return -1;
+ }
+ hashbufin_add_cookie(session,session->server_kex.cookie);
+ memset(strings,0,sizeof(char *)*10);
+ for(i=0;i<10;++i){
+ str=buffer_get_ssh_string(session->in_buffer);
+ if(!str)
+ break;
+ if(str){
+ buffer_add_ssh_string(session->in_hashbuf,str);
+ strings[i]=string_to_char(str);
+ free(str);
+ } else
+ strings[i]=NULL;
+ }
+ /* copy the server kex info into an array of strings */
+ if(server_kex){
+ session->client_kex.methods=malloc( 10 * sizeof(char **));
+ for(i=0;i<10;++i)
+ session->client_kex.methods[i]=strings[i];
+ } else { // client
+ session->server_kex.methods=malloc( 10 * sizeof(char **));
+ for(i=0;i<10;++i)
+ session->server_kex.methods[i]=strings[i];
+ }
+ return 0;
+}
+
+void list_kex(KEX *kex){
+ int i=0;
+#ifdef DEBUG_CRYPTO
+ ssh_print_hexa("session cookie",kex->cookie,16);
+#endif
+ for(i=0;i<10;i++){
+ ssh_say(2,"%s : %s\n",ssh_kex_nums[i],kex->methods[i]);
+ }
+}
+
+/* set_kex basicaly look at the option structure of the session and set the output kex message */
+/* it must be aware of the server kex message */
+/* it can fail if option is null, not any user specified kex method matches the server one, if not any default kex matches */
+
+int set_kex(SSH_SESSION *session){
+ KEX *server = &session->server_kex;
+ KEX *client=&session->client_kex;
+ SSH_OPTIONS *options=session->options;
+ int i;
+ char *wanted;
+ /* the client might ask for a specific cookie to be sent. useful for server debugging */
+ if(options->wanted_cookie)
+ memcpy(client->cookie,options->wanted_cookie,16);
+ else
+ ssh_get_random(client->cookie,16);
+ client->methods=malloc(10 * sizeof(char **));
+ memset(client->methods,0,10*sizeof(char **));
+ for (i=0;i<10;i++){
+ if(!(wanted=options->wanted_methods[i]))
+ wanted=default_methods[i];
+ client->methods[i]=find_matching(server->methods[i],wanted);
+ if(!client->methods[i] && i < SSH_LANG_C_S){
+ ssh_set_error(session,SSH_FATAL,"kex error : did not find one of algos %s in list %s for %s",
+ wanted,server->methods[i],ssh_kex_nums[i]);
+ return -1;
+ } else {
+ if(i>=SSH_LANG_C_S && !client->methods[i])
+ client->methods[i]=strdup(""); // we can safely do that for languages
+ }
+ }
+ return 0;
+}
+
+/* this function only sends the predefined set of kex methods */
+void send_kex(SSH_SESSION *session, int server_kex){
+ STRING *str;
+ int i=0;
+ KEX *kex=(server_kex ? &session->server_kex : &session->client_kex);
+ packet_clear_out(session);
+ buffer_add_u8(session->out_buffer,SSH2_MSG_KEXINIT);
+ buffer_add_data(session->out_buffer,kex->cookie,16);
+ hashbufout_add_cookie(session);
+ list_kex(kex);
+ for(i=0;i<10;i++){
+ str=string_from_char(kex->methods[i]);
+ buffer_add_ssh_string(session->out_hashbuf,str);
+ buffer_add_ssh_string(session->out_buffer,str);
+ free(str);
+ }
+ i=0;
+ buffer_add_u8(session->out_buffer,0);
+ buffer_add_u32(session->out_buffer,0);
+ packet_send(session);
+}
+
+/* returns 1 if at least one of the name algos is in the default algorithms table */
+int verify_existing_algo(int algo, char *name){
+ char *ptr;
+ if(algo>9 || algo <0)
+ return -1;
+ ptr=find_matching(supported_methods[algo],name);
+ if(ptr){
+ free(ptr);
+ return 1;
+ }
+ return 0;
+}
+
+/* makes a STRING contating 3 strings : ssh-rsa1,e and n */
+/* this is a public key in openssh's format */
+static STRING *make_rsa1_string(STRING *e, STRING *n){
+ BUFFER *buffer=buffer_new();
+ STRING *rsa=string_from_char("ssh-rsa1");
+ STRING *ret;
+ buffer_add_ssh_string(buffer,rsa);
+ free(rsa);
+ buffer_add_ssh_string(buffer,e);
+ buffer_add_ssh_string(buffer,n);
+ ret=string_new(buffer_get_len(buffer));
+ string_fill(ret,buffer_get(buffer),buffer_get_len(buffer));
+ buffer_free(buffer);
+ return ret;
+}
+
+static void build_session_id1(SSH_SESSION *session, STRING *servern,
+ STRING *hostn){
+ MD5CTX *md5=md5_init();
+ ssh_print_hexa("host modulus",hostn->string,string_len(hostn));
+ ssh_print_hexa("server modulus",servern->string,string_len(servern));
+ md5_update(md5,hostn->string,string_len(hostn));
+ md5_update(md5,servern->string,string_len(servern));
+ md5_update(md5,session->server_kex.cookie,8);
+ md5_final(session->next_crypto->session_id,md5);
+ ssh_print_hexa("session_id",session->next_crypto->session_id,MD5_DIGEST_LEN);
+}
+
+STRING *encrypt_session_key(SSH_SESSION *session, PUBLIC_KEY *svrkey,
+ PUBLIC_KEY *hostkey){
+ char buffer[32];
+ int i;
+ STRING *data1,*data2;
+ /* first, generate a session key */
+
+ ssh_get_random(session->next_crypto->encryptkey,32);
+ memcpy(buffer,session->next_crypto->encryptkey,32);
+ memcpy(session->next_crypto->decryptkey,
+ session->next_crypto->encryptkey,32);
+ ssh_print_hexa("session key",buffer,32);
+ /* xor session key with session_id */
+ for (i=0;i<16;++i)
+ buffer[i]^=session->next_crypto->session_id[i];
+ data1=string_new(32);
+ string_fill(data1,buffer,32);
+ data2=ssh_encrypt_rsa1(session,data1,svrkey);
+ free(data1);
+ data1=ssh_encrypt_rsa1(session,data2,hostkey);
+ return data1;
+}
+
+
+/* SSH-1 functions */
+/* 2 SSH_SMSG_PUBLIC_KEY
+ *
+ * 8 bytes anti_spoofing_cookie
+ * 32-bit int server_key_bits
+ * mp-int server_key_public_exponent
+ * mp-int server_key_public_modulus
+ * 32-bit int host_key_bits
+ * mp-int host_key_public_exponent
+ * mp-int host_key_public_modulus
+ * 32-bit int protocol_flags
+ * 32-bit int supported_ciphers_mask
+ * 32-bit int supported_authentications_mask
+ */
+
+int ssh_get_kex1(SSH_SESSION *session){
+ u32 server_bits, host_bits, protocol_flags,
+ supported_ciphers_mask, supported_authentications_mask;
+ STRING *server_exp=NULL;
+ STRING *server_mod=NULL;
+ STRING *host_exp=NULL;
+ STRING *host_mod=NULL;
+ STRING *serverkey;
+ STRING *hostkey;
+ STRING *enc_session;
+ PUBLIC_KEY *svr,*host;
+ int ko;
+ u16 bits;
+ ssh_say(3,"Waiting for a SSH_SMSG_PUBLIC_KEY\n");
+ if(packet_wait(session,SSH_SMSG_PUBLIC_KEY,1)){
+ return -1;
+ }
+ ssh_say(3,"Got a SSH_SMSG_PUBLIC_KEY\n");
+ if(buffer_get_data(session->in_buffer,session->server_kex.cookie,8)!=8){
+ ssh_set_error(NULL,SSH_FATAL,"Can't get cookie in buffer");
+ return -1;
+ }
+ buffer_get_u32(session->in_buffer,&server_bits);
+ server_exp=buffer_get_mpint(session->in_buffer);
+ server_mod=buffer_get_mpint(session->in_buffer);
+ buffer_get_u32(session->in_buffer,&host_bits);
+ host_exp=buffer_get_mpint(session->in_buffer);
+ host_mod=buffer_get_mpint(session->in_buffer);
+ buffer_get_u32(session->in_buffer,&protocol_flags);
+ buffer_get_u32(session->in_buffer,&supported_ciphers_mask);
+ ko=buffer_get_u32(session->in_buffer,&supported_authentications_mask);
+ if((ko!=sizeof(u32)) || !host_mod || !host_exp || !server_mod || !server_exp){
+ ssh_say(2,"Invalid SSH_SMSG_PUBLIC_KEY packet\n");
+ ssh_set_error(NULL,SSH_FATAL,"Invalid SSH_SMSG_PUBLIC_KEY packet");
+ if(host_mod)
+ free(host_mod);
+ if(host_exp)
+ free(host_exp);
+ if(server_mod)
+ free(server_mod);
+ if(server_exp)
+ free(server_exp);
+ return -1;
+ }
+ server_bits=ntohl(server_bits);
+ host_bits=ntohl(host_bits);
+ protocol_flags=ntohl(protocol_flags);
+ supported_ciphers_mask=ntohl(supported_ciphers_mask);
+ supported_authentications_mask=ntohl(supported_authentications_mask);
+ ssh_say(1,"server bits: %d ; host bits: %d\nProtocol flags : %.8lx ; "
+ "cipher mask : %.8lx ; auth mask: %.8lx\n",server_bits,
+ host_bits,protocol_flags,supported_ciphers_mask,
+ supported_authentications_mask);
+ serverkey=make_rsa1_string(server_exp,server_mod);
+ hostkey=make_rsa1_string(host_exp,host_mod);
+ build_session_id1(session,server_mod,host_mod);
+ free(server_exp);
+ free(server_mod);
+ free(host_exp);
+ free(host_mod);
+ svr=publickey_from_string(serverkey);
+ host=publickey_from_string(hostkey);
+ session->next_crypto->server_pubkey=string_copy(hostkey);
+ session->next_crypto->server_pubkey_type="ssh-rsa1";
+
+ /* now, we must choose an encryption algo */
+ /* hardcode 3des */
+ if(!(supported_ciphers_mask & (1<<SSH_CIPHER_3DES))){
+ ssh_set_error(NULL,SSH_FATAL,"Remote server doesn't accept 3des");
+ return -1;
+ }
+ packet_clear_out(session);
+ buffer_add_u8(session->out_buffer,SSH_CMSG_SESSION_KEY);
+ buffer_add_u8(session->out_buffer,SSH_CIPHER_3DES);
+ buffer_add_data(session->out_buffer,session->server_kex.cookie,8);
+
+ enc_session=encrypt_session_key(session,svr,host);
+ bits=string_len(enc_session)*8 - 7;
+ bits=htons(bits);
+ /* the encrypted mpint */
+ buffer_add_data(session->out_buffer,&bits,sizeof(u16));
+ buffer_add_data(session->out_buffer,enc_session->string,
+ string_len(enc_session));
+ /* the protocol flags */
+ buffer_add_u32(session->out_buffer,0);
+
+ packet_send(session);
+ /* we can set encryption */
+ if(crypt_set_algorithms(session))
+ return -1;
+ session->current_crypto=session->next_crypto;
+ session->next_crypto=NULL;
+ if(packet_wait(session,SSH_SMSG_SUCCESS,1)){
+ printf("qqchose a merdé: %s\n",ssh_get_error(session));
+ exit(1);
+ return -1;
+ }
+ ssh_say(1,"received SSH_SMSG_SUCCESS\n");
+ return 0;
+
+}
+