Subversion Repositories planix.SVN

Rev

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

/*
 * p9sk1, p9sk2 - Plan 9 secret (private) key authentication.
 * p9sk2 is an incomplete flawed variant of p9sk1.
 *
 * Client protocol:
 *      write challenge[challen]        (p9sk1 only)
 *      read tickreq[tickreqlen]
 *      write ticket[ticketlen]
 *      read authenticator[authentlen]
 *
 * Server protocol:
 *      read challenge[challen] (p9sk1 only)
 *      write tickreq[tickreqlen]
 *      read ticket[ticketlen]
 *      write authenticator[authentlen]
 */

#include "dat.h"

struct State
{
        int vers;
        Key *key;
        Ticket t;
        Ticketreq tr;
        char cchal[CHALLEN];
        char tbuf[TICKETLEN+AUTHENTLEN];
        char authkey[DESKEYLEN];
        uchar *secret;
        int speakfor;
};

enum
{
        /* client phases */
        CHaveChal,
        CNeedTreq,
        CHaveTicket,
        CNeedAuth,

        /* server phases */
        SNeedChal,
        SHaveTreq,
        SNeedTicket,
        SHaveAuth,

        Maxphase,
};

static char *phasenames[Maxphase] =
{
[CHaveChal]             "CHaveChal",
[CNeedTreq]             "CNeedTreq",
[CHaveTicket]           "CHaveTicket",
[CNeedAuth]             "CNeedAuth",

[SNeedChal]             "SNeedChal",
[SHaveTreq]             "SHaveTreq",
[SNeedTicket]           "SNeedTicket",
[SHaveAuth]             "SHaveAuth",
};

static int gettickets(State*, char*, char*);

static int
p9skinit(Proto *p, Fsstate *fss)
{
        State *s;
        int iscli, ret;
        Key *k;
        Keyinfo ki;
        Attr *attr;

        if((iscli = isclient(_strfindattr(fss->attr, "role"))) < 0)
                return failure(fss, nil);

        s = emalloc(sizeof *s);
        fss = fss;
        fss->phasename = phasenames;
        fss->maxphase = Maxphase;
        if(p == &p9sk1)
                s->vers = 1;
        else if(p == &p9sk2)
                s->vers = 2;
        else
                abort();
        if(iscli){
                switch(s->vers){
                case 1:
                        fss->phase = CHaveChal;
                        memrandom(s->cchal, CHALLEN);
                        break;
                case 2:
                        fss->phase = CNeedTreq;
                        break;
                }
        }else{
                s->tr.type = AuthTreq;
                attr = setattr(_copyattr(fss->attr), "proto=p9sk1");
                mkkeyinfo(&ki, fss, attr);
                ki.user = nil;
                ret = findkey(&k, &ki, "user? dom?");
                _freeattr(attr);
                if(ret != RpcOk){
                        free(s);
                        return ret;
                }
                safecpy(s->tr.authid, _strfindattr(k->attr, "user"), sizeof(s->tr.authid));
                safecpy(s->tr.authdom, _strfindattr(k->attr, "dom"), sizeof(s->tr.authdom));
                s->key = k;
                memrandom(s->tr.chal, sizeof s->tr.chal);
                switch(s->vers){
                case 1:
                        fss->phase = SNeedChal;
                        break;
                case 2:
                        fss->phase = SHaveTreq;
                        memmove(s->cchal, s->tr.chal, CHALLEN);
                        break;
                }
        }
        fss->ps = s;
        return RpcOk;
}

static int
p9skread(Fsstate *fss, void *a, uint *n)
{
        int m;
        State *s;

        s = fss->ps;
        switch(fss->phase){
        default:
                return phaseerror(fss, "read");

        case CHaveChal:
                m = CHALLEN;
                if(*n < m)
                        return toosmall(fss, m);
                *n = m;
                memmove(a, s->cchal, m);
                fss->phase = CNeedTreq;
                return RpcOk;

        case SHaveTreq:
                m = TICKREQLEN;
                if(*n < m)
                        return toosmall(fss, m);
                *n = m;
                convTR2M(&s->tr, a);
                fss->phase = SNeedTicket;
                return RpcOk;

        case CHaveTicket:
                m = TICKETLEN+AUTHENTLEN;
                if(*n < m)
                        return toosmall(fss, m);
                *n = m;
                memmove(a, s->tbuf, m);
                fss->phase = CNeedAuth;
                return RpcOk;

        case SHaveAuth:
                m = AUTHENTLEN;
                if(*n < m)
                        return toosmall(fss, m);
                *n = m;
                memmove(a, s->tbuf+TICKETLEN, m);
                fss->ai.cuid = s->t.cuid;
                fss->ai.suid = s->t.suid;
                s->secret = emalloc(8);
                des56to64((uchar*)s->t.key, s->secret);
                fss->ai.secret = s->secret;
                fss->ai.nsecret = 8;
                fss->haveai = 1;
                fss->phase = Established;
                return RpcOk;
        }
}

static int
p9skwrite(Fsstate *fss, void *a, uint n)
{
        int m, ret, sret;
        char tbuf[2*TICKETLEN], trbuf[TICKREQLEN], *user;
        Attr *attr;
        Authenticator auth;
        State *s;
        Key *srvkey;
        Keyinfo ki;

        s = fss->ps;
        switch(fss->phase){
        default:
                return phaseerror(fss, "write");

        case SNeedChal:
                m = CHALLEN;
                if(n < m)
                        return toosmall(fss, m);
                memmove(s->cchal, a, m);
                fss->phase = SHaveTreq;
                return RpcOk;

        case CNeedTreq:
                m = TICKREQLEN;
                if(n < m)
                        return toosmall(fss, m);

                /* remember server's chal */
                convM2TR(a, &s->tr);
                if(s->vers == 2)
                        memmove(s->cchal, s->tr.chal, CHALLEN);

                if(s->key != nil)
                        closekey(s->key);

                attr = _delattr(_delattr(_copyattr(fss->attr), "role"), "user");
                attr = setattr(attr, "proto=p9sk1");
                user = _strfindattr(fss->attr, "user");
                /*
                 * If our client is the user who started factotum (client==owner), then
                 * he can use whatever keys we have to speak as whoever he pleases.
                 * If, on the other hand, we're speaking on behalf of someone else,
                 * we will only vouch for their name on the local system.
                 *
                 * We do the sysuser findkey second so that if we return RpcNeedkey,
                 * the correct key information gets asked for.
                 */
                srvkey = nil;
                s->speakfor = 0;
                sret = RpcFailure;
                if(user==nil || strcmp(user, fss->sysuser) == 0){
                        mkkeyinfo(&ki, fss, attr);
                        ki.user = nil;
                        sret = findkey(&srvkey, &ki,
                                "role=speakfor dom=%q user?", s->tr.authdom);
                }
                if(user != nil)
                        attr = setattr(attr, "user=%q", user);
                mkkeyinfo(&ki, fss, attr);
                ret = findkey(&s->key, &ki,
                        "role=client dom=%q %s", s->tr.authdom, p9sk1.keyprompt);
                if(ret == RpcOk)
                        closekey(srvkey);
                else if(sret == RpcOk){
                        s->key = srvkey;
                        s->speakfor = 1;
                }else if(ret == RpcConfirm || sret == RpcConfirm){
                        _freeattr(attr);
                        return RpcConfirm;
                }else{
                        _freeattr(attr);
                        return ret;
                }

                /* fill in the rest of the request */
                s->tr.type = AuthTreq;
                safecpy(s->tr.hostid, _strfindattr(s->key->attr, "user"), sizeof s->tr.hostid);
                if(s->speakfor)
                        safecpy(s->tr.uid, fss->sysuser, sizeof s->tr.uid);
                else
                        safecpy(s->tr.uid, s->tr.hostid, sizeof s->tr.uid);

                convTR2M(&s->tr, trbuf);

                /* get tickets, from auth server or invent if we can */
                if(gettickets(s, trbuf, tbuf) < 0){
                        _freeattr(attr);
                        return failure(fss, nil);
                }

                convM2T(tbuf, &s->t, (char*)s->key->priv);
                if(s->t.num != AuthTc){
                        if(s->key->successes == 0 && !s->speakfor)
                                disablekey(s->key);
                        if(askforkeys && !s->speakfor){
                                snprint(fss->keyinfo, sizeof fss->keyinfo,
                                        "%A %s", attr, p9sk1.keyprompt);
                                _freeattr(attr);
                                return RpcNeedkey;
                        }else{
                                _freeattr(attr);
                                return failure(fss, Ebadkey);
                        }
                }
                s->key->successes++;
                _freeattr(attr);
                memmove(s->tbuf, tbuf+TICKETLEN, TICKETLEN);

                auth.num = AuthAc;
                memmove(auth.chal, s->tr.chal, CHALLEN);
                auth.id = 0;
                convA2M(&auth, s->tbuf+TICKETLEN, s->t.key);
                fss->phase = CHaveTicket;
                return RpcOk;

        case SNeedTicket:
                m = TICKETLEN+AUTHENTLEN;
                if(n < m)
                        return toosmall(fss, m);
                convM2T(a, &s->t, (char*)s->key->priv);
                if(s->t.num != AuthTs
                || memcmp(s->t.chal, s->tr.chal, CHALLEN) != 0)
                        return failure(fss, Easproto);
                convM2A((char*)a+TICKETLEN, &auth, s->t.key);
                if(auth.num != AuthAc
                || memcmp(auth.chal, s->tr.chal, CHALLEN) != 0
                || auth.id != 0)
                        return failure(fss, Easproto);
                auth.num = AuthAs;
                memmove(auth.chal, s->cchal, CHALLEN);
                auth.id = 0;
                convA2M(&auth, s->tbuf+TICKETLEN, s->t.key);
                fss->phase = SHaveAuth;
                return RpcOk;

        case CNeedAuth:
                m = AUTHENTLEN;
                if(n < m)
                        return toosmall(fss, m);
                convM2A(a, &auth, s->t.key);
                if(auth.num != AuthAs
                || memcmp(auth.chal, s->cchal, CHALLEN) != 0
                || auth.id != 0)
                        return failure(fss, Easproto);
                fss->ai.cuid = s->t.cuid;
                fss->ai.suid = s->t.suid;
                s->secret = emalloc(8);
                des56to64((uchar*)s->t.key, s->secret);
                fss->ai.secret = s->secret;
                fss->ai.nsecret = 8;
                fss->haveai = 1;
                fss->phase = Established;
                return RpcOk;
        }
}

static void
p9skclose(Fsstate *fss)
{
        State *s;

        s = fss->ps;
        if(s->secret != nil){
                free(s->secret);
                s->secret = nil;
        }
        if(s->key != nil){
                closekey(s->key);
                s->key = nil;
        }
        free(s);
}

static int
unhex(char c)
{
        if('0' <= c && c <= '9')
                return c-'0';
        if('a' <= c && c <= 'f')
                return c-'a'+10;
        if('A' <= c && c <= 'F')
                return c-'A'+10;
        abort();
        return -1;
}

static int
hexparse(char *hex, uchar *dat, int ndat)
{
        int i;

        if(strlen(hex) != 2*ndat)
                return -1;
        if(hex[strspn(hex, "0123456789abcdefABCDEF")] != '\0')
                return -1;
        for(i=0; i<ndat; i++)
                dat[i] = (unhex(hex[2*i])<<4)|unhex(hex[2*i+1]);
        return 0;
}

static int
p9skaddkey(Key *k, int before)
{
        char *s;

        k->priv = emalloc(DESKEYLEN);
        if(s = _strfindattr(k->privattr, "!hex")){
                if(hexparse(s, k->priv, 7) < 0){
                        free(k->priv);
                        k->priv = nil;
                        werrstr("malformed key data");
                        return -1;
                }
        }else if(s = _strfindattr(k->privattr, "!password")){
                passtokey((char*)k->priv, s);
        }else{
                werrstr("no key data");
                free(k->priv);
                k->priv = nil;
                return -1;
        }
        return replacekey(k, before);
}

static void
p9skclosekey(Key *k)
{
        free(k->priv);
}

static int
getastickets(State *s, char *trbuf, char *tbuf)
{
        int asfd, rv;
        char *dom;

        if((dom = _strfindattr(s->key->attr, "dom")) == nil){
                werrstr("auth key has no domain");
                return -1;
        }
        asfd = _authdial(nil, dom);
        if(asfd < 0)
                return -1;
        rv = _asgetticket(asfd, trbuf, tbuf);
        close(asfd);
        return rv;
}

static int
mkserverticket(State *s, char *tbuf)
{
        Ticketreq *tr = &s->tr;
        Ticket t;

        if(strcmp(tr->authid, tr->hostid) != 0)
                return -1;
/* this keeps creating accounts on martha from working.  -- presotto
        if(strcmp(tr->uid, "none") == 0)
                return -1;
*/
        memset(&t, 0, sizeof(t));
        memmove(t.chal, tr->chal, CHALLEN);
        strcpy(t.cuid, tr->uid);
        strcpy(t.suid, tr->uid);
        memrandom(t.key, DESKEYLEN);
        t.num = AuthTc;
        convT2M(&t, tbuf, s->key->priv);
        t.num = AuthTs;
        convT2M(&t, tbuf+TICKETLEN, s->key->priv);
        return 0;
}

static int
gettickets(State *s, char *trbuf, char *tbuf)
{
/*
        if(mktickets(s, trbuf, tbuf) >= 0)
                return 0;
*/
        if(getastickets(s, trbuf, tbuf) >= 0)
                return 0;
        return mkserverticket(s, tbuf);
}

Proto p9sk1 = {
.name=  "p9sk1",
.init=          p9skinit,
.write= p9skwrite,
.read=  p9skread,
.close= p9skclose,
.addkey=        p9skaddkey,
.closekey=      p9skclosekey,
.keyprompt=     "user? !password?"
};

Proto p9sk2 = {
.name=  "p9sk2",
.init=          p9skinit,
.write= p9skwrite,
.read=  p9skread,
.close= p9skclose,
};