1518 lines
26 KiB
C
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;
|
|
}
|