/*
* 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 <libsec.h>
#define NOSPOOKS 1
typedef struct OneWay OneWay;
enum
{
/* encryption algorithms */
Noencryption= 0,
DESCBC= 1,
DESECB= 2,
RC4= 3
};
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 OneWay
{
QLock q; /* locks io access */
QLock ctlq; /* locks one-way paramaters */
ulong seq;
int keyed; /* have key, waiting for cipher enable */
int protected; /* cipher is enabled */
RC4state rc4;
uchar mackey[64];
};
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)(TlsRec*, uchar*, uchar*, uchar*, uchar*, int, uchar*);
DigestState *(*mac)(uchar*, ulong, uchar*, ulong, uchar*, DigestState*);
int maclen;
/* 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 void setsecret(OneWay*, uchar*, int);
static Block* encryptb(TlsRec*, Block*, int);
static Block* decryptb(TlsRec*, Block*);
static Block* digestb(TlsRec*, Block*, int);
static void checkdigestb(TlsRec*, Block*);
static Chan* buftochan(char*);
static void tlshangup(TlsRec*);
static TlsRec* dsclone(Chan *c);
static void dsnew(Chan *c, TlsRec **);
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 Chan*
tlsopen(Chan *c, int omode)
{
TlsRec *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("tlsopen");
case Qtopdir:
case Qprotodir:
case Qconvdir:
if(omode != OREAD)
error(Eperm);
break;
case Qclonus:
s = dsclone(c);
if(s == 0)
error(Enodev);
break;
case Qctl:
case Qdata:
case Qhand:
if(waserror()) {
unlock(&dslock);
nexterror();
}
lock(&dslock);
pp = &dstate[CONV(c->qid)];
s = *pp;
if(s == 0)
dsnew(c, pp);
else {
if((perm & (s->perm>>6)) != perm
&& (strcmp(up->user, s->user) != 0
|| (perm & s->perm) != perm))
error(Eperm);
s->ref++;
}
unlock(&dslock);
poperror();
break;
case Qencalgs:
case Qhashalgs:
if(omode != OREAD)
error(Eperm);
break;
}
c->mode = openmode(omode);
c->flag |= COPEN;
c->offset = 0;
return c;
}
static void
tlswstat(Chan *c, char *dp)
{
Dir d;
TlsRec *s;
convM2D(dp, &d);
s = dstate[CONV(c->qid)];
if(s == nil)
error(Ebadusefd);
if(strcmp(s->user, up->user) != 0)
error(Eperm);
memmove(s->user, d.uid, NAMELEN);
s->perm = d.mode;
}
static void
tlsclose(Chan *c)
{
TlsRec *s;
switch(TYPE(c->qid)) {
case Qctl:
case Qdata:
case Qhand:
if((c->flag & COPEN) == 0)
break;
s = dstate[CONV(c->qid)];
if(s == nil)
break;
lock(&dslock);
if(--s->ref > 0) {
unlock(&dslock);
break;
}
dstate[CONV(c->qid)] = nil;
unlock(&dslock);
tlshangup(s);
if(s->c)
cclose(s->c);
free(s);
}
}
/*
* 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.q 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're screwed. Make
* sure nothing we call can sleep. Luckily, allocb
* won't sleep, it'll just error out.
* grab the next message and decode/decrypt it
*/
b = qremove(&tr->unprocessed, len, 0);
in = &tr->in;
if(waserror()){
qunlock(&in->ctlq);
if(b != nil)
freeb(b);
nexterror();
}
qlock(&in->ctlq);
b = pullupblock(b, len);
p = b->rp;
if(in->protected) {
if(len <= tr->maclen)
tlsError(tr, EDecodeError, "record message too short for mac");
rc4(&in->rc4, p, len);
len -= tr->maclen;
/* update length */
put16(header+3, len);
put64(seq, in->seq);
in->seq++;
(*tr->packMac)(tr, in->mackey, seq, header, p, len, hmac);
if(memcmp(hmac, p+len, tr->maclen) != 0)
tlsError(tr, EBadRecordMac, "record mac mismatch");
}
qunlock(&tr->in.ctlq);
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->ctlq);
if(!tr->in.keyed){
qunlock(&in->ctlq);
tlsError(tr, EUnexpectedMessage, "unexpected change cipher spec");
}
tr->in.keyed = 0;
tr->in.protected = 1;
tr->in.seq = 0;
qunlock(&in->ctlq);
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.q);
nexterror();
}
qlock(&tr->in.q);
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.q);
poperror();
}else{
//ZZZ race setting state
while(tr->state == SHandshake && !qcanread(tr->handq))
tlsrecread(tr);
qunlock(&tr->in.q);
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[128];
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:
sprint(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->q);
if(bb != nil)
freeb(bb);
nexterror();
}
qlock(&out->q);
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->ctlq);
nexterror();
}
qlock(&out->ctlq);
maclen = 0;
if(out->protected)
maclen = tr->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->protected){
put64(seq, out->seq);
out->seq++;
(*tr->packMac)(tr, out->mackey, seq, p, p + RecHdrLen, n, nb->wp);
b->wp += maclen;
n += maclen;
/* update length */
put16(p+3, n);
/* encrypt */
rc4(&out->rc4, p+RecHdrLen, n);
}
qunlock(&out->ctlq);
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->q);
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;
}
static void
setsecret(OneWay *w, uchar *secret, int n)
{
if(w->secret)
free(w->secret);
w->secret = smalloc(n);
memmove(w->secret, secret, n);
w->slen = n;
}
/*
* 128 bit RC4 is the same as n-bit RC4. However,
* we ignore all but the first 128 bits of the key.
*/
static void
initRC4key_128(OneWay *w)
{
if(w->state){
free(w->state);
w->state = 0;
}
if(w->slen > 16)
w->slen = 16;
w->state = malloc(sizeof(RC4state));
setupRC4state(w->state, w->secret, w->slen);
}
typedef struct Hashalg Hashalg;
struct Hashalg
{
char *name;
int diglen;
DigestState *(*hf)(uchar*, ulong, uchar*, DigestState*);
};
Hashalg hashtab[] =
{
{ "md5", MD5dlen, md5, },
{ 0 }
};
static int
parsehashalg(char *p, TlsRec *s)
{
Hashalg *ha;
for(ha = hashtab; ha->name; ha++){
if(strcmp(p, ha->name) == 0){
s->hf = ha->hf;
s->diglen = ha->diglen;
s->state &= ~Sclear;
s->state |= Sdigesting;
return 0;
}
}
return -1;
}
typedef struct Encalg Encalg;
struct Encalg
{
char *name;
int blocklen;
int alg;
void (*keyinit)(OneWay*);
};
Encalg encrypttab[] =
{
{ "rc4_128", 1, RC4, initRC4key_128, },
{ 0 }
};
static int
parseencryptalg(char *p, TlsRec *s)
{
Encalg *ea;
for(ea = encrypttab; ea->name; ea++){
if(strcmp(p, ea->name) == 0){
s->encryptalg = ea->alg;
s->blocklen = ea->blocklen;
(*ea->keyinit)(&s->in);
(*ea->keyinit)(&s->out);
s->state &= ~Sclear;
s->state |= Sencrypting;
return 0;
}
}
return -1;
}
static long
tlswrite(Chan *c, void *a, long n, vlong off)
{
TlsRec *volatile tr;
Block *volatile b;
int m, t;
char *p, *np, *e, buf[128];
uchar *x;
ulong offset = off;
tr = dstate[CONV(c->qid)];
if(tr == nil)
panic("tlswrite");
t = TYPE(c->qid);
if(t == Qdata || t == 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;
}
/* mutex with operations using what we're about to change */
if(waserror()){
qunlock(&s->in.ctlq);
qunlock(&s->out.q);
nexterror();
}
qlock(&s->in.ctlq);
qlock(&s->out.q);
switch(t){
default:
error(Ebadusefd);
case Qctl:
break;
}
if(n >= sizeof(buf))
error("arg too long");
strncpy(buf, a, n);
buf[n] = 0;
p = strchr(buf, '\n');
if(p)
*p = 0;
p = strchr(buf, ' ');
if(p)
*p++ = 0;
if(strcmp(buf, "fd") == 0){
if(s->c != nil)
error(Einuse);
s->c = buftochan(p);
/* default is clear (msg delimiters only) */
s->state = Sclear;
s->blocklen = 1;
s->diglen = 0;
s->in.mid = 0;
s->out.mid = 0;
} else if(strcmp(buf, "alg") == 0 && p != 0){
s->blocklen = 1;
s->diglen = 0;
if(s->c == 0)
error("must set fd before algorithm");
s->state = Sclear;
s->maxpad = s->max = (1<<15) - s->diglen - 1;
if(strcmp(p, "clear") == 0){
goto out;
}
if(s->in.secret && s->out.secret == 0)
setsecret(&s->out, s->in.secret, s->in.slen);
if(s->out.secret && s->in.secret == 0)
setsecret(&s->in, s->out.secret, s->out.slen);
if(s->in.secret == 0 || s->out.secret == 0)
error("algorithm but no secret");
s->hf = 0;
s->encryptalg = Noencryption;
s->blocklen = 1;
for(;;){
np = strchr(p, ' ');
if(np)
*np++ = 0;
if(parsehashalg(p, s) < 0)
if(parseencryptalg(p, s) < 0)
error("bad algorithm");
if(np == 0)
break;
p = np;
}
if(s->hf == 0 && s->encryptalg == Noencryption)
error("bad algorithm");
if(s->blocklen != 1){
s->max = (1<<15) - s->diglen - 1;
s->max -= s->max % s->blocklen;
s->maxpad = (1<<14) - s->diglen - 1;
s->maxpad -= s->maxpad % s->blocklen;
} else
s->maxpad = s->max = (1<<15) - s->diglen - 1;
} else if(strcmp(buf, "secretin") == 0 && p != 0) {
m = (strlen(p)*3)/2;
x = smalloc(m);
n = dec64(x, m, p, strlen(p));
setsecret(&s->in, x, n);
free(x);
} else if(strcmp(buf, "secretout") == 0 && p != 0) {
m = (strlen(p)*3)/2 + 1;
x = smalloc(m);
n = dec64(x, m, p, strlen(p));
setsecret(&s->out, x, n);
free(x);
} else
error(Ebadarg);
out:
qunlock(&s->in.ctlq);
qunlock(&s->out.q);
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,
};
static Block*
encryptb(TlsRec *s, Block *b, int offset)
{
uchar *p, *ep, *p2, *ip, *eip;
DESstate *ds;
switch(s->encryptalg){
case DESECB:
ds = s->out.state;
ep = b->rp + BLEN(b);
for(p = b->rp + offset; p < ep; p += 8)
block_cipher(ds->expanded, p, 0);
break;
case DESCBC:
ds = s->out.state;
ep = b->rp + BLEN(b);
for(p = b->rp + offset; p < ep; p += 8){
p2 = p;
ip = ds->ivec;
for(eip = ip+8; ip < eip; )
*p2++ ^= *ip++;
block_cipher(ds->expanded, p, 0);
memmove(ds->ivec, p, 8);
}
break;
case RC4:
rc4(s->out.state, b->rp + offset, BLEN(b) - offset);
break;
}
return b;
}
static Block*
decryptb(TlsRec *s, Block *bin)
{
Block *b, **l;
uchar *p, *ep, *tp, *ip, *eip;
DESstate *ds;
uchar tmp[8];
int i;
l = &bin;
for(b = bin; b; b = b->next){
/* make sure we have a multiple of s->blocklen */
if(s->blocklen > 1){
i = BLEN(b);
if(i % s->blocklen){
*l = b = pullupblock(b, i + s->blocklen - (i%s->blocklen));
if(b == 0)
error("tls encrypted message too short");
}
}
l = &b->next;
/* decrypt */
switch(s->encryptalg){
case DESECB:
ds = s->in.state;
ep = b->rp + BLEN(b);
for(p = b->rp; p < ep; p += 8)
block_cipher(ds->expanded, p, 1);
break;
case DESCBC:
ds = s->in.state;
ep = b->rp + BLEN(b);
for(p = b->rp; p < ep;){
memmove(tmp, p, 8);
block_cipher(ds->expanded, p, 1);
tp = tmp;
ip = ds->ivec;
for(eip = ip+8; ip < eip; ){
*p++ ^= *ip;
*ip++ = *tp++;
}
}
break;
case RC4:
rc4(s->in.state, b->rp, BLEN(b));
break;
}
}
return bin;
}
static Block*
digestb(TlsRec *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(TlsRec *s, Block *bin)
{
uchar *p;
DigestState ss;
uchar msgid[4];
int n, h;
OneWay *w;
uchar digest[128];
Block *b;
w = &s->in;
memset(&ss, 0, sizeof(ss));
/* hash secret */
(*s->hf)(w->secret, w->slen, 0, &ss);
/* hash message */
h = s->diglen;
for(b = bin; b; b = b->next){
n = BLEN(b) - h;
if(n < 0)
panic("checkdigestb");
(*s->hf)(b->rp + h, n, 0, &ss);
h = 0;
}
/* hash message id */
p = msgid;
n = w->mid;
*p++ = n>>24;
*p++ = n>>16;
*p++ = n>>8;
*p = n;
(*s->hf)(msgid, 4, digest, &ss);
if(memcmp(digest, bin->rp, s->diglen) != 0)
error("bad digest");
}
/* get channel associated with an fd */
static Chan*
buftochan(char *p)
{
Chan *c;
int fd;
if(p == 0)
error(Ebadarg);
fd = strtoul(p, 0, 0);
if(fd < 0)
error(Ebadarg);
c = fdtochan(fd, -1, 0, 1); /* error check and inc ref */
return c;
}
/* hand up a digest connection */
static void
tlshangup(TlsRec *s)
{
Block *b;
qlock(&s->in.q);
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 = Sincomplete;
qunlock(&s->in.q);
}
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 = Sincomplete;
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;
}
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];
}