diff options
author | rsc <devnull@localhost> | 2005-02-13 18:04:00 +0000 |
---|---|---|
committer | rsc <devnull@localhost> | 2005-02-13 18:04:00 +0000 |
commit | ce94dbe662155bd60d6839b5e8c82ad708667bcd (patch) | |
tree | 0c405ef046615640061cc6080b9fee516c5c80f8 | |
parent | ea77b9ce7c579a2e625806dc01104d5f6929cc43 (diff) | |
download | plan9port-ce94dbe662155bd60d6839b5e8c82ad708667bcd.tar.gz plan9port-ce94dbe662155bd60d6839b5e8c82ad708667bcd.tar.bz2 plan9port-ce94dbe662155bd60d6839b5e8c82ad708667bcd.zip |
add ssh-agent via factotum
-rw-r--r-- | src/cmd/auth/factotum/ctl.c | 5 | ||||
-rw-r--r-- | src/cmd/auth/factotum/dat.h | 6 | ||||
-rw-r--r-- | src/cmd/auth/factotum/dsa.c | 14 | ||||
-rw-r--r-- | src/cmd/auth/factotum/fs.c | 2 | ||||
-rw-r--r-- | src/cmd/auth/factotum/pkcs1.c | 57 | ||||
-rw-r--r-- | src/cmd/auth/factotum/rpc.c | 35 | ||||
-rw-r--r-- | src/cmd/auth/factotum/rsa.c | 148 | ||||
-rw-r--r-- | src/cmd/auth/secstore/secstore.c | 12 | ||||
-rw-r--r-- | src/cmd/auth/ssh-agent.c | 1047 |
9 files changed, 1267 insertions, 59 deletions
diff --git a/src/cmd/auth/factotum/ctl.c b/src/cmd/auth/factotum/ctl.c index df44b97d..85fbe5d8 100644 --- a/src/cmd/auth/factotum/ctl.c +++ b/src/cmd/auth/factotum/ctl.c @@ -40,6 +40,9 @@ ctlwrite(char *a) Key *k; Proto *proto; + while(*a == ' ' || *a == '\t' || *a == '\n') + a++; + if(a[0] == '#' || a[0] == '\0') return 0; @@ -63,7 +66,7 @@ ctlwrite(char *a) *p++ = '\0'; switch(classify(a)){ default: - werrstr("unknown verb"); + werrstr("unknown verb %s", a); return -1; case 0: /* key */ attr = parseattr(p); diff --git a/src/cmd/auth/factotum/dat.h b/src/cmd/auth/factotum/dat.h index 11648328..678594a2 100644 --- a/src/cmd/auth/factotum/dat.h +++ b/src/cmd/auth/factotum/dat.h @@ -9,6 +9,8 @@ enum RpcRead, RpcStart, RpcWrite, + RpcReadHex, + RpcWriteHex, /* thread stack size - big buffers for printing */ STACK = 65536, @@ -27,6 +29,7 @@ struct Rpc int op; void *data; int count; + int hex; /* should result of read be turned into hex? */ }; struct Conv @@ -214,9 +217,10 @@ extern int xiowrite(int, void*, int); extern int xioasrdresp(int, void*, int); extern int xioasgetticket(int, char*, char*); -/* pkcs1.c */ +/* pkcs1.c - maybe should be in libsec */ typedef DigestState *DigestAlg(uchar*, ulong, uchar*, DigestState*); int rsasign(RSApriv*, DigestAlg*, uchar*, uint, uchar*, uint); +int rsaverify(RSApub*, DigestAlg*, uchar*, uint, uchar*, uint); void mptoberjust(mpint*, uchar*, uint); diff --git a/src/cmd/auth/factotum/dsa.c b/src/cmd/auth/factotum/dsa.c index 73f8d296..c0d199e4 100644 --- a/src/cmd/auth/factotum/dsa.c +++ b/src/cmd/auth/factotum/dsa.c @@ -23,7 +23,7 @@ xdsasign(Conv *c) { int n; mpint *m; - uchar digest[SHA1dlen]; + uchar digest[SHA1dlen], sigblob[20+20]; DSAsig *sig; Key *k; @@ -46,7 +46,13 @@ xdsasign(Conv *c) mpfree(m); if(sig == nil) return -1; - convprint(c, "%B %B", sig->r, sig->s); + if(mpsignif(sig->r) > 20*8 || mpsignif(sig->s) > 20*8){ + werrstr("signature too long"); + return -1; + } + mptoberjust(sig->r, sigblob, 20); + mptoberjust(sig->s, sigblob+20, 20); + convwrite(c, sigblob, sizeof sigblob); dsasigfree(sig); return 0; } @@ -80,11 +86,11 @@ readdsapriv(Key *k) || (priv->pub.q=strtomp(a, nil, 16, nil))==nil) goto Error; strlwr(a); - if((a=strfindattr(k->privattr, "alpha"))==nil + if((a=strfindattr(k->attr, "alpha"))==nil || (priv->pub.alpha=strtomp(a, nil, 16, nil))==nil) goto Error; strlwr(a); - if((a=strfindattr(k->privattr, "key"))==nil + if((a=strfindattr(k->attr, "key"))==nil || (priv->pub.key=strtomp(a, nil, 16, nil))==nil) goto Error; strlwr(a); diff --git a/src/cmd/auth/factotum/fs.c b/src/cmd/auth/factotum/fs.c index f9ad785b..88bbfd8b 100644 --- a/src/cmd/auth/factotum/fs.c +++ b/src/cmd/auth/factotum/fs.c @@ -166,7 +166,7 @@ static int keylist(int i, char *a, uint nn) { int n; - char buf[512]; + char buf[4096]; Key *k; if(i >= ring.nkey) diff --git a/src/cmd/auth/factotum/pkcs1.c b/src/cmd/auth/factotum/pkcs1.c index fb35ce83..f3278454 100644 --- a/src/cmd/auth/factotum/pkcs1.c +++ b/src/cmd/auth/factotum/pkcs1.c @@ -36,7 +36,7 @@ rsasign(RSApriv *key, DigestAlg *hash, uchar *digest, uint dlen, /* * Create number to sign. */ - len = (mpsignif(key->pub.n)+7)/8; + len = (mpsignif(key->pub.n)+7)/8 - 1; if(len < n+2){ werrstr("rsa key too short"); return -1; @@ -65,9 +65,41 @@ rsasign(RSApriv *key, DigestAlg *hash, uchar *digest, uint dlen, mpfree(m); if(s == nil) return -1; - mptoberjust(s, sig, len); + mptoberjust(s, sig, len+1); mpfree(s); - return len; + return len+1; +} + +int +rsaverify(RSApub *key, DigestAlg *hash, uchar *digest, uint dlen, + uchar *sig, uint siglen) +{ + uchar asn1[64], xasn1[64]; + int n, nn; + mpint *m, *s; + + /* + * Create ASN.1 + */ + n = mkasn1(asn1, hash, digest, dlen); + + /* + * Extract plaintext of signature. + */ + s = betomp(sig, siglen, nil); + if(s == nil) + return -1; + m = rsaencrypt(key, s, nil); + mpfree(s); + if(m == nil) + return -1; + nn = mptobe(m, xasn1, sizeof xasn1, nil); + mpfree(m); + if(n != nn || memcmp(asn1, xasn1, n) != 0){ + werrstr("signature did not verify"); + return -1; + } + return 0; } /* @@ -116,6 +148,15 @@ uchar oidmd5[] = { O0(1, 2), O2(840), O3(113549), 2, 5 }; * digestAlgorithm AlgorithmIdentifier, * digest OCTET STRING * } + * + * except that OpenSSL seems to sign + * + * DigestInfo ::= SEQUENCE { + * SEQUENCE{ digestAlgorithm AlgorithmIdentifier, NULL } + * digest OCTET STRING + * } + * + * instead. Sigh. */ static int mkasn1(uchar *asn1, DigestAlg *alg, uchar *d, uint dlen) @@ -138,17 +179,25 @@ mkasn1(uchar *asn1, DigestAlg *alg, uchar *d, uint dlen) *p++ = 0x30; /* sequence */ p++; + *p++ = 0x30; /* another sequence */ + p++; + *p++ = 0x06; /* object id */ *p++ = olen; memmove(p, obj, olen); p += olen; + *p++ = 0x05; /* null */ + *p++ = 0; + + asn1[3] = p - (asn1+4); /* end of inner sequence */ + *p++ = 0x04; /* octet string */ *p++ = dlen; memmove(p, d, dlen); p += dlen; - asn1[1] = p - (asn1+2); + asn1[1] = p - (asn1+2); /* end of outer sequence */ return p-asn1; } diff --git a/src/cmd/auth/factotum/rpc.c b/src/cmd/auth/factotum/rpc.c index e9c163aa..8e2b17a0 100644 --- a/src/cmd/auth/factotum/rpc.c +++ b/src/cmd/auth/factotum/rpc.c @@ -40,6 +40,8 @@ char *rpcname[] = "read", "start", "write", + "readhex", + "writehex", }; static int @@ -153,7 +155,20 @@ rpcexec(Conv *c) { uchar *p; + c->rpc.hex = 0; switch(c->rpc.op){ + case RpcWriteHex: + c->rpc.op = RpcWrite; + if(dec16(c->rpc.data, c->rpc.count, c->rpc.data, c->rpc.count) != c->rpc.count/2){ + rpcrespond(c, "bad hex"); + break; + } + c->rpc.count /= 2; + goto Default; + case RpcReadHex: + c->rpc.hex = 1; + c->rpc.op = RpcRead; + /* fall through */ case RpcRead: if(c->rpc.count > 0){ rpcrespond(c, "error read takes no parameters"); @@ -161,6 +176,7 @@ rpcexec(Conv *c) } /* fall through */ default: + Default: if(!c->active){ if(c->done) rpcrespond(c, "done"); @@ -224,11 +240,18 @@ void rpcrespondn(Conv *c, char *verb, void *data, int count) { char *p; + int need, hex; if(c->hangup) return; - if(strlen(verb)+1+count > sizeof c->reply){ + need = strlen(verb)+1+count; + hex = 0; + if(c->rpc.hex && strcmp(verb, "ok") == 0){ + need += count; + hex = 1; + } + if(need > sizeof c->reply){ print("RPC response too large; caller %#lux", getcallerpc(&c)); return; } @@ -236,8 +259,14 @@ rpcrespondn(Conv *c, char *verb, void *data, int count) strcpy(c->reply, verb); p = c->reply + strlen(c->reply); *p++ = ' '; - memmove(p, data, count); - c->nreply = count + (p - c->reply); + if(hex){ + enc16(p, 2*count, data, count); + p += 2*count; + }else{ + memmove(p, data, count); + p += count; + } + c->nreply = p - c->reply; (*c->kickreply)(c); c->rpc.op = RpcUnknown; } diff --git a/src/cmd/auth/factotum/rsa.c b/src/cmd/auth/factotum/rsa.c index 327dbc5b..34ddb784 100644 --- a/src/cmd/auth/factotum/rsa.c +++ b/src/cmd/auth/factotum/rsa.c @@ -4,65 +4,116 @@ /* * RSA authentication. * - * Client: + * Encrypt/Decrypt: * start n=xxx ek=xxx * write msg - * read decrypt(msg) + * read encrypt/decrypt(msg) * * Sign (PKCS #1 using hash=sha1 or hash=md5) * start n=xxx ek=xxx * write hash(msg) * read signature(hash(msg)) * + * Verify: + * start n=xxx ek=xxx + * write hash(msg) + * write signature(hash(msg)) + * read ok or fail + * * all numbers are hexadecimal biginits parsable with strtomp. * must be lower case for attribute matching in start. */ static int -rsaclient(Conv *c) +xrsadecrypt(Conv *c) { - char *chal; - mpint *m; + char *txt, buf[4096], *role; + int n, ret; + mpint *m, *mm; Key *k; + RSApriv *key; + + ret = -1; + txt = nil; + m = nil; + mm = nil; + /* fetch key */ + c->state = "keylookup"; k = keylookup("%A", c->attr); if(k == nil) - return -1; - c->state = "read challenge"; - if(convreadm(c, &chal) < 0){ - keyclose(k); - return -1; + goto out; + key = k->priv; + + /* make sure have private half if needed */ + role = strfindattr(c->attr, "role"); + if(strcmp(role, "decrypt") == 0 && !key->c2){ + werrstr("missing private half of key -- cannot decrypt"); + goto out; } - if(strlen(chal) < 32){ - badchal: - free(chal); - convprint(c, "bad challenge"); - keyclose(k); - return -1; + + /* read text */ + c->state = "read"; + if((n=convreadm(c, &txt)) < 0) + goto out; + if(n < 32){ + convprint(c, "data too short"); + goto out; } - m = strtomp(chal, nil, 16, nil); + + /* encrypt/decrypt */ + m = betomp(txt, n, nil); if(m == nil) - goto badchal; - free(chal); - m = rsadecrypt(k->priv, m, m); - convprint(c, "%B", m); + goto out; + if(strcmp(role, "decrypt") == 0) + mm = rsadecrypt(key, m, m); + else + mm = rsaencrypt(&key->pub, m, nil); + if(mm == nil) + goto out; + n = mptobe(m, buf, sizeof buf, nil); + + /* send response */ + c->state = "write"; + convwrite(c, buf, n); + ret = 0; + +out: mpfree(m); + mpfree(mm); keyclose(k); - return 0; + free(txt); + return ret; } static int xrsasign(Conv *c) { - char *hash; - int dlen, n; + char *hash, *role; + int dlen, n, ret; DigestAlg *hashfn; Key *k; + RSApriv *key; uchar sig[1024], digest[64]; + char *sig2; + ret = -1; + + /* fetch key */ + c->state = "keylookup"; k = keylookup("%A", c->attr); if(k == nil) - return -1; + goto out; + + /* make sure have private half if needed */ + key = k->priv; + role = strfindattr(c->attr, "role"); + if(strcmp(role, "sign") == 0 && !key->c2){ + werrstr("missing private half of key -- cannot sign"); + goto out; + } + + /* get hash type from key */ hash = strfindattr(k->attr, "hash"); if(hash == nil) hash = "sha1"; @@ -74,20 +125,38 @@ xrsasign(Conv *c) dlen = MD5dlen; }else{ werrstr("unknown hash function %s", hash); - return -1; + goto out; } - c->state = "read data"; - if((n=convread(c, digest, dlen)) < 0){ - keyclose(k); - return -1; + + /* read hash */ + c->state = "read hash"; + if((n=convread(c, digest, dlen)) < 0) + goto out; + + if(strcmp(role, "sign") == 0){ + /* sign */ + if((n=rsasign(key, hashfn, digest, dlen, sig, sizeof sig)) < 0) + goto out; + + /* write */ + convwrite(c, sig, n); + }else{ + /* read signature */ + if((n = convreadm(c, &sig2)) < 0) + goto out; + + /* verify */ + if(rsaverify(&key->pub, hashfn, digest, dlen, (uchar*)sig2, n) == 0) + convprint(c, "ok"); + else + convprint(c, "signature does not verify"); + free(sig2); } - memset(sig, 0xAA, sizeof sig); - n = rsasign(k->priv, hashfn, digest, dlen, sig, sizeof sig); + ret = 0; + +out: keyclose(k); - if(n < 0) - return -1; - convwrite(c, sig, n); - return 0; + return ret; } /* @@ -119,6 +188,9 @@ readrsapriv(Key *k) || (priv->pub.n=strtomp(a, nil, 16, nil))==nil) goto Error; strlwr(a); + if(k->privattr == nil) /* only public half */ + return priv; + if((a=strfindattr(k->privattr, "!p"))==nil || (priv->p=strtomp(a, nil, 16, nil))==nil) goto Error; @@ -177,8 +249,10 @@ rsaclose(Key *k) static Role rsaroles[] = { - "client", rsaclient, "sign", xrsasign, + "verify", xrsasign, /* public operation */ + "decrypt", xrsadecrypt, + "encrypt", xrsadecrypt, /* public operation */ 0 }; diff --git a/src/cmd/auth/secstore/secstore.c b/src/cmd/auth/secstore/secstore.c index 864aa88d..cb6e585e 100644 --- a/src/cmd/auth/secstore/secstore.c +++ b/src/cmd/auth/secstore/secstore.c @@ -494,7 +494,9 @@ main(int argc, char **argv) char *serve, *tcpserve, *user; AuthConn *c; - serve = "$auth"; + serve = getenv("secstore"); + if(serve == nil) + serve = "secstore"; user = getuser(); memset(Gflag, 0, sizeof Gflag); fmtinstall('B', mpfmt); @@ -559,14 +561,8 @@ main(int argc, char **argv) exits("usage"); } - rc = strlen(serve)+sizeof("tcp!!99990"); - tcpserve = emalloc(rc); - if(strchr(serve,'!')) - strcpy(tcpserve, serve); - else - snprint(tcpserve, rc, "tcp!%s!5356", serve); + tcpserve = netmkaddr(serve, "tcp", "secstore"); c = login(user, tcpserve, pass_stdin, pass_nvram); - free(tcpserve); if(c == nil){ fprint(2, "secstore authentication failed\n"); exits("secstore authentication failed"); diff --git a/src/cmd/auth/ssh-agent.c b/src/cmd/auth/ssh-agent.c new file mode 100644 index 00000000..f711ff06 --- /dev/null +++ b/src/cmd/auth/ssh-agent.c @@ -0,0 +1,1047 @@ +/* + * Present factotum in ssh agent clothing. + */ +#include <u.h> +#include <libc.h> +#include <mp.h> +#include <libsec.h> +#include <auth.h> +#include <thread.h> +#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; +}; + +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"); +} + +void +threadmain(int argc, char **argv) +{ + int fd, pid, export, dotextlist; + char dir[100]; + char sock[200], addr[200]; + uvlong x; + + export = 0; + dotextlist = 0; + pid = getpid(); + fmtinstall('B', mpfmt); + fmtinstall('H', encodefmt); + fmtinstall('[', encodefmt); + + ARGBEGIN{ + 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(); + + 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); + + proccreate(listenproc, nil, STACK); + + 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); + 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 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); +} + +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; + return mm; +} + +uchar* +ensure(Msg *m, int n) +{ + int len, plen; + uchar *p; + + len = m->ep - m->bp; + if(m->p+n > m->ep){ + plen = m->p - m->bp; + m->bp = erealloc(m->bp, len+n+1024); + m->p = m->bp+plen; + m->ep = m->bp+len+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 +newmsg(Msg *m) +{ + memset(m, 0, sizeof *m); +} + +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: %.*H\n", p[4], n, m->bp+4); + write(a->fd, p, n+4); + free(p); + memset(m, 0, sizeof *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<nf; i++) /* i=1: f[0] is "key" */ + if(strncmp(f[i], k, len) == 0 && f[i][len] == '=') + return f[i]+len+1; + return nil; +} + +static int +putrsa1(Msg *m, char **f, int nf) +{ + char *p; + mpint *mod, *ek; + + p = find(f, nf, "n"); + if(p == nil || (mod = strtomp(p, nil, 16, nil)) == nil) + return -1; + p = find(f, nf, "ek"); + if(p == nil || (ek = strtomp(p, nil, 16, nil)) == nil){ + mpfree(mod); + return -1; + } + p = find(f, nf, "comment"); + if(p == nil) + p = ""; + put4(m, mpsignif(mod)); + putmp(m, ek); + putmp(m, mod); + putstr(m, p); + mpfree(mod); + mpfree(ek); + return 0; +} + +void +printattr(char **f, int nf) +{ + int i; + + print("#"); + for(i=0; i<nf; i++) + print(" %s", f[i]); + print("\n"); +} + +void +printrsa1(char **f, int nf) +{ + char *p; + mpint *mod, *ek; + + p = find(f, nf, "n"); + if(p == nil || (mod = strtomp(p, nil, 16, nil)) == nil) + return; + p = find(f, nf, "ek"); + if(p == nil || (ek = strtomp(p, nil, 16, nil)) == nil){ + mpfree(mod); + return; + } + p = find(f, nf, "comment"); + if(p == nil) + p = ""; + + if(chatty) + printattr(f, nf); + print("%d %.10B %.10B %s\n", mpsignif(mod), ek, mod, p); + mpfree(ek); + mpfree(mod); +} + +static int +putrsa(Msg *m, char **f, int nf) +{ + char *p; + mpint *mod, *ek; + + p = find(f, nf, "n"); + if(p == nil || (mod = strtomp(p, nil, 16, nil)) == nil) + return -1; + p = find(f, nf, "ek"); + if(p == nil || (ek = strtomp(p, nil, 16, nil)) == nil){ + mpfree(mod); + return -1; + } + putstr(m, "ssh-rsa"); + putmp2(m, ek); + putmp2(m, mod); + mpfree(ek); + mpfree(mod); + return 0; +} + +RSApub* +getrsapub(Msg *m) +{ + RSApub *k; + + k = rsapuballoc(); + if(k == nil) + return nil; + k->ek = getmp2(m); + k->n = getmp2(m); + if(k->ek == nil || k->n == nil){ + rsapubfree(k); + return nil; + } + return k; +} + +static int +putdsa(Msg *m, char **f, int nf) +{ + char *p; + int ret; + mpint *dp, *dq, *dalpha, *dkey; + + ret = -1; + dp = dq = dalpha = dkey = nil; + p = find(f, nf, "p"); + if(p == nil || (dp = strtomp(p, nil, 16, nil)) == nil) + goto out; + p = find(f, nf, "q"); + if(p == nil || (dq = strtomp(p, nil, 16, nil)) == nil) + goto out; + p = find(f, nf, "alpha"); + if(p == nil || (dalpha = strtomp(p, nil, 16, nil)) == nil) + goto out; + p = find(f, nf, "key"); + if(p == nil || (dkey = strtomp(p, nil, 16, nil)) == nil) + goto out; + putstr(m, "ssh-dss"); + putmp2(m, dp); + putmp2(m, dq); + putmp2(m, dalpha); + putmp2(m, dkey); + ret = 0; +out: + mpfree(dp); + mpfree(dq); + mpfree(dalpha); + mpfree(dkey); + return ret; +} + +static int +putkey2(Msg *m, int (*put)(Msg*,char**,int), char **f, int nf) +{ + char *p; + Msg mm; + + newmsg(&mm); + if(put(&mm, f, nf) < 0) + return -1; + putm(m, &mm); + free(mm.bp); + p = find(f, nf, "comment"); + if(p == nil) + p = ""; + putstr(m, p); + return 0; +} + +static int +printkey(char *type, int (*put)(Msg*,char**,int), char **f, int nf) +{ + Msg m; + char *p; + + newmsg(&m); + if(put(&m, f, nf) < 0) + return -1; + p = find(f, nf, "comment"); + if(p == nil) + p = ""; + if(chatty) + printattr(f, nf); + print("%s %.*[ %s\n", type, m.p-m.bp, m.bp, p); + free(m.bp); + return 0; +} + +DSApub* +getdsapub(Msg *m) +{ + DSApub *k; + + k = dsapuballoc(); + if(k == nil) + return nil; + k->p = getmp2(m); + k->q = getmp2(m); + k->alpha = getmp2(m); + k->key = getmp2(m); + if(!k->p || !k->q || !k->alpha || !k->key){ + dsapubfree(k); + return nil; + } + return k; +} + +static int +listkeys(Msg *m, int version) +{ + char buf[8192+1], *line[100], *f[20], *p, *s; + uchar *pnk; + int i, n, nl, nf, nk; + CFid *fid; + + nk = 0; + pnk = m->p; + put4(m, 0); + if((fid = nsopen("factotum", nil, "ctl", OREAD)) == nil){ + fprint(2, "ssh-agent: open factotum: %r\n"); + return -1; + } + for(;;){ + if((n = fsread(fid, buf, sizeof buf-1)) <= 0) + break; + buf[n] = 0; + nl = getfields(buf, line, nelem(line), 1, "\n"); + for(i=0; i<nl; i++){ + nf = tokenize(line[i], f, nelem(f)); + if(nf == 0 || strcmp(f[0], "key") != 0) + continue; + p = find(f, nf, "proto"); + if(p == nil) + continue; + s = find(f, nf, "service"); + if(s == nil) + continue; + + if(version == 1 && strcmp(p, "rsa") == 0 && strcmp(s, "ssh") == 0) + if(putrsa1(m, f, nf) >= 0) + nk++; + if(version == 2 && strcmp(p, "rsa") == 0 && strcmp(s, "ssh-rsa") == 0) + if(putkey2(m, putrsa, f, nf) >= 0) + nk++; + if(version == 2 && strcmp(p, "dsa") == 0 && strcmp(s, "ssh-dss") == 0) + if(putkey2(m, putdsa, f, nf) >= 0) + nk++; + } + } + fsclose(fid); + pnk[0] = (nk>>24)&0xFF; + pnk[1] = (nk>>16)&0xFF; + pnk[2] = (nk>>8)&0xFF; + pnk[3] = nk&0xFF; + return nk; +} + +void +listkeystext(void) +{ + char buf[4096+1], *line[100], *f[20], *p, *s; + int i, n, nl, nf; + CFid *fid; + + if((fid = nsopen(factotum, nil, "ctl", OREAD)) == nil){ + fprint(2, "ssh-agent: open factotum: %r\n"); + return; + } + for(;;){ + if((n = fsread(fid, buf, sizeof buf-1)) <= 0) + break; + buf[n] = 0; + nl = getfields(buf, line, nelem(line), 1, "\n"); + for(i=0; i<nl; i++){ + nf = tokenize(line[i], f, nelem(f)); + if(nf == 0 || strcmp(f[0], "key") != 0) + continue; + p = find(f, nf, "proto"); + if(p == nil) + continue; + s = find(f, nf, "service"); + if(s == nil) + continue; + + if(strcmp(p, "rsa") == 0 && strcmp(s, "ssh") == 0) + printrsa1(f, nf); + if(strcmp(p, "rsa") == 0 && strcmp(s, "ssh-rsa") == 0) + printkey("ssh-rsa", putrsa, f, nf); + if(strcmp(p, "dsa") == 0 && strcmp(s, "ssh-dss") == 0) + printkey("ssh-dss", putdsa, f, nf); + } + } + fsclose(fid); + threadexitsall(nil); +} + +mpint* +rsaunpad(mpint *b) +{ + int i, n; + uchar buf[2560]; + + n = (mpsignif(b)+7)/8; + if(n > sizeof buf){ + werrstr("rsaunpad: too big"); + return nil; + } + mptobe(b, buf, n, nil); + + /* the initial zero has been eaten by the betomp -> mptobe sequence */ + if(buf[0] != 2){ + werrstr("rsaunpad: expected leading 2"); + return nil; + } + for(i=1; i<n; i++) + if(buf[i]==0) + break; + return betomp(buf+i, n-i, nil); +} + +void +mptoberjust(mpint *b, uchar *buf, int len) +{ + int n; + + n = mptobe(b, buf, len, nil); + assert(n >= 0); + if(n < len){ + len -= n; + memmove(buf+len, buf, n); + memset(buf, 0, len); + } +} + +static int +dorsa(Aconn *a, mpint *mod, mpint *exp, mpint *chal, uchar chalbuf[32]) +{ + AuthRpc *rpc; + mpint *m; + char buf[4096], *p; + mpint *decr, *unpad; + + USED(exp); + if((rpc = auth_allocrpc()) == nil){ + fprint(2, "ssh-agent: auth_allocrpc: %r\n"); + return -1; + } + snprint(buf, sizeof buf, "proto=rsa service=ssh role=client n=%lB ek=%lB", mod, exp); + 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; + } + m = nil; + if(auth_rpc(rpc, "read", nil, 0) != ARok){ + fprint(2, "ssh-agent: did not find negotiated key\n"); + goto Die; + } + if(chatty) + fprint(2, "read key %s\n", (char*)rpc->arg); + m = strtomp(rpc->arg, nil, 16, nil); + if(mpcmp(m, mod) != 0){ + fprint(2, "ssh-agent: found wrong key\n"); + mpfree(m); + goto Die; + } + mpfree(m); + + p = mptoa(chal, 16, nil, 0); + if(p == nil){ + fprint(2, "ssh-agent: dorsa: mptoa: %r\n"); + goto Die; + } + if(chatty) + fprint(2, "ssh-agent: challenge %B => %s\n", chal, p); + if(auth_rpc(rpc, "write", p, strlen(p)) != ARok){ + fprint(2, "ssh-agent: dorsa: auth 'write': %r\n"); + free(p); + goto Die; + } + free(p); + if(auth_rpc(rpc, "read", 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){ + free(m.bp); + goto Failure; + } + if(chatty) + fprint(2, "request identities\n", nk); + reply(a, &m); + break; + + case SSH_AGENTC_RSA_CHALLENGE: + n = get4(&m); + ek = getmp(&m); + mod = getmp(&m); + chal = getmp(&m); + if((p = 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); + + 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) < 0 + || getm(&m, &mdata) < 0) + 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); + free(msig.bp); + 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; +} + |