ref: 7e5c23755e9363d9c87ebe4045370a5e0f8fc781
parent: 8f934bc2f7c80ed4f14bb43c7add9e7f9dbb64cc
author: Russ Cox <[email protected]>
date: Tue Apr 4 07:38:40 EDT 2006
fix audio for mac; add devtls
--- a/include/lib.h
+++ b/include/lib.h
@@ -230,6 +230,7 @@
extern char* fmtstrflush(Fmt*);
extern int runefmtstrinit(Fmt*);
extern Rune* runefmtstrflush(Fmt*);
+extern int encodefmt(Fmt*);
extern int fmtstrcpy(Fmt*, char*);
extern int fmtprint(Fmt*, char*, ...);
extern int fmtvprint(Fmt*, char*, va_list);
--- a/kern/Makefile
+++ b/kern/Makefile
@@ -21,6 +21,7 @@
devpipe.$O\
devroot.$O\
devssl.$O\
+ devtls.$O\
devtab.$O\
error.$O\
parse.$O\
--- a/kern/devaudio-none.c
+++ b/kern/devaudio-none.c
@@ -21,6 +21,20 @@
error("no audio support");
}
+int
+audiodevread(void *a, int n)
+{
+ error("no audio support");
+ return -1;
+}
+
+int
+audiodevwrite(void *a, int n)
+{
+ error("no audio support");
+ return -1;
+}
+
void
audiodevsetvol(int what, int left, int right)
{
--- a/kern/devtab.c
+++ b/kern/devtab.c
@@ -8,6 +8,7 @@
extern Dev rootdevtab;
extern Dev pipedevtab;
extern Dev ssldevtab;
+extern Dev tlsdevtab;
extern Dev mousedevtab;
extern Dev drawdevtab;
extern Dev ipdevtab;
@@ -21,6 +22,7 @@
&consdevtab,
&pipedevtab,
&ssldevtab,
+ &tlsdevtab,
&mousedevtab,
&drawdevtab,
&ipdevtab,
--- /dev/null
+++ b/kern/devtls.c
@@ -1,0 +1,2179 @@
+/*
+ * devtls - record layer for transport layer security 1.0 and secure sockets layer 3.0
+ */
+#include "u.h"
+#include "lib.h"
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+#include "libsec.h"
+
+typedef struct OneWay OneWay;
+typedef struct Secret Secret;
+typedef struct TlsRec TlsRec;
+typedef struct TlsErrs TlsErrs;
+
+enum {
+ Statlen= 1024, /* max. length of status or stats message */
+ /* buffer limits */
+ MaxRecLen = 1<<14, /* max payload length of a record layer message */
+ MaxCipherRecLen = MaxRecLen + 2048,
+ RecHdrLen = 5,
+ MaxMacLen = SHA1dlen,
+
+ /* protocol versions we can accept */
+ TLSVersion = 0x0301,
+ SSL3Version = 0x0300,
+ ProtocolVersion = 0x0301, /* maximum version we speak */
+ MinProtoVersion = 0x0300, /* limits on version we accept */
+ MaxProtoVersion = 0x03ff,
+
+ /* connection states */
+ SHandshake = 1 << 0, /* doing handshake */
+ SOpen = 1 << 1, /* application data can be sent */
+ SRClose = 1 << 2, /* remote side has closed down */
+ SLClose = 1 << 3, /* sent a close notify alert */
+ SAlert = 1 << 5, /* sending or sent a fatal alert */
+ SError = 1 << 6, /* some sort of error has occured */
+ SClosed = 1 << 7, /* it is all over */
+
+ /* record types */
+ RChangeCipherSpec = 20,
+ RAlert,
+ RHandshake,
+ RApplication,
+
+ SSL2ClientHello = 1,
+ HSSL2ClientHello = 9, /* local convention; see tlshand.c */
+
+ /* alerts */
+ 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
+{
+ char *encalg; /* name of encryption alg */
+ char *hashalg; /* name of hash alg */
+ int (*enc)(Secret*, uchar*, int);
+ int (*dec)(Secret*, uchar*, int);
+ int (*unpad)(uchar*, int, int);
+ DigestState *(*mac)(uchar*, ulong, uchar*, ulong, uchar*, DigestState*);
+ int block; /* encryption block len, 0 if none */
+ int maclen;
+ void *enckey;
+ uchar mackey[MaxMacLen];
+};
+
+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 */
+};
+
+struct TlsRec
+{
+ Chan *c; /* io channel */
+ int ref; /* serialized by tdlock for atomic destroy */
+ int version; /* version of the protocol we are speaking */
+ char verset; /* version has been set */
+ char opened; /* opened command every issued? */
+ char err[ERRMAX]; /* error message to return to handshake requests */
+ vlong handin; /* bytes communicated by the record layer */
+ vlong handout;
+ vlong datain;
+ vlong dataout;
+
+ Lock statelk;
+ int state;
+ int debug;
+
+ /* record layer mac functions for different protocol versions */
+ void (*packMac)(Secret*, uchar*, uchar*, uchar*, uchar*, int, uchar*);
+
+ /* input side -- protected by in.io */
+ OneWay in;
+ Block *processed; /* next bunch of application data */
+ Block *unprocessed; /* data read from c but not parsed into records */
+
+ /* handshake queue */
+ Lock hqlock; /* protects hqref, alloc & free of handq, hprocessed */
+ int hqref;
+ Queue *handq; /* queue of handshake messages */
+ Block *hprocessed; /* remainder of last block read from handq */
+ QLock hqread; /* protects reads for hprocessed, handq */
+
+ /* output side */
+ OneWay out;
+
+ /* protections */
+ char *user;
+ int perm;
+};
+
+struct TlsErrs{
+ int err;
+ int sslerr;
+ int tlserr;
+ int fatal;
+ char *msg;
+};
+
+static TlsErrs tlserrs[] = {
+ {ECloseNotify, ECloseNotify, ECloseNotify, 0, "close notify"},
+ {EUnexpectedMessage, EUnexpectedMessage, EUnexpectedMessage, 1, "unexpected message"},
+ {EBadRecordMac, EBadRecordMac, EBadRecordMac, 1, "bad record mac"},
+ {EDecryptionFailed, EIllegalParameter, EDecryptionFailed, 1, "decryption failed"},
+ {ERecordOverflow, EIllegalParameter, ERecordOverflow, 1, "record too long"},
+ {EDecompressionFailure, EDecompressionFailure, EDecompressionFailure, 1, "decompression failed"},
+ {EHandshakeFailure, EHandshakeFailure, EHandshakeFailure, 1, "could not negotiate acceptable security parameters"},
+ {ENoCertificate, ENoCertificate, ECertificateUnknown, 1, "no appropriate certificate available"},
+ {EBadCertificate, EBadCertificate, EBadCertificate, 1, "corrupted or invalid certificate"},
+ {EUnsupportedCertificate, EUnsupportedCertificate, EUnsupportedCertificate, 1, "unsupported certificate type"},
+ {ECertificateRevoked, ECertificateRevoked, ECertificateRevoked, 1, "revoked certificate"},
+ {ECertificateExpired, ECertificateExpired, ECertificateExpired, 1, "expired certificate"},
+ {ECertificateUnknown, ECertificateUnknown, ECertificateUnknown, 1, "unacceptable certificate"},
+ {EIllegalParameter, EIllegalParameter, EIllegalParameter, 1, "illegal parameter"},
+ {EUnknownCa, EHandshakeFailure, EUnknownCa, 1, "unknown certificate authority"},
+ {EAccessDenied, EHandshakeFailure, EAccessDenied, 1, "access denied"},
+ {EDecodeError, EIllegalParameter, EDecodeError, 1, "error decoding message"},
+ {EDecryptError, EIllegalParameter, EDecryptError, 1, "error decrypting message"},
+ {EExportRestriction, EHandshakeFailure, EExportRestriction, 1, "export restriction violated"},
+ {EProtocolVersion, EIllegalParameter, EProtocolVersion, 1, "protocol version not supported"},
+ {EInsufficientSecurity, EHandshakeFailure, EInsufficientSecurity, 1, "stronger security routines required"},
+ {EInternalError, EHandshakeFailure, EInternalError, 1, "internal error"},
+ {EUserCanceled, ECloseNotify, EUserCanceled, 0, "handshake canceled by user"},
+ {ENoRenegotiation, EUnexpectedMessage, ENoRenegotiation, 0, "no renegotiation"},
+};
+
+enum
+{
+ /* max. open tls connections */
+ MaxTlsDevs = 1024
+};
+
+static Lock tdlock;
+static int tdhiwat;
+static int maxtlsdevs = 128;
+static TlsRec **tlsdevs;
+static char **trnames;
+static char *encalgs;
+static char *hashalgs;
+
+enum{
+ Qtopdir = 1, /* top level directory */
+ Qprotodir,
+ Qclonus,
+ Qencalgs,
+ Qhashalgs,
+ Qconvdir, /* directory for a conversation */
+ Qdata,
+ Qctl,
+ Qhand,
+ Qstatus,
+ Qstats,
+};
+
+#define TYPE(x) ((x).path & 0xf)
+#define CONV(x) (((x).path >> 5)&(MaxTlsDevs-1))
+#define QID(c, y) (((c)<<5) | (y))
+
+static void checkstate(TlsRec *, int, int);
+static void ensure(TlsRec*, Block**, int);
+static void consume(Block**, uchar*, int);
+static Chan* buftochan(char*);
+static void tlshangup(TlsRec*);
+static void tlsError(TlsRec*, char *);
+static void alertHand(TlsRec*, char *);
+static TlsRec *newtls(Chan *c);
+static TlsRec *mktlsrec(void);
+static DigestState*sslmac_md5(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s);
+static DigestState*sslmac_sha1(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s);
+static DigestState*nomac(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 tlsSetState(TlsRec *tr, int new, int old);
+static void rcvAlert(TlsRec *tr, int err);
+static void sendAlert(TlsRec *tr, int err);
+static void rcvError(TlsRec *tr, int err, char *msg, ...);
+static int rc4enc(Secret *sec, uchar *buf, int n);
+static int des3enc(Secret *sec, uchar *buf, int n);
+static int des3dec(Secret *sec, uchar *buf, int n);
+static int noenc(Secret *sec, uchar *buf, int n);
+static int sslunpad(uchar *buf, int n, int block);
+static int tlsunpad(uchar *buf, int n, int block);
+static void freeSec(Secret *sec);
+static char *tlsstate(int s);
+static void pdump(int, void*, char*);
+
+static char *tlsnames[] = {
+[Qclonus] "clone",
+[Qencalgs] "encalgs",
+[Qhashalgs] "hashalgs",
+[Qdata] "data",
+[Qctl] "ctl",
+[Qhand] "hand",
+[Qstatus] "status",
+[Qstats] "stats",
+};
+
+static int convdir[] = { Qctl, Qdata, Qhand, Qstatus, Qstats };
+
+static int
+tlsgen(Chan *c, char*unused1, Dirtab *unused2, int unused3, int s, Dir *dp)
+{
+ Qid q;
+ TlsRec *tr;
+ char *name, *nm;
+ int perm, t;
+
+ q.vers = 0;
+ q.type = QTFILE;
+
+ t = TYPE(c->qid);
+ switch(t) {
+ case Qtopdir:
+ if(s == DEVDOTDOT){
+ q.path = QID(0, Qtopdir);
+ q.type = QTDIR;
+ devdir(c, q, "#a", 0, eve, 0555, dp);
+ return 1;
+ }
+ if(s > 0)
+ return -1;
+ q.path = QID(0, Qprotodir);
+ q.type = QTDIR;
+ devdir(c, q, "tls", 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 < 3){
+ switch(s) {
+ default:
+ return -1;
+ case 0:
+ q.path = QID(0, Qclonus);
+ break;
+ case 1:
+ q.path = QID(0, Qencalgs);
+ break;
+ case 2:
+ q.path = QID(0, Qhashalgs);
+ break;
+ }
+ perm = 0444;
+ if(TYPE(q) == Qclonus)
+ perm = 0555;
+ devdir(c, q, tlsnames[TYPE(q)], 0, eve, perm, dp);
+ return 1;
+ }
+ s -= 3;
+ if(s >= tdhiwat)
+ return -1;
+ q.path = QID(s, Qconvdir);
+ q.type = QTDIR;
+ lock(&tdlock);
+ tr = tlsdevs[s];
+ if(tr != nil)
+ nm = tr->user;
+ else
+ nm = eve;
+ if((name = trnames[s]) == nil){
+ name = trnames[s] = smalloc(16);
+ sprint(name, "%d", s);
+ }
+ devdir(c, q, name, 0, nm, 0555, dp);
+ unlock(&tdlock);
+ return 1;
+ case Qconvdir:
+ if(s == DEVDOTDOT){
+ q.path = QID(0, Qprotodir);
+ q.type = QTDIR;
+ devdir(c, q, "tls", 0, eve, 0555, dp);
+ return 1;
+ }
+ if(s < 0 || s >= nelem(convdir))
+ return -1;
+ lock(&tdlock);
+ tr = tlsdevs[CONV(c->qid)];
+ if(tr != nil){
+ nm = tr->user;
+ perm = tr->perm;
+ }else{
+ perm = 0;
+ nm = eve;
+ }
+ t = convdir[s];
+ if(t == Qstatus || t == Qstats)
+ perm &= 0444;
+ q.path = QID(CONV(c->qid), t);
+ devdir(c, q, tlsnames[t], 0, nm, perm, dp);
+ unlock(&tdlock);
+ return 1;
+ case Qclonus:
+ case Qencalgs:
+ case Qhashalgs:
+ perm = 0444;
+ if(t == Qclonus)
+ perm = 0555;
+ devdir(c, c->qid, tlsnames[t], 0, eve, perm, dp);
+ return 1;
+ default:
+ lock(&tdlock);
+ tr = tlsdevs[CONV(c->qid)];
+ if(tr != nil){
+ nm = tr->user;
+ perm = tr->perm;
+ }else{
+ perm = 0;
+ nm = eve;
+ }
+ if(t == Qstatus || t == Qstats)
+ perm &= 0444;
+ devdir(c, c->qid, tlsnames[t], 0, nm, perm, dp);
+ unlock(&tdlock);
+ return 1;
+ }
+ return -1;
+}
+
+static Chan*
+tlsattach(char *spec)
+{
+ Chan *c;
+
+ c = devattach('a', spec);
+ c->qid.path = QID(0, Qtopdir);
+ c->qid.type = QTDIR;
+ c->qid.vers = 0;
+ return c;
+}
+
+static Walkqid*
+tlswalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ return devwalk(c, nc, name, nname, nil, 0, tlsgen);
+}
+
+static int
+tlsstat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, nil, 0, tlsgen);
+}
+
+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 = newtls(c);
+ if(tr == nil)
+ error(Enodev);
+ break;
+ case Qctl:
+ case Qdata:
+ case Qhand:
+ case Qstatus:
+ case Qstats:
+ if((t == Qstatus || t == Qstats) && omode != OREAD)
+ error(Eperm);
+ if(waserror()) {
+ unlock(&tdlock);
+ nexterror();
+ }
+ lock(&tdlock);
+ pp = &tlsdevs[CONV(c->qid)];
+ tr = *pp;
+ if(tr == nil)
+ error("must open connection using clone");
+ if((perm & (tr->perm>>6)) != perm
+ && (strcmp(up->user, tr->user) != 0
+ || (perm & tr->perm) != perm))
+ error(Eperm);
+ if(t == Qhand){
+ if(waserror()){
+ unlock(&tr->hqlock);
+ nexterror();
+ }
+ lock(&tr->hqlock);
+ if(tr->handq != nil)
+ error(Einuse);
+ tr->handq = qopen(2 * MaxCipherRecLen, 0, nil, nil);
+ if(tr->handq == nil)
+ error("cannot allocate handshake queue");
+ tr->hqref = 1;
+ unlock(&tr->hqlock);
+ poperror();
+ }
+ tr->ref++;
+ unlock(&tdlock);
+ poperror();
+ break;
+ case Qencalgs:
+ case Qhashalgs:
+ if(omode != OREAD)
+ error(Eperm);
+ break;
+ }
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ c->iounit = qiomaxatomic;
+ return c;
+}
+
+static int
+tlswstat(Chan *c, uchar *dp, int n)
+{
+ Dir *d;
+ TlsRec *tr;
+ int rv;
+
+ d = nil;
+ if(waserror()){
+ free(d);
+ unlock(&tdlock);
+ nexterror();
+ }
+
+ lock(&tdlock);
+ tr = tlsdevs[CONV(c->qid)];
+ if(tr == nil)
+ error(Ebadusefd);
+ if(strcmp(tr->user, up->user) != 0)
+ error(Eperm);
+
+ d = smalloc(n + sizeof *d);
+ rv = convM2D(dp, n, &d[0], (char*) &d[1]);
+ if(rv == 0)
+ error(Eshortstat);
+ if(!emptystr(d->uid))
+ kstrdup(&tr->user, d->uid);
+ if(d->mode != ~0UL)
+ tr->perm = d->mode;
+
+ free(d);
+ poperror();
+ unlock(&tdlock);
+
+ return rv;
+}
+
+static void
+dechandq(TlsRec *tr)
+{
+ lock(&tr->hqlock);
+ if(--tr->hqref == 0){
+ if(tr->handq != nil){
+ qfree(tr->handq);
+ tr->handq = nil;
+ }
+ if(tr->hprocessed != nil){
+ freeb(tr->hprocessed);
+ tr->hprocessed = nil;
+ }
+ }
+ unlock(&tr->hqlock);
+}
+
+static void
+tlsclose(Chan *c)
+{
+ TlsRec *tr;
+ int t;
+
+ t = TYPE(c->qid);
+ switch(t) {
+ case Qctl:
+ case Qdata:
+ case Qhand:
+ case Qstatus:
+ case Qstats:
+ if((c->flag & COPEN) == 0)
+ break;
+
+ tr = tlsdevs[CONV(c->qid)];
+ if(tr == nil)
+ break;
+
+ if(t == Qhand)
+ dechandq(tr);
+
+ lock(&tdlock);
+ if(--tr->ref > 0) {
+ unlock(&tdlock);
+ return;
+ }
+ tlsdevs[CONV(c->qid)] = nil;
+ unlock(&tdlock);
+
+ if(tr->c != nil && !waserror()){
+ checkstate(tr, 0, SOpen|SHandshake|SRClose);
+ sendAlert(tr, ECloseNotify);
+ poperror();
+ }
+ tlshangup(tr);
+ if(tr->c != nil)
+ cclose(tr->c);
+ freeSec(tr->in.sec);
+ freeSec(tr->in.new);
+ freeSec(tr->out.sec);
+ freeSec(tr->out.new);
+ free(tr->user);
+ free(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, MaxCipherRecLen + RecHdrLen, 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;
+ }
+if(s->debug) pprint("ensure read %d\n", sofar);
+}
+
+/*
+ * 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
+ */
+static Block*
+qgrab(Block **l, int n)
+{
+ Block *bb, *b;
+ int i;
+
+ b = *l;
+ if(BLEN(b) == n){
+ *l = b->next;
+ b->next = nil;
+ return b;
+ }
+
+ i = 0;
+ for(bb = b; bb != nil && i < n; bb = bb->next)
+ i += BLEN(bb);
+ if(i > n)
+ i = n;
+
+ bb = allocb(i);
+ consume(l, bb->wp, i);
+ bb->wp += i;
+ return bb;
+}
+
+static void
+tlsclosed(TlsRec *tr, int new)
+{
+ lock(&tr->statelk);
+ if(tr->state == SOpen || tr->state == SHandshake)
+ tr->state = new;
+ else if((new | tr->state) == (SRClose|SLClose))
+ tr->state = SClosed;
+ unlock(&tr->statelk);
+ alertHand(tr, "close notify");
+}
+
+/*
+ * read and process one tls record layer message
+ * must be called with tr->in.io held
+ * We can't let Eintrs lose data, since doing so will get
+ * us out of sync with the sender and break the reliablity
+ * of the channel. Eintr only happens during the reads in
+ * consume. Therefore we put back any bytes consumed before
+ * the last call to ensure.
+ */
+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, unpad_len;
+
+ nconsumed = 0;
+ if(waserror()){
+ if(strcmp(up->errstr, Eintr) == 0 && !waserror()){
+ regurgitate(tr, header, nconsumed);
+ poperror();
+ }else
+ tlsError(tr, "channel error");
+ nexterror();
+ }
+ ensure(tr, &tr->unprocessed, RecHdrLen);
+ consume(&tr->unprocessed, header, RecHdrLen);
+if(tr->debug)pprint("consumed %d header\n", RecHdrLen);
+ nconsumed = RecHdrLen;
+
+ if((tr->handin == 0) && (header[0] & 0x80)){
+ /* Cope with an SSL3 ClientHello expressed in SSL2 record format.
+ This is sent by some clients that we must interoperate
+ with, such as Java's JSSE and Microsoft's Internet Explorer. */
+ len = (get16(header) & ~0x8000) - 3;
+ type = header[2];
+ ver = get16(header + 3);
+ if(type != SSL2ClientHello || len < 22)
+ rcvError(tr, EProtocolVersion, "invalid initial SSL2-like message");
+ }else{ /* normal SSL3 record format */
+ type = header[0];
+ ver = get16(header+1);
+ len = get16(header+3);
+ }
+ if(ver != tr->version && (tr->verset || ver < MinProtoVersion || ver > MaxProtoVersion))
+ rcvError(tr, EProtocolVersion, "devtls expected ver=%x%s, saw (len=%d) type=%x ver=%x '%.12s'",
+ tr->version, tr->verset?"/set":"", len, type, ver, (char*)header);
+ if(len > MaxCipherRecLen || len < 0)
+ rcvError(tr, ERecordOverflow, "record message too long %d", len);
+ ensure(tr, &tr->unprocessed, len);
+ nconsumed = 0;
+ poperror();
+
+ /*
+ * 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 = nil;
+ if(waserror()){
+ if(b != nil)
+ freeb(b);
+ tlsError(tr, "channel error");
+ nexterror();
+ }
+ b = qgrab(&tr->unprocessed, len);
+if(tr->debug) pprint("consumed unprocessed %d\n", len);
+
+ in = &tr->in;
+ if(waserror()){
+ qunlock(&in->seclock);
+ nexterror();
+ }
+ qlock(&in->seclock);
+ p = b->rp;
+ if(in->sec != nil) {
+ /* to avoid Canvel-Hiltgen-Vaudenay-Vuagnoux attack, all errors here
+ should look alike, including timing of the response. */
+ unpad_len = (*in->sec->dec)(in->sec, p, len);
+ if(unpad_len >= in->sec->maclen)
+ len = unpad_len - in->sec->maclen;
+if(tr->debug) pprint("decrypted %d\n", unpad_len);
+if(tr->debug) pdump(unpad_len, p, "decrypted:");
+
+ /* 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(unpad_len < in->sec->maclen)
+ rcvError(tr, EBadRecordMac, "short record mac");
+ if(memcmp(hmac, p+len, in->sec->maclen) != 0)
+ rcvError(tr, EBadRecordMac, "record mac mismatch");
+ b->wp = b->rp + len;
+ }
+ qunlock(&in->seclock);
+ poperror();
+ if(len < 0)
+ rcvError(tr, EDecodeError, "runt record message");
+
+ switch(type) {
+ default:
+ rcvError(tr, EIllegalParameter, "invalid record message 0x%x", type);
+ break;
+ case RChangeCipherSpec:
+ if(len != 1 || p[0] != 1)
+ rcvError(tr, EDecodeError, "invalid change cipher spec");
+ qlock(&in->seclock);
+ if(in->new == nil){
+ qunlock(&in->seclock);
+ rcvError(tr, EUnexpectedMessage, "unexpected change cipher spec");
+ }
+ freeSec(in->sec);
+ in->sec = in->new;
+ in->new = nil;
+ in->seq = 0;
+ qunlock(&in->seclock);
+ break;
+ case RAlert:
+ if(len != 2)
+ rcvError(tr, EDecodeError, "invalid alert");
+ if(p[0] == 2)
+ rcvAlert(tr, p[1]);
+ if(p[0] != 1)
+ rcvError(tr, EIllegalParameter, "invalid alert fatal code");
+
+ /*
+ * propate non-fatal alerts to handshaker
+ */
+ if(p[1] == ECloseNotify) {
+ tlsclosed(tr, SRClose);
+ if(tr->opened)
+ error("tls hungup");
+ error("close notify");
+ }
+ if(p[1] == ENoRenegotiation)
+ alertHand(tr, "no renegotiation");
+ else if(p[1] == EUserCanceled)
+ alertHand(tr, "handshake canceled by user");
+ else
+ rcvError(tr, EIllegalParameter, "invalid alert code");
+ break;
+ case RHandshake:
+ /*
+ * don't worry about dropping the block
+ * qbwrite always queues even if flow controlled and interrupted.
+ *
+ * if there isn't any handshaker, ignore the request,
+ * but notify the other side we are doing so.
+ */
+ lock(&tr->hqlock);
+ if(tr->handq != nil){
+ tr->hqref++;
+ unlock(&tr->hqlock);
+ if(waserror()){
+ dechandq(tr);
+ nexterror();
+ }
+ b = padblock(b, 1);
+ *b->rp = RHandshake;
+ qbwrite(tr->handq, b);
+ b = nil;
+ poperror();
+ dechandq(tr);
+ }else{
+ unlock(&tr->hqlock);
+ if(tr->verset && tr->version != SSL3Version && !waserror()){
+ sendAlert(tr, ENoRenegotiation);
+ poperror();
+ }
+ }
+ break;
+ case SSL2ClientHello:
+ lock(&tr->hqlock);
+ if(tr->handq != nil){
+ tr->hqref++;
+ unlock(&tr->hqlock);
+ if(waserror()){
+ dechandq(tr);
+ nexterror();
+ }
+ /* Pass the SSL2 format data, so that the handshake code can compute
+ the correct checksums. HSSL2ClientHello = HandshakeType 9 is
+ unused in RFC2246. */
+ b = padblock(b, 8);
+ b->rp[0] = RHandshake;
+ b->rp[1] = HSSL2ClientHello;
+ put24(&b->rp[2], len+3);
+ b->rp[5] = SSL2ClientHello;
+ put16(&b->rp[6], ver);
+ qbwrite(tr->handq, b);
+ b = nil;
+ poperror();
+ dechandq(tr);
+ }else{
+ unlock(&tr->hqlock);
+ if(tr->verset && tr->version != SSL3Version && !waserror()){
+ sendAlert(tr, ENoRenegotiation);
+ poperror();
+ }
+ }
+ break;
+ case RApplication:
+ if(!tr->opened)
+ rcvError(tr, EUnexpectedMessage, "application message received before handshake completed");
+ if(BLEN(b) > 0){
+ tr->processed = b;
+ b = nil;
+ }
+ break;
+ }
+ if(b != nil)
+ freeb(b);
+ poperror();
+}
+
+/*
+ * got a fatal alert message
+ */
+static void
+rcvAlert(TlsRec *tr, int err)
+{
+ char *s;
+ int i;
+
+ s = "unknown error";
+ for(i=0; i < nelem(tlserrs); i++){
+ if(tlserrs[i].err == err){
+ s = tlserrs[i].msg;
+ break;
+ }
+ }
+if(tr->debug) pprint("rcvAlert: %s\n", s);
+
+ tlsError(tr, s);
+ if(!tr->opened)
+ error(s);
+ error("tls error");
+}
+
+/*
+ * found an error while decoding the input stream
+ */
+static void
+rcvError(TlsRec *tr, int err, char *fmt, ...)
+{
+ char msg[ERRMAX];
+ va_list arg;
+
+ va_start(arg, fmt);
+ vseprint(msg, msg+sizeof(msg), fmt, arg);
+ va_end(arg);
+if(tr->debug) pprint("rcvError: %s\n", msg);
+
+ sendAlert(tr, err);
+
+ if(!tr->opened)
+ error(msg);
+ error("tls error");
+}
+
+/*
+ * make sure the next hand operation returns with a 'msg' error
+ */
+static void
+alertHand(TlsRec *tr, char *msg)
+{
+ Block *b;
+ int n;
+
+ lock(&tr->hqlock);
+ if(tr->handq == nil){
+ unlock(&tr->hqlock);
+ return;
+ }
+ tr->hqref++;
+ unlock(&tr->hqlock);
+
+ n = strlen(msg);
+ if(waserror()){
+ dechandq(tr);
+ nexterror();
+ }
+ b = allocb(n + 2);
+ *b->wp++ = RAlert;
+ memmove(b->wp, msg, n + 1);
+ b->wp += n + 1;
+
+ qbwrite(tr->handq, b);
+
+ poperror();
+ dechandq(tr);
+}
+
+static void
+checkstate(TlsRec *tr, int ishand, int ok)
+{
+ int state;
+
+ lock(&tr->statelk);
+ state = tr->state;
+ unlock(&tr->statelk);
+ if(state & ok)
+ return;
+ switch(state){
+ case SHandshake:
+ case SOpen:
+ break;
+ case SError:
+ case SAlert:
+ if(ishand)
+ error(tr->err);
+ error("tls error");
+ case SRClose:
+ case SLClose:
+ case SClosed:
+ error("tls hungup");
+ }
+ error("tls improperly configured");
+}
+
+static Block*
+tlsbread(Chan *c, long n, ulong offset)
+{
+ int ty;
+ Block *b;
+ TlsRec *volatile tr;
+
+ ty = TYPE(c->qid);
+ switch(ty) {
+ default:
+ return devbread(c, n, offset);
+ case Qhand:
+ case Qdata:
+ break;
+ }
+
+ tr = tlsdevs[CONV(c->qid)];
+ if(tr == nil)
+ panic("tlsbread");
+
+ if(waserror()){
+ qunlock(&tr->in.io);
+ nexterror();
+ }
+ qlock(&tr->in.io);
+ if(ty == Qdata){
+ checkstate(tr, 0, SOpen);
+ while(tr->processed == nil)
+ tlsrecread(tr);
+
+ /* return at most what was asked for */
+ b = qgrab(&tr->processed, n);
+if(tr->debug) pprint("consumed processed %d\n", BLEN(b));
+if(tr->debug) pdump(BLEN(b), b->rp, "consumed:");
+ qunlock(&tr->in.io);
+ poperror();
+ tr->datain += BLEN(b);
+ }else{
+ checkstate(tr, 1, SOpen|SHandshake|SLClose);
+
+ /*
+ * it's ok to look at state without the lock
+ * since it only protects reading records,
+ * and we have that tr->in.io held.
+ */
+ while(!tr->opened && tr->hprocessed == nil && !qcanread(tr->handq))
+ tlsrecread(tr);
+
+ qunlock(&tr->in.io);
+ poperror();
+
+ if(waserror()){
+ qunlock(&tr->hqread);
+ nexterror();
+ }
+ qlock(&tr->hqread);
+ if(tr->hprocessed == nil){
+ b = qbread(tr->handq, MaxRecLen + 1);
+ if(*b->rp++ == RAlert){
+ kstrcpy(up->errstr, (char*)b->rp, ERRMAX);
+ freeb(b);
+ nexterror();
+ }
+ tr->hprocessed = b;
+ }
+ b = qgrab(&tr->hprocessed, n);
+ poperror();
+ qunlock(&tr->hqread);
+ tr->handin += BLEN(b);
+ }
+
+ return b;
+}
+
+static long
+tlsread(Chan *c, void *a, long n, vlong off)
+{
+ Block *volatile b;
+ Block *nb;
+ uchar *va;
+ int i, ty;
+ char *buf, *s, *e;
+ ulong offset = off;
+ TlsRec * tr;
+
+ if(c->qid.type & QTDIR)
+ return devdirread(c, a, n, 0, 0, tlsgen);
+
+ tr = tlsdevs[CONV(c->qid)];
+ ty = TYPE(c->qid);
+ switch(ty) {
+ default:
+ error(Ebadusefd);
+ case Qstatus:
+ buf = smalloc(Statlen);
+ qlock(&tr->in.seclock);
+ qlock(&tr->out.seclock);
+ s = buf;
+ e = buf + Statlen;
+ s = seprint(s, e, "State: %s\n", tlsstate(tr->state));
+ s = seprint(s, e, "Version: 0x%x\n", tr->version);
+ if(tr->in.sec != nil)
+ s = seprint(s, e, "EncIn: %s\nHashIn: %s\n", tr->in.sec->encalg, tr->in.sec->hashalg);
+ if(tr->in.new != nil)
+ s = seprint(s, e, "NewEncIn: %s\nNewHashIn: %s\n", tr->in.new->encalg, tr->in.new->hashalg);
+ if(tr->out.sec != nil)
+ s = seprint(s, e, "EncOut: %s\nHashOut: %s\n", tr->out.sec->encalg, tr->out.sec->hashalg);
+ if(tr->out.new != nil)
+ seprint(s, e, "NewEncOut: %s\nNewHashOut: %s\n", tr->out.new->encalg, tr->out.new->hashalg);
+ qunlock(&tr->in.seclock);
+ qunlock(&tr->out.seclock);
+ n = readstr(offset, a, n, buf);
+ free(buf);
+ return n;
+ case Qstats:
+ buf = smalloc(Statlen);
+ s = buf;
+ e = buf + Statlen;
+ s = seprint(s, e, "DataIn: %lld\n", tr->datain);
+ s = seprint(s, e, "DataOut: %lld\n", tr->dataout);
+ s = seprint(s, e, "HandIn: %lld\n", tr->handin);
+ seprint(s, e, "HandOut: %lld\n", tr->handout);
+ n = readstr(offset, a, n, buf);
+ free(buf);
+ return n;
+ case Qctl:
+ buf = smalloc(Statlen);
+ snprint(buf, Statlen, "%llud", CONV(c->qid));
+ n = readstr(offset, a, n, buf);
+ free(buf);
+ return n;
+ case Qdata:
+ case Qhand:
+ 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;
+}
+
+/*
+ * 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, pad, ok;
+
+ out = &tr->out;
+ bb = b;
+ if(waserror()){
+ qunlock(&out->io);
+ if(bb != nil)
+ freeb(bb);
+ nexterror();
+ }
+ qlock(&out->io);
+if(tr->debug)pprint("send %d\n", BLEN(b));
+if(tr->debug)pdump(BLEN(b), b->rp, "sent:");
+
+
+ ok = SHandshake|SOpen|SRClose;
+ if(type == RAlert)
+ ok |= SAlert;
+ while(bb != nil){
+ checkstate(tr, type != RApplication, ok);
+
+ /*
+ * get at most one maximal record's input,
+ * with padding on the front for header and
+ * back for mac and maximal block padding.
+ */
+ if(waserror()){
+ qunlock(&out->seclock);
+ nexterror();
+ }
+ qlock(&out->seclock);
+ maclen = 0;
+ pad = 0;
+ if(out->sec != nil){
+ maclen = out->sec->maclen;
+ pad = maclen + out->sec->block;
+ }
+ n = BLEN(bb);
+ if(n > MaxRecLen){
+ n = MaxRecLen;
+ nb = allocb(n + pad + RecHdrLen);
+ memmove(nb->wp + RecHdrLen, bb->rp, n);
+ bb->rp += n;
+ }else{
+ /*
+ * carefully reuse bb so it will get freed if we're out of memory
+ */
+ bb = padblock(bb, RecHdrLen);
+ if(pad)
+ nb = padblock(bb, -pad);
+ 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, p + RecHdrLen + n);
+ n += maclen;
+
+ /* encrypt */
+ n = (*out->sec->enc)(out->sec, p + RecHdrLen, n);
+ nb->wp = p + RecHdrLen + n;
+
+ /* update length */
+ put16(p+3, n);
+ }
+ if(type == RChangeCipherSpec){
+ if(out->new == nil)
+ error("change cipher without a new cipher");
+ freeSec(out->sec);
+ out->sec = out->new;
+ out->new = nil;
+ out->seq = 0;
+ }
+ 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.
+ */
+ if(waserror()){
+ if(strcmp(up->errstr, "interrupted") != 0)
+ tlsError(tr, "channel error");
+ nexterror();
+ }
+ devtab[tr->c->type]->bwrite(tr->c, nb, 0);
+ poperror();
+ }
+ qunlock(&out->io);
+ poperror();
+}
+
+static long
+tlsbwrite(Chan *c, Block *b, ulong offset)
+{
+ int ty;
+ ulong n;
+ TlsRec *tr;
+
+ n = BLEN(b);
+
+ tr = tlsdevs[CONV(c->qid)];
+ if(tr == nil)
+ panic("tlsbread");
+
+ ty = TYPE(c->qid);
+ switch(ty) {
+ default:
+ return devbwrite(c, b, offset);
+ case Qhand:
+ tlsrecwrite(tr, RHandshake, b);
+ tr->handout += n;
+ break;
+ case Qdata:
+ checkstate(tr, 0, SOpen);
+ tlsrecwrite(tr, RApplication, b);
+ tr->dataout += n;
+ 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);
+}
+
+static void
+initclearmac(Hashalg *unused1, int unused2, Secret *s, uchar *unused3)
+{
+ s->maclen = 0;
+ s->mac = nomac;
+}
+
+static void
+initsha1key(Hashalg *ha, int version, Secret *s, uchar *p)
+{
+ s->maclen = ha->maclen;
+ if(version == SSL3Version)
+ s->mac = sslmac_sha1;
+ else
+ s->mac = hmac_sha1;
+ memmove(s->mackey, p, ha->maclen);
+}
+
+static Hashalg hashtab[] =
+{
+ { "clear", 0, initclearmac, },
+ { "md5", MD5dlen, initmd5key, },
+ { "sha1", SHA1dlen, initsha1key, },
+ { 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 *unused1)
+{
+ s->enckey = smalloc(sizeof(RC4state));
+ s->enc = rc4enc;
+ s->dec = rc4enc;
+ s->block = 0;
+ setupRC4state(s->enckey, p, ea->keylen);
+}
+
+static void
+initDES3key(Encalg *unused1, Secret *s, uchar *p, uchar *iv)
+{
+ s->enckey = smalloc(sizeof(DES3state));
+ s->enc = des3enc;
+ s->dec = des3dec;
+ s->block = 8;
+ setupDES3state(s->enckey, (uchar(*)[8])p, iv);
+}
+
+static void
+initclearenc(Encalg *unused1, Secret *s, uchar *unused2, uchar *unused3)
+{
+ s->enc = noenc;
+ s->dec = noenc;
+ s->block = 0;
+}
+
+static Encalg encrypttab[] =
+{
+ { "clear", 0, 0, initclearenc },
+ { "rc4_128", 128/8, 0, initRC4key },
+ { "3des_ede_cbc", 3 * 8, 8, initDES3key },
+ { 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;
+ Secret *volatile tos, *volatile toc;
+ Block *volatile b;
+ Cmdbuf *volatile cb;
+ int m, ty;
+ char *p, *e;
+ uchar *volatile x;
+ ulong offset = off;
+
+ tr = tlsdevs[CONV(c->qid)];
+ if(tr == nil)
+ panic("tlswrite");
+
+ ty = TYPE(c->qid);
+ switch(ty){
+ 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(a, n);
+ if(waserror()){
+ free(cb);
+ nexterror();
+ }
+ if(cb->nf < 1)
+ error("short control request");
+
+ /* mutex with operations using what we're about to change */
+ 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 open-fd version");
+ if(tr->c != nil)
+ error(Einuse);
+ m = strtol(cb->f[2], nil, 0);
+ if(m < MinProtoVersion || m > MaxProtoVersion)
+ error("unsupported version");
+ tr->c = buftochan(cb->f[1]);
+ tr->version = m;
+ tlsSetState(tr, SHandshake, SClosed);
+ }else if(strcmp(cb->f[0], "version") == 0){
+ if(cb->nf != 2)
+ error("usage: version vers");
+ if(tr->c == nil)
+ error("must set fd before version");
+ if(tr->verset)
+ error("version already set");
+ m = strtol(cb->f[1], nil, 0);
+ if(m == SSL3Version)
+ tr->packMac = sslPackMac;
+ else if(m == TLSVersion)
+ tr->packMac = tlsPackMac;
+ else
+ error("unsupported version");
+ tr->verset = 1;
+ tr->version = m;
+ }else if(strcmp(cb->f[0], "secret") == 0){
+ if(cb->nf != 5)
+ error("usage: secret hashalg encalg isclient secretdata");
+ if(tr->c == nil || !tr->verset)
+ error("must set fd and version before secrets");
+
+ if(tr->in.new != nil){
+ freeSec(tr->in.new);
+ tr->in.new = nil;
+ }
+ if(tr->out.new != nil){
+ freeSec(tr->out.new);
+ tr->out.new = nil;
+ }
+
+ ha = parsehashalg(cb->f[1]);
+ ea = parseencalg(cb->f[2]);
+
+ p = cb->f[4];
+ m = (strlen(p)*3)/2;
+ x = smalloc(m);
+ tos = nil;
+ toc = nil;
+ if(waserror()){
+ freeSec(tos);
+ freeSec(toc);
+ free(x);
+ nexterror();
+ }
+ m = dec64(x, m, p, strlen(p));
+ if(m < 2 * ha->maclen + 2 * ea->keylen + 2 * ea->ivlen)
+ error("not enough secret data provided");
+
+ tos = smalloc(sizeof(Secret));
+ toc = smalloc(sizeof(Secret));
+ if(!ha->initkey || !ea->initkey)
+ error("misimplemented secret algorithm");
+ (*ha->initkey)(ha, tr->version, tos, &x[0]);
+ (*ha->initkey)(ha, tr->version, toc, &x[ha->maclen]);
+ (*ea->initkey)(ea, tos, &x[2 * ha->maclen], &x[2 * ha->maclen + 2 * ea->keylen]);
+ (*ea->initkey)(ea, toc, &x[2 * ha->maclen + ea->keylen], &x[2 * ha->maclen + 2 * ea->keylen + ea->ivlen]);
+
+ if(!tos->mac || !tos->enc || !tos->dec
+ || !toc->mac || !toc->enc || !toc->dec)
+ error("missing algorithm implementations");
+ if(strtol(cb->f[3], nil, 0) == 0){
+ tr->in.new = tos;
+ tr->out.new = toc;
+ }else{
+ tr->in.new = toc;
+ tr->out.new = tos;
+ }
+ if(tr->version == SSL3Version){
+ toc->unpad = sslunpad;
+ tos->unpad = sslunpad;
+ }else{
+ toc->unpad = tlsunpad;
+ tos->unpad = tlsunpad;
+ }
+ toc->encalg = ea->name;
+ toc->hashalg = ha->name;
+ tos->encalg = ea->name;
+ tos->hashalg = ha->name;
+
+ free(x);
+ poperror();
+ }else if(strcmp(cb->f[0], "changecipher") == 0){
+ if(cb->nf != 1)
+ error("usage: changecipher");
+ if(tr->out.new == nil)
+ error("cannot change cipher spec without setting secret");
+
+ qunlock(&tr->in.seclock);
+ qunlock(&tr->out.seclock);
+ poperror();
+ free(cb);
+ poperror();
+
+ /*
+ * the real work is done as the message is written
+ * so the stream is encrypted in sync.
+ */
+ b = allocb(1);
+ *b->wp++ = 1;
+ tlsrecwrite(tr, RChangeCipherSpec, b);
+ return n;
+ }else if(strcmp(cb->f[0], "opened") == 0){
+ if(cb->nf != 1)
+ error("usage: opened");
+ if(tr->in.sec == nil || tr->out.sec == nil)
+ error("cipher must be configured before enabling data messages");
+ lock(&tr->statelk);
+ if(tr->state != SHandshake && tr->state != SOpen){
+ unlock(&tr->statelk);
+ error("cannot enable data messages");
+ }
+ tr->state = SOpen;
+ unlock(&tr->statelk);
+ tr->opened = 1;
+ }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");
+ m = strtol(cb->f[1], nil, 0);
+
+ qunlock(&tr->in.seclock);
+ qunlock(&tr->out.seclock);
+ poperror();
+ free(cb);
+ poperror();
+
+ sendAlert(tr, m);
+
+ if(m == ECloseNotify)
+ tlsclosed(tr, SLClose);
+
+ return n;
+ } else if(strcmp(cb->f[0], "debug") == 0){
+ if(cb->nf == 2){
+ if(strcmp(cb->f[1], "on") == 0)
+ tr->debug = 1;
+ else
+ tr->debug = 0;
+ } else
+ tr->debug = 1;
+ } 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;
+ static int already;
+
+ if(!already){
+ fmtinstall('H', encodefmt);
+ already = 1;
+ }
+
+ tlsdevs = smalloc(sizeof(TlsRec*) * maxtlsdevs);
+ trnames = smalloc((sizeof *trnames) * maxtlsdevs);
+
+ 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,
+ devshutdown,
+ tlsattach,
+ 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;
+}
+
+static void
+sendAlert(TlsRec *tr, int err)
+{
+ Block *b;
+ int i, fatal;
+ char *msg;
+
+if(tr->debug)pprint("sendAlert %d\n", err);
+ fatal = 1;
+ msg = "tls unknown alert";
+ for(i=0; i < nelem(tlserrs); i++) {
+ if(tlserrs[i].err == err) {
+ msg = tlserrs[i].msg;
+ if(tr->version == SSL3Version)
+ err = tlserrs[i].sslerr;
+ else
+ err = tlserrs[i].tlserr;
+ fatal = tlserrs[i].fatal;
+ break;
+ }
+ }
+
+ if(!waserror()){
+ b = allocb(2);
+ *b->wp++ = fatal + 1;
+ *b->wp++ = err;
+ if(fatal)
+ tlsSetState(tr, SAlert, SOpen|SHandshake|SRClose);
+ tlsrecwrite(tr, RAlert, b);
+ poperror();
+ }
+ if(fatal)
+ tlsError(tr, msg);
+}
+
+static void
+tlsError(TlsRec *tr, char *msg)
+{
+ int s;
+
+if(tr->debug)pprint("tleError %s\n", msg);
+ lock(&tr->statelk);
+ s = tr->state;
+ tr->state = SError;
+ if(s != SError){
+ strncpy(tr->err, msg, ERRMAX - 1);
+ tr->err[ERRMAX - 1] = '\0';
+ }
+ unlock(&tr->statelk);
+ if(s != SError)
+ alertHand(tr, msg);
+}
+
+static void
+tlsSetState(TlsRec *tr, int new, int old)
+{
+ lock(&tr->statelk);
+ if(tr->state & old)
+ tr->state = new;
+ unlock(&tr->statelk);
+}
+
+/* hand up a digest connection */
+static void
+tlshangup(TlsRec *tr)
+{
+ Block *b;
+
+ qlock(&tr->in.io);
+ for(b = tr->processed; b; b = tr->processed){
+ tr->processed = b->next;
+ freeb(b);
+ }
+ if(tr->unprocessed != nil){
+ freeb(tr->unprocessed);
+ tr->unprocessed = nil;
+ }
+ qunlock(&tr->in.io);
+
+ tlsSetState(tr, SClosed, ~0);
+}
+
+static TlsRec*
+newtls(Chan *ch)
+{
+ TlsRec **pp, **ep, **np;
+ char **nmp;
+ int t, newmax;
+
+ if(waserror()) {
+ unlock(&tdlock);
+ nexterror();
+ }
+ lock(&tdlock);
+ ep = &tlsdevs[maxtlsdevs];
+ for(pp = tlsdevs; pp < ep; pp++)
+ if(*pp == nil)
+ break;
+ if(pp >= ep) {
+ if(maxtlsdevs >= MaxTlsDevs) {
+ unlock(&tdlock);
+ poperror();
+ return nil;
+ }
+ newmax = 2 * maxtlsdevs;
+ if(newmax > MaxTlsDevs)
+ newmax = MaxTlsDevs;
+ np = smalloc(sizeof(TlsRec*) * newmax);
+ memmove(np, tlsdevs, sizeof(TlsRec*) * maxtlsdevs);
+ tlsdevs = np;
+ pp = &tlsdevs[maxtlsdevs];
+ memset(pp, 0, sizeof(TlsRec*)*(newmax - maxtlsdevs));
+
+ nmp = smalloc(sizeof *nmp * newmax);
+ memmove(nmp, trnames, sizeof *nmp * maxtlsdevs);
+ trnames = nmp;
+
+ maxtlsdevs = newmax;
+ }
+ *pp = mktlsrec();
+ if(pp - tlsdevs >= tdhiwat)
+ tdhiwat++;
+ t = TYPE(ch->qid);
+ if(t == Qclonus)
+ t = Qctl;
+ ch->qid.path = QID(pp - tlsdevs, t);
+ ch->qid.vers = 0;
+ unlock(&tdlock);
+ poperror();
+ return *pp;
+}
+
+static TlsRec *
+mktlsrec(void)
+{
+ TlsRec *tr;
+
+ tr = mallocz(sizeof(*tr), 1);
+ if(tr == nil)
+ error(Enomem);
+ tr->state = SClosed;
+ tr->ref = 1;
+ kstrdup(&tr->user, up->user);
+ tr->perm = 0660;
+ return tr;
+}
+
+static char*
+tlsstate(int s)
+{
+ switch(s){
+ case SHandshake:
+ return "Handshaking";
+ case SOpen:
+ return "Established";
+ case SRClose:
+ return "RemoteClosed";
+ case SLClose:
+ return "LocalClosed";
+ case SAlert:
+ return "Alerting";
+ case SError:
+ return "Errored";
+ case SClosed:
+ return "Closed";
+ }
+ return "Unknown";
+}
+
+static void
+freeSec(Secret *s)
+{
+ if(s != nil){
+ free(s->enckey);
+ free(s);
+ }
+}
+
+static int
+noenc(Secret *unused1, uchar *unused2, int n)
+{
+ return n;
+}
+
+static int
+rc4enc(Secret *sec, uchar *buf, int n)
+{
+ rc4(sec->enckey, buf, n);
+ return n;
+}
+
+static int
+tlsunpad(uchar *buf, int n, int block)
+{
+ int pad, nn;
+
+ pad = buf[n - 1];
+ nn = n - 1 - pad;
+ if(nn <= 0 || n % block)
+ return -1;
+ while(--n > nn)
+ if(pad != buf[n - 1])
+ return -1;
+ return nn;
+}
+
+static int
+sslunpad(uchar *buf, int n, int block)
+{
+ int pad, nn;
+
+ pad = buf[n - 1];
+ nn = n - 1 - pad;
+ if(nn <= 0 || n % block)
+ return -1;
+ return nn;
+}
+
+static int
+blockpad(uchar *buf, int n, int block)
+{
+ int pad, nn;
+
+ nn = n + block;
+ nn -= nn % block;
+ pad = nn - (n + 1);
+ while(n < nn)
+ buf[n++] = pad;
+ return nn;
+}
+
+static int
+des3enc(Secret *sec, uchar *buf, int n)
+{
+ n = blockpad(buf, n, 8);
+ des3CBCencrypt(buf, n, sec->enckey);
+ return n;
+}
+
+static int
+des3dec(Secret *sec, uchar *buf, int n)
+{
+ des3CBCdecrypt(buf, n, sec->enckey);
+ return (*sec->unpad)(buf, n, 8);
+}
+static DigestState*
+nomac(uchar *unused1, ulong unused2, uchar *unused3, ulong unused4,
+ uchar *unused5, DigestState *unused6)
+{
+ return nil;
+}
+
+/*
+ * 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; i<padlen; i++)
+ pad[i] = 0x36;
+ s = (*x)(key, klen, nil, nil);
+ s = (*x)(pad, padlen, nil, s);
+ if(s == nil)
+ return nil;
+ }
+
+ s = (*x)(p, len, nil, s);
+ if(digest == nil)
+ return s;
+
+ /* last time through */
+ for(i=0; i<padlen; i++)
+ pad[i] = 0x5c;
+ (*x)(nil, 0, innerdigest, s);
+ s = (*x)(key, klen, nil, nil);
+ s = (*x)(pad, padlen, nil, s);
+ (*x)(innerdigest, xlen, digest, s);
+ return nil;
+}
+
+static DigestState*
+sslmac_sha1(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s)
+{
+ return sslmac_x(p, len, key, klen, digest, s, sha1, SHA1dlen, 40);
+}
+
+static DigestState*
+sslmac_md5(uchar *p, ulong len, uchar *key, ulong klen, uchar *digest, DigestState *s)
+{
+ return sslmac_x(p, len, key, klen, digest, s, md5, MD5dlen, 48);
+}
+
+static void
+sslPackMac(Secret *sec, uchar *mackey, uchar *seq, uchar *header, uchar *body, int len, uchar *mac)
+{
+ DigestState *s;
+ uchar buf[11];
+
+ memmove(buf, seq, 8);
+ buf[8] = header[0];
+ buf[9] = header[3];
+ buf[10] = header[4];
+
+ s = (*sec->mac)(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
+put24(uchar *p, int x)
+{
+ p[0] = x>>16;
+ p[1] = x>>8;
+ p[2] = 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];
+}
+
+static char *charmap = "0123456789abcdef";
+
+static void
+pdump(int len, void *a, char *tag)
+{
+ uchar *p;
+ int i;
+ char buf[65+32];
+ char *q;
+
+ p = a;
+ strcpy(buf, tag);
+ while(len > 0){
+ q = buf + strlen(tag);
+ for(i = 0; len > 0 && i < 32; i++){
+ if(*p >= ' ' && *p < 0x7f){
+ *q++ = ' ';
+ *q++ = *p;
+ } else {
+ *q++ = charmap[*p>>4];
+ *q++ = charmap[*p & 0xf];
+ }
+ len--;
+ p++;
+ }
+ *q = 0;
+
+ if(len > 0)
+ pprint("%s...\n", buf);
+ else
+ pprint("%s\n", buf);
+ }
+}
--- a/libc/Makefile
+++ b/libc/Makefile
@@ -18,6 +18,7 @@
dirwstat.$O\
dofmt.$O\
dorfmt.$O\
+ encodefmt.$O\
fcallfmt.$O\
fltfmt.$O\
fmt.$O\
@@ -39,6 +40,7 @@
nsec.$O\
pow10.$O\
pushssl.$O\
+ pushtls.$O\
read9pmsg.$O\
readn.$O\
rune.$O\
--- /dev/null
+++ b/libc/encodefmt.c
@@ -1,0 +1,77 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+
+int
+encodefmt(Fmt *f)
+{
+ char *out;
+ char *buf;
+ int len;
+ int ilen;
+ int rv;
+ uchar *b;
+ char *p;
+ char obuf[64]; // rsc optimization
+
+ if(!(f->flags&FmtPrec) || f->prec < 1)
+ goto error;
+
+ b = va_arg(f->args, uchar*);
+ if(b == 0)
+ return fmtstrcpy(f, "<nil>");
+
+ ilen = f->prec;
+ f->prec = 0;
+ f->flags &= ~FmtPrec;
+ switch(f->r){
+ case '<':
+ len = (8*ilen+4)/5 + 3;
+ break;
+ case '[':
+ len = (8*ilen+5)/6 + 4;
+ break;
+ case 'H':
+ len = 2*ilen + 1;
+ break;
+ default:
+ goto error;
+ }
+
+ if(len > sizeof(obuf)){
+ buf = malloc(len);
+ if(buf == nil)
+ goto error;
+ } else
+ buf = obuf;
+
+ // convert
+ out = buf;
+ switch(f->r){
+ case '<':
+ rv = enc32(out, len, b, ilen);
+ break;
+ case '[':
+ rv = enc64(out, len, b, ilen);
+ break;
+ case 'H':
+ rv = enc16(out, len, b, ilen);
+ if(rv >= 0 && (f->flags & FmtLong))
+ for(p = buf; *p; p++)
+ *p = tolower(*p);
+ break;
+ default:
+ rv = -1;
+ break;
+ }
+ if(rv < 0)
+ goto error;
+
+ fmtstrcpy(f, buf);
+ if(buf != obuf)
+ free(buf);
+ return 0;
+
+error:
+ return fmtstrcpy(f, "<encodefmt>");
+}
--- /dev/null
+++ b/libc/pushtls.c
@@ -1,0 +1,99 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <mp.h>
+#include <libsec.h>
+
+enum {
+ TLSFinishedLen = 12,
+ HFinished = 20,
+};
+
+static int
+finished(int hand, int isclient)
+{
+ int i, n;
+ uchar buf[500], buf2[500];
+
+ buf[0] = HFinished;
+ buf[1] = TLSFinishedLen>>16;
+ buf[2] = TLSFinishedLen>>8;
+ buf[3] = TLSFinishedLen;
+ n = TLSFinishedLen+4;
+
+ for(i=0; i<2; i++){
+ if(i==0)
+ memmove(buf+4, "client finished", TLSFinishedLen);
+ else
+ memmove(buf+4, "server finished", TLSFinishedLen);
+ if(isclient == 1-i){
+ if(write(hand, buf, n) != n)
+ return -1;
+ }else{
+ if(readn(hand, buf2, n) != n || memcmp(buf,buf2,n) != 0)
+ return -1;
+ }
+ }
+ return 1;
+}
+
+
+// given a plain fd and secrets established beforehand, return encrypted connection
+int
+pushtls(int fd, char *hashalg, char *encalg, int isclient, char *secret, char *dir)
+{
+ char buf[8];
+ char dname[64];
+ int n, data, ctl, hand;
+
+ // open a new filter; get ctl fd
+ data = hand = -1;
+ // /net/tls uses decimal file descriptors to name channels, hence a
+ // user-level file server can't stand in for #a; may as well hard-code it.
+ ctl = open("#a/tls/clone", ORDWR);
+ if(ctl < 0)
+ goto error;
+ n = read(ctl, buf, sizeof(buf)-1);
+ if(n < 0)
+ goto error;
+ buf[n] = 0;
+ if(dir)
+ sprint(dir, "#a/tls/%s", buf);
+
+ // get application fd
+ sprint(dname, "#a/tls/%s/data", buf);
+ data = open(dname, ORDWR);
+ if(data < 0)
+ goto error;
+
+ // get handshake fd
+ sprint(dname, "#a/tls/%s/hand", buf);
+ hand = open(dname, ORDWR);
+ if(hand < 0)
+ goto error;
+
+ // speak a minimal handshake
+ if(fprint(ctl, "fd %d 0x301", fd) < 0 ||
+ fprint(ctl, "version 0x301") < 0 ||
+ fprint(ctl, "secret %s %s %d %s", hashalg, encalg, isclient, secret) < 0 ||
+ fprint(ctl, "changecipher") < 0 ||
+ finished(hand, isclient) < 0 ||
+ fprint(ctl, "opened") < 0){
+ close(hand);
+ hand = -1;
+ goto error;
+ }
+ close(ctl);
+ close(hand);
+ close(fd);
+ return data;
+
+error:
+ if(data>=0)
+ close(data);
+ if(ctl>=0)
+ close(ctl);
+ if(hand>=0)
+ close(hand);
+ return -1;
+}