2006-04-04 13:38:40 +02:00
|
|
|
/*
|
|
|
|
* 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);
|
2006-05-21 18:32:29 +02:00
|
|
|
/* static u32int get32(uchar *p); */
|
2006-04-04 13:38:40 +02:00
|
|
|
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[] = {
|
2006-05-21 18:32:29 +02:00
|
|
|
/* unused */ 0,
|
|
|
|
/* topdir */ 0,
|
|
|
|
/* protodir */ 0,
|
|
|
|
"clone",
|
|
|
|
"encalgs",
|
|
|
|
"hashalgs",
|
|
|
|
/* convdir */ 0,
|
|
|
|
"data",
|
|
|
|
"ctl",
|
|
|
|
"hand",
|
|
|
|
"status",
|
|
|
|
"stats",
|
2006-04-04 13:38:40 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
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);
|
2006-05-21 19:02:05 +02:00
|
|
|
tr->handq = qopen(2 * MaxCipherRecLen, 0, 0, nil);
|
2006-04-04 13:38:40 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2006-05-21 18:32:29 +02:00
|
|
|
/*
|
2006-04-04 13:38:40 +02:00
|
|
|
static u32int
|
|
|
|
get32(uchar *p)
|
|
|
|
{
|
|
|
|
return (p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3];
|
|
|
|
}
|
2006-05-21 18:32:29 +02:00
|
|
|
*/
|
2006-04-04 13:38:40 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|