Subversion Repositories planix.SVN

Rev

Rev 67 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 4th Edition p9any/p9sk1 authentication based on auth9p1.c
 * Nigel Roles (nigel@9fs.org) 2003
 */

#include <plan9.h>
#include <fcall.h>
#include <u9fs.h>
#include <stdlib.h>     /* for random stuff */

typedef struct  Ticket          Ticket;
typedef struct  Ticketreq       Ticketreq;
typedef struct  Authenticator   Authenticator;

enum
{
        DOMLEN=         48,             /* length of an authentication domain name */
        CHALLEN=        8               /* length of a challenge */
};

enum {
        HaveProtos,
        NeedProto,
        NeedChal,
        HaveTreq,
        NeedTicket,
        HaveAuth,
        Established,
};

/* encryption numberings (anti-replay) */
enum
{
        AuthTreq=1,     /* ticket request */
        AuthChal=2,     /* challenge box request */
        AuthPass=3,     /* change password */
        AuthOK=4,       /* fixed length reply follows */
        AuthErr=5,      /* error follows */
        AuthMod=6,      /* modify user */
        AuthApop=7,     /* apop authentication for pop3 */
        AuthOKvar=9,    /* variable length reply follows */
        AuthChap=10,    /* chap authentication for ppp */
        AuthMSchap=11,  /* MS chap authentication for ppp */
        AuthCram=12,    /* CRAM verification for IMAP (RFC2195 & rfc2104) */
        AuthHttp=13,    /* http domain login */
        AuthVNC=14,     /* http domain login */


        AuthTs=64,      /* ticket encrypted with server's key */
        AuthTc,         /* ticket encrypted with client's key */
        AuthAs,         /* server generated authenticator */
        AuthAc,         /* client generated authenticator */
        AuthTp,         /* ticket encrypted with client's key for password change */
        AuthHr          /* http reply */
};

struct Ticketreq
{
        char    type;
        char    authid[NAMELEN];        /* server's encryption id */
        char    authdom[DOMLEN];        /* server's authentication domain */
        char    chal[CHALLEN];          /* challenge from server */
        char    hostid[NAMELEN];        /* host's encryption id */
        char    uid[NAMELEN];           /* uid of requesting user on host */
};
#define TICKREQLEN      (3*NAMELEN+CHALLEN+DOMLEN+1)

struct Ticket
{
        char    num;                    /* replay protection */
        char    chal[CHALLEN];          /* server challenge */
        char    cuid[NAMELEN];          /* uid on client */
        char    suid[NAMELEN];          /* uid on server */
        char    key[DESKEYLEN];         /* nonce DES key */
};
#define TICKETLEN       (CHALLEN+2*NAMELEN+DESKEYLEN+1)

struct Authenticator
{
        char    num;                    /* replay protection */
        char    chal[CHALLEN];
        ulong   id;                     /* authenticator id, ++'d with each auth */
};
#define AUTHENTLEN      (CHALLEN+4+1)

extern int chatty9p;

static  int     convT2M(Ticket*, char*, char*);
static  void    convM2T(char*, Ticket*, char*);
static  void    convM2Tnoenc(char*, Ticket*);
static  int     convA2M(Authenticator*, char*, char*);
static  void    convM2A(char*, Authenticator*, char*);
static  int     convTR2M(Ticketreq*, char*);
static  void    convM2TR(char*, Ticketreq*);
static  int     passtokey(char*, char*);

/*
 * destructively encrypt the buffer, which
 * must be at least 8 characters long.
 */
static int
encrypt9p(void *key, void *vbuf, int n)
{
        char ekey[128], *buf;
        int i, r;

        if(n < 8)
                return 0;
        key_setup(key, ekey);
        buf = vbuf;
        n--;
        r = n % 7;
        n /= 7;
        for(i = 0; i < n; i++){
                block_cipher(ekey, buf, 0);
                buf += 7;
        }
        if(r)
                block_cipher(ekey, buf - 7 + r, 0);
        return 1;
}

/*
 * destructively decrypt the buffer, which
 * must be at least 8 characters long.
 */
static int
decrypt9p(void *key, void *vbuf, int n)
{
        char ekey[128], *buf;
        int i, r;

        if(n < 8)
                return 0;
        key_setup(key, ekey);
        buf = vbuf;
        n--;
        r = n % 7;
        n /= 7;
        buf += n * 7;
        if(r)
                block_cipher(ekey, buf - 7 + r, 1);
        for(i = 0; i < n; i++){
                buf -= 7;
                block_cipher(ekey, buf, 1);
        }
        return 1;
}

#define CHAR(x)         *p++ = f->x
#define SHORT(x)        p[0] = f->x; p[1] = f->x>>8; p += 2
#define VLONG(q)        p[0] = (q); p[1] = (q)>>8; p[2] = (q)>>16; p[3] = (q)>>24; p += 4
#define LONG(x)         VLONG(f->x)
#define STRING(x,n)     memmove(p, f->x, n); p += n

static int
convTR2M(Ticketreq *f, char *ap)
{
        int n;
        uchar *p;

        p = (uchar*)ap;
        CHAR(type);
        STRING(authid, NAMELEN);
        STRING(authdom, DOMLEN);
        STRING(chal, CHALLEN);
        STRING(hostid, NAMELEN);
        STRING(uid, NAMELEN);
        n = p - (uchar*)ap;
        return n;
}

static int
convT2M(Ticket *f, char *ap, char *key)
{
        int n;
        uchar *p;

        p = (uchar*)ap;
        CHAR(num);
        STRING(chal, CHALLEN);
        STRING(cuid, NAMELEN);
        STRING(suid, NAMELEN);
        STRING(key, DESKEYLEN);
        n = p - (uchar*)ap;
        if(key)
                encrypt9p(key, ap, n);
        return n;
}

int
convA2M(Authenticator *f, char *ap, char *key)
{
        int n;
        uchar *p;

        p = (uchar*)ap;
        CHAR(num);
        STRING(chal, CHALLEN);
        LONG(id);
        n = p - (uchar*)ap;
        if(key)
                encrypt9p(key, ap, n);
        return n;
}

#undef CHAR
#undef SHORT
#undef VLONG
#undef LONG
#undef STRING

#define CHAR(x)         f->x = *p++
#define SHORT(x)        f->x = (p[0] | (p[1]<<8)); p += 2
#define VLONG(q)        q = (p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24)); p += 4
#define LONG(x)         VLONG(f->x)
#define STRING(x,n)     memmove(f->x, p, n); p += n

void
convM2A(char *ap, Authenticator *f, char *key)
{
        uchar *p;

        if(key)
                decrypt9p(key, ap, AUTHENTLEN);
        p = (uchar*)ap;
        CHAR(num);
        STRING(chal, CHALLEN);
        LONG(id);
        USED(p);
}

void
convM2T(char *ap, Ticket *f, char *key)
{
        uchar *p;

        if(key)
                decrypt9p(key, ap, TICKETLEN);
        p = (uchar*)ap;
        CHAR(num);
        STRING(chal, CHALLEN);
        STRING(cuid, NAMELEN);
        f->cuid[NAMELEN-1] = 0;
        STRING(suid, NAMELEN);
        f->suid[NAMELEN-1] = 0;
        STRING(key, DESKEYLEN);
        USED(p);
}

#undef CHAR
#undef SHORT
#undef LONG
#undef VLONG
#undef STRING

static int
passtokey(char *key, char *p)
{
        uchar buf[NAMELEN], *t;
        int i, n;

        n = strlen(p);
        if(n >= NAMELEN)
                n = NAMELEN-1;
        memset(buf, ' ', 8);
        t = buf;
        strncpy((char*)t, p, n);
        t[n] = 0;
        memset(key, 0, DESKEYLEN);
        for(;;){
                for(i = 0; i < DESKEYLEN; i++)
                        key[i] = (t[i] >> i) + (t[i+1] << (8 - (i+1)));
                if(n <= 8)
                        return 1;
                n -= 8;
                t += 8;
                if(n < 8){
                        t -= 8 - n;
                        n = 8;
                }
                encrypt9p(key, t, 8);
        }
        return 1;       /* not reached */
}

static char authkey[DESKEYLEN];
static char *authid;
static char *authdom;
static char *haveprotosmsg;
static char *needprotomsg;

static void
p9anyinit(void)
{
        int n, fd;
        char abuf[200];
        char *af, *f[4];

        af = autharg;
        if(af == nil)
                af = "/etc/u9fs.key";

        if((fd = open(af, OREAD)) < 0)
                sysfatal("can't open key file '%s'", af);

        if((n = readn(fd, abuf, sizeof(abuf)-1)) < 0)
                sysfatal("can't read key file '%s'", af);
        if (n > 0 && abuf[n - 1] == '\n')
                n--;
        abuf[n] = '\0';

        if(getfields(abuf, f, nelem(f), 0, "\n") != 3)
                sysfatal("key file '%s' not exactly 3 lines", af);

        passtokey(authkey, f[0]);
        authid = strdup(f[1]);
        authdom = strdup(f[2]);
        haveprotosmsg = malloc(strlen("p9sk1") + 1 + strlen(authdom) + 1);
        sprint(haveprotosmsg, "p9sk1@%s", authdom);
        needprotomsg = malloc(strlen("p9sk1") + 1 + strlen(authdom) + 1);
        sprint(needprotomsg, "p9sk1 %s", authdom);
}

typedef struct AuthSession {
        int state;
        char *uname;
        char *aname;
        char cchal[CHALLEN];
        Ticketreq tr;
        Ticket t;
} AuthSession;

static char*
p9anyauth(Fcall *rx, Fcall *tx)
{
        AuthSession *sp;
        Fid *f;
        char *ep;

        sp = malloc(sizeof(AuthSession));
        f = newauthfid(rx->afid, sp, &ep);
        if (f == nil) {
                free(sp);
                return ep;
        }
        if (chatty9p)
                fprint(2, "p9anyauth: afid %d\n", rx->afid);
        sp->state = HaveProtos;
        sp->uname = strdup(rx->uname);
        sp->aname = strdup(rx->aname);
        tx->aqid.type = QTAUTH;
        tx->aqid.path = 1;
        tx->aqid.vers = 0;
        return nil;
}

static char *
p9anyattach(Fcall *rx, Fcall *tx)
{
        AuthSession *sp;
        Fid *f;
        char *ep;

        f = oldauthfid(rx->afid, (void **)&sp, &ep);
        if (f == nil)
                return ep;
        if (chatty9p)
                fprint(2, "p9anyattach: afid %d state %d\n", rx->afid, sp->state);
        if (sp->state == Established && strcmp(rx->uname, sp->uname) == 0
                && strcmp(rx->aname, sp->aname) == 0)
                return nil;
        return "authentication failed";
}

static int
readstr(Fcall *rx, Fcall *tx, char *s, int len)
{
        if (rx->offset >= len)
                return 0;
        tx->count = len - rx->offset;
        if (tx->count > rx->count)
                tx->count = rx->count;
        memcpy(tx->data, s + rx->offset, tx->count);
        return tx->count;
}

static char *
p9anyread(Fcall *rx, Fcall *tx)
{
        AuthSession *sp;
        char *ep;

        Fid *f;
        f = oldauthfid(rx->afid, (void **)&sp, &ep);
        if (f == nil)
                return ep;
        if (chatty9p)
                fprint(2, "p9anyread: afid %d state %d\n", rx->fid, sp->state);
        switch (sp->state) {
        case HaveProtos:
                readstr(rx, tx, haveprotosmsg, strlen(haveprotosmsg) + 1);
                if (rx->offset + tx->count == strlen(haveprotosmsg) + 1)
                        sp->state = NeedProto;
                return nil;
        case HaveTreq:
                if (rx->count != TICKREQLEN)
                        goto botch;
                convTR2M(&sp->tr, tx->data);
                tx->count = TICKREQLEN;
                sp->state = NeedTicket;
                return nil;
        case HaveAuth: {
                Authenticator a;
                if (rx->count != AUTHENTLEN)
                        goto botch;
                a.num = AuthAs;
                memmove(a.chal, sp->cchal, CHALLEN);
                a.id = 0;
                convA2M(&a, (char*)tx->data, sp->t.key);
                memset(sp->t.key, 0, sizeof(sp->t.key));
                tx->count = rx->count;
                sp->state = Established;
                return nil;
        }
        default:
        botch:
                return "protocol botch";
        }
}

static char *
p9anywrite(Fcall *rx, Fcall *tx)
{
        AuthSession *sp;
        char *ep;

        Fid *f;

        f = oldauthfid(rx->afid, (void **)&sp, &ep);
        if (f == nil)
                return ep;
        if (chatty9p)
                fprint(2, "p9anywrite: afid %d state %d\n", rx->fid, sp->state);
        switch (sp->state) {
        case NeedProto:
                if (rx->count != strlen(needprotomsg) + 1)
                        return "protocol response wrong length";
                if (memcmp(rx->data, needprotomsg, rx->count) != 0)
                        return "unacceptable protocol";
                sp->state = NeedChal;
                tx->count = rx->count;
                return nil;
        case NeedChal:
                if (rx->count != CHALLEN)
                        goto botch;
                memmove(sp->cchal, rx->data, CHALLEN);
                sp->tr.type = AuthTreq;
                safecpy(sp->tr.authid, authid, sizeof(sp->tr.authid));
                safecpy(sp->tr.authdom, authdom, sizeof(sp->tr.authdom));
                randombytes((uchar *)sp->tr.chal, CHALLEN);
                safecpy(sp->tr.hostid, "", sizeof(sp->tr.hostid));
                safecpy(sp->tr.uid, "", sizeof(sp->tr.uid));
                tx->count = rx->count;
                sp->state = HaveTreq;
                return nil;
        case NeedTicket: {
                Authenticator a;

                if (rx->count != TICKETLEN + AUTHENTLEN) {
                        fprint(2, "bad length in attach");
                        goto botch;
                }
                convM2T((char*)rx->data, &sp->t, authkey);
                if (sp->t.num != AuthTs) {
                        fprint(2, "bad AuthTs in attach\n");
                        goto botch;
                }
                if (memcmp(sp->t.chal, sp->tr.chal, CHALLEN) != 0) {
                        fprint(2, "bad challenge in attach\n");
                        goto botch;
                }
                convM2A((char*)rx->data + TICKETLEN, &a, sp->t.key);
                if (a.num != AuthAc) {
                        fprint(2, "bad AuthAs in attach\n");
                        goto botch;
                }
                if(memcmp(a.chal, sp->tr.chal, CHALLEN) != 0) {
                        fprint(2, "bad challenge in attach 2\n");
                        goto botch;
                }
                sp->state = HaveAuth;
                tx->count = rx->count;
                return nil;
        }
        default:
        botch:
                return "protocol botch";
        }
}

static void
safefree(char *p)
{
        if (p) {
                memset(p, 0, strlen(p));
                free(p);
        }
}

static char *
p9anyclunk(Fcall *rx, Fcall *tx)
{
        Fid *f;
        AuthSession *sp;
        char *ep;

        f = oldauthfid(rx->afid, (void **)&sp, &ep);
        if (f == nil)
                return ep;
        if (chatty9p)
                fprint(2, "p9anyclunk: afid %d\n", rx->fid);
        safefree(sp->uname);
        safefree(sp->aname);
        memset(sp, 0, sizeof(AuthSession));
        free(sp);
        return nil;
}

Auth authp9any = {
        "p9any",
        p9anyauth,
        p9anyattach,
        p9anyinit,
        p9anyread,
        p9anywrite,
        p9anyclunk,
};