/* * devtls - record layer for transport layer security 1.0 and secure sockets layer 3.0 */ #include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "../port/error.h" #include typedef struct OneWay OneWay; typedef struct Secret Secret; enum { MaxRecLen = 1<<14, /* max payload length of a record layer message */ RecHdrLen = 5, TLSVersion = 0x0301, SSL3Version = 0x0300, ProtocolVersion = 0x0301, /* maximum version we speak */ MinProtoVersion = 0x0300, /* limits on version we accept */ MaxProtoVersion = 0x03ff, }; /* connection states */ enum { SHandshake, // doing handshake SOpen, // application data can be sent SRemoteClosed, // remote side has closed down SError, // some sort of error has occured SClosed, // it is all over }; /* record types */ enum { RChangeCipherSpec = 20, RAlert, RHandshake, RApplication, }; /* alerts */ enum { ECloseNotify = 0, EUnexpectedMessage = 10, EBadRecordMac = 20, EDecryptionFailed = 21, ERecordOverflow = 22, EDecompressionFailure = 30, EHandshakeFailure = 40, ENoCertificate = 41, EBadCertificate = 42, EUnsupportedCertificate = 43, ECertificateRevoked = 44, ECertificateExpired = 45, ECertificateUnknown = 46, EIllegalParameter = 47, EUnknownCa = 48, EAccessDenied = 49, EDecodeError = 50, EDecryptError = 51, EExportRestriction = 60, EProtocolVersion = 70, EInsufficientSecurity = 71, EInternalError = 80, EUserCanceled = 90, ENoRenegotiation = 100, EMAX = 256 }; struct Secret { DigestState *(*mac)(uchar*, ulong, uchar*, ulong, uchar*, DigestState*); int maclen; RC4state rc4; uchar mackey[64]; }; struct OneWay { QLock io; /* locks io access */ QLock seclock; /* locks secret paramaters */ ulong seq; Secret *sec; /* cipher in use */ Secret *new; /* cipher waiting for enable */ }; typedef struct TlsRec TlsRec; struct TlsRec { Chan *c; /* io channel */ int ref; /* serialized by dslock for atomic destroy */ int version; /* version of the protocol we are speaking */ int verset; /* version has been set */ Lock statelk; int state; /* must be set using setstate */ /* record layer mac functions for different protocol versions */ void (*packMac)(Secret*, uchar*, uchar*, uchar*, uchar*, int, uchar*); /* input side -- protected by in.q */ OneWay in; Queue *handq; /* queue of handshake messages */ Block *processed; /* next bunch of application data */ Block *unprocessed; /* data read from c but not parsed into records */ /* output side */ OneWay out; /* protections */ char user[NAMELEN]; int perm; }; static Lock dslock; static int dshiwat; static int maxdstate = 128; static TlsRec** dstate; static char *encalgs; static char *hashalgs; enum { Maxdmsg= 1<<16, Maxdstate= 64 }; enum{ Qtopdir = 1, /* top level directory */ Qprotodir, Qclonus, Qconvdir, /* directory for a conversation */ Qdata, Qctl, Qhand, 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(TlsRec*, Block**, int); static void consume(Block**, uchar*, int); static Chan* buftochan(char*); static void tlshangup(TlsRec*); static TlsRec* dsclone(Chan *c); static void dsnew(Chan *c, TlsRec **); static DigestState*sslmac_md5(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s); static void sslPackMac(Secret *sec, uchar *mackey, uchar *seq, uchar *header, uchar *body, int len, uchar *mac); static void tlsPackMac(Secret *sec, uchar *mackey, uchar *seq, uchar *header, uchar *body, int len, uchar *mac); static void put64(uchar *p, vlong x); static void put32(uchar *p, u32int); static void put24(uchar *p, int); static void put16(uchar *p, int); static u32int get32(uchar *p); static int get16(uchar *p); static void tlsAlert(TlsRec *tr, int err); static void tlsError(TlsRec *tr, int err, char *msg, ...); #pragma varargck argpos tlsError 3 static char *tlsnames[] = { [Qclonus] "clone", [Qdata] "data", [Qctl] "ctl", [Qhand] "hand", [Qencalgs] "encalgs", [Qhashalgs] "hashalgs", }; static int tlsgen(Chan *c, Dirtab *d, int nd, int s, Dir *dp) { Qid q; TlsRec *ds; char name[16], *p, *nm; USED(nd); USED(d); q.vers = 0; switch(TYPE(c->qid)) { case Qtopdir: if(s == DEVDOTDOT){ q.path = QID(0, Qtopdir)|CHDIR; devdir(c, q, "#D", 0, eve, CHDIR|0555, dp); return 1; } if(s > 0) return -1; q.path = QID(0, Qprotodir)|CHDIR; devdir(c, q, "tls", 0, eve, CHDIR|0555, dp); return 1; case Qprotodir: if(s == DEVDOTDOT){ q.path = QID(0, Qtopdir)|CHDIR; devdir(c, q, ".", 0, eve, CHDIR|0555, dp); return 1; } 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: if(s == DEVDOTDOT){ q.path = QID(0, Qprotodir)|CHDIR; devdir(c, q, "tls", 0, eve, CHDIR|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), Qhand); p = "hand"; break; case 3: q.path = QID(CONV(c->qid), Qencalgs); p = "encalgs"; break; case 4: 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, tlsnames[TYPE(c->qid)], 0, eve, 0555, dp); return 1; default: ds = dstate[CONV(c->qid)]; devdir(c, c->qid, tlsnames[TYPE(c->qid)], 0, ds->user, 0660, dp); return 1; } return -1; } static Chan* tlsattach(char *spec) { Chan *c; c = devattach('a', spec); c->qid.path = QID(0, Qtopdir)|CHDIR; c->qid.vers = 0; return c; } static int tlswalk(Chan *c, char *name) { return devwalk(c, name, 0, 0, tlsgen); } static void tlsstat(Chan *c, char *db) { devstat(c, db, 0, 0, tlsgen); } static void closedown(Chan *c, TlsRec *tr) { lock(&dslock); if(--tr->ref > 0) { unlock(&dslock); return; } dstate[CONV(c->qid)] = nil; unlock(&dslock); tlshangup(tr); if(tr->c) cclose(tr->c); free(tr->in.sec); free(tr->in.new); free(tr->out.sec); free(tr->out.new); free(tr); } static Chan* tlsopen(Chan *c, int omode) { TlsRec *tr, **pp; int t, perm; perm = 0; omode &= 3; switch(omode) { case OREAD: perm = 4; break; case OWRITE: perm = 2; break; case ORDWR: perm = 6; break; } t = TYPE(c->qid); switch(t) { default: panic("tlsopen"); case Qtopdir: case Qprotodir: case Qconvdir: if(omode != OREAD) error(Eperm); break; case Qclonus: tr = dsclone(c); if(tr == nil) error(Enodev); break; case Qctl: case Qdata: case Qhand: if(waserror()) { unlock(&dslock); nexterror(); } lock(&dslock); pp = &dstate[CONV(c->qid)]; tr = *pp; if(tr == nil) dsnew(c, pp); else { if((perm & (tr->perm>>6)) != perm && (strcmp(up->user, tr->user) != 0 || (perm & tr->perm) != perm)) error(Eperm); tr->ref++; } unlock(&dslock); poperror(); if(t == Qhand){ if(waserror()){ qunlock(&tr->in.io); closedown(c, tr); } qlock(&tr->in.io); if(tr->handq != nil) error(Einuse); tr->handq = qopen(MaxRecLen, 0, nil, nil); if(tr->handq == nil) error("can't allocate handshake queue"); qunlock(&tr->in.io); 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 void tlswstat(Chan *c, char *dp) { Dir d; TlsRec *tr; convM2D(dp, &d); tr = dstate[CONV(c->qid)]; if(tr == nil) error(Ebadusefd); if(strcmp(tr->user, up->user) != 0) error(Eperm); memmove(tr->user, d.uid, NAMELEN); tr->perm = d.mode; } static void tlsclose(Chan *c) { TlsRec *tr; int t; t = TYPE(c->qid); switch(t) { case Qctl: case Qdata: case Qhand: if((c->flag & COPEN) == 0) break; tr = dstate[CONV(c->qid)]; if(tr == nil) break; if(t == Qhand){ qlock(&tr->in.io); if(tr->handq != nil){ qfree(tr->handq); tr->handq = nil; } qunlock(&tr->in.io); } closedown(c, tr); break; } } /* * make sure we have at least 'n' bytes in list 'l' */ static void ensure(TlsRec *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); } } /* * give back n bytes */ static void regurgitate(TlsRec *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* 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; 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("qremove"); return first; } else n -= i; if(BLEN(b) < 0) panic("qremove"); } *l = 0; return first; } /* * read and process one tls record layer message * must be called with tr->in.io held */ static void tlsrecread(TlsRec *tr) { OneWay *volatile in; Block *volatile b; uchar *p, seq[8], header[RecHdrLen], hmac[MD5dlen]; int volatile nconsumed; int len, type, ver; nconsumed = 0; if(waserror()){ if(strcmp(up->error, Eintr) == 0) regurgitate(tr, header, nconsumed); nexterror(); } ensure(tr, &tr->unprocessed, RecHdrLen); consume(&tr->unprocessed, header, RecHdrLen); nconsumed = RecHdrLen; type = header[0]; ver = get16(header+1); len = get16(header+3); if(ver != tr->version && (tr->verset || ver < MinProtoVersion || ver > MaxProtoVersion)) tlsError(tr, EProtocolVersion, "invalid version in record layer"); if(len <= 0) tlsError(tr, EIllegalParameter, "invalid length in record layer"); if(len > MaxRecLen) tlsError(tr, ERecordOverflow, "record message too long"); ensure(tr, &tr->unprocessed, len); nconsumed = 0; /* * If an Eintr happens after this, we'll get out of sync. * Make sure nothing we call can sleep. * Errors are ok, as they kill the connection. * Luckily, allocb won't sleep, it'll just error out. */ b = qremove(&tr->unprocessed, len, 0); in = &tr->in; if(waserror()){ //ZZZ kill the connection qunlock(&in->seclock); if(b != nil) freeb(b); nexterror(); } qlock(&in->seclock); b = pullupblock(b, len); p = b->rp; if(in->sec != nil) { if(len <= in->sec->maclen) tlsError(tr, EDecodeError, "record message too short for mac"); rc4(&in->sec->rc4, p, len); len -= in->sec->maclen; /* update length */ put16(header+3, len); put64(seq, in->seq); in->seq++; (*tr->packMac)(in->sec, in->sec->mackey, seq, header, p, len, hmac); if(memcmp(hmac, p+len, in->sec->maclen) != 0) tlsError(tr, EBadRecordMac, "record mac mismatch"); } qunlock(&tr->in.seclock); poperror(); if(len <= 0) tlsError(tr, EDecodeError, "runt record message"); switch(type) { default: tlsError(tr, EIllegalParameter, "invalid record message 0x%x", type); return; case RChangeCipherSpec: if(len != 1 || p[0] != 1) tlsError(tr, EHandshakeFailure, "invalid change cipher spec"); qlock(&in->seclock); if(in->new == nil){ qunlock(&in->seclock); tlsError(tr, EUnexpectedMessage, "unexpected change cipher spec"); } free(tr->in.sec); tr->in.sec = tr->in.new; tr->in.new = nil; tr->in.seq = 0; qunlock(&in->seclock); break; case RAlert: if(len != 2) tlsError(tr, EDecodeError, "invalid alert"); if(p[0] == 1) { if(p[1] == ECloseNotify) { tlsError(tr, ECloseNotify, "remote close"); tlsSetState(tr, SRemoteClosed); } } else { tlsSetState(tr, SError); tlsAlert(tr, p[1]); } break; case RHandshake: /* * don't worry about dropping the block * qbwrite always queue it even if flow controlled and interrupted. */ if(tr->handq != nil){ qbwrite(tr->handq, b); b = nil; } break; case RApplication: //ZZZ race on state if(tr->state != SOpen) tlsError(tr, EUnexpectedMessage, "application message received before handshake completed"); tr->processed = b; b = nil; break; } if(b != nil) freeb(b); } /* * 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* tlsbread(Chan *c, long n, ulong offset) { TlsRec *volatile tr; Block *b; switch(TYPE(c->qid)) { default: return devbread(c, n, offset); case Qhand: case Qdata: break; } tr = dstate[CONV(c->qid)]; if(tr == nil) panic("tlsbread"); if(waserror()){ qunlock(&tr->in.io); nexterror(); } qlock(&tr->in.io); if(TYPE(c->qid) == Qdata){ //ZZZ race setting state if(tr->state != SOpen) error(Ebadusefd); while(tr->processed == nil) tlsrecread(tr); /* return at most what was asked for */ b = qremove(&tr->processed, n, 0); qunlock(&tr->in.io); poperror(); }else{ //ZZZ race setting state while(tr->state == SHandshake && !qcanread(tr->handq)) tlsrecread(tr); qunlock(&tr->in.io); poperror(); b = qbread(tr->handq, n); } return b; } static long tlsread(Chan *c, void *a, long n, vlong off) { Block *volatile b; Block *nb; uchar *va; int i; char buf[16]; ulong offset = off; if(c->qid.path & CHDIR) return devdirread(c, a, n, 0, 0, tlsgen); switch(TYPE(c->qid)) { default: error(Ebadusefd); case Qctl: snprint(buf, sizeof(buf), "%lud", CONV(c->qid)); return readstr(offset, a, n, buf); case Qdata: b = tlsbread(c, n, offset); break; case Qencalgs: return readstr(offset, a, n, encalgs); case Qhashalgs: return readstr(offset, a, n, hashalgs); } 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++ = nrand(256); } /* * write a block in tls records */ static void tlsrecwrite(TlsRec *tr, int type, Block *b) { Block *volatile bb; Block *nb; uchar *p, seq[8]; OneWay *volatile out; int n, maclen; out = &tr->out; bb = b; if(waserror()){ qunlock(&out->io); if(bb != nil) freeb(bb); nexterror(); } qlock(&out->io); while(bb != nil){ //ZZZ race on state if(tr->state != SHandshake && tr->state != SOpen) error(Ebadusefd); /* * get at most one maximal record's input, * with padding on the front for header and back for mac */ if(waserror()){ qunlock(&out->seclock); nexterror(); } qlock(&out->seclock); maclen = 0; if(out->sec != nil) maclen = out->sec->maclen; n = BLEN(bb); if(n > MaxRecLen){ n = MaxRecLen; nb = allocb(n + RecHdrLen + maclen); memmove(nb->wp + RecHdrLen, bb->rp, n); nb->wp += n + RecHdrLen; bb->rp += n; }else{ /* * carefully reuse bb so it will get freed if we're out of memory */ bb = padblock(bb, RecHdrLen); if(maclen) nb = padblock(bb, -maclen); else nb = bb; bb = nil; } p = nb->rp; p[0] = type; put16(p+1, tr->version); put16(p+3, n); if(out->sec != nil){ put64(seq, out->seq); out->seq++; (*tr->packMac)(out->sec, out->sec->mackey, seq, p, p + RecHdrLen, n, nb->wp); b->wp += maclen; n += maclen; /* update length */ put16(p+3, n); /* encrypt */ rc4(&out->sec->rc4, p+RecHdrLen, n); } qunlock(&out->seclock); poperror(); /* * if bwrite error's, we assume the block is queued. * if not, we're out of sync with the receiver and will not recover. */ devtab[tr->c->type]->bwrite(tr->c, nb, 0); } qunlock(&out->io); poperror(); } static long tlsbwrite(Chan *c, Block *b, ulong offset) { TlsRec *tr; ulong n; n = BLEN(b); tr = dstate[CONV(c->qid)]; if(tr == nil) panic("tlsbread"); switch(TYPE(c->qid)) { default: return devbwrite(c, b, offset); case Qhand: //ZZZ race setting state if(tr->state != SHandshake && tr->state != SOpen) error(Ebadusefd); tlsrecwrite(tr, RHandshake, b); break; case Qdata: //ZZZ race setting state if(tr->state != SOpen) error(Ebadusefd); tlsrecwrite(tr, RApplication, b); break; } return n; } typedef struct Hashalg Hashalg; struct Hashalg { char *name; int maclen; void (*initkey)(Hashalg *, int, Secret *, uchar*); }; static void initmd5key(Hashalg *ha, int version, Secret *s, uchar *p) { s->maclen = ha->maclen; if(version == SSL3Version) s->mac = sslmac_md5; else s->mac = hmac_md5; memmove(s->mackey, p, ha->maclen); } Hashalg hashtab[] = { { "clear" }, { "md5", MD5dlen, initmd5key, }, { 0 } }; static Hashalg* parsehashalg(char *p) { Hashalg *ha; for(ha = hashtab; ha->name; ha++) if(strcmp(p, ha->name) == 0) return ha; error("unsupported hash algorithm"); return nil; } typedef struct Encalg Encalg; struct Encalg { char *name; int keylen; int ivlen; void (*initkey)(Encalg *ea, Secret *, uchar*, uchar*); }; static void initRC4key(Encalg *ea, Secret *s, uchar *p, uchar *) { setupRC4state(&s->rc4, p, ea->keylen); } Encalg encrypttab[] = { { "clear" }, { "rc4_128", 128/8, 0, initRC4key, }, { 0 } }; static Encalg* parseencalg(char *p) { Encalg *ea; for(ea = encrypttab; ea->name; ea++) if(strcmp(p, ea->name) == 0) return ea; error("unsupported encryption algorithm"); return nil; } static long tlswrite(Chan *c, void *a, long n, vlong off) { Encalg *ea; Hashalg *ha; TlsRec *volatile tr; Block *volatile b; Cmdbuf *volatile cb; int m; char *p, *e, buf[128]; uchar *x; ulong offset = off; tr = dstate[CONV(c->qid)]; if(tr == nil) panic("tlswrite"); switch(TYPE(c->qid)){ case Qdata: case Qhand: p = a; e = p + n; do{ m = e - p; if(m > MaxRecLen) m = MaxRecLen; b = allocb(m); if(waserror()){ freeb(b); nexterror(); } memmove(b->wp, p, m); poperror(); b->wp += m; tlsbwrite(c, b, offset); p += m; }while(p < e); return n; case Qctl: break; default: error(Ebadusefd); return -1; } cb = parsecmd(buf, n); if(waserror()){ free(cb); nexterror(); } if(cb->nf < 1) error("short control request"); /* mutex with operations using what we're about to change */ //ZZZ check this locking if(waserror()){ qunlock(&tr->in.seclock); qunlock(&tr->out.seclock); nexterror(); } qlock(&tr->in.seclock); qlock(&tr->out.seclock); if(strcmp(cb->f[0], "fd") == 0){ if(cb->nf != 3) error("usage: fd n version"); if(tr->c != nil) error(Einuse); n = strtol(cb->f[2], nil, 0); if(n < MinProtoVersion || n > MinProtoVersion) error("unsupported version"); tr->c = buftochan(cb->f[1]); tr->version = n; }else if(strcmp(cb->f[0], "version") == 0){ if(cb->nf != 2) error("usage: version n"); if(tr->c == nil) error("must set fd before version"); if(tr->verset) error("version already set"); n = strtol(cb->f[1], nil, 0); if(n == SSL3Version) tr->packMac = sslPackMac; else if(n == TLSVersion) tr->packMac = tlsPackMac; else error("unsupported version"); tr->verset = 1; tr->version = n; }else if(strcmp(cb->f[0], "alert") == 0){ if(cb->nf != 2) error("usage: alert n"); if(tr->c == nil) error("must set fd before sending alerts"); n = strtol(cb->f[1], nil, 0); tlsError(tr, n); b = allocb(2); //ZZZ need to check for fatal error *b->wp++ = 1; *b->wp++ = n; tlsrecwrite(tr, RAlert, b); freeb(b); //ZZZ race on state tlsSetState(tr, SError); }else if(strcmp(cb->f[0], "changecipher") == 0){ if(cb->nf != 1) error("usage: changecipher"); if(tr->out.new == nil) error("can't change cipher spec without setting secret"); b = allocb(1); *b->wp++ = 1; tlsrecwrite(tr, RChangeCipherSpec, b); freeb(b); free(tr->out.sec); tr->out.sec = tr->out.new; tr->out.new = nil; }else if(strcmp(cb->f[0], "secret") == 0){ if(cb->nf != 4) error("usage: secret hashalg encalg secretdata"); if(tr->c == nil || !tr->verset) error("must set fd and version before secrets"); if(tr->in.new != nil){ free(tr->in.new); tr->in.new = nil; } if(tr->out.new != nil){ free(tr->out.new); tr->out.new = nil; } ha = parsehashalg(cb->f[1]); ea = parseencalg(cb->f[2]); m = (strlen(cb->f[3])*3)/2; x = smalloc(m); if(waserror()){ free(x); poperror(); } m = dec64(x, m, cb->f[3], strlen(cb->f[3])); if(m != 2 * ha->maclen + 2 * ea->keylen + 2 * ea->ivlen) error("bad secret data length"); tr->out.new = smalloc(sizeof(Secret)); tr->in.new = smalloc(sizeof(Secret)); if(ha->initkey){ (*ha->initkey)(ha, tr->version, tr->in.new, &x[0]); (*ha->initkey)(ha, tr->version, tr->out.new, &x[ha->maclen]); } if(ea->initkey){ (*ea->initkey)(ea, tr->in.new, &x[2 * ha->maclen], &x[2 * ha->maclen + 2 * ea->keylen]); (*ea->initkey)(ea, tr->out.new, &x[2 * ha->maclen + ea->keylen], &x[2 * ha->maclen + 2 * ea->keylen + ea->ivlen]); } free(x); poperror(); } else error(Ebadarg); qunlock(&tr->in.seclock); qunlock(&tr->out.seclock); poperror(); free(cb); poperror(); return n; } static void tlsinit(void) { struct Encalg *e; struct Hashalg *h; int n; char *cp; if((dstate = smalloc(sizeof(TlsRec*) * maxdstate)) == 0) panic("tlsinit"); 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 tlsdevtab = { 'a', "tls", devreset, tlsinit, tlsattach, devclone, tlswalk, tlsstat, tlsopen, devcreate, tlsclose, tlsread, tlsbread, tlswrite, tlsbwrite, devremove, tlswstat, }; /* 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 */ return c; } /* hand up a digest connection */ static void tlshangup(TlsRec *s) { Block *b; qlock(&s->in.io); for(b = s->processed; b; b = s->processed){ s->processed = b->next; freeb(b); } if(s->unprocessed != nil){ freeb(s->unprocessed); s->unprocessed = nil; } s->state = SClosed; qunlock(&s->in.io); } static TlsRec* dsclone(Chan *ch) { TlsRec **pp, **ep, **np; int newmax; if(waserror()) { unlock(&dslock); nexterror(); } lock(&dslock); ep = &dstate[maxdstate]; for(pp = dstate; pp < ep; pp++) { if(*pp == nil) { dsnew(ch, pp); break; } } if(pp >= ep) { if(maxdstate >= Maxdstate) { unlock(&dslock); poperror(); return nil; } newmax = 2 * maxdstate; if(newmax > Maxdstate) newmax = Maxdstate; np = smalloc(sizeof(TlsRec*) * newmax); if(np == nil) error(Enomem); memmove(np, dstate, sizeof(TlsRec*) * maxdstate); dstate = np; pp = &dstate[maxdstate]; memset(pp, 0, sizeof(TlsRec*)*(newmax - maxdstate)); maxdstate = newmax; dsnew(ch, pp); } unlock(&dslock); poperror(); return *pp; } static void dsnew(Chan *ch, TlsRec **pp) { TlsRec *s; int t; *pp = s = malloc(sizeof(*s)); if(!s) error(Enomem); if(pp - dstate >= dshiwat) dshiwat++; memset(s, 0, sizeof(*s)); s->state = SClosed; s->ref = 1; strncpy(s->user, up->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; } /* * sslmac: mac calculations for ssl 3.0 only; tls 1.0 uses the standard hmac. */ static DigestState* sslmac_x(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s, DigestState*(*x)(uchar*, ulong, uchar*, DigestState*), int xlen, int padlen) { int i; uchar pad[48], innerdigest[20]; if(xlen > sizeof(innerdigest) || padlen > sizeof(pad)) return nil; if(klen>64) return nil; /* first time through */ if(s == nil){ for(i=0; imac)(buf, 11, mackey, sec->maclen, 0, 0); (*sec->mac)(body, len, mackey, sec->maclen, mac, s); } static void tlsPackMac(Secret *sec, uchar *mackey, uchar *seq, uchar *header, uchar *body, int len, uchar *mac) { DigestState *s; uchar buf[13]; memmove(buf, seq, 8); memmove(&buf[8], header, 5); s = (*sec->mac)(buf, 13, mackey, sec->maclen, 0, 0); (*sec->mac)(body, len, mackey, sec->maclen, mac, s); } static void put32(uchar *p, u32int x) { p[0] = x>>24; p[1] = x>>16; p[2] = x>>8; p[3] = x; } static void put64(uchar *p, vlong x) { put32(p, (u32int)(x >> 32)); put32(p+4, (u32int)x); } static void put16(uchar *p, int x) { p[0] = x>>8; p[1] = x; } static u32int get32(uchar *p) { return (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3]; } static int get16(uchar *p) { return (p[0]<<8)|p[1]; }