drawterm/kern/devssl.c
2005-11-04 19:19:11 +00:00

1518 lines
26 KiB
C

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