diff 3a47c8bfbe9298fed2b6dcc5ac19a7af4c96c52f uncommitted --- a/sys/src/cmd/ssh.c +++ b/sys/src/cmd/ssh.c @@ -1,3 +1,14 @@ +/* + 2020-June (Romano) + I think this would be better if refactored into its own subdir with separate files, e.g.: + ssh/ssh.h <-- structs, enums, et c. + ssh/debug.c <-- debugging statement stuff, e.g. printKexinitp() + ssh/alloc.c <-- kexinitalloc() et c. + ssh/kex.c <-- key exchange and host server algorithm stuff + ssh/algs.c <-- cipher, mac, zip, and lang stuff + ssh/ssh.c <-- main loop +*/ + #include #include #include @@ -5,6 +16,7 @@ #include #include +/* Cf. RFC2450 & RFC8268 */ enum { MSG_DISCONNECT = 1, MSG_IGNORE, @@ -47,7 +59,7 @@ enum { - Overhead = 256, // enougth for MSG_CHANNEL_DATA header + Overhead = 256, // enough for MSG_CHANNEL_DATA header MaxPacket = 1<<15, WinPackets = 8, // (1<<15) * 8 = 256K }; @@ -76,6 +88,280 @@ Rendez; } Oneway; +/* BEGIN RFC 4253 name-list agreement structure and implementation */ +struct nameseq { + char *val; + struct nameseq *next; +}; +typedef struct nameseq Nameseq; + +Nameseq* +nameseqalloc(void) +{ + Nameseq *nameseq; + + nameseq = mallocz(sizeof(*nameseq), 1); + if(nameseq == nil) + sysfatal("nameseqalloc"); + return nameseq; +} + +void +nameseqfree(Nameseq *nameseq) +{ + if(nameseq == nil) + return; + nameseqfree(nameseq->next); + free(nameseq); +} + +Nameseq * +getnameseq(char *c) +{ + Nameseq *orderp = nameseqalloc(); + Nameseq *pp = orderp; + char *str = strdup(c); + while (str = strtok(str, ",")) { + pp->val = str; + pp->next = nameseqalloc(); + pp = pp->next; + str = nil; + } + return orderp; +} + +char * +nameseqagree(char *c, char *s) +{ + Nameseq *cp, *sp, *startsp; + char *val = nil; + + if (strlen(c) == 0 && strlen(s) == 0 ) + return ""; + + cp = getnameseq(c); + startsp = sp = getnameseq(s); + while (cp) { + if (cp->val && sp->val && !strcmp(cp->val, sp->val)) { + val = strdup(cp->val); + break; + } + if(!(sp = sp->next)) { + if(!(cp = cp->next)) + break; + sp = startsp; + } + } + + nameseqfree(cp); + nameseqfree(sp); + return val; +} +/* END RFC 4253 name-list agreement implementation */ + +typedef struct algs { + char *cipher; + char *mac; + char *zip; + char *lang; +} Algs; +typedef Algs *Algsp; + +typedef struct kexhost { + char *kex; + char *host; +} Kexhost; +typedef Kexhost *Kexhostp; + +typedef struct kexother { + uint firstkexpkt; + ulong reserved; + uchar msg; + uchar cookie[16]; +} Kexother; +typedef Kexother *Kexotherp; + +typedef struct kexinit { + Kexhostp kh; + Algsp ctos; + Algsp stoc; + Kexotherp ko; +} Kexinit; +typedef Kexinit *Kexinitp; + +Algsp +algsalloc(void) +{ + Algsp data; + + data = mallocz(sizeof(data), 1); + if(data == nil) + sysfatal("algsalloc"); + return data; +} + +Kexhostp +kexhostalloc(void) +{ + Kexhostp data; + + data = mallocz(sizeof(data), 1); + if(data == nil) + sysfatal("kexhostalloc"); + return data; +} + +Kexotherp +kexotheralloc(void) +{ + Kexotherp data; + + data = mallocz(sizeof(data), 1); + if(data == nil) + sysfatal("kexotheralloc"); + return data; +} + +void +printKexhostp(char *prefix, Kexhostp data) { + fprint(2, "%s ->%s: %s\n", prefix, "kex", data->kex ); + fprint(2, "%s ->%s: %s\n", prefix, "host", data->host ); +} + +Kexinitp +kexinitalloc(void) +{ + Kexinitp data; + + data = mallocz(sizeof(data), 1); + if(data == nil) + sysfatal("kexinitalloc"); + data->ko = kexotheralloc(); + data->kh = kexhostalloc(); + data->ctos = algsalloc(); + data->stoc = algsalloc(); + return data; +} + +void +defKexotherp(Kexotherp kop) { + kop->msg = MSG_KEXINIT; + kop->firstkexpkt = 0; + kop->reserved = 0L; + genrandom(kop->cookie, sizeof(kop->cookie)); +} + +void +defKexhostp(Kexhostp khp) { + khp->kex = "curve25519-sha256,curve25519-sha256@libssh.org"; + + /* + ssh-dss is required per RFC4253. OpenSSH has deprecated its use, but + does there's no real basis to support that decision for larger modulo. + Support it as a secondary algorithm, most likely until ssh-ed25519 is + supported. + */ + khp->host = "ssh-rsa,ssh-dss"; +} + +void +defAlgsp(Algsp algsp) { + /* + 'At some future time, it is expected that another algorithm, one with better + strength, will become so prevalent and ubiquitous that the use of + "3des-cbc" will be deprecated by another STANDARDS ACTION.' - RFC4253 + No standards action has yet deprecated it, but have not seen it supported + by default in any server. + */ + algsp->cipher = "chacha20-poly1305@openssh.com"; + + /* + hmac-sha1 is a required algorithm by RFC4253, and some servers still check + for it; it's effectively ignored since chacha20-poly1305 cipher is AEAD. + No RFC has deprecated it, only provided other algorithms to be placed + before it (e.g., SHA-2). + */ + algsp->mac = "hmac-sha1"; + + algsp->zip = "none"; + algsp->lang = ""; +} + +void +printKexotherp(char *prefix, Kexotherp data) { + fprint(2, "%s ->%s: %uc\n", prefix, "msg", data->msg ); + fprint(2, "%s ->%s: %uhd\n", prefix, "cookie", *data->cookie ); + fprint(2, "%s ->%s: %ud\n", prefix, "firstkexpkt", data->firstkexpkt ); + fprint(2, "%s ->%s: %uld\n", prefix, "reserved", data->reserved ); +} + +void +printAlgsp(char *prefix, Algsp data) { + fprint(2, "%s ->%s: %s\n", prefix, "cipher", data->cipher ); + fprint(2, "%s ->%s: %s\n", prefix, "mac", data->mac ); + fprint(2, "%s ->%s: %s\n", prefix, "zip", data->zip ); + fprint(2, "%s ->%s: %s\n", prefix, "lang", data->lang ); +} + +void +printKexinitp(char *prefix, Kexinitp data) { + fprint(2, "%s %llX:\n", prefix, (vlong)data); + fprint(2, "%s ko->\n", prefix); + printKexotherp(prefix, data->ko ); + fprint(2, "%s kh->\n", prefix); + printKexhostp(prefix, data->kh ); + fprint(2, "%s ctos->\n", prefix); + printAlgsp(prefix, data->ctos ); + fprint(2, "%s stoc->\n", prefix); + printAlgsp(prefix, data->stoc ); +} + +Kexhostp +khagree(Kexhostp a, Kexhostp b) { + Kexhostp c = kexhostalloc(); + c->kex = nameseqagree(a->kex, b->kex); + if(!c->kex) + sysfatal("Cannot agree on key exchange algorithm"); + + c->host = nameseqagree(a->host, b->host); + if(!c->host) + sysfatal("Cannot agree on host server algorithm"); + + return c; +} + +Algsp +algsagree(Algsp a, Algsp b, char * cxt) { + Algsp c = algsalloc(); + c->cipher = nameseqagree(a->cipher, b->cipher); + if(!c->cipher) + sysfatal(smprint("Cannot agree on cipher algorithm for %s", cxt)); + + c->mac = nameseqagree(a->mac, b->mac); + if(!c->mac) + sysfatal(smprint("Cannot agree on mac algorithm for %s", cxt)); + + c->zip = nameseqagree(a->zip, b->zip); + if(!c->zip) + sysfatal(smprint("Cannot agree on zip algorithm for %s", cxt)); + + c->lang = nameseqagree(a->lang, b->lang); + if(!c->lang) + sysfatal(smprint("Cannot agree on lang algorithm for %s", cxt)); + + return c; +} + +Kexinitp agreep; + +void +authagree(Kexinitp client, Kexinitp server) { + agreep = kexinitalloc(); + agreep->kh = khagree(client->kh, server->kh); + agreep->ctos = algsagree(client->ctos, server->ctos, "ctos"); + agreep->stoc = algsagree(client->stoc, server->stoc, "stoc"); +} + int nsid; uchar sid[256]; char thumb[2*SHA2_256dlen+1], *thumbfile; @@ -84,6 +370,7 @@ char *user, *service, *status, *host, *remote, *cmd; Oneway recv, send; + void dispatch(void); void @@ -163,6 +450,16 @@ memmove(p, s, u); p += u; break; + /* Why does 's' exist with passing the string length, when it can just be calculated with strlen()? */ + case 'S': + s = va_arg(a, char*); + u = strlen(s); + if(p+4 > e) goto err; + PUT4(p, u), p += 4; + if(u > e-p) goto err; + memmove(p, s, u); + p += u; + break; case 'u': u = va_arg(a, int); if(p+4 > e) goto err; @@ -211,6 +508,14 @@ *va_arg(a, int*) = u; p += u; break; + /* Why does 's' exist with passing the string length? casting to (char *) is null-terminated and can simply move that way: */ + case 'S': + if(p+4 > e) goto err; + u = GET4(p), p += 4; + if(u > e-p) goto err; + *va_arg(a, char**)= (char*)p; + p += u; + break; case '[': s = va_arg(a, void*); u = va_arg(a, int); @@ -280,7 +585,7 @@ send.r = send.b; send.w = send.b+n; -if(debug > 1) +if(debug > 2) fprint(2, "sendpkt: (%d) %.*H\n", send.r[0], (int)(send.w-send.r), send.r); if(nsid){ @@ -309,6 +614,26 @@ send.seq++; } +void +sendkexinitpkt(Kexinitp data) { + sendpkt( + "b[SSSSSSSSSSbu", + data->ko->msg, + data->ko->cookie, sizeof(data->ko->cookie), + data->kh->kex, + data->kh->host, + data->ctos->cipher, + data->stoc->cipher, + data->ctos->mac, + data->stoc->mac, + data->ctos->zip, + data->stoc->zip, + data->ctos->lang, + data->stoc->lang, + data->ko->firstkexpkt, + data->ko->reserved); +} + int readall(int fd, uchar *data, int len) { @@ -366,52 +691,101 @@ recv.w = recv.r + n; recv.seq++; -if(debug > 1) +if(debug > 2) fprint(2, "recvpkt: (%d) %.*H\n", recv.r[0], (int)(recv.w-recv.r), recv.r); return recv.r[0]; } -static char sshrsa[] = "ssh-rsa"; +Kexinitp +recvkexinit(void) { + Kexinitp spropp = kexinitalloc(); + if(unpack(recv.r, recv.w-recv.r, "b[SSSSSSSSSSbu", &(spropp->ko->msg), spropp->ko->cookie, sizeof(spropp->ko->cookie), &(spropp->kh->kex), &(spropp->kh->host), &(spropp->ctos->cipher), &(spropp->stoc->cipher), &(spropp->ctos->mac), &(spropp->stoc->mac), &(spropp->ctos->zip), &(spropp->stoc->zip), &(spropp->ctos->lang), &(spropp->stoc->lang), &(spropp->ko->firstkexpkt), &(spropp->ko->reserved) ) < 0) + sysfatal("bad MSG_KEXINIT reply"); + + if(debug > 1) + printKexinitp("server kexinit proposal", spropp); + + return spropp; +} + +typedef struct { + RSApub *rsa; + DSApub *dsa; +} Pub; + +static Pub *pub; + int -rsapub2ssh(RSApub *rsa, uchar *data, int len) +dsapub2ssh(uchar *data, int len) { - return pack(data, len, "smm", sshrsa, sizeof(sshrsa)-1, rsa->ek, rsa->n); + return pack(data, len, "smmmm", "ssh-dss", sizeof("ssh-dss")-1, pub->dsa->p, pub->dsa->q, pub->dsa->alpha, pub->dsa->key); } -RSApub* -ssh2rsapub(uchar *data, int len) +void +ssh2dsapub(uchar *data, int len) { - RSApub *pub; char *s; int n; - pub = rsapuballoc(); - pub->n = mpnew(0); - pub->ek = mpnew(0); - if(unpack(data, len, "smm", &s, &n, pub->ek, pub->n) < 0 - || n != sizeof(sshrsa)-1 || memcmp(s, sshrsa, n) != 0){ - rsapubfree(pub); - return nil; + pub->dsa = dsapuballoc(); + pub->dsa->p = mpnew(0); + pub->dsa->q = mpnew(0); + pub->dsa->alpha = mpnew(0); + pub->dsa->key = mpnew(0); + // RFC4253 Section 6.6: alpha is g, key is y + if(unpack(data, len, "smmmm", &s, &n, pub->dsa->p, pub->dsa->q, pub->dsa->alpha, pub->dsa->key ) < 0 + || n != sizeof("ssh-dss")-1 || memcmp(s, "ssh-dss", n) != 0){ + dsapubfree(pub->dsa); } - return pub; } static char rsasha256[] = "rsa-sha2-256"; int -rsasig2ssh(RSApub *pub, mpint *S, uchar *data, int len) +dsasig2ssh(mpint *S, uchar *data, int len) { - int l = (mpsignif(pub->n)+7)/8; - if(4+12+4+l > len) - return -1; - mptober(S, data+4+12+4, l); - return pack(data, len, "ss", rsasha256, sizeof(rsasha256)-1, data+4+12+4, l); + DSApriv *dsapriv = dsagen(pub->dsa); + DSAsig *dsasig = dsasign(dsapriv, S); + mptobe(dsasig->r, data, SHA1dlen, nil); + mptobe(dsasig->s, data+SHA1dlen, SHA1dlen, nil); + dsasigfree(dsasig); + dsaprivfree(dsapriv); + return pack(data, len, "ss", "ssh-dss", sizeof("ssh-dss")-1, data, len+2*SHA1dlen); } +int +dsasha1verify(uchar *data, int len, mpint *S) +{ + mpint *V; + int ret; + uchar digest[SHA1dlen]; + + sha1(data, len, digest, nil); + V = betomp(digest, SHA1dlen, nil); + ret = V != nil; + if(ret){ + DSAsig *dsasig = dsasigalloc(); + dsasig->r = mpnew(0); + dsasig->s = mpnew(0); + mpright(S, 160, dsasig->r); + mptrunc(S, 160, dsasig->s); + if(debug > 2) { + fmtinstall('B', mpfmt); + fprint(2, "V: %B\n", V); + fprint(2, "S: %B\n", S); + fprint(2, "r:%B; s:%B\n", dsasig->r, dsasig->s); + } + ret = !dsaverify(pub->dsa, dsasig, V); + dsasigfree(dsasig); + mpfree(V); + } + return ret; +} + mpint* -ssh2rsasig(uchar *data, int len) +ssh2sig(uchar *data, int len) { mpint *m; char *s; @@ -419,7 +793,7 @@ m = mpnew(0); if(unpack(data, len, "sm", &s, &n, m) < 0 - || n != sizeof(rsasha256)-1 || memcmp(s, rsasha256, n) != 0){ + || n != sizeof(agreep->kh->host)-1 || memcmp(s, agreep->kh->host, n) != 0){ mpfree(m); return nil; } @@ -426,25 +800,58 @@ return m; } +static char sshrsa[] = "ssh-rsa"; + +int +rsapub2ssh(uchar *data, int len) +{ + return pack(data, len, "smm", agreep->kh->host, sizeof(agreep->kh->host)-1, pub->rsa->ek, pub->rsa->n); +} + +void +ssh2rsapub(uchar *data, int len) +{ + char *s; + int n; + + pub->rsa = rsapuballoc(); + pub->rsa->n = mpnew(0); + pub->rsa->ek = mpnew(0); + if(unpack(data, len, "smm", &s, &n, pub->rsa->ek, pub->rsa->n) < 0 + || n != sizeof(agreep->kh->host)-1 || memcmp(s, agreep->kh->host, n) != 0){ + rsapubfree(pub->rsa); + } +} + +int +rsasig2ssh(mpint *S, uchar *data, int len) +{ + int l = (mpsignif(pub->rsa->n)+7)/8; + if(4+7+4+l > len) + return -1; + mptober(S, data+4+7+4, l); + return pack(data, len, "ss", agreep->kh->host, sizeof(agreep->kh->host)-1, data+4+7+4, l); +} + mpint* -pkcs1digest(uchar *data, int len, RSApub *pub) +pkcs1digest(uchar *data, int len) { uchar digest[SHA2_256dlen], buf[256]; sha2_256(data, len, digest, nil); - return pkcs1padbuf(buf, asn1encodedigest(sha2_256, digest, buf, sizeof(buf)), pub->n, 1); + return pkcs1padbuf(buf, asn1encodedigest(sha1, digest, buf, sizeof(buf)), pub->rsa->n, 1); } int -pkcs1verify(uchar *data, int len, RSApub *pub, mpint *S) +pkcs1verify(uchar *data, int len, mpint *S) { mpint *V; int ret; - V = pkcs1digest(data, len, pub); + V = pkcs1digest(data, len); ret = V != nil; if(ret){ - rsaencrypt(pub, S, S); + rsaencrypt(pub->rsa, S, S); ret = mpcmp(V, S) == 0; mpfree(V); } @@ -488,37 +895,28 @@ void kex(int gotkexinit) { - static char kexalgs[] = "curve25519-sha256,curve25519-sha256@libssh.org"; - static char cipheralgs[] = "chacha20-poly1305@openssh.com"; - static char zipalgs[] = "none"; - static char macalgs[] = ""; - static char langs[] = ""; + Kexinitp cpropp = kexinitalloc(); + defKexotherp(cpropp->ko); + defKexhostp(cpropp->kh); + defAlgsp(cpropp->ctos); + defAlgsp(cpropp->stoc); - uchar cookie[16], x[32], yc[32], z[32], k[32+1], h[SHA2_256dlen], *ys, *ks, *sig; + if(debug > 1) + printKexinitp("client kexinit properties", cpropp); + + uchar x[32], yc[32], z[32], k[32+1], h[SHA2_256dlen], *ys, *ks, *sig; uchar k12[2*ChachaKeylen]; int i, nk, nys, nks, nsig; DigestState *ds; mpint *S, *K; - RSApub *pub; + if (pub==nil) + pub = mallocz(sizeof(pub), 1); + ds = hashstr(send.v, strlen(send.v), nil); ds = hashstr(recv.v, strlen(recv.v), ds); - genrandom(cookie, sizeof(cookie)); - sendpkt("b[ssssssssssbu", MSG_KEXINIT, - cookie, sizeof(cookie), - kexalgs, sizeof(kexalgs)-1, - rsasha256, sizeof(rsasha256)-1, - cipheralgs, sizeof(cipheralgs)-1, - cipheralgs, sizeof(cipheralgs)-1, - macalgs, sizeof(macalgs)-1, - macalgs, sizeof(macalgs)-1, - zipalgs, sizeof(zipalgs)-1, - zipalgs, sizeof(zipalgs)-1, - langs, sizeof(langs)-1, - langs, sizeof(langs)-1, - 0, - 0); + sendkexinitpkt(cpropp); ds = hashstr(send.r, send.w-send.r, ds); if(!gotkexinit){ @@ -532,26 +930,12 @@ } ds = hashstr(recv.r, recv.w-recv.r, ds); - if(debug){ - char *tab[] = { - "kexalgs", "hostalgs", - "cipher1", "cipher2", - "mac1", "mac2", - "zip1", "zip2", - "lang1", "lang2", - nil, - }, **t, *s; - uchar *p = recv.r+17; - int n; - for(t=tab; *t != nil; t++){ - if(unpack(p, recv.w-p, "s.", &s, &n, &p) < 0) - break; - fprint(2, "%s: %.*s\n", *t, utfnlen(s, n), s); - } - } - + Kexinitp spropp = recvkexinit(); + authagree(cpropp, spropp); + if (debug > 1) + printKexinitp("agreement", agreep); curve25519_dh_new(x, yc); - yc[31] &= ~0x80; + yc[31] &= ~0x80; /* curve25519_dh_new does & 0x80, so why reverse? */ sendpkt("bs", MSG_ECDH_INIT, yc, sizeof(yc)); Next1: switch(recvpkt()){ @@ -561,6 +945,8 @@ case MSG_KEXINIT: sysfatal("inception"); case MSG_ECDH_REPLY: + + /* RFC5656; ys is being set to Q_S */ if(unpack(recv.r, recv.w-recv.r, "_sss", &ks, &nks, &ys, &nys, &sig, &nsig) < 0) sysfatal("bad ECDH_REPLY"); break; @@ -567,7 +953,7 @@ } if(nys != 32) - sysfatal("bad server ECDH ephermal public key length"); + sysfatal("bad server ECDH ephemeral public key length"); ds = hashstr(ks, nks, ds); ds = hashstr(yc, 32, ds); @@ -589,18 +975,29 @@ if(ok == nil || !okThumbprint(h, sizeof(h), ok)){ if(ok != nil) werrstr("unknown host"); fprint(2, "%s: %r\n", argv0); - fprint(2, "verify hostkey: %s %.*[\n", sshrsa, nks, ks); + fprint(2, "verify hostkey: %s %.*[\n", agreep->kh->host, nks, ks); fprint(2, "add thumbprint after verification:\n"); - fprint(2, "\techo 'ssh sha256=%s server=%s' >> %q\n", thumb, host, thumbfile); + fprint(2, "\techo 'ssh sha256=%s server=%s type=%s' >> %q\n", thumb, host, agreep->kh->host, thumbfile); sysfatal("checking hostkey failed: %r"); } freeThumbprints(ok); } - if((pub = ssh2rsapub(ks, nks)) == nil) - sysfatal("bad server public key"); - if((S = ssh2rsasig(sig, nsig)) == nil) - sysfatal("bad server signature"); + // TODO: branch for ed25519 + if(!strcmp(agreep->kh->host,"ssh-dss")) { + ssh2dsapub(ks, nks); + if(pub->dsa == nil) + sysfatal("bad server dss public key"); + if((S = ssh2sig(sig, nsig)) == nil) + sysfatal("no server dss signature"); + } + else { + ssh2rsapub(ks, nks); + if(pub->rsa == nil) + sysfatal("bad server rsa public key"); + if((S = ssh2sig(sig, nsig)) == nil) + sysfatal("no server rsa signature"); + } if(!curve25519_dh_finish(x, ys, z)) sysfatal("unlucky shared key"); @@ -612,10 +1009,17 @@ ds = hashstr(k, nk, ds); sha2_256(nil, 0, h, ds); - if(!pkcs1verify(h, sizeof(h), pub, S)) - sysfatal("server verification failed"); + // TODO: branch for ssh-ed25519 + if(!strcmp(agreep->kh->host,"ssh-dss")){ + if(!dsasha1verify(h, sizeof(h), S)) + sysfatal("server dss verification failed"); + } + else { + if(!pkcs1verify(h, sizeof(h), S)) + sysfatal("server rsa verification failed"); + rsapubfree(pub->rsa); + } mpfree(S); - rsapubfree(pub); sendpkt("b", MSG_NEWKEYS); Next2: switch(recvpkt()){ @@ -708,7 +1112,6 @@ char *s; mpint *S; AuthRpc *rpc; - RSApub *pub; if(!authok(authmeth)) return -1; @@ -720,7 +1123,7 @@ return -1; } - s = "proto=rsa service=ssh role=client"; + s = !strcmp(agreep->kh->host,"ssh-dss") ? "proto=dsa service=ssh role=client" : "proto=rsa service=ssh role=client"; if(auth_rpc(rpc, "start", s, strlen(s)) != ARok){ auth_freerpc(rpc); close(afd); @@ -727,19 +1130,50 @@ return -1; } - pub = rsapuballoc(); - pub->n = mpnew(0); - pub->ek = mpnew(0); + // TODO: branch for ssh-ed25519 + if(!strcmp(agreep->kh->host,"ssh-dss")){ + pub->dsa = dsapuballoc(); + pub->dsa->p = mpnew(0); + pub->dsa->q = mpnew(0); + pub->dsa->alpha = mpnew(0); + pub->dsa->key = mpnew(0); + } + else { + pub->rsa = rsapuballoc(); + pub->rsa->n = mpnew(0); + pub->rsa->ek = mpnew(0); + } while(auth_rpc(rpc, "read", nil, 0) == ARok){ s = rpc->arg; - if(strtomp(s, &s, 16, pub->n) == nil) - break; - if(*s++ != ' ') - continue; - if(strtomp(s, nil, 16, pub->ek) == nil) - continue; - npk = rsapub2ssh(pub, pk, sizeof(pk)); + // TODO: branch for ssh-ed25519 + if(!strcmp(agreep->kh->host,"ssh-dss")){ + // This seems brittle and dependent upon the order of values set in factotum + if(strtomp(s, &s, 16, pub->dsa->p) == nil) + break; + if(*s++ != ' ') + continue; + if(strtomp(s, nil, 16, pub->dsa->q) == nil) + continue; + if(*s++ != ' ') + continue; + if(strtomp(s, nil, 16, pub->dsa->alpha) == nil) + continue; + if(*s++ != ' ') + continue; + if(strtomp(s, nil, 16, pub->dsa->key) == nil) + continue; + npk = dsapub2ssh(pk, sizeof(pk)); + } + else { + if(strtomp(s, &s, 16, pub->rsa->n) == nil) + break; + if(*s++ != ' ') + continue; + if(strtomp(s, nil, 16, pub->rsa->ek) == nil) + continue; + npk = rsapub2ssh(pk, sizeof(pk)); + } sendpkt("bsssbss", MSG_USERAUTH_REQUEST, user, strlen(user), @@ -746,7 +1180,7 @@ service, strlen(service), authmeth, sizeof(authmeth)-1, 0, - rsasha256, sizeof(rsasha256)-1, + agreep->kh->host, sizeof(agreep->kh->host)-1, pk, npk); Next1: switch(recvpkt()){ default: @@ -769,9 +1203,16 @@ service, strlen(service), authmeth, sizeof(authmeth)-1, 1, - rsasha256, sizeof(rsasha256)-1, + agreep->kh->host, sizeof(agreep->kh->host)-1, pk, npk); - S = pkcs1digest(send.b, n, pub); + // TODO branch for ssh-ed25519 + if (!strcmp(agreep->kh->host,"ssh-dss")){ + sha1(send.b, n, sig, nil); + S = strtomp((char *)sig, nil, 10, nil); + } + else { + S = pkcs1digest(send.b, n); + } n = snprint((char*)send.b, sizeof(send.b), "%B", S); mpfree(S); @@ -781,7 +1222,13 @@ break; S = strtomp(rpc->arg, nil, 16, nil); - nsig = rsasig2ssh(pub, S, sig, sizeof(sig)); + // TODO branch for ssh-ed25519 + if (!strcmp(agreep->kh->host,"ssh-dss")){ + nsig = dsasig2ssh(S, sig, sizeof(sig)); + } + else { + nsig = rsasig2ssh(S, sig, sizeof(sig)); + } mpfree(S); /* send final userauth request with the signature */ @@ -790,7 +1237,7 @@ service, strlen(service), authmeth, sizeof(authmeth)-1, 1, - rsasha256, sizeof(rsasha256)-1, + agreep->kh->host, sizeof(agreep->kh->host)-1, pk, npk, sig, nsig); Next2: switch(recvpkt()){ @@ -804,13 +1251,25 @@ case MSG_USERAUTH_SUCCESS: break; } - rsapubfree(pub); + // TODO: branch for ssh-ed25519 + if (!strcmp(agreep->kh->host,"ssh-dss")){ + dsapubfree(pub->dsa); + } + else { + rsapubfree(pub->rsa); + } auth_freerpc(rpc); close(afd); return 0; } Failed: - rsapubfree(pub); + // TODO: branch for ssh-ed25519 + if (!strcmp(agreep->kh->host,"ssh-dss")){ + dsapubfree(pub->dsa); + } + else { + rsapubfree(pub->rsa); + } auth_freerpc(rpc); close(afd); return -1; @@ -1171,7 +1630,7 @@ void usage(void) { - fprint(2, "usage: %s [-dR] [-t thumbfile] [-T tries] [-u user] [-h] [user@]host [-W remote!port] [cmd args...]\n", argv0); + fprint(2, "usage: %s [-d] [-R] [-r] [-t thumbfile] [-T tries] [-u user] [-h] [user@]host [-W remote!port] [cmd args...]\n", argv0); exits("usage"); }