/* * Present factotum in ssh agent clothing. */ #include #include #include #include #include #include #include <9pclient.h> enum { STACK = 65536 }; enum /* agent protocol packet types */ { SSH_AGENTC_NONE = 0, SSH_AGENTC_REQUEST_RSA_IDENTITIES, SSH_AGENT_RSA_IDENTITIES_ANSWER, SSH_AGENTC_RSA_CHALLENGE, SSH_AGENT_RSA_RESPONSE, SSH_AGENT_FAILURE, SSH_AGENT_SUCCESS, SSH_AGENTC_ADD_RSA_IDENTITY, SSH_AGENTC_REMOVE_RSA_IDENTITY, SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES, SSH2_AGENTC_REQUEST_IDENTITIES = 11, SSH2_AGENT_IDENTITIES_ANSWER, SSH2_AGENTC_SIGN_REQUEST, SSH2_AGENT_SIGN_RESPONSE, SSH2_AGENTC_ADD_IDENTITY = 17, SSH2_AGENTC_REMOVE_IDENTITY, SSH2_AGENTC_REMOVE_ALL_IDENTITIES, SSH2_AGENTC_ADD_SMARTCARD_KEY, SSH2_AGENTC_REMOVE_SMARTCARD_KEY, SSH_AGENTC_LOCK, SSH_AGENTC_UNLOCK, SSH_AGENTC_ADD_RSA_ID_CONSTRAINED, SSH2_AGENTC_ADD_ID_CONSTRAINED, SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED, SSH_AGENT_CONSTRAIN_LIFETIME = 1, SSH_AGENT_CONSTRAIN_CONFIRM = 2, SSH2_AGENT_FAILURE = 30, SSH_COM_AGENT2_FAILURE = 102, SSH_AGENT_OLD_SIGNATURE = 0x01 }; typedef struct Aconn Aconn; struct Aconn { uchar *data; uint ndata; int ctl; int fd; char dir[40]; }; typedef struct Msg Msg; struct Msg { uchar *bp; uchar *p; uchar *ep; int bpalloc; }; char adir[40]; int afd; int chatty; char *factotum = "factotum"; void agentproc(void *v); void* emalloc(int n); void* erealloc(void *v, int n); void listenproc(void *v); int runmsg(Aconn *a); void listkeystext(void); void usage(void) { fprint(2, "usage: 9 ssh-agent [-D] [factotum]\n"); threadexitsall("usage"); } int threadmaybackground(void) { return 1; } void threadmain(int argc, char **argv) { int fd, pid, export, dotextlist; char dir[100], *ns; char sock[200], addr[200]; uvlong x; export = 0; dotextlist = 0; pid = getpid(); fmtinstall('B', mpfmt); fmtinstall('H', encodefmt); fmtinstall('[', encodefmt); ARGBEGIN{ case '9': chatty9pclient++; break; case 'D': chatty++; break; case 'e': export = 1; break; case 'l': dotextlist = 1; break; default: usage(); }ARGEND if(argc > 1) usage(); if(argc == 1) factotum = argv[0]; if(dotextlist) listkeystext(); ns = getns(); snprint(sock, sizeof sock, "%s/ssh-agent.socket", ns); if(0){ x = ((uvlong)fastrand()<<32) | fastrand(); x ^= ((uvlong)fastrand()<<32) | fastrand(); snprint(dir, sizeof dir, "/tmp/ssh-%llux", x); if((fd = create(dir, OREAD, DMDIR|0700)) < 0) sysfatal("mkdir %s: %r", dir); close(fd); snprint(sock, sizeof sock, "%s/agent.%d", dir, pid); } snprint(addr, sizeof addr, "unix!%s", sock); if((afd = announce(addr, adir)) < 0) sysfatal("announce %s: %r", addr); print("SSH_AUTH_SOCK=%s;\n", sock); if(export) print("export SSH_AUTH_SOCK;\n"); print("SSH_AGENT_PID=%d;\n", pid); if(export) print("export SSH_AGENT_PID;\n"); close(1); rfork(RFNOTEG); proccreate(listenproc, nil, STACK); threadexits(0); } void listenproc(void *v) { Aconn *a; USED(v); for(;;){ a = emalloc(sizeof *a); a->ctl = listen(adir, a->dir); if(a->ctl < 0) sysfatal("listen: %r"); proccreate(agentproc, a, STACK); } } void agentproc(void *v) { Aconn *a; int n; a = v; a->fd = accept(a->ctl, a->dir); close(a->ctl); a->ctl = -1; for(;;){ a->data = erealloc(a->data, a->ndata+1024); n = read(a->fd, a->data+a->ndata, 1024); if(n <= 0) break; a->ndata += n; while(runmsg(a)) ; } close(a->fd); free(a); threadexits(nil); } int get1(Msg *m) { if(m->p >= m->ep) return 0; return *m->p++; } int get2(Msg *m) { uint x; if(m->p+2 > m->ep) return 0; x = (m->p[0]<<8)|m->p[1]; m->p += 2; return x; } int get4(Msg *m) { uint x; if(m->p+4 > m->ep) return 0; x = (m->p[0]<<24)|(m->p[1]<<16)|(m->p[2]<<8)|m->p[3]; m->p += 4; return x; } uchar* getn(Msg *m, uint n) { uchar *p; if(m->p+n > m->ep) return nil; p = m->p; m->p += n; return p; } char* getstr(Msg *m) { uint n; uchar *p; n = get4(m); p = getn(m, n); if(p == nil) return nil; p--; memmove(p, p+1, n); p[n] = 0; return (char*)p; } mpint* getmp(Msg *m) { int n; uchar *p; n = (get2(m)+7)/8; if((p=getn(m, n)) == nil) return nil; return betomp(p, n, nil); } mpint* getmp2(Msg *m) { int n; uchar *p; n = get4(m); if((p = getn(m, n)) == nil) return nil; return betomp(p, n, nil); } void newmsg(Msg *m) { memset(m, 0, sizeof *m); } void mreset(Msg *m) { if(m->bpalloc){ memset(m->bp, 0, m->ep-m->bp); free(m->bp); } memset(m, 0, sizeof *m); } Msg* getm(Msg *m, Msg *mm) { uint n; uchar *p; n = get4(m); if((p = getn(m, n)) == nil) return nil; mm->bp = p; mm->p = p; mm->ep = p+n; mm->bpalloc = 0; return mm; } uchar* ensure(Msg *m, int n) { int len; uchar *p; uchar *obp; if(m->bp == nil) m->bpalloc = 1; if(!m->bpalloc){ p = emalloc(m->ep - m->bp); memmove(p, m->bp, m->ep - m->bp); obp = m->bp; m->bp = p; m->ep += m->bp - obp; m->p += m->bp - obp; m->bpalloc = 1; } len = m->ep - m->bp; if(m->p+n > m->ep){ obp = m->bp; m->bp = erealloc(m->bp, len+n+1024); m->p += m->bp - obp; m->ep += m->bp - obp; m->ep += n+1024; } p = m->p; m->p += n; return p; } void put4(Msg *m, uint n) { uchar *p; p = ensure(m, 4); p[0] = (n>>24)&0xFF; p[1] = (n>>16)&0xFF; p[2] = (n>>8)&0xFF; p[3] = n&0xFF; } void put2(Msg *m, uint n) { uchar *p; p = ensure(m, 2); p[0] = (n>>8)&0xFF; p[1] = n&0xFF; } void put1(Msg *m, uint n) { uchar *p; p = ensure(m, 1); p[0] = n&0xFF; } void putn(Msg *m, void *a, uint n) { uchar *p; p = ensure(m, n); memmove(p, a, n); } void putmp(Msg *m, mpint *b) { int bits, n; uchar *p; bits = mpsignif(b); put2(m, bits); n = (bits+7)/8; p = ensure(m, n); mptobe(b, p, n, nil); } void putmp2(Msg *m, mpint *b) { int bits, n; uchar *p; if(mpcmp(b, mpzero) == 0){ put4(m, 0); return; } bits = mpsignif(b); n = (bits+7)/8; if(bits%8 == 0){ put4(m, n+1); put1(m, 0); }else put4(m, n); p = ensure(m, n); mptobe(b, p, n, nil); } void putstr(Msg *m, char *s) { int n; n = strlen(s); put4(m, n); putn(m, s, n); } void putm(Msg *m, Msg *mm) { uint n; n = mm->p - mm->bp; put4(m, n); putn(m, mm->bp, n); } void newreply(Msg *m, int type) { memset(m, 0, sizeof *m); put4(m, 0); put1(m, type); } void reply(Aconn *a, Msg *m) { uint n; uchar *p; n = (m->p - m->bp) - 4; p = m->bp; p[0] = (n>>24)&0xFF; p[1] = (n>>16)&0xFF; p[2] = (n>>8)&0xFF; p[3] = n&0xFF; if(chatty) fprint(2, "respond %d t=%d: %.*H\n", n, p[4], n, m->bp+4); write(a->fd, p, n+4); mreset(m); } typedef struct Key Key; struct Key { mpint *mod; mpint *ek; char *comment; }; static char* find(char **f, int nf, char *k) { int i, len; len = strlen(k); for(i=1; i %s\n", chal, p); if(auth_rpc(rpc, "writehex", p, strlen(p)) != ARok){ fprint(2, "ssh-agent: dorsa: auth 'write': %r\n"); free(p); goto Die; } free(p); if(auth_rpc(rpc, "readhex", nil, 0) != ARok){ fprint(2, "ssh-agent: dorsa: auth 'read': %r\n"); goto Die; } decr = strtomp(rpc->arg, nil, 16, nil); if(chatty) fprint(2, "ssh-agent: response %s => %B\n", rpc->arg, decr); if(decr == nil){ fprint(2, "ssh-agent: dorsa: strtomp: %r\n"); goto Die; } unpad = rsaunpad(decr); if(chatty) fprint(2, "ssh-agent: unpad %B => %B\n", decr, unpad); if(unpad == nil){ fprint(2, "ssh-agent: dorsa: rsaunpad: %r\n"); mpfree(decr); goto Die; } mpfree(decr); mptoberjust(unpad, chalbuf, 32); mpfree(unpad); auth_freerpc(rpc); return 0; } int keysign(Msg *mkey, Msg *mdata, Msg *msig) { char *s; AuthRpc *rpc; RSApub *rsa; DSApub *dsa; char buf[4096]; uchar digest[SHA1dlen]; s = getstr(mkey); if(strcmp(s, "ssh-rsa") == 0){ rsa = getrsapub(mkey); if(rsa == nil) return -1; snprint(buf, sizeof buf, "proto=rsa service=ssh-rsa role=sign n=%lB ek=%lB", rsa->n, rsa->ek); rsapubfree(rsa); }else if(strcmp(s, "ssh-dss") == 0){ dsa = getdsapub(mkey); if(dsa == nil) return -1; snprint(buf, sizeof buf, "proto=dsa service=ssh-dss role=sign p=%lB q=%lB alpha=%lB key=%lB", dsa->p, dsa->q, dsa->alpha, dsa->key); dsapubfree(dsa); }else{ fprint(2, "ssh-agent: cannot sign key type %s\n", s); werrstr("unknown key type %s", s); return -1; } if((rpc = auth_allocrpc()) == nil){ fprint(2, "ssh-agent: auth_allocrpc: %r\n"); return -1; } if(chatty) fprint(2, "ssh-agent: start %s\n", buf); if(auth_rpc(rpc, "start", buf, strlen(buf)) != ARok){ fprint(2, "ssh-agent: auth 'start' failed: %r\n"); Die: auth_freerpc(rpc); return -1; } sha1(mdata->bp, mdata->ep-mdata->bp, digest, nil); if(auth_rpc(rpc, "write", digest, SHA1dlen) != ARok){ fprint(2, "ssh-agent: auth 'write in sign failed: %r\n"); goto Die; } if(auth_rpc(rpc, "read", nil, 0) != ARok){ fprint(2, "ssh-agent: auth 'read' failed: %r\n"); goto Die; } newmsg(msig); putstr(msig, s); put4(msig, rpc->narg); putn(msig, rpc->arg, rpc->narg); auth_freerpc(rpc); return 0; } int runmsg(Aconn *a) { char *p; int n, nk, type, rt, vers; mpint *ek, *mod, *chal; uchar sessid[16], chalbuf[32], digest[MD5dlen]; uint len, flags; DigestState *s; Msg m, mkey, mdata, msig; if(a->ndata < 4) return 0; len = (a->data[0]<<24)|(a->data[1]<<16)|(a->data[2]<<8)|a->data[3]; if(a->ndata < 4+len) return 0; m.p = a->data+4; m.ep = m.p+len; type = get1(&m); if(chatty) fprint(2, "msg %d: %.*H\n", type, len, m.p); switch(type){ default: Failure: newreply(&m, SSH_AGENT_FAILURE); reply(a, &m); break; case SSH_AGENTC_REQUEST_RSA_IDENTITIES: vers = 1; newreply(&m, SSH_AGENT_RSA_IDENTITIES_ANSWER); goto Identities; case SSH2_AGENTC_REQUEST_IDENTITIES: vers = 2; newreply(&m, SSH2_AGENT_IDENTITIES_ANSWER); Identities: nk = listkeys(&m, vers); if(nk < 0){ mreset(&m); goto Failure; } if(chatty) fprint(2, "request identities\n", nk); reply(a, &m); break; case SSH_AGENTC_RSA_CHALLENGE: n = get4(&m); USED(n); ek = getmp(&m); mod = getmp(&m); chal = getmp(&m); if((p = (char*)getn(&m, 16)) == nil){ Failchal: mpfree(ek); mpfree(mod); mpfree(chal); goto Failure; } memmove(sessid, p, 16); rt = get4(&m); if(rt != 1 || dorsa(a, mod, ek, chal, chalbuf) < 0) goto Failchal; s = md5(chalbuf, 32, nil, nil); if(s == nil) goto Failchal; md5(sessid, 16, digest, s); print("md5 %.*H %.*H => %.*H\n", 32, chalbuf, 16, sessid, MD5dlen, digest); newreply(&m, SSH_AGENT_RSA_RESPONSE); putn(&m, digest, 16); reply(a, &m); mpfree(ek); mpfree(mod); mpfree(chal); break; case SSH2_AGENTC_SIGN_REQUEST: if(getm(&m, &mkey) == nil || getm(&m, &mdata) == nil) goto Failure; flags = get4(&m); if(flags & SSH_AGENT_OLD_SIGNATURE) goto Failure; if(keysign(&mkey, &mdata, &msig) < 0) goto Failure; if(chatty) fprint(2, "signature: %.*H\n", msig.p-msig.bp, msig.bp); newreply(&m, SSH2_AGENT_SIGN_RESPONSE); putm(&m, &msig); mreset(&msig); reply(a, &m); break; case SSH_AGENTC_ADD_RSA_IDENTITY: /* msg: n[4] mod[mp] pubexp[exp] privexp[mp] p^-1 mod q[mp] p[mp] q[mp] comment[str] */ goto Failure; case SSH_AGENTC_REMOVE_RSA_IDENTITY: /* msg: n[4] mod[mp] pubexp[mp] */ goto Failure; } a->ndata -= 4+len; memmove(a->data, a->data+4+len, a->ndata); return 1; } void* emalloc(int n) { void *v; v = mallocz(n, 1); if(v == nil){ abort(); sysfatal("out of memory allocating %d", n); } return v; } void* erealloc(void *v, int n) { v = realloc(v, n); if(v == nil){ abort(); sysfatal("out of memory reallocating %d", n); } return v; }