implement Authproto; include "sys.m"; sys: Sys; include "draw.m"; include "keyring.m"; keyring: Keyring; IPint: import keyring; SK, PK, Certificate, DigestState: import Keyring; include "security.m"; include "bufio.m"; include "sexprs.m"; sexprs: Sexprs; Sexp: import sexprs; include "spki.m"; spki: SPKI; include "daytime.m"; daytime: Daytime; include "keyreps.m"; keyreps: Keyreps; Keyrep: import keyreps; include "../authio.m"; authio: Authio; Aattr, Aval, Aquery: import Authio; Attr, IO, Key, Authinfo: import authio; # at end of authentication, sign a hash of the authenticated username and # a secret known only to factotum. that certificate can act as # a later proof that this factotum has authenticated that user, # and hence factotum will disclose certificates that allow disclosure # only to that username. Debug: con 0; Maxmsg: con 4000; Error0, Error1: exception(string); init(f: Authio): string { authio = f; sys = load Sys Sys->PATH; spki = load SPKI SPKI->PATH; spki->init(); sexprs = load Sexprs Sexprs->PATH; sexprs->init(); keyring = load Keyring Keyring->PATH; daytime = load Daytime Daytime->PATH; keyreps = load Keyreps Keyreps->PATH; keyreps->init(); return nil; } interaction(attrs: list of ref Attr, io: ref IO): string { ai: ref Authinfo; (key, err) := io.findkey(attrs, "proto=infauth"); if(key == nil) return err; info: ref Keyring->Authinfo; (info, err) = keytoauthinfo(key); if(info == nil) return err; anysigner := int authio->lookattrval(key.attrs, "anysigner"); rattrs: list of ref Sexp; { # send auth protocol version number sendmsg(io, array of byte "1"); # get auth protocol version number if(int string getmsg(io) != 1) raise Error0("incompatible authentication protocol"); # generate alpha**r0 p := info.p; low := p.shr(p.bits()/4); r0 := rand(low, p, Random->NotQuiteRandom); αr0 := info.alpha.expmod(r0, p); # trim(αr0); the IPint library should do this for us, i think. # send alpha**r0 mod p, mycert, and mypk sendmsg(io, array of byte αr0.iptob64()); sendmsg(io, array of byte keyring->certtostr(info.cert)); sendmsg(io, array of byte keyring->pktostr(info.mypk)); # get alpha**r1 mod p, hiscert, hispk αr1 := IPint.b64toip(string getmsg(io)); # trying a fast one if(p.cmp(αr1) <= 0) raise Error0("implausible parameter value"); # if alpha**r1 == alpha**r0, someone may be trying a replay if(αr0.eq(αr1)) raise Error0("possible replay attack"); hiscert := keyring->strtocert(string getmsg(io)); if(hiscert == nil && !anysigner) raise Error0(sys->sprint("bad certificate: %r")); buf := getmsg(io); hispk := keyring->strtopk(string buf); if(!anysigner){ # verify their public key if(verify(info.spk, hiscert, buf) == 0) raise Error0("pk doesn't match certificate"); # likely the signers don't match. # check expiration date - in seconds of epoch if(hiscert.exp != 0 && hiscert.exp <= now()) raise Error0("certificate expired"); } buf = nil; # sign alpha**r0 and alpha**r1 and send αcert := sign(info.mysk, "sha", 0, array of byte (αr0.iptob64() + αr1.iptob64())); sendmsg(io, array of byte keyring->certtostr(αcert)); # get signature of alpha**r1 and alpha**r0 and verify αcert = keyring->strtocert(string getmsg(io)); if(αcert == nil) raise Error0("alpha**r1 doesn't match certificate"); if(verify(hispk, αcert, array of byte (αr1.iptob64() + αr0.iptob64())) == 0) raise Error0(sys->sprint("bad certificate: %r")); ai = ref Authinfo; # we are now authenticated and have a common secret, alpha**(r0*r1) if(!anysigner) rattrs = sl(ss("signer") :: principal(info.spk) :: nil) :: rattrs; rattrs = sl(ss("remote-pk") :: principal(hispk) :: nil) :: rattrs; rattrs = sl(ss("local-pk") :: principal(info.mypk) :: nil) :: rattrs; rattrs = sl(ss("secret") :: sb(αr1.expmod(r0, p).iptobytes()) :: nil) :: rattrs; ai.suid = hispk.owner; ai.cuid = info.mypk.owner; sendmsg(io, array of byte "OK"); }exception e{ Error0 => err = e; senderr(io, e); break; Error1 => senderr(io, "failed"); # acknowledge error return remote(e); } { while(string getmsg(io) != "OK") ; }exception e{ Error0 => return e; Error1 => return remote(e); } if(err != nil) return err; return negotiatecrypto(io, key, ai, rattrs); } remote(s: string): string { # account for strange earlier interface if(len s < 6 || s[0: 6] != "remote") return "remote: "+s; return s; } # TO DO: exchange attr/value pairs, covered by hmac (use part of secret up to hmac block size of 64 bytes) # the old scheme can be distinguished either by a prefix "attrs " or simply because the string contains "=", # and the server side can then reply. the hmac is to prevent tampering. negotiatecrypto(io: ref IO, key: ref Key, ai: ref Authinfo, attrs: list of ref Sexp): string { role := authio->lookattrval(key.attrs, "role"); alg: string; { if(role == "client"){ alg = authio->lookattrval(key.attrs, ":alg"); if(alg == nil) alg = authio->lookattrval(key.attrs, "alg"); # old way if(alg == nil) alg = "md5/rc4_256"; sendmsg(io, array of byte alg); }else if(role == "server"){ alg = string getmsg(io); if(!algcompatible(alg, sys->tokenize(authio->lookattrval(key.attrs, "algs"), " ").t1)) raise Error0("unsupported client algorithm"); } }exception e{ Error0 or Error1 => return e; } if(alg != nil) attrs = sl(ss("alg") :: ss(alg) :: nil) :: attrs; ai.secret = sl(attrs).pack(); if(role == "server") ai.cap = capability(nil, ai.suid); io.done(ai); return nil; } capability(ufrom, uto: string): string { capfd := sys->open("#¤/caphash", Sys->OWRITE); if(capfd == nil) return nil; key := IPint.random(0, 160).iptob64(); if(key == nil) return nil; users := uto; if(ufrom != nil) users = ufrom+"@"+uto; digest := array[Keyring->SHA1dlen] of byte; ausers := array of byte users; keyring->hmac_sha1(ausers, len ausers, array of byte key, digest, nil); if(sys->write(capfd, digest, len digest) < 0) return nil; return users+"@"+key; } algcompatible(nil: string, nil: list of string): int { return 1; # XXX } principal(pk: ref Keyring->PK): ref Sexp { return spki->(Keyrep.pk(pk).mkkey()).sexp(); } ipint(i: int): ref IPint { return IPint.inttoip(i); } rand(p, q: ref IPint, nil: int): ref IPint { if(p.cmp(q) > 0) (p, q) = (q, p); diff := q.sub(p); q = nil; if(diff.cmp(ipint(2)) < 0){ sys->print("rand range must be at least 2"); return IPint.inttoip(0); } l := diff.bits(); T := ipint(1).shl(l); l = ((l + 7) / 8) * 8; slop := T.div(diff).t1; r: ref IPint; do{ r = IPint.random(0, l); }while(r.cmp(slop) < 0); r = r.div(diff).t1.add(p); return r; } now(): int { return daytime->now(); } Hashfn: type ref fn(a: array of byte, alen: int, digest: array of byte, state: ref DigestState): ref DigestState; hashalg(ha: string): Hashfn { case ha { "sha" or "sha1" => return keyring->sha1; "md4" => return keyring->md4; "md5" => return keyring->md5; } return nil; } sign(sk: ref SK, ha: string, exp: int, buf: array of byte): ref Certificate { state := hashalg(ha)(buf, len buf, nil, nil); return keyring->sign(sk, exp, state, ha); } verify(pk: ref PK, cert: ref Certificate, buf: array of byte): int { state := hashalg(cert.ha)(buf, len buf, nil, nil); return keyring->verify(pk, cert, state); } getmsg(io: ref IO): array of byte raises (Error0, Error1) { while((buf := io.read()) == nil || (n := len buf) < 5) io.toosmall(5); if(len buf != 5) raise Error0("io error: (impossible?) msg length " + string n); h := string buf; if(h[0] == '!') m := int h[1:]; else m = int h; while((buf = io.read()) == nil || (n = len buf) < m) io.toosmall(m); if(len buf != m) raise Error0("io error: (impossible?) msg length " + string m); if(h[0] == '!'){ if(0) sys->print("got remote error: %q, len %d\n", string buf, len string buf); raise Error1(string buf); } return buf; } sendmsg(io: ref IO, buf: array of byte) { h := sys->aprint("%4.4d\n", len buf); io.write(h, len h); io.write(buf, len buf); } senderr(io: ref IO, e: string) { buf := array of byte e; h := sys->aprint("!%3.3d\n", len buf); io.write(h, len h); io.write(buf, len buf); } # both the s-expression and k=v form are interim, until all # the factotum implementations can manage public keys # the s-expression form was the original one used by Inferno factotum # the form in which Authinfo components are separate attributes is the # one now used by Plan 9 and Plan 9 Ports factotum implementations keytoauthinfo(key:ref Key): (ref Keyring->Authinfo, string) { if((s := authio->lookattrval(key.secrets, "!authinfo")) != nil) return strtoauthinfo(s); # TO DO: could look up authinfo by hash ai := ref Keyring->Authinfo; if((s = kv(key.secrets, "!sk")) == nil || (ai.mysk = keyring->strtosk(s)) == nil) return (nil, "bad secret key"); if((s = kv(key.attrs, "pk")) == nil || (ai.mypk = keyring->strtopk(s)) == nil) return (nil, "bad public key"); if((s = kv(key.attrs, "cert")) == nil || (ai.cert = keyring->strtocert(s)) == nil) return (nil, "bad certificate"); if((s = kv(key.attrs, "spk")) == nil || (ai.spk = keyring->strtopk(s)) == nil) return (nil, "bad signer public key"); if((s = kv(key.attrs, "dh-alpha")) == nil || (ai.alpha = IPint.strtoip(s, 16)) == nil) return (nil, "bad value for alpha"); if((s = kv(key.attrs, "dh-p")) == nil || (ai.p = IPint.strtoip(s, 16)) == nil) return (nil, "bad value for p"); return (ai, nil); } kv(a: list of ref Attr, name: string): string { return rnl(authio->lookattrval(a, name)); } rnl(s: string): string { for(i := 0; i < len s; i++) if(s[i] == '^') s[i] = '\n'; return s; } # s-expression form strtoauthinfo(s: string): (ref Keyring->Authinfo, string) { (se, err, nil) := Sexp.parse(s); if(se == nil) return (nil, err); els := se.els(); if(len els != 5) return (nil, "bad authinfo contents"); ai := ref Keyring->Authinfo; if((ai.spk = keyring->strtopk((hd els).astext())) == nil) return (nil, "bad signer public key"); els = tl els; if((ai.cert = keyring->strtocert((hd els).astext())) == nil) return (nil, "bad certificate"); els = tl els; if((ai.mysk = keyring->strtosk((hd els).astext())) == nil) return (nil, "bad secret/public key"); if((ai.mypk = keyring->sktopk(ai.mysk)) == nil) return (nil, "cannot make pk from sk"); els = tl els; if((ai.alpha = IPint.bytestoip((hd els).asdata())) == nil) return (nil, "bad value for alpha"); els = tl els; if((ai.p = IPint.bytestoip((hd els).asdata())) == nil) return (nil, "bad value for p"); return (ai, nil); } authinfotostr(ai: ref Keyring->Authinfo): string { return (ref Sexp.List( ss(keyring->pktostr(ai.spk)) :: ss(keyring->certtostr(ai.cert)) :: ss(keyring->sktostr(ai.mysk)) :: sb(ai.alpha.iptobytes()) :: sb(ai.p.iptobytes()) :: nil )).b64text(); } ss(s: string): ref Sexp.String { return ref Sexp.String(s, nil); } sb(d: array of byte): ref Sexp.Binary { return ref Sexp.Binary(d, nil); } sl(l: list of ref Sexp): ref Sexp { return ref Sexp.List(l); } keycheck(nil: ref Authio->Key): string { return nil; }