#include #include #include extern const char *__progname; #include "pp.h" #include "bpp.h" #include "rnd.h" #include "str.h" #include "algs.h" #include "msgs.h" #include "cmdline.h" #include "pkt-util.h" #include "stdio-util.h" #include "transport.h" typedef struct transport_state TRANSPORT_STATE; typedef struct kex_state KEX_STATE; struct transport_state { int first_kex; int whoami; #define WHOAMI_CLIENT 1 #define WHOAMI_SERVER 2 int stage; #define TSS_REKEY 1 #define TSS_SENT_KEXINIT 2 #define TSS_RUNNING_KEX 3 #define TSS_NEWKEY_WAIT 4 #define TSS_DATA 5 void *method_state; KEX_STATE *kex_state; PKTQ oq; int queuestate; #define TQS_IDLE 1 #define TQS_QUEUEING 2 } ; struct kex_state { ENCALG *enc_c2s; ENCALG *enc_s2c; MACALG *mac_c2s; MACALG *mac_s2c; COMPALG *comp_c2s; COMPALG *comp_s2c; int ignorenext; } ; static void *put_alg_list(void *opp, ALGLIST *algs, const char *(*namefn)(void *)) { char *buf; int n; FILE *f; static void foo(void *alg) { const char *name; name = (*namefn)(alg); fprintf(f,",%s",name); } f = fopen_alloc(&buf,&n); for (;algs;algs=algs->link) foo(algs->alg); fclose(f); if (n < 1) abort(); opp = put_string(opp,buf+1,n-1); free(buf); return(opp); } static void print_offer(const char *tag, const void *name, int namelen, void *alg) { static const char *prevtag = 0; if (tag != prevtag) { if (! prevtag) fprintf(stderr,"%s: offered algorithms (* = unrecognized):",__progname); fprintf(stderr,"\n"); prevtag = tag; if (! tag) return; fprintf(stderr,"%12s:",tag); } if (tag) fprintf(stderr," %s%.*s",alg?"":"*",namelen,(const char *)name); } static void setalgs_list(ROSTR s, ALGLIST **listp, void *(*vfind)(ROSTR), const char *tag) { static int foo(const void *name, int len) { ROSTR n; void *alg; ALGLIST *l; n.len = len; n.data = name; alg = (*vfind)(n); if (showoffer) print_offer(tag,name,len,alg); if (alg) { l = malloc(sizeof(ALGLIST)); l->alg = alg; *listp = l; listp = &l->link; } return(0); } comma_list(s.data,s.len,foo); *listp = 0; } #define FOO(specific,short,str)\ static void setalgs_##specific(ROSTR s, void *listpv)\ { setalgs_list(s,listpv,short##alg_vfind_rostr,str); } FOO(kex,kex,"kex") FOO(hk,hk,"hk") FOO(enc_c2s,enc,"enc c->s") FOO(enc_s2c,enc,"enc s->c") FOO(mac_c2s,mac,"mac c->s") FOO(mac_s2c,mac,"mac s->c") FOO(comp_c2s,comp,"comp c->s") FOO(comp_s2c,comp,"comp s->c") #undef FOO static void dump_alglists(ALGLIST *clist, ALGLIST *slist, const char *(*name)(void *)) { ALGLIST *l; fprintf(stderr,"Client list:"); for (l=clist;l;l=l->link) fprintf(stderr," %s",(*name)(l->alg)); fprintf(stderr,"\n"); fprintf(stderr,"Server list:"); for (l=slist;l;l=l->link) fprintf(stderr," %s",(*name)(l->alg)); fprintf(stderr,"\n"); } static void *derive_blob(BPP *b, char c, int len) { unsigned char *t0; unsigned char *t; void *h; void (*last)(void); static void c_and_id(void) { (*b->kex_hash->process)(h,&c,1); (*b->kex_hash->process)(h,b->sessid,b->sessidlen); } static void blob_so_far(void) { (*b->kex_hash->process)(h,t0,t-t0); } last = &c_and_id; t0 = malloc(len); t = t0; while (1) { h = (*b->kex_hash->init)(); (*b->kex_hash->process)(h,b->kex_k,b->kex_klen); (*b->kex_hash->process)(h,b->kex_h,b->kex_hlen); (*last)(); if (len < b->kex_hash->hashlen) { (*b->kex_hash->done)(h,b->hashbuf); bcopy(b->hashbuf,t,len); return(t0); } else { (*b->kex_hash->done)(h,t); if (len == b->kex_hash->hashlen) return(t0); t += b->kex_hash->hashlen; last = &blob_so_far; len -= b->kex_hash->hashlen; } } } #if 0 static void dumpblob(const char *tag, const void *data, int len) { int i; printf("%s",tag); for (i=0;ilink) { if ((*((HKALG *)k->alg)->canuse)(forserver)) { *tail = k; tail = &k->link; } } *tail = 0; } static void send_kexinit(TRANSPORT_STATE *s, LAYER *l) { unsigned char *opp; unsigned char lclcookie[16]; filter_hkalgs(s->whoami==WHOAMI_SERVER); if (! algs_hk) { fprintf(stderr,"%s: no host keys available\n",__progname); send_disconnect(SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,"No host keys available",-1,"en",-1); exit(0); } random_data(&lclcookie[0],16); opp = &l->b->opkt[0]; *opp++ = SSH_MSG_KEXINIT; bcopy(&lclcookie[0],opp,16); opp += 16; opp = put_alg_list(opp,algs_kex,kexalg_name); opp = put_alg_list(opp,algs_hk,hkalg_name); opp = put_alg_list(opp,algs_enc_c2s,encalg_name); opp = put_alg_list(opp,algs_enc_s2c,encalg_name); opp = put_alg_list(opp,algs_mac_c2s,macalg_name); opp = put_alg_list(opp,algs_mac_s2c,macalg_name); opp = put_alg_list(opp,algs_comp_c2s,compalg_name); opp = put_alg_list(opp,algs_comp_s2c,compalg_name); opp = put_string(opp,lang_c2s?:"",-1); opp = put_string(opp,lang_s2c?:"",-1); *opp++ = 0; opp = put_uint32(opp,0); l->b->oplen = opp - &l->b->opkt[0]; switch (s->whoami) { case WHOAMI_CLIENT: free(l->b->c_kexinit_payload); l->b->c_kexinit_paylen = l->b->oplen; l->b->c_kexinit_payload = malloc(l->b->c_kexinit_paylen); bcopy(&l->b->opkt[0],l->b->c_kexinit_payload,l->b->c_kexinit_paylen); break; case WHOAMI_SERVER: free(l->b->s_kexinit_payload); l->b->s_kexinit_paylen = l->b->oplen; l->b->s_kexinit_payload = malloc(l->b->s_kexinit_paylen); bcopy(&l->b->opkt[0],l->b->s_kexinit_payload,l->b->s_kexinit_paylen); break; default: abort(); break; } below_opkt(l); } static KEX_STATE *recv_kexinit(TRANSPORT_STATE *ts, LAYER *l, int firsttime) { BPP *b; KEX_STATE *ks; unsigned char remcookie[16]; ALGLIST *remalgs_kex; ALGLIST **c_kex; ALGLIST **s_kex; ALGLIST *remalgs_hk; ALGLIST **c_hk; ALGLIST **s_hk; ALGLIST *remalgs_enc_c2s; ALGLIST *remalgs_enc_s2c; ALGLIST *remalgs_mac_c2s; ALGLIST *remalgs_mac_s2c; ALGLIST *remalgs_comp_c2s; ALGLIST *remalgs_comp_s2c; STR remlang_c2s; STR remlang_s2c; KEXALG *guessed_kex; int guessed_pkt; unsigned int extensions; b = l->b; ks = malloc(sizeof(KEX_STATE)); switch (ts->whoami) { case WHOAMI_CLIENT: if (! firsttime) free(b->s_kexinit_payload); b->s_kexinit_paylen = b->iplen; b->s_kexinit_payload = malloc(b->s_kexinit_paylen); bcopy(&b->ipkt[0],b->s_kexinit_payload,b->s_kexinit_paylen); c_kex = &algs_kex; s_kex = &remalgs_kex; c_hk = &algs_hk; s_hk = &remalgs_hk; break; case WHOAMI_SERVER: if (! firsttime) free(b->c_kexinit_payload); b->c_kexinit_paylen = b->iplen; b->c_kexinit_payload = malloc(b->c_kexinit_paylen); bcopy(&b->ipkt[0],b->c_kexinit_payload,b->c_kexinit_paylen); c_kex = &remalgs_kex; s_kex = &algs_kex; c_hk = &remalgs_hk; s_hk = &algs_hk; break; default: abort(); break; } parse_packet(b,pp_fail, PP_IGNORE(1), PP_BLOB(16,&remcookie[0]), PP_STRING_CALL(setalgs_kex,&remalgs_kex), PP_STRING_CALL(setalgs_hk,&remalgs_hk), PP_STRING_CALL(setalgs_enc_c2s,&remalgs_enc_c2s), PP_STRING_CALL(setalgs_enc_s2c,&remalgs_enc_s2c), PP_STRING_CALL(setalgs_mac_c2s,&remalgs_mac_c2s), PP_STRING_CALL(setalgs_mac_s2c,&remalgs_mac_s2c), PP_STRING_CALL(setalgs_comp_c2s,&remalgs_comp_c2s), PP_STRING_CALL(setalgs_comp_s2c,&remalgs_comp_s2c), PP_STRING(&remlang_c2s), PP_STRING(&remlang_s2c), PP_BOOL(&guessed_pkt), PP_UINT32(&extensions), PP_ENDSHERE ); if (showoffer) print_offer(0,0,0,0); /* Choose algorithms. */ do <"kexfound"> { ALGLIST *i1; ALGLIST *i2; ALGLIST *i3; /* Check for matching guesses */ if (remalgs_kex) { guessed_kex = remalgs_kex->alg; if (algs_kex && (algs_kex->alg == guessed_kex)) { b->kex = guessed_kex; break <"kexfound">; } } /* Kex algorithm is the first client kex algorithm for which */ for <"nextkex"> (i1=*c_kex;i1;i1=i1->link) { /* + the server also supports the algorithm */ do <"serversupport"> { for (i2=*s_kex;i2;i2=i2->link) { if (i1->alg == i2->alg) break <"serversupport">; } continue <"nextkex">; } while (0); /* + if the algorithm requires an encryption-capable host key, */ if (((KEXALG *)i1->alg)->need_enc) { /* there is an encryption-capable algorithm on the server's server_host_key_algorithms that is also supported by the client */ do <"shkfound"> { for <"nextshk"> (i2=*s_hk;i2;i2=i2->link) { do <"chkfound"> { for (i3=*c_hk;i3;i3=i3->link) { if (i2->alg == i3->alg) { if (! ((HKALG *)i3->alg)->can_enc) continue; break <"chkfound">; } } continue <"nextshk">; } while (0); break <"shkfound">; } continue <"nextkex">; } while (0); } /* + if the algorithm requires a signature-capable host key, */ if (((KEXALG *)i1->alg)->need_sig) { /* there is a signature-capable algorithm on the server's server_host_key_algorithms that is also supported by the client */ do <"shkfound"> { for <"nextshk"> (i2=*s_hk;i2;i2=i2->link) { do <"chkfound"> { for (i3=*c_hk;i3;i3=i3->link) { if (i2->alg == i3->alg) { if (! ((HKALG *)i3->alg)->can_sig) continue; break <"chkfound">; } } continue <"nextshk">; } while (0); break <"shkfound">; } continue <"nextkex">; } while (0); } b->kex = i1->alg; break <"kexfound">; } send_disconnect(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,0,0,0,0); fprintf(stderr,"%s: no usable key-exchange algorithm found\n",__progname); dump_alglists(*c_kex,*s_kex,kexalg_name); exit(1); } while (0); if (verbose) printf("Using kex %s\n",b->kex->name); do <"hkfound"> { ALGLIST *i1; ALGLIST *i2; for <"nexthk"> (i1=*c_hk;i1;i1=i1->link) { b->hk = i1->alg; if ((b->kex->need_enc && !b->hk->can_enc) || (b->kex->need_sig && !b->hk->can_sig) ) continue; do <"serversupport"> { for (i2=*s_hk;i2;i2=i2->link) { if (i2->alg == i1->alg) break <"serversupport">; } continue <"nexthk">; } while (0); b->hk = i1->alg; break <"hkfound">; } send_disconnect(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,0,0,0,0); fprintf(stderr,"%s: no usable server host key algorithm found\n",__progname); dump_alglists(*c_hk,*s_hk,hkalg_name); exit(1); } while (0); if (verbose) printf("Using hk %s\n",b->hk->name); #define FOO(lcllist,algvar,remlist,namefn,msgtxt) do {\ do <"found"> \ { ALGLIST *i1; \ ALGLIST *i2; \ for <"nextc"> (i1=(ts->whoami==WHOAMI_CLIENT)?lcllist:remlist;i1;i1=i1->link)\ { do <"serversupport"> \ { for (i2=(ts->whoami==WHOAMI_CLIENT)?remlist:lcllist;i2;i2=i2->link)\ { if (i1->alg == i2->alg) break <"serversupport">; \ } \ continue <"nextc">; \ } while (0); \ ks->algvar = i1->alg; \ break <"found">; \ } \ send_disconnect(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,0,0,0,0); \ fprintf(stderr,"%s: no usable %s algorithm found\n",__progname,msgtxt);\ dump_alglists( (ts->whoami == WHOAMI_CLIENT) ? lcllist : remlist,\ (ts->whoami == WHOAMI_CLIENT) ? remlist : lcllist,\ namefn ); \ exit(1); \ } while (0); \ if (verbose) printf("Using %s %s\n",msgtxt,ks->algvar->name); \ } while (0) FOO(algs_enc_c2s,enc_c2s,remalgs_enc_c2s,encalg_name,"client->server encryption"); FOO(algs_enc_s2c,enc_s2c,remalgs_enc_s2c,encalg_name,"server->client encryption"); FOO(algs_mac_c2s,mac_c2s,remalgs_mac_c2s,macalg_name,"client->server MAC"); FOO(algs_mac_s2c,mac_s2c,remalgs_mac_s2c,macalg_name,"server->client MAC"); FOO(algs_comp_c2s,comp_c2s,remalgs_comp_c2s,compalg_name,"client->server compression"); FOO(algs_comp_s2c,comp_s2c,remalgs_comp_s2c,compalg_name,"server->client compression"); #undef FOO if (verbose) printf("Guessed kex packet sent by peer: %s\n",guessed_pkt?"yes":"no"); ks->ignorenext = (guessed_pkt && (guessed_kex != b->kex)); if (verbose && ks->ignorenext) printf("...ignoring it\n"); alglist_free(remalgs_kex); alglist_free(remalgs_hk); alglist_free(remalgs_enc_c2s); alglist_free(remalgs_enc_s2c); alglist_free(remalgs_mac_c2s); alglist_free(remalgs_mac_s2c); alglist_free(remalgs_comp_c2s); alglist_free(remalgs_comp_s2c); free_str(remlang_c2s); free_str(remlang_s2c); return(ks); } static void kex_done(LAYER *l, int firsttime) { if (firsttime) { l->b->sessidlen = l->b->kex_hlen; l->b->sessid = malloc(l->b->sessidlen); bcopy(l->b->kex_h,l->b->sessid,l->b->sessidlen); } if (! firsttime) free(l->b->hashbuf); l->b->hashbuf = malloc(l->b->kex_hash->hashlen); l->b->opkt[0] = SSH_MSG_NEWKEYS; l->b->oplen = 1; below_opkt(l); } static void install_new_keys_w(BPP *b, TRANSPORT_STATE *s) { void *enc_iv; void *enc_key; void *mac_key; (*b->w_enc->done)(b->w_encstate); (*b->w_comp->done)(b->w_compstate); (*b->w_mac->done)(b->w_macstate); switch (s->whoami) { case WHOAMI_CLIENT: enc_iv = derive_blob(b,'A',s->kex_state->enc_c2s->ivsize); enc_key = derive_blob(b,'C',s->kex_state->enc_c2s->keysize); mac_key = derive_blob(b,'E',s->kex_state->mac_c2s->keylen); b->w_enc = s->kex_state->enc_c2s; b->w_mac = s->kex_state->mac_c2s; break; case WHOAMI_SERVER: enc_iv = derive_blob(b,'B',s->kex_state->enc_s2c->ivsize); enc_key = derive_blob(b,'D',s->kex_state->enc_s2c->keysize); mac_key = derive_blob(b,'F',s->kex_state->mac_s2c->keylen); b->w_enc = s->kex_state->enc_s2c; b->w_mac = s->kex_state->mac_s2c; break; default: abort(); break; } b->w_encstate = encalg_init(b->w_enc,enc_key,enc_iv,'w'); b->w_macstate = macalg_init(b->w_mac,mac_key); free(enc_iv); free(enc_key); free(mac_key); } static void install_new_keys_r(BPP *b, TRANSPORT_STATE *s) { void *enc_iv; void *enc_key; void *mac_key; (*b->r_enc->done)(b->r_encstate); (*b->r_comp->done)(b->r_compstate); (*b->r_mac->done)(b->r_macstate); switch (s->whoami) { case WHOAMI_CLIENT: enc_iv = derive_blob(b,'B',s->kex_state->enc_s2c->ivsize); enc_key = derive_blob(b,'D',s->kex_state->enc_s2c->keysize); mac_key = derive_blob(b,'F',s->kex_state->mac_s2c->keylen); b->r_enc = s->kex_state->enc_s2c; b->r_mac = s->kex_state->mac_s2c; break; case WHOAMI_SERVER: enc_iv = derive_blob(b,'A',s->kex_state->enc_c2s->ivsize); enc_key = derive_blob(b,'C',s->kex_state->enc_c2s->keysize); mac_key = derive_blob(b,'E',s->kex_state->mac_c2s->keylen); b->r_enc = s->kex_state->enc_c2s; b->r_mac = s->kex_state->mac_c2s; break; default: abort(); break; } b->r_encstate = encalg_init(b->r_enc,enc_key,enc_iv,'r'); b->r_macstate = macalg_init(b->r_mac,mac_key); free(enc_iv); free(enc_key); free(mac_key); } static void *transport_init(int whoami) { TRANSPORT_STATE *s; s = malloc(sizeof(TRANSPORT_STATE)); s->whoami = whoami; s->stage = TSS_REKEY; s->first_kex = 1; pktq_init(&s->oq); s->queuestate = TQS_IDLE; return(s); } static void *transport_init_c(LAYER *l __attribute__((__unused__))) { return(transport_init(WHOAMI_CLIENT)); } static void *transport_init_s(LAYER *l __attribute__((__unused__))) { return(transport_init(WHOAMI_SERVER)); } static void init_kex(TRANSPORT_STATE *s, LAYER *l) { switch (s->whoami) { case WHOAMI_CLIENT: s->method_state = (*l->b->kex->init_c)(l); break; case WHOAMI_SERVER: s->method_state = (*l->b->kex->init_s)(l); break; default: abort(); break; } } static void transport_i(LAYER *l, void *arg) { TRANSPORT_STATE *s; BPP *b; s = arg; b = l->b; switch (s->stage) { case TSS_DATA: if (! b->want_rekey) { switch (b->ipkt[0]) { case SSH_MSG_KEXINIT: s->kex_state = recv_kexinit(s,l,0); send_kexinit(s,l); s->stage = TSS_RUNNING_KEX; init_kex(s,l); break; case SSH_MSG_NEWKEYS: badmsg("DATA","MSG_NEWKEYS"); break; default: above_ipkt(l); break; } break; } /* s->stage = TSS_REKEY; overwritten just below */ /* fall through */ case TSS_REKEY: send_kexinit(s,l); s->stage = TSS_SENT_KEXINIT; /* fall through */ case TSS_SENT_KEXINIT: switch (b->ipkt[0]) { case SSH_MSG_KEXINIT: s->kex_state = recv_kexinit(s,l,s->first_kex); s->stage = TSS_RUNNING_KEX; init_kex(s,l); break; case SSH_MSG_NEWKEYS: fprintf(stderr,"%s: misplaced NEWKEYS (at SENT_KEXINIT)\n",__progname); exit(1); break; default: if (s->first_kex) { fprintf(stderr,"%s: KEXINIT packet isn't\n",__progname); exit(1); } above_ipkt(l); break; } break; case TSS_RUNNING_KEX: if (s->kex_state->ignorenext) { /* XXX: should we ignore the next packet, or the next packet with a type field in the key-exchange range? We ignore the next packet, since that's how the draft is worded. Since only key-exchange and debug/ignore/disconnect packets are allowed during key exchange anyway, receiving anything else is a protocol error (we arguably should check). */ s->kex_state->ignorenext = 0; return; } switch (b->ipkt[0]) { case SSH_MSG_KEXINIT: badmsg("RUNNING_KEX","MSG_KEXINIT"); break; case SSH_MSG_NEWKEYS: badmsg("RUNNING_KEX","MSG_NEWKEYS"); break; default: if ((b->ipkt[0] >= 30) && (b->ipkt[0] <= 49)) { if ((*b->kex->run)(l,s->method_state)) { kex_done(l,s->first_kex); install_new_keys_w(b,s); s->stage = TSS_NEWKEY_WAIT; } } else { badmsg("RUNNING_KEX","type %d",b->ipkt[0]); } break; } break; case TSS_NEWKEY_WAIT: switch (b->ipkt[0]) { case SSH_MSG_KEXINIT: badmsg("NEWKEY_WAIT","MSG_NEWKEYS"); break; case SSH_MSG_NEWKEYS: { static int rekey_pkts(int bb) { return(1<<((bb<8)?16:(bb<16)?bb*2:30)); } install_new_keys_r(b,s); free(s->kex_state); s->stage = TSS_DATA; s->first_kex = 0; b->ibytes_to_rekey = 1<<30; b->obytes_to_rekey = 1<<30; b->ipkts_to_rekey = rekey_pkts(b->r_enc->blksize); b->opkts_to_rekey = rekey_pkts(b->w_enc->blksize); b->want_rekey = 0; if (s->queuestate == TQS_QUEUEING) { while (pktq_get(&s->oq,&b->opkt[0],&b->oplen,sizeof(b->opkt))) { below_opkt(l); } s->queuestate = TQS_IDLE; } } break; default: badmsg("NEWKEY_WAIT","type %d",b->ipkt[0]); break; } break; } } static void transport_o(LAYER *l, void *arg) { TRANSPORT_STATE *s; s = arg; if (s->queuestate != TQS_IDLE) { pktq_put(&s->oq,&l->b->opkt[0],l->b->oplen); return; } switch (s->stage) { case TSS_REKEY: s->queuestate = TQS_QUEUEING; pktq_put(&s->oq,&l->b->opkt[0],l->b->oplen); send_kexinit(s,l); s->stage = TSS_SENT_KEXINIT; break; default: s->queuestate = TQS_QUEUEING; pktq_put(&s->oq,&l->b->opkt[0],l->b->oplen); return; case TSS_DATA: below_opkt(l); break; } } LAYERDESC layer_transport_c = { &transport_init_c, &transport_i, &transport_o }; LAYERDESC layer_transport_s = { &transport_init_s, &transport_i, &transport_o };