/* * devssl - secure sockets layer emulation */ #include "lib9.h" #include "dat.h" #include "fns.h" #include "error.h" #include "libcrypt.h" 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, Sencrypting, Sdigesting, /* encryption algorithms */ Noencryption= 0, 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[NAMELEN]; int perm; }; Lock dslock; int dshiwat; int maxdstate = 20; Dstate** dstate; enum { Maxdmsg= 1<<16, Maxdstate= 1<<12 }; enum{ Qtopdir = 1, /* top level directory */ Qclonus, Qconvdir, /* directory for a conversation */ Qdata, Qctl, Qsecretin, Qsecretout }; #define TYPE(x) ((x).path & 0xf) #define CONV(x) (((x).path >> 4)&(Maxdstate-1)) #define QID(c, y) (((c)<<4) | (y)) /* for generating random fill */ ulong badlong; uchar *badarray = (uchar*)&badlong; void producerand(void); 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 **); int sslgen(Chan *c, Dirtab *d, int nd, int s, Dir *dp) { Qid q; Dstate *ds; char name[16], *p, *nm; USED(nd); USED(d); q.vers = 0; switch(TYPE(c->qid)) { case Qtopdir: if(s < dshiwat) { sprint(name, "%d", s); q.path = QID(s, Qconvdir)|CHDIR; ds = dstate[s]; if(ds != 0) nm = ds->user; else nm = eve; devdir(c, q, name, 0, nm, CHDIR|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: 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; } devdir(c, q, p, 0, nm, 0660, dp); return 1; } return -1; } void sslinit(void) { if((dstate = malloc(sizeof(Dstate*) * maxdstate)) == 0) panic("sslinit"); } Chan * sslattach(void *spec) { Chan *c; c = devattach('D', spec); c->qid.path = QID(0, Qtopdir)|CHDIR; c->qid.vers = 0; return c; } Chan * sslclone(Chan *c, Chan *nc) { Dstate *s; return devclone(c, nc); } int sslwalk(Chan *c, char *name) { return devwalk(c, name, 0, 0, sslgen); } void sslstat(Chan *c, char *db) { devstat(c, db, 0, 0, sslgen); } Chan * sslopen(Chan *c, int omode) { Dstate *s, **pp; int perm; perm = 0; omode &= 3; switch(omode) { case OREAD: perm = 4; break; case OWRITE: perm = 2; break; case ORDWR: perm = 6; break; } switch(TYPE(c->qid)) { default: panic("sslopen"); case Qtopdir: 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->env->user, s->user) != 0 || (perm & s->perm) != perm)) error(Eperm); s->ref++; } unlock(&dslock); poperror(); break; } c->mode = openmode(omode); c->flag |= COPEN; c->offset = 0; return c; } void sslcreate(Chan *c, char *name, int omode, ulong perm) { USED(c); USED(name); USED(omode); USED(perm); error(Eperm); } void sslremove(Chan *c) { USED(c); error(Eperm); } void sslwstat(Chan *c, char *dp) { Dir d; Dstate *s; convM2D(dp, &d); s = dstate[CONV(c->qid)]; if(s == 0) error(Ebadusefd); if(strcmp(s->user, up->env->user) != 0) error(Eperm); memmove(s->user, d.uid, NAMELEN); s->perm = d.mode; } void sslclose(Chan *c) { Dstate *s; switch(TYPE(c->qid)) { 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); 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); } } static int blen(Block *bp) { int i = 0; for(; bp; bp = bp->next) i += BLEN(bp); return i; } /* * 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) error(Ehungup); *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); } } /* * remove at most n bytes from the queue, if discard is set * dump the remainder */ static Block* qremove(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; break; } 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("qremove"); return first; } else n -= i; if(BLEN(b) < 0) panic("qremove"); } *l = 0; return first; } Block* sslbread(Chan *c, long n, ulong offset) { volatile struct { Dstate *s; } s; Block *b; uchar count[2]; int len, pad; USED(offset); s.s = dstate[CONV(c->qid)]; if(s.s == 0) panic("sslbread"); if(s.s->state == Sincomplete) error(Ebadusefd); if(waserror()){ qunlock(&s.s->in.q); sslhangup(s.s); nexterror(); } qlock(&s.s->in.q); if(s.s->processed == 0){ /* read in the whole message */ ensure(s.s, &s.s->unprocessed, 2); consume(&s.s->unprocessed, count, 2); if(count[0] & 0x80){ len = ((count[0] & 0x7f)<<8) | count[1]; ensure(s.s, &s.s->unprocessed, len); pad = 0; } else { len = ((count[0] & 0x3f)<<8) | count[1]; ensure(s.s, &s.s->unprocessed, len+1); consume(&s.s->unprocessed, count, 1); pad = count[0]; if(pad > len){ print("pad %d buf len %d\n", pad, len); error("bad pad in ssl message"); } } /* put extra on unprocessed queue */ s.s->processed = qremove(&s.s->unprocessed, len, 0); if(waserror()){ qunlock(&s.s->in.ctlq); nexterror(); } qlock(&s.s->in.ctlq); switch(s.s->state){ case Sencrypting: s.s->processed = decryptb(s.s, s.s->processed); break; case Sdigesting: s.s->processed = pullupblock(s.s->processed, s.s->diglen); if(s.s->processed == 0) error("ssl message too short"); checkdigestb(s.s, s.s->processed); s.s->processed->rp += s.s->diglen; break; } qunlock(&s.s->in.ctlq); poperror(); /* remove pad */ if(pad) s.s->processed = qremove(&s.s->processed, len - pad, 1); } /* return at most what was asked for */ b = qremove(&s.s->processed, n, 0); qunlock(&s.s->in.q); poperror(); return b; } long sslread(Chan *c, void *a, long n, ulong offset) { volatile struct { Block *b; } b; Block *nb; uchar *va; int i; char buf[128]; if(c->qid.path & CHDIR) return devdirread(c, a, n, 0, 0, sslgen); switch(TYPE(c->qid)) { default: error(Ebadusefd); case Qctl: sprint(buf, "%d", CONV(c->qid)); return readstr(offset, a, n, buf); case Qdata: b.b = sslbread(c, n, offset); break; } if(waserror()){ freeblist(b.b); nexterror(); } n = 0; va = a; for(nb = b.b; nb; nb = nb->next){ i = BLEN(nb); memmove(va+n, nb->rp, i); n += i; } freeblist(b.b); poperror(); return n; } /* * this algorithm doesn't have to be great since we're just * trying to obscure the block fill */ static void initbadrand(void) { badlong = fastrand(); } static void randfill(uchar *buf, int len) { int j; while(len-- > 0){ j = (((ulong)buf) & 3); if(j == 3) badlong = (badlong*48271)%(2^31 - 1); *buf++ = badarray[j]; } } /* * use SSL record format, add in count and digest or encrypt */ long sslbwrite(Chan *c, Block *b, ulong offset) { volatile struct { Dstate *s; } s; volatile struct { Block *b; } bb; Block *nb; int h, n, m, pad, rv; uchar *p; bb.b = b; s.s = dstate[CONV(c->qid)]; if(s.s == 0) panic("sslbwrite"); if(s.s->state == Sincomplete){ freeb(b); error(Ebadusefd); } if(waserror()){ qunlock(&s.s->out.q); if(bb.b) freeb(bb.b); sslhangup(s.s); nexterror(); } qlock(&s.s->out.q); rv = 0; while(bb.b){ m = n = BLEN(bb.b); h = s.s->diglen + 2; /* trim to maximum block size */ pad = 0; if(m > s.s->max){ m = s.s->max; } else if(s.s->blocklen != 1){ pad = m%s.s->blocklen; if(pad){ if(m > s.s->maxpad){ pad = 0; m = s.s->maxpad; } else { pad = s.s->blocklen - pad; h++; } } } rv += m; if(m != n){ nb = allocb(m + h + pad); memmove(nb->wp + h, bb.b->rp, m); nb->wp += m + h; bb.b->rp += m; } else { /* add header space */ nb = padblock(bb.b, h); bb.b = 0; } m += s.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.s->state){ case Sencrypting: nb = encryptb(s.s, nb, offset); break; case Sdigesting: nb = digestb(s.s, nb, offset); break; } m = BLEN(nb); (*devtab[s.s->c->type].bwrite)(s.s->c, nb, s.s->c->offset); s.s->c->offset += m; } qunlock(&s.s->out.q); poperror(); return rv; } static void setsecret(OneWay *w, uchar *secret, int n) { if(w->secret) free(w->secret); w->secret = malloc(n); memmove(w->secret, secret, n); w->slen = n; w->mid = 0; } static void initRC4key(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); } long sslwrite(Chan *c, void *a, long n, ulong offset) { volatile struct { Dstate *s; } s; volatile struct { Block *b; } b; int m, t; char *p, *e, buf[32]; s.s = dstate[CONV(c->qid)]; if(s.s == 0) panic("sslwrite"); t = TYPE(c->qid); if(t == Qdata){ if(s.s->state == Sincomplete) error(Ebadusefd); p = a; e = p + n; do { m = e - p; if(m > s.s->max) m = s.s->max; b.b = allocb(m); if(waserror()){ freeb(b.b); nexterror(); } memmove(b.b->wp, p, m); poperror(); b.b->wp += m; sslbwrite(c, b.b, offset); p += m; } while(p < e); return n; } /* mutex with operations using what we're about to change */ if(waserror()){ qunlock(&s.s->in.ctlq); qunlock(&s.s->out.q); nexterror(); } qlock(&s.s->in.ctlq); qlock(&s.s->out.q); switch(t){ default: panic("sslwrite"); case Qsecretin: setsecret(&s.s->in, a, n); goto out; return n; case Qsecretout: setsecret(&s.s->out, a, n); goto out; return n; case Qctl: break; } if(n >= sizeof(buf)) error(Ebadarg); 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.s->c = buftochan(p); /* default is clear (msg delimiters only) */ s.s->state = Sclear; s.s->blocklen = 1; s.s->diglen = 0; s.s->maxpad = s.s->max = (1<<15) - s.s->diglen - 1; } else if(strcmp(buf, "alg") == 0 && p != 0){ s.s->blocklen = 1; s.s->diglen = 0; if(s.s->c == 0) error("must set fd before algorithm"); if(strcmp(p, "clear") == 0){ s.s->state = Sclear; s.s->maxpad = s.s->max = (1<<15) - s.s->diglen - 1; goto out; } if(s.s->in.secret && s.s->out.secret == 0) setsecret(&s.s->out, s.s->in.secret, s.s->in.slen); if(s.s->out.secret && s.s->in.secret == 0) setsecret(&s.s->in, s.s->out.secret, s.s->out.slen); if(strcmp(p, "md5") == 0){ s.s->hf = md5; s.s->diglen = MD5dlen; s.s->state = Sdigesting; } else if(strcmp(p, "sha") == 0){ s.s->hf = sha; s.s->diglen = SHAdlen; s.s->state = Sdigesting; } else if(strcmp(p, "rc4") == 0){ if(s.s->in.secret == 0 || s.s->out.secret == 0) error(Ebadarg); s.s->encryptalg = RC4; s.s->blocklen = 1; initRC4key(&s.s->in); initRC4key(&s.s->out); s.s->state = Sencrypting; } else error(Ebadarg); if(s.s->blocklen != 1){ s.s->max = (1<<15) - s.s->diglen - 1; s.s->max -= s.s->max % s.s->blocklen; s.s->maxpad = (1<<14) - s.s->diglen - 1; s.s->maxpad -= s.s->maxpad % s.s->blocklen; } else s.s->maxpad = s.s->max = (1<<15) - s.s->diglen - 1; } else error(Ebadarg); out: qunlock(&s.s->in.ctlq); qunlock(&s.s->out.q); poperror(); return n; } static Block* encryptb(Dstate *s, Block *b, int offset) { switch(s->encryptalg){ case RC4: rc4(s->out.state, b->rp + offset, BLEN(b) - offset); break; } return b; } static Block* decryptb(Dstate *s, Block *inb) { Block *b, **l; int i; l = &inb; for(b = inb; 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 RC4: rc4(s->in.state, b->rp, BLEN(b)); break; } } return inb; } 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 *inb) { 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 = inb; 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, inb->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(up->env->fgrp, fd, -1, 0, 1); /* error check and inc ref */ 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); } /* * crypt's interface to system, included here to override the * library version */ void handle_exception(int type, char *exception) { if(type == CRITICAL) panic("crypt library: %s: %r", exception); else print("crypt library: %s: %r\n", exception); } extern void rbcheck(char*); void* crypt_malloc(int size) { void *x; x = malloc(size); if(x == 0) handle_exception(CRITICAL, "out of memory"); return x; } void crypt_free(void *x) { if(x == 0) handle_exception(CRITICAL, "freeing null pointer"); free(x); } static Dstate* dsclone(Chan *ch) { Dstate **pp, **ep, **np; int newmax; if(waserror()) { unlock(&dslock); nexterror(); } lock(&dslock); ep = &dstate[maxdstate]; for(pp = dstate; pp < ep; pp++) { if(*pp == 0) { dsnew(ch, pp); break; } } if(pp >= ep) { if(maxdstate >= Maxdstate) { unlock(&dslock); poperror(); return 0; } newmax = 2 * maxdstate; if(newmax > Maxdstate) newmax = Maxdstate; np = realloc(dstate, sizeof(Dstate*) * newmax); if(np == 0) error(Enomem); dstate = np; pp = &dstate[maxdstate]; memset(pp, 0, sizeof(Dstate*)*(newmax - maxdstate)); maxdstate = newmax; dsnew(ch, pp); } unlock(&dslock); poperror(); return *pp; } 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; strncpy(s->user, up->env->user, sizeof(s->user)); s->perm = 0660; t = TYPE(ch->qid); if(t == Qclonus) t = Qctl; ch->qid.path = QID(pp - dstate, t); ch->qid.vers = 0; }