diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/unix/drawterm/kern/devssl.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/unix/drawterm/kern/devssl.c')
-rwxr-xr-x | sys/src/cmd/unix/drawterm/kern/devssl.c | 1517 |
1 files changed, 1517 insertions, 0 deletions
diff --git a/sys/src/cmd/unix/drawterm/kern/devssl.c b/sys/src/cmd/unix/drawterm/kern/devssl.c new file mode 100755 index 000000000..3ad021f9f --- /dev/null +++ b/sys/src/cmd/unix/drawterm/kern/devssl.c @@ -0,0 +1,1517 @@ +/* + * devssl - secure sockets layer + */ +#include "u.h" +#include "lib.h" +#include "dat.h" +#include "fns.h" +#include "error.h" + +#include "libsec.h" + +#define NOSPOOKS 1 + +typedef struct OneWay OneWay; +struct OneWay +{ + QLock q; + QLock ctlq; + + void *state; /* encryption state */ + int slen; /* hash data length */ + uchar *secret; /* secret */ + ulong mid; /* message id */ +}; + +enum +{ + /* connection states */ + Sincomplete= 0, + Sclear= 1, + Sencrypting= 2, + Sdigesting= 4, + Sdigenc= Sencrypting|Sdigesting, + + /* encryption algorithms */ + Noencryption= 0, + DESCBC= 1, + DESECB= 2, + RC4= 3 +}; + +typedef struct Dstate Dstate; +struct Dstate +{ + Chan *c; /* io channel */ + uchar state; /* state of connection */ + int ref; /* serialized by dslock for atomic destroy */ + + uchar encryptalg; /* encryption algorithm */ + ushort blocklen; /* blocking length */ + + ushort diglen; /* length of digest */ + DigestState *(*hf)(uchar*, ulong, uchar*, DigestState*); /* hash func */ + + /* for SSL format */ + int max; /* maximum unpadded data per msg */ + int maxpad; /* maximum padded data per msg */ + + /* input side */ + OneWay in; + Block *processed; + Block *unprocessed; + + /* output side */ + OneWay out; + + /* protections */ + char *user; + int perm; +}; + +enum +{ + Maxdmsg= 1<<16, + Maxdstate= 128, /* must be a power of 2 */ +}; + +Lock dslock; +int dshiwat; +char *dsname[Maxdstate]; +Dstate *dstate[Maxdstate]; +char *encalgs; +char *hashalgs; + +enum{ + Qtopdir = 1, /* top level directory */ + Qprotodir, + Qclonus, + Qconvdir, /* directory for a conversation */ + Qdata, + Qctl, + Qsecretin, + Qsecretout, + Qencalgs, + Qhashalgs, +}; + +#define TYPE(x) ((x).path & 0xf) +#define CONV(x) (((x).path >> 5)&(Maxdstate-1)) +#define QID(c, y) (((c)<<5) | (y)) + +static void ensure(Dstate*, Block**, int); +static void consume(Block**, uchar*, int); +static void setsecret(OneWay*, uchar*, int); +static Block* encryptb(Dstate*, Block*, int); +static Block* decryptb(Dstate*, Block*); +static Block* digestb(Dstate*, Block*, int); +static void checkdigestb(Dstate*, Block*); +static Chan* buftochan(char*); +static void sslhangup(Dstate*); +static Dstate* dsclone(Chan *c); +static void dsnew(Chan *c, Dstate **); +static long sslput(Dstate *s, Block * volatile b); + +char *sslnames[] = { + /* unused */ 0, + /* topdir */ 0, + /* protodir */ 0, + "clone", + /* convdir */ 0, + "data", + "ctl", + "secretin", + "secretout", + "encalgs", + "hashalgs", +}; + +static int +sslgen(Chan *c, char *n, Dirtab *d, int nd, int s, Dir *dp) +{ + Qid q; + Dstate *ds; + char name[16], *p, *nm; + int ft; + + USED(n); + USED(nd); + USED(d); + + q.type = QTFILE; + q.vers = 0; + + ft = TYPE(c->qid); + switch(ft) { + case Qtopdir: + if(s == DEVDOTDOT){ + q.path = QID(0, Qtopdir); + q.type = QTDIR; + devdir(c, q, "#D", 0, eve, 0555, dp); + return 1; + } + if(s > 0) + return -1; + q.path = QID(0, Qprotodir); + q.type = QTDIR; + devdir(c, q, "ssl", 0, eve, 0555, dp); + return 1; + case Qprotodir: + if(s == DEVDOTDOT){ + q.path = QID(0, Qtopdir); + q.type = QTDIR; + devdir(c, q, ".", 0, eve, 0555, dp); + return 1; + } + if(s < dshiwat) { + q.path = QID(s, Qconvdir); + q.type = QTDIR; + ds = dstate[s]; + if(ds != 0) + nm = ds->user; + else + nm = eve; + if(dsname[s] == nil){ + sprint(name, "%d", s); + kstrdup(&dsname[s], name); + } + devdir(c, q, dsname[s], 0, nm, 0555, dp); + return 1; + } + if(s > dshiwat) + return -1; + q.path = QID(0, Qclonus); + devdir(c, q, "clone", 0, eve, 0555, dp); + return 1; + case Qconvdir: + if(s == DEVDOTDOT){ + q.path = QID(0, Qprotodir); + q.type = QTDIR; + devdir(c, q, "ssl", 0, eve, 0555, dp); + return 1; + } + ds = dstate[CONV(c->qid)]; + if(ds != 0) + nm = ds->user; + else + nm = eve; + switch(s) { + default: + return -1; + case 0: + q.path = QID(CONV(c->qid), Qctl); + p = "ctl"; + break; + case 1: + q.path = QID(CONV(c->qid), Qdata); + p = "data"; + break; + case 2: + q.path = QID(CONV(c->qid), Qsecretin); + p = "secretin"; + break; + case 3: + q.path = QID(CONV(c->qid), Qsecretout); + p = "secretout"; + break; + case 4: + q.path = QID(CONV(c->qid), Qencalgs); + p = "encalgs"; + break; + case 5: + q.path = QID(CONV(c->qid), Qhashalgs); + p = "hashalgs"; + break; + } + devdir(c, q, p, 0, nm, 0660, dp); + return 1; + case Qclonus: + devdir(c, c->qid, sslnames[TYPE(c->qid)], 0, eve, 0555, dp); + return 1; + default: + ds = dstate[CONV(c->qid)]; + if(ds != 0) + nm = ds->user; + else + nm = eve; + devdir(c, c->qid, sslnames[TYPE(c->qid)], 0, nm, 0660, dp); + return 1; + } + return -1; +} + +static Chan* +sslattach(char *spec) +{ + Chan *c; + + c = devattach('D', spec); + c->qid.path = QID(0, Qtopdir); + c->qid.vers = 0; + c->qid.type = QTDIR; + return c; +} + +static Walkqid* +sslwalk(Chan *c, Chan *nc, char **name, int nname) +{ + return devwalk(c, nc, name, nname, nil, 0, sslgen); +} + +static int +sslstat(Chan *c, uchar *db, int n) +{ + return devstat(c, db, n, nil, 0, sslgen); +} + +static Chan* +sslopen(Chan *c, int omode) +{ + Dstate *s, **pp; + int perm; + int ft; + + perm = 0; + omode &= 3; + switch(omode) { + case OREAD: + perm = 4; + break; + case OWRITE: + perm = 2; + break; + case ORDWR: + perm = 6; + break; + } + + ft = TYPE(c->qid); + switch(ft) { + default: + panic("sslopen"); + case Qtopdir: + case Qprotodir: + case Qconvdir: + if(omode != OREAD) + error(Eperm); + break; + case Qclonus: + s = dsclone(c); + if(s == 0) + error(Enodev); + break; + case Qctl: + case Qdata: + case Qsecretin: + case Qsecretout: + if(waserror()) { + unlock(&dslock); + nexterror(); + } + lock(&dslock); + pp = &dstate[CONV(c->qid)]; + s = *pp; + if(s == 0) + dsnew(c, pp); + else { + if((perm & (s->perm>>6)) != perm + && (strcmp(up->user, s->user) != 0 + || (perm & s->perm) != perm)) + error(Eperm); + + s->ref++; + } + unlock(&dslock); + poperror(); + break; + case Qencalgs: + case Qhashalgs: + if(omode != OREAD) + error(Eperm); + break; + } + c->mode = openmode(omode); + c->flag |= COPEN; + c->offset = 0; + return c; +} + +static int +sslwstat(Chan *c, uchar *db, int n) +{ + Dir *dir; + Dstate *s; + int m; + + s = dstate[CONV(c->qid)]; + if(s == 0) + error(Ebadusefd); + if(strcmp(s->user, up->user) != 0) + error(Eperm); + + dir = smalloc(sizeof(Dir)+n); + m = convM2D(db, n, &dir[0], (char*)&dir[1]); + if(m == 0){ + free(dir); + error(Eshortstat); + } + + if(!emptystr(dir->uid)) + kstrdup(&s->user, dir->uid); + if(dir->mode != ~0) + s->perm = dir->mode; + + free(dir); + return m; +} + +static void +sslclose(Chan *c) +{ + Dstate *s; + int ft; + + ft = TYPE(c->qid); + switch(ft) { + case Qctl: + case Qdata: + case Qsecretin: + case Qsecretout: + if((c->flag & COPEN) == 0) + break; + + s = dstate[CONV(c->qid)]; + if(s == 0) + break; + + lock(&dslock); + if(--s->ref > 0) { + unlock(&dslock); + break; + } + dstate[CONV(c->qid)] = 0; + unlock(&dslock); + + if(s->user != nil) + free(s->user); + sslhangup(s); + if(s->c) + cclose(s->c); + if(s->in.secret) + free(s->in.secret); + if(s->out.secret) + free(s->out.secret); + if(s->in.state) + free(s->in.state); + if(s->out.state) + free(s->out.state); + free(s); + + } +} + +/* + * make sure we have at least 'n' bytes in list 'l' + */ +static void +ensure(Dstate *s, Block **l, int n) +{ + int sofar, i; + Block *b, *bl; + + sofar = 0; + for(b = *l; b; b = b->next){ + sofar += BLEN(b); + if(sofar >= n) + return; + l = &b->next; + } + + while(sofar < n){ + bl = devtab[s->c->type]->bread(s->c, Maxdmsg, 0); + if(bl == 0) + nexterror(); + *l = bl; + i = 0; + for(b = bl; b; b = b->next){ + i += BLEN(b); + l = &b->next; + } + if(i == 0) + error(Ehungup); + sofar += i; + } +} + +/* + * copy 'n' bytes from 'l' into 'p' and free + * the bytes in 'l' + */ +static void +consume(Block **l, uchar *p, int n) +{ + Block *b; + int i; + + for(; *l && n > 0; n -= i){ + b = *l; + i = BLEN(b); + if(i > n) + i = n; + memmove(p, b->rp, i); + b->rp += i; + p += i; + if(BLEN(b) < 0) + panic("consume"); + if(BLEN(b)) + break; + *l = b->next; + freeb(b); + } +} + +/* + * give back n bytes +static void +regurgitate(Dstate *s, uchar *p, int n) +{ + Block *b; + + if(n <= 0) + return; + b = s->unprocessed; + if(s->unprocessed == nil || b->rp - b->base < n) { + b = allocb(n); + memmove(b->wp, p, n); + b->wp += n; + b->next = s->unprocessed; + s->unprocessed = b; + } else { + b->rp -= n; + memmove(b->rp, p, n); + } +} + */ + +/* + * remove at most n bytes from the queue, if discard is set + * dump the remainder + */ +static Block* +qtake(Block **l, int n, int discard) +{ + Block *nb, *b, *first; + int i; + + first = *l; + for(b = first; b; b = b->next){ + i = BLEN(b); + if(i == n){ + if(discard){ + freeblist(b->next); + *l = 0; + } else + *l = b->next; + b->next = 0; + return first; + } else if(i > n){ + i -= n; + if(discard){ + freeblist(b->next); + b->wp -= i; + *l = 0; + } else { + nb = allocb(i); + memmove(nb->wp, b->rp+n, i); + nb->wp += i; + b->wp -= i; + nb->next = b->next; + *l = nb; + } + b->next = 0; + if(BLEN(b) < 0) + panic("qtake"); + return first; + } else + n -= i; + if(BLEN(b) < 0) + panic("qtake"); + } + *l = 0; + return first; +} + +/* + * We can't let Eintr's lose data since the program + * doing the read may be able to handle it. The only + * places Eintr is possible is during the read's in consume. + * Therefore, we make sure we can always put back the bytes + * consumed before the last ensure. + */ +static Block* +sslbread(Chan *c, long n, ulong o) +{ + Dstate * volatile s; + Block *b; + uchar consumed[3], *p; + int toconsume; + int len, pad; + + USED(o); + s = dstate[CONV(c->qid)]; + if(s == 0) + panic("sslbread"); + if(s->state == Sincomplete) + error(Ebadusefd); + + qlock(&s->in.q); + if(waserror()){ + qunlock(&s->in.q); + nexterror(); + } + + if(s->processed == 0){ + /* + * Read in the whole message. Until we've got it all, + * it stays on s->unprocessed, so that if we get Eintr, + * we'll pick up where we left off. + */ + ensure(s, &s->unprocessed, 3); + s->unprocessed = pullupblock(s->unprocessed, 2); + p = s->unprocessed->rp; + if(p[0] & 0x80){ + len = ((p[0] & 0x7f)<<8) | p[1]; + ensure(s, &s->unprocessed, len); + pad = 0; + toconsume = 2; + } else { + s->unprocessed = pullupblock(s->unprocessed, 3); + len = ((p[0] & 0x3f)<<8) | p[1]; + pad = p[2]; + if(pad > len){ + print("pad %d buf len %d\n", pad, len); + error("bad pad in ssl message"); + } + toconsume = 3; + } + ensure(s, &s->unprocessed, toconsume+len); + + /* skip header */ + consume(&s->unprocessed, consumed, toconsume); + + /* grab the next message and decode/decrypt it */ + b = qtake(&s->unprocessed, len, 0); + + if(blocklen(b) != len) + print("devssl: sslbread got wrong count %d != %d", blocklen(b), len); + + if(waserror()){ + qunlock(&s->in.ctlq); + if(b != nil) + freeb(b); + nexterror(); + } + qlock(&s->in.ctlq); + switch(s->state){ + case Sencrypting: + if(b == nil) + error("ssl message too short (encrypting)"); + b = decryptb(s, b); + break; + case Sdigesting: + b = pullupblock(b, s->diglen); + if(b == nil) + error("ssl message too short (digesting)"); + checkdigestb(s, b); + pullblock(&b, s->diglen); + len -= s->diglen; + break; + case Sdigenc: + b = decryptb(s, b); + b = pullupblock(b, s->diglen); + if(b == nil) + error("ssl message too short (dig+enc)"); + checkdigestb(s, b); + pullblock(&b, s->diglen); + len -= s->diglen; + break; + } + + /* remove pad */ + if(pad) + s->processed = qtake(&b, len - pad, 1); + else + s->processed = b; + b = nil; + s->in.mid++; + qunlock(&s->in.ctlq); + poperror(); + } + + /* return at most what was asked for */ + b = qtake(&s->processed, n, 0); + + qunlock(&s->in.q); + poperror(); + + return b; +} + +static long +sslread(Chan *c, void *a, long n, vlong off) +{ + Block * volatile b; + Block *nb; + uchar *va; + int i; + char buf[128]; + ulong offset = off; + int ft; + + if(c->qid.type & QTDIR) + return devdirread(c, a, n, 0, 0, sslgen); + + ft = TYPE(c->qid); + switch(ft) { + default: + error(Ebadusefd); + case Qctl: + ft = CONV(c->qid); + sprint(buf, "%d", ft); + return readstr(offset, a, n, buf); + case Qdata: + b = sslbread(c, n, offset); + break; + case Qencalgs: + return readstr(offset, a, n, encalgs); + break; + case Qhashalgs: + return readstr(offset, a, n, hashalgs); + break; + } + + if(waserror()){ + freeblist(b); + nexterror(); + } + + n = 0; + va = a; + for(nb = b; nb; nb = nb->next){ + i = BLEN(nb); + memmove(va+n, nb->rp, i); + n += i; + } + + freeblist(b); + poperror(); + + return n; +} + +/* + * this algorithm doesn't have to be great since we're just + * trying to obscure the block fill + */ +static void +randfill(uchar *buf, int len) +{ + while(len-- > 0) + *buf++ = fastrand(); +} + +static long +sslbwrite(Chan *c, Block *b, ulong o) +{ + Dstate * volatile s; + long rv; + + USED(o); + s = dstate[CONV(c->qid)]; + if(s == nil) + panic("sslbwrite"); + + if(s->state == Sincomplete){ + freeb(b); + error(Ebadusefd); + } + + /* lock so split writes won't interleave */ + if(waserror()){ + qunlock(&s->out.q); + nexterror(); + } + qlock(&s->out.q); + + rv = sslput(s, b); + + poperror(); + qunlock(&s->out.q); + + return rv; +} + +/* + * use SSL record format, add in count, digest and/or encrypt. + * the write is interruptable. if it is interrupted, we'll + * get out of sync with the far side. not much we can do about + * it since we don't know if any bytes have been written. + */ +static long +sslput(Dstate *s, Block * volatile b) +{ + Block *nb; + int h, n, m, pad, rv; + uchar *p; + int offset; + + if(waserror()){ +iprint("error: %s\n", up->errstr); + if(b != nil) + free(b); + nexterror(); + } + + rv = 0; + while(b != nil){ + m = n = BLEN(b); + h = s->diglen + 2; + + /* trim to maximum block size */ + pad = 0; + if(m > s->max){ + m = s->max; + } else if(s->blocklen != 1){ + pad = (m + s->diglen)%s->blocklen; + if(pad){ + if(m > s->maxpad){ + pad = 0; + m = s->maxpad; + } else { + pad = s->blocklen - pad; + h++; + } + } + } + + rv += m; + if(m != n){ + nb = allocb(m + h + pad); + memmove(nb->wp + h, b->rp, m); + nb->wp += m + h; + b->rp += m; + } else { + /* add header space */ + nb = padblock(b, h); + b = 0; + } + m += s->diglen; + + /* SSL style count */ + if(pad){ + nb = padblock(nb, -pad); + randfill(nb->wp, pad); + nb->wp += pad; + m += pad; + + p = nb->rp; + p[0] = (m>>8); + p[1] = m; + p[2] = pad; + offset = 3; + } else { + p = nb->rp; + p[0] = (m>>8) | 0x80; + p[1] = m; + offset = 2; + } + + switch(s->state){ + case Sencrypting: + nb = encryptb(s, nb, offset); + break; + case Sdigesting: + nb = digestb(s, nb, offset); + break; + case Sdigenc: + nb = digestb(s, nb, offset); + nb = encryptb(s, nb, offset); + break; + } + + s->out.mid++; + + m = BLEN(nb); + devtab[s->c->type]->bwrite(s->c, nb, s->c->offset); + s->c->offset += m; + } + + poperror(); + return rv; +} + +static void +setsecret(OneWay *w, uchar *secret, int n) +{ + if(w->secret) + free(w->secret); + + w->secret = smalloc(n); + memmove(w->secret, secret, n); + w->slen = n; +} + +static void +initDESkey(OneWay *w) +{ + if(w->state){ + free(w->state); + w->state = 0; + } + + w->state = smalloc(sizeof(DESstate)); + if(w->slen >= 16) + setupDESstate(w->state, w->secret, w->secret+8); + else if(w->slen >= 8) + setupDESstate(w->state, w->secret, 0); + else + error("secret too short"); +} + +/* + * 40 bit DES is the same as 56 bit DES. However, + * 16 bits of the key are masked to zero. + */ +static void +initDESkey_40(OneWay *w) +{ + uchar key[8]; + + if(w->state){ + free(w->state); + w->state = 0; + } + + if(w->slen >= 8){ + memmove(key, w->secret, 8); + key[0] &= 0x0f; + key[2] &= 0x0f; + key[4] &= 0x0f; + key[6] &= 0x0f; + } + + w->state = malloc(sizeof(DESstate)); + if(w->slen >= 16) + setupDESstate(w->state, key, w->secret+8); + else if(w->slen >= 8) + setupDESstate(w->state, key, 0); + else + error("secret too short"); +} + +static void +initRC4key(OneWay *w) +{ + if(w->state){ + free(w->state); + w->state = 0; + } + + w->state = smalloc(sizeof(RC4state)); + setupRC4state(w->state, w->secret, w->slen); +} + +/* + * 40 bit RC4 is the same as n-bit RC4. However, + * we ignore all but the first 40 bits of the key. + */ +static void +initRC4key_40(OneWay *w) +{ + if(w->state){ + free(w->state); + w->state = 0; + } + + if(w->slen > 5) + w->slen = 5; + + w->state = malloc(sizeof(RC4state)); + setupRC4state(w->state, w->secret, w->slen); +} + +/* + * 128 bit RC4 is the same as n-bit RC4. However, + * we ignore all but the first 128 bits of the key. + */ +static void +initRC4key_128(OneWay *w) +{ + if(w->state){ + free(w->state); + w->state = 0; + } + + if(w->slen > 16) + w->slen = 16; + + w->state = malloc(sizeof(RC4state)); + setupRC4state(w->state, w->secret, w->slen); +} + + +typedef struct Hashalg Hashalg; +struct Hashalg +{ + char *name; + int diglen; + DigestState *(*hf)(uchar*, ulong, uchar*, DigestState*); +}; + +Hashalg hashtab[] = +{ + { "md4", MD4dlen, md4, }, + { "md5", MD5dlen, md5, }, + { "sha1", SHA1dlen, sha1, }, + { "sha", SHA1dlen, sha1, }, + { 0 } +}; + +static int +parsehashalg(char *p, Dstate *s) +{ + Hashalg *ha; + + for(ha = hashtab; ha->name; ha++){ + if(strcmp(p, ha->name) == 0){ + s->hf = ha->hf; + s->diglen = ha->diglen; + s->state &= ~Sclear; + s->state |= Sdigesting; + return 0; + } + } + return -1; +} + +typedef struct Encalg Encalg; +struct Encalg +{ + char *name; + int blocklen; + int alg; + void (*keyinit)(OneWay*); +}; + +#ifdef NOSPOOKS +Encalg encrypttab[] = +{ + { "descbc", 8, DESCBC, initDESkey, }, /* DEPRECATED -- use des_56_cbc */ + { "desecb", 8, DESECB, initDESkey, }, /* DEPRECATED -- use des_56_ecb */ + { "des_56_cbc", 8, DESCBC, initDESkey, }, + { "des_56_ecb", 8, DESECB, initDESkey, }, + { "des_40_cbc", 8, DESCBC, initDESkey_40, }, + { "des_40_ecb", 8, DESECB, initDESkey_40, }, + { "rc4", 1, RC4, initRC4key_40, }, /* DEPRECATED -- use rc4_X */ + { "rc4_256", 1, RC4, initRC4key, }, + { "rc4_128", 1, RC4, initRC4key_128, }, + { "rc4_40", 1, RC4, initRC4key_40, }, + { 0 } +}; +#else +Encalg encrypttab[] = +{ + { "des_40_cbc", 8, DESCBC, initDESkey_40, }, + { "des_40_ecb", 8, DESECB, initDESkey_40, }, + { "rc4", 1, RC4, initRC4key_40, }, /* DEPRECATED -- use rc4_X */ + { "rc4_40", 1, RC4, initRC4key_40, }, + { 0 } +}; +#endif /* NOSPOOKS */ + +static int +parseencryptalg(char *p, Dstate *s) +{ + Encalg *ea; + + for(ea = encrypttab; ea->name; ea++){ + if(strcmp(p, ea->name) == 0){ + s->encryptalg = ea->alg; + s->blocklen = ea->blocklen; + (*ea->keyinit)(&s->in); + (*ea->keyinit)(&s->out); + s->state &= ~Sclear; + s->state |= Sencrypting; + return 0; + } + } + return -1; +} + +static long +sslwrite(Chan *c, void *a, long n, vlong o) +{ + Dstate * volatile s; + Block * volatile b; + int m, t; + char *p, *np, *e, buf[128]; + uchar *x; + + USED(o); + s = dstate[CONV(c->qid)]; + if(s == 0) + panic("sslwrite"); + + t = TYPE(c->qid); + if(t == Qdata){ + if(s->state == Sincomplete) + error(Ebadusefd); + + /* lock should a write gets split over multiple records */ + if(waserror()){ + qunlock(&s->out.q); + nexterror(); + } + qlock(&s->out.q); + p = a; +if(0) iprint("write %d %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux\n", + n, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); + e = p + n; + do { + m = e - p; + if(m > s->max) + m = s->max; + + b = allocb(m); + if(waserror()){ + freeb(b); + nexterror(); + } + memmove(b->wp, p, m); + poperror(); + b->wp += m; + + sslput(s, b); + + p += m; + } while(p < e); + p = a; +if(0) iprint("wrote %d %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux %.2ux\n", + n, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); + poperror(); + qunlock(&s->out.q); + return n; + } + + /* mutex with operations using what we're about to change */ + if(waserror()){ + qunlock(&s->in.ctlq); + qunlock(&s->out.q); + nexterror(); + } + qlock(&s->in.ctlq); + qlock(&s->out.q); + + switch(t){ + default: + panic("sslwrite"); + case Qsecretin: + setsecret(&s->in, a, n); + goto out; + case Qsecretout: + setsecret(&s->out, a, n); + goto out; + case Qctl: + break; + } + + if(n >= sizeof(buf)) + error("arg too long"); + strncpy(buf, a, n); + buf[n] = 0; + p = strchr(buf, '\n'); + if(p) + *p = 0; + p = strchr(buf, ' '); + if(p) + *p++ = 0; + + if(strcmp(buf, "fd") == 0){ + s->c = buftochan(p); + + /* default is clear (msg delimiters only) */ + s->state = Sclear; + s->blocklen = 1; + s->diglen = 0; + s->maxpad = s->max = (1<<15) - s->diglen - 1; + s->in.mid = 0; + s->out.mid = 0; + } else if(strcmp(buf, "alg") == 0 && p != 0){ + s->blocklen = 1; + s->diglen = 0; + + if(s->c == 0) + error("must set fd before algorithm"); + + s->state = Sclear; + s->maxpad = s->max = (1<<15) - s->diglen - 1; + if(strcmp(p, "clear") == 0){ + goto out; + } + + if(s->in.secret && s->out.secret == 0) + setsecret(&s->out, s->in.secret, s->in.slen); + if(s->out.secret && s->in.secret == 0) + setsecret(&s->in, s->out.secret, s->out.slen); + if(s->in.secret == 0 || s->out.secret == 0) + error("algorithm but no secret"); + + s->hf = 0; + s->encryptalg = Noencryption; + s->blocklen = 1; + + for(;;){ + np = strchr(p, ' '); + if(np) + *np++ = 0; + + if(parsehashalg(p, s) < 0) + if(parseencryptalg(p, s) < 0) + error("bad algorithm"); + + if(np == 0) + break; + p = np; + } + + if(s->hf == 0 && s->encryptalg == Noencryption) + error("bad algorithm"); + + if(s->blocklen != 1){ + s->max = (1<<15) - s->diglen - 1; + s->max -= s->max % s->blocklen; + s->maxpad = (1<<14) - s->diglen - 1; + s->maxpad -= s->maxpad % s->blocklen; + } else + s->maxpad = s->max = (1<<15) - s->diglen - 1; + } else if(strcmp(buf, "secretin") == 0 && p != 0) { + m = (strlen(p)*3)/2; + x = smalloc(m); + t = dec64(x, m, p, strlen(p)); + setsecret(&s->in, x, t); + free(x); + } else if(strcmp(buf, "secretout") == 0 && p != 0) { + m = (strlen(p)*3)/2 + 1; + x = smalloc(m); + t = dec64(x, m, p, strlen(p)); + setsecret(&s->out, x, t); + free(x); + } else + error(Ebadarg); + +out: + qunlock(&s->in.ctlq); + qunlock(&s->out.q); + poperror(); + return n; +} + +static void +sslinit(void) +{ + struct Encalg *e; + struct Hashalg *h; + int n; + char *cp; + + n = 1; + for(e = encrypttab; e->name != nil; e++) + n += strlen(e->name) + 1; + cp = encalgs = smalloc(n); + for(e = encrypttab;;){ + strcpy(cp, e->name); + cp += strlen(e->name); + e++; + if(e->name == nil) + break; + *cp++ = ' '; + } + *cp = 0; + + n = 1; + for(h = hashtab; h->name != nil; h++) + n += strlen(h->name) + 1; + cp = hashalgs = smalloc(n); + for(h = hashtab;;){ + strcpy(cp, h->name); + cp += strlen(h->name); + h++; + if(h->name == nil) + break; + *cp++ = ' '; + } + *cp = 0; +} + +Dev ssldevtab = { + 'D', + "ssl", + + devreset, + sslinit, + devshutdown, + sslattach, + sslwalk, + sslstat, + sslopen, + devcreate, + sslclose, + sslread, + sslbread, + sslwrite, + sslbwrite, + devremove, + sslwstat, +}; + +static Block* +encryptb(Dstate *s, Block *b, int offset) +{ + uchar *p, *ep, *p2, *ip, *eip; + DESstate *ds; + + switch(s->encryptalg){ + case DESECB: + ds = s->out.state; + ep = b->rp + BLEN(b); + for(p = b->rp + offset; p < ep; p += 8) + block_cipher(ds->expanded, p, 0); + break; + case DESCBC: + ds = s->out.state; + ep = b->rp + BLEN(b); + for(p = b->rp + offset; p < ep; p += 8){ + p2 = p; + ip = ds->ivec; + for(eip = ip+8; ip < eip; ) + *p2++ ^= *ip++; + block_cipher(ds->expanded, p, 0); + memmove(ds->ivec, p, 8); + } + break; + case RC4: + rc4(s->out.state, b->rp + offset, BLEN(b) - offset); + break; + } + return b; +} + +static Block* +decryptb(Dstate *s, Block *bin) +{ + Block *b, **l; + uchar *p, *ep, *tp, *ip, *eip; + DESstate *ds; + uchar tmp[8]; + int i; + + l = &bin; + for(b = bin; b; b = b->next){ + /* make sure we have a multiple of s->blocklen */ + if(s->blocklen > 1){ + i = BLEN(b); + if(i % s->blocklen){ + *l = b = pullupblock(b, i + s->blocklen - (i%s->blocklen)); + if(b == 0) + error("ssl encrypted message too short"); + } + } + l = &b->next; + + /* decrypt */ + switch(s->encryptalg){ + case DESECB: + ds = s->in.state; + ep = b->rp + BLEN(b); + for(p = b->rp; p < ep; p += 8) + block_cipher(ds->expanded, p, 1); + break; + case DESCBC: + ds = s->in.state; + ep = b->rp + BLEN(b); + for(p = b->rp; p < ep;){ + memmove(tmp, p, 8); + block_cipher(ds->expanded, p, 1); + tp = tmp; + ip = ds->ivec; + for(eip = ip+8; ip < eip; ){ + *p++ ^= *ip; + *ip++ = *tp++; + } + } + break; + case RC4: + rc4(s->in.state, b->rp, BLEN(b)); + break; + } + } + return bin; +} + +static Block* +digestb(Dstate *s, Block *b, int offset) +{ + uchar *p; + DigestState ss; + uchar msgid[4]; + ulong n, h; + OneWay *w; + + w = &s->out; + + memset(&ss, 0, sizeof(ss)); + h = s->diglen + offset; + n = BLEN(b) - h; + + /* hash secret + message */ + (*s->hf)(w->secret, w->slen, 0, &ss); + (*s->hf)(b->rp + h, n, 0, &ss); + + /* hash message id */ + p = msgid; + n = w->mid; + *p++ = n>>24; + *p++ = n>>16; + *p++ = n>>8; + *p = n; + (*s->hf)(msgid, 4, b->rp + offset, &ss); + + return b; +} + +static void +checkdigestb(Dstate *s, Block *bin) +{ + uchar *p; + DigestState ss; + uchar msgid[4]; + int n, h; + OneWay *w; + uchar digest[128]; + Block *b; + + w = &s->in; + + memset(&ss, 0, sizeof(ss)); + + /* hash secret */ + (*s->hf)(w->secret, w->slen, 0, &ss); + + /* hash message */ + h = s->diglen; + for(b = bin; b; b = b->next){ + n = BLEN(b) - h; + if(n < 0) + panic("checkdigestb"); + (*s->hf)(b->rp + h, n, 0, &ss); + h = 0; + } + + /* hash message id */ + p = msgid; + n = w->mid; + *p++ = n>>24; + *p++ = n>>16; + *p++ = n>>8; + *p = n; + (*s->hf)(msgid, 4, digest, &ss); + + if(memcmp(digest, bin->rp, s->diglen) != 0) + error("bad digest"); +} + +/* get channel associated with an fd */ +static Chan* +buftochan(char *p) +{ + Chan *c; + int fd; + + if(p == 0) + error(Ebadarg); + fd = strtoul(p, 0, 0); + if(fd < 0) + error(Ebadarg); + c = fdtochan(fd, -1, 0, 1); /* error check and inc ref */ + if(devtab[c->type] == &ssldevtab){ + cclose(c); + error("cannot ssl encrypt devssl files"); + } + return c; +} + +/* hand up a digest connection */ +static void +sslhangup(Dstate *s) +{ + Block *b; + + qlock(&s->in.q); + for(b = s->processed; b; b = s->processed){ + s->processed = b->next; + freeb(b); + } + if(s->unprocessed){ + freeb(s->unprocessed); + s->unprocessed = 0; + } + s->state = Sincomplete; + qunlock(&s->in.q); +} + +static Dstate* +dsclone(Chan *ch) +{ + int i; + Dstate *ret; + + if(waserror()) { + unlock(&dslock); + nexterror(); + } + lock(&dslock); + ret = nil; + for(i=0; i<Maxdstate; i++){ + if(dstate[i] == nil){ + dsnew(ch, &dstate[i]); + ret = dstate[i]; + break; + } + } + unlock(&dslock); + poperror(); + return ret; +} + +static void +dsnew(Chan *ch, Dstate **pp) +{ + Dstate *s; + int t; + + *pp = s = malloc(sizeof(*s)); + if(!s) + error(Enomem); + if(pp - dstate >= dshiwat) + dshiwat++; + memset(s, 0, sizeof(*s)); + s->state = Sincomplete; + s->ref = 1; + kstrdup(&s->user, up->user); + s->perm = 0660; + t = TYPE(ch->qid); + if(t == Qclonus) + t = Qctl; + ch->qid.path = QID(pp - dstate, t); + ch->qid.vers = 0; +} |