diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
| commit | 37da2899f40661e3e9631e497da8dc59b971cbd0 (patch) | |
| tree | cbc6d4680e347d906f5fa7fca73214418741df72 /appl/cmd/auth | |
| parent | 54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff) | |
20060303a
Diffstat (limited to 'appl/cmd/auth')
26 files changed, 5516 insertions, 0 deletions
diff --git a/appl/cmd/auth/aescbc.b b/appl/cmd/auth/aescbc.b new file mode 100644 index 00000000..c5b6e301 --- /dev/null +++ b/appl/cmd/auth/aescbc.b @@ -0,0 +1,254 @@ +implement Aescbc; + +# +# broadly transliterated from the Plan 9 command +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "keyring.m"; + kr: Keyring; + AESbsize, MD5dlen, SHA1dlen: import Keyring; + +include "arg.m"; + +Aescbc: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +# +# encrypted file: v2hdr, 16 byte IV, AES-CBC(key, random || file), HMAC_SHA1(md5(key), AES-CBC(random || file)) +# + +Checkpat: con "XXXXXXXXXXXXXXXX"; +Checklen: con len Checkpat; +Bufsize: con 4096; +AESmaxkey: con 32; + +V2hdr: con "AES CBC SHA1 2\n"; + +bin: ref Iobuf; +bout: ref Iobuf; +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + bufio = load Bufio Bufio->PATH; + + sys->pctl(Sys->FORKFD, nil); + stderr = sys->fildes(2); + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("auth/aescbc -d [-k key] [-f keyfile] <file.aes >clear.txt\n or: auth/aescbc -e [-k key] [-f keyfile] <clear.txt >file.aes"); + encrypt := -1; + keyfile: string; + pass: string; + while((o := arg->opt()) != 0) + case o { + 'd' or 'e' => + if(encrypt >= 0) + arg->usage(); + encrypt = o == 'e'; + 'f' => + keyfile = arg->earg(); + 'k' => + pass = arg->earg(); + * => + arg->usage(); + } + args = arg->argv(); + if(args != nil || encrypt < 0) + arg->usage(); + arg = nil; + + bin = bufio->fopen(sys->fildes(0), Bufio->OREAD); + bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE); + + buf := array[Bufsize+SHA1dlen] of byte; # Checklen <= SHA1dlen + + pwd: array of byte; + if(keyfile != nil){ + fd := sys->open(keyfile, Sys->OREAD); + if(fd == nil) + error(sys->sprint("can't open %q: %r", keyfile), "keyfile"); + n := readn(fd, buf, len buf); + while(n > 0 && buf[n-1] == byte '\n') + n--; + if(n <= 0) + error("no key", "no key"); + pwd = buf[0:n]; + }else{ + if(pass == nil) + pass = readpassword("password"); + if(pass == nil) + error("no key", "no key"); + pwd = array of byte pass; + for(i := 0; i < len pass; i++) + pass[i] = 0; + } + key := array[AESmaxkey] of byte; + key2 := array[SHA1dlen] of byte; + dstate := kr->sha1(array of byte "aescbc file", 11, nil, nil); + kr->sha1(pwd, len pwd, key2, dstate); + for(i := 0; i < len pwd; i++) + pwd[i] = byte 0; + key[0:] = key2[0:MD5dlen]; + nkey := MD5dlen; + kr->md5(key, nkey, key2, nil); # protect key even if HMAC_SHA1 is broken + key2 = key2[0:MD5dlen]; + + if(encrypt){ + Write(array of byte V2hdr, AESbsize); + genrandom(buf, 2*AESbsize); # CBC is semantically secure if IV is unpredictable. + aes := kr->aessetup(key[0:nkey], buf); # use first AESbsize bytes as IV + kr->aescbc(aes, buf[AESbsize:], AESbsize, Keyring->Encrypt); # use second AESbsize bytes as initial plaintext + Write(buf, 2*AESbsize); + dstate = kr->hmac_sha1(buf[AESbsize:], AESbsize, key2, nil, nil); + while((n := bin.read(buf, Bufsize)) > 0){ + kr->aescbc(aes, buf, n, Keyring->Encrypt); + Write(buf, n); + dstate = kr->hmac_sha1(buf, n, key2, nil, dstate); + if(n < Bufsize) + break; + } + if(n < 0) + error(sys->sprint("read error: %r"), "read error"); + kr->hmac_sha1(nil, 0, key2, buf, dstate); + Write(buf, SHA1dlen); + }else{ # decrypt + Read(buf, AESbsize); + if(string buf[0:AESbsize] == V2hdr){ + Read(buf, 2*AESbsize); # read IV and random initial plaintext + aes := kr->aessetup(key[0:nkey], buf); + dstate = kr->hmac_sha1(buf[AESbsize:], AESbsize, key2, nil, nil); + kr->aescbc(aes, buf[AESbsize:], AESbsize, Keyring->Decrypt); + Read(buf, SHA1dlen); + while((n := bin.read(buf[SHA1dlen:], Bufsize)) > 0){ + dstate = kr->hmac_sha1(buf, n, key2, nil, dstate); + kr->aescbc(aes, buf, n, Keyring->Decrypt); + Write(buf, n); + buf[0:] = buf[n:n+SHA1dlen]; # these bytes are not yet decrypted + } + kr->hmac_sha1(nil, 0, key2, buf[SHA1dlen:], dstate); + if(!eqbytes(buf, buf[SHA1dlen:], SHA1dlen)) + error("decrypted file failed to authenticate", "failed to authenticate"); + }else{ # compatibility with past mistake; assume we're decrypting secstore files + aes := kr->aessetup(key[0:AESbsize], buf); + Read(buf, Checklen); + kr->aescbc(aes, buf, Checklen, Keyring->Decrypt); + while((n := bin.read(buf[Checklen:], Bufsize)) > 0){ + kr->aescbc(aes, buf[Checklen:], n, Keyring->Decrypt); + Write(buf, n); + buf[0:] = buf[n:n+Checklen]; + } + if(string buf[0:Checklen] != Checkpat) + error("decrypted file failed to authenticate", "failed to authenticate"); + } + } + bout.flush(); +} + +error(s: string, why: string) +{ + bout.flush(); + sys->fprint(stderr, "aescbc: %s\n", s); + raise "fail:"+why; +} + +eqbytes(a: array of byte, b: array of byte, n: int): int +{ + if(len a < n || len b < n) + return 0; + for(i := 0; i < n; i++) + if(a[i] != b[i]) + return 0; + return 1; +} + +readn(fd: ref Sys->FD, buf: array of byte, nb: int): int +{ + for(nr := 0; nr < nb;){ + n := sys->read(fd, buf[nr:], nb-nr); + if(n <= 0){ + if(nr == 0) + return n; + break; + } + nr += n; + } + return nr; +} + +Read(buf: array of byte, n: int) +{ + if(bin.read(buf, n) != n){ + sys->fprint(sys->fildes(2), "aescbc: unexpectedly short read\n"); + raise "fail:read error"; + } +} + +Write(buf: array of byte, n: int) +{ + if(bout.write(buf, n) != n){ + sys->fprint(sys->fildes(2), "aescbc: write error: %r\n"); + raise "fail:write error"; + } +} + +readpassword(prompt: string): string +{ + cons := sys->open("/dev/cons", Sys->ORDWR); + if(cons == nil) + return nil; + stdin := bufio->fopen(cons, Sys->OREAD); + if(stdin == nil) + return nil; + cfd := sys->open("/dev/consctl", Sys->OWRITE); + if (cfd == nil || sys->fprint(cfd, "rawon") <= 0) + sys->fprint(stderr, "aescbc: warning: cannot hide typed password\n"); + s: string; +L: + for(;;){ + sys->fprint(cons, "%s: ", prompt); + s = ""; + while ((c := stdin.getc()) >= 0){ + case c { + '\n' => + break L; + '\b' or 8r177 => + if(len s > 0) + s = s[0:len s - 1]; + 'u' & 8r037 => + sys->fprint(cons, "\n"); + continue L; + * => + s[len s] = c; + } + } + } + sys->fprint(cons, "\n"); + return s; +} + +genrandom(b: array of byte, n: int) +{ + fd := sys->open("/dev/notquiterandom", Sys->OREAD); + if(fd == nil){ + sys->fprint(stderr, "aescbc: can't open /dev/notquiterandom: %r\n"); + raise "fail:random"; + } + if(sys->read(fd, b, n) != n){ + sys->fprint(stderr, "aescbc: can't read random numbers: %r\n"); + raise "fail:read random"; + } +} diff --git a/appl/cmd/auth/changelogin.b b/appl/cmd/auth/changelogin.b new file mode 100644 index 00000000..97141408 --- /dev/null +++ b/appl/cmd/auth/changelogin.b @@ -0,0 +1,305 @@ +implement Changelogin; + +include "sys.m"; + sys: Sys; + +include "daytime.m"; + daytime: Daytime; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + +Changelogin: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr, stdin, stdout: ref Sys->FD; +keydb := "/mnt/keys"; + +init(nil: ref Draw->Context, args: list of string) +{ + ok: int; + word: string; + + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + + stdin = sys->fildes(0); + stdout = sys->fildes(1); + stderr = sys->fildes(2); + + argv0 := hd args; + args = tl args; + + if(args == nil){ + sys->fprint(stderr, "usage: %s userid\n", argv0); + raise "fail:usage"; + } + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) { + sys->fprint(stderr, "%s: can't load Daytime: %r\n", argv0); + raise "fail:load"; + } + + # get password + id := hd args; + (dbdir, secret, expiry, err) := getuser(id); + if(dbdir == nil){ + if(err != nil){ + sys->fprint(stderr, "%s: can't get auth info for %s in %s: %s\n", argv0, id, keydb, err); + raise "fail:no key"; + } + sys->print("new account\n"); + } + for(;;){ + if(secret != nil) + sys->print("secret [default = don't change]: "); + else + sys->print("secret: "); + (ok, word) = readline(stdin, "rawon"); + if(!ok) + exit; + if(word == "" && secret != nil) + break; + if(len word >= 8) + break; + sys->print("!secret must be at least 8 characters\n"); + } + newsecret: array of byte; + if(word != ""){ + # confirm password change + word1 := word; + sys->print("confirm: "); + (ok, word) = readline(stdin, "rawon"); + if(!ok || word != word1) { + sys->print("Entries do not match. Authinfo record unchanged.\n"); + raise "fail:mismatch"; + } + + pwbuf := array of byte word; + newsecret = array[Keyring->SHA1dlen] of byte; + kr->sha1(pwbuf, len pwbuf, newsecret, nil); + } + + # get expiration time (midnight of date specified) + maxdate := "17012038"; # largest date possible without incurring integer overflow + now := daytime->now(); + tm := daytime->local(now); + tm.sec = 59; + tm.min = 59; + tm.hour = 23; + tm.year += 1; + if(dbdir == nil) + expsecs := daytime->tm2epoch(tm); # set expiration date to 23:59:59 one year from today + else + expsecs = expiry; + for(;;){ + defexpdate := "permanent"; + if(expsecs != 0) { + otm := daytime->local(expsecs); + defexpdate = sys->sprint("%2.2d%2.2d%4.4d", otm.mday, otm.mon+1, otm.year+1900); + } + sys->print("expires [DDMMYYYY/permanent, return = %s]: ", defexpdate); + (ok, word) = readline(stdin, "rawoff"); + if(!ok) + exit; + if(word == "") + word = defexpdate; + if(word == "permanent"){ + expsecs = 0; + break; + } + if(len word != 8){ + sys->print("!bad date format %s\n", word); + continue; + } + tm.mday = int word[0:2]; + if(tm.mday > 31 || tm.mday < 1){ + sys->print("!bad day of month %d\n", tm.mday); + continue; + } + tm.mon = int word[2:4] - 1; + if(tm.mon > 11 || tm.mday < 0){ + sys->print("!bad month %d\n", tm.mon + 1); + continue; + } + tm.year = int word[4:8] - 1900; + if(tm.year < 70){ + sys->print("!bad year %d (year may be no earlier than 1970)\n", tm.year + 1900); + continue; + } + expsecs = daytime->tm2epoch(tm); + if(expsecs > now) + break; + else { + newexpdate := sys->sprint("%2.2d%2.2d%4.4d", tm.mday, tm.mon+1, tm.year+1900); + tm = daytime->local(daytime->now()); + today := sys->sprint("%2.2d%2.2d%4.4d", tm.mday, tm.mon+1, tm.year+1900); + sys->print("!bad expiration date %s (must be between %s and %s)\n", newexpdate, today, maxdate); + expsecs = now; + } + } + newexpiry := expsecs; + +# # get the free form field +# if(pw != nil) +# npw.other = pw.other; +# else +# npw.other = ""; +# sys->print("free form info [return = %s]: ", npw.other); +# (ok, word) = readline(stdin,"rawoff"); +# if(!ok) +# exit; +# if(word != "") +# npw.other = word; + + if(dbdir == nil){ + dbdir = keydb+"/"+id; + fd := sys->create(dbdir, Sys->OREAD, Sys->DMDIR|8r700); + if(fd == nil){ + sys->fprint(stderr, "%s: can't create account %s: %r\n", argv0, id); + raise "fail:create user"; + } + } + changed := 0; + if(!eq(newsecret, secret)){ + if(putsecret(dbdir, newsecret) < 0){ + sys->fprint(stderr, "%s: can't update secret for %s: %r\n", argv0, id); + raise "fail:update"; + } + changed = 1; + } + if(newexpiry != expiry){ + if(putexpiry(dbdir, newexpiry) < 0){ + sys->fprint(stderr, "%s: can't update expiry time for %s: %r\n", argv0, id); + raise "fail:update"; + } + changed = 1; + } + sys->print("change written\n"); +} + +getuser(id: string): (string, array of byte, int, string) +{ + (ok, nil) := sys->stat(keydb); + if(ok < 0) + return (nil, nil, 0, sys->sprint("can't stat %s: %r", id)); + dbdir := keydb+"/"+id; + (ok, nil) = sys->stat(dbdir); + if(ok < 0) + return (nil, nil, 0, nil); + fd := sys->open(dbdir+"/secret", Sys->OREAD); + if(fd == nil) + return (nil, nil, 0, sys->sprint("can't open %s/secret: %r", id)); + d: Sys->Dir; + (ok, d) = sys->fstat(fd); + if(ok < 0) + return (nil, nil, 0, sys->sprint("can't stat %s/secret: %r", id)); + l := int d.length; + secret: array of byte; + if(l > 0){ + secret = array[l] of byte; + if(sys->read(fd, secret, len secret) != len secret) + return (nil, nil, 0, sys->sprint("error reading %s/secret: %r", id)); + } + expiry := 0; + fd = sys->open(dbdir+"/expire", Sys->OREAD); + if(fd == nil) + return (nil, nil, 0, sys->sprint("can't open %s/expiry: %r", id)); + b := array[32] of byte; + n := sys->read(fd, b, len b); + if(n <= 0) + return (nil, nil, 0, sys->sprint("error reading %s/expiry: %r", id)); + return (dbdir, secret, int string b[0:n], nil); +} + +eq(a, b: array of byte): int +{ + if(len a != len b) + return 0; + for(i := 0; i < len a; i++) + if(a[i] != b[i]) + return 0; + return 1; +} + +putsecret(dir: string, secret: array of byte): int +{ + fd := sys->create(dir+"/secret", Sys->OWRITE, 8r600); + if(fd == nil) + return -1; + return sys->write(fd, secret, len secret); +} + +putexpiry(dir: string, expiry: int): int +{ + fd := sys->open(dir+"/expire", Sys->OWRITE); + if(fd == nil) + return -1; + return sys->fprint(fd, "%d", expiry); +} + +readline(io: ref Sys->FD, mode: string): (int, string) +{ + r : int; + line : string; + buf := array[8192] of byte; + fdctl : ref Sys->FD; + rawoff := array of byte "rawoff"; + + # + # Change console mode to rawon + # + if(mode == "rawon"){ + fdctl = sys->open("/dev/consctl", sys->OWRITE); + if(fdctl == nil || sys->write(fdctl,array of byte mode,len mode) != len mode){ + sys->fprint(stderr, "unable to change console mode"); + return (0,nil); + } + } + + # + # Read up to the CRLF + # + line = ""; + for(;;) { + r = sys->read(io, buf, len buf); + if(r <= 0){ + sys->fprint(stderr, "error read from console mode"); + if(mode == "rawon") + sys->write(fdctl,rawoff,6); + return (0, nil); + } + + line += string buf[0:r]; + if ((len line >= 1) && (line[(len line)-1] == '\n')){ + if(mode == "rawon"){ + r = sys->write(stdout,array of byte "\n",1); + if(r <= 0) { + sys->write(fdctl,rawoff,6); + return (0, nil); + } + } + break; + } + else { + if(mode == "rawon"){ + #r = sys->write(stdout, array of byte "*",1); + if(r <= 0) { + sys->write(fdctl,rawoff,6); + return (0, nil); + } + } + } + } + + if(mode == "rawon") + sys->write(fdctl,rawoff,6); + + # Total success! + return (1, line[0:len line - 1]); +} diff --git a/appl/cmd/auth/convpasswd.b b/appl/cmd/auth/convpasswd.b new file mode 100644 index 00000000..8463b0fb --- /dev/null +++ b/appl/cmd/auth/convpasswd.b @@ -0,0 +1,120 @@ +implement Convpasswd; + +include "sys.m"; + sys: Sys; +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "keyring.m"; + keyring: Keyring; + IPint: import keyring; + +include "security.m"; + +include "arg.m"; + +Convpasswd: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +PW: adt { + id: string; # user id + pw: array of byte; # password hashed by SHA + expire: int; # expiration time (epoch seconds) + other: string; # about the account +}; + +mntpt := "/mnt/keys"; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + keyring = load Keyring Keyring->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + noload(Arg->PATH); + arg := load Arg Arg->PATH; + if(arg == nil) + noload(Arg->PATH); + force := 0; + verbose := 0; + arg->init(args); + arg->setusage("convpasswd [-f] [-v] [-m /mnt/keys] [passwordfile]"); + while((o := arg->opt()) != 0) + case o { + 'f' => force = 1; + 'm' => mntpt = arg->earg(); + 'v' => verbose = 1; + * => arg->usage(); + } + args = arg->argv(); + arg = nil; + + f := "/keydb/password"; + if(args != nil) + f = hd args; + iob := bufio->open(f, Bufio->OREAD); + if(iob == nil) + error(sys->sprint("%s: %r", f)); + for(line := 1; (s := iob.gets('\n')) != nil; line++) { + (n, tokl) := sys->tokenize(s, ":\n"); + if (n < 3){ + sys->fprint(sys->fildes(2), "convpasswd: %s:%d: invalid format\n", f, line); + continue; + } + pw := ref PW; + pw.id = hd tokl; + pw.pw = IPint.b64toip(hd tl tokl).iptobytes(); + pw.expire = int hd tl tl tokl; + if (n==3) + pw.other = nil; + else + pw.other = hd tl tl tl tokl; + err := writekey(pw, force); + if(err != nil) + error(sys->sprint("error writing /mnt/keys entry for %s: %s", pw.id, err)); + if(verbose) + sys->print("%s\n", pw.id); + } +} + +noload(p: string) +{ + error(sys->sprint("can't load %s: %r", p)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "convpasswd: %s\n", s); + raise "fail:error"; +} + +writekey(pw: ref PW, force: int): string +{ + dir := mntpt+"/"+pw.id; + if(sys->open(dir, Sys->OREAD) == nil){ + # make it + d := sys->create(dir, Sys->OREAD, Sys->DMDIR|8r600); + if(d == nil) + return sys->sprint("can't create %s: %r", dir); + }else if(!force) + return nil; # leave existing entry alone + secret := dir+"/secret"; + fd := sys->open(secret, Sys->OWRITE); + if(fd == nil) + return sys->sprint("can't open %s: %r", secret); + if(sys->write(fd, pw.pw, len pw.pw) != len pw.pw) + return sys->sprint("error writing %s: %r", secret); + expire := dir+"/expire"; + fd = sys->open(expire, Sys->OWRITE); + if(fd == nil) + return sys->sprint("can't open %s: %r", expire); + if(sys->fprint(fd, "%d", pw.expire) < 0) + return sys->sprint("error writing %s: %r", expire); + # no equivalent of `other' + return nil; +} diff --git a/appl/cmd/auth/countersigner.b b/appl/cmd/auth/countersigner.b new file mode 100644 index 00000000..a444f807 --- /dev/null +++ b/appl/cmd/auth/countersigner.b @@ -0,0 +1,59 @@ +implement Countersigner; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + draw: Draw; + +include "keyring.m"; + kr: Keyring; + +include "security.m"; + +Countersigner: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr, stdin, stdout: ref Sys->FD; + +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + + stdin = sys->fildes(0); + stdout = sys->fildes(1); + stderr = sys->fildes(2); + + sys->pctl(Sys->FORKNS, nil); + if(sys->chdir("/keydb") < 0){ + sys->fprint(stderr, "countersigner: no key database\n"); + raise "fail:no keydb"; + } + + # get boxid + buf := kr->getmsg(stdin); + if(buf == nil){ + sys->fprint(stderr, "countersigner: client hung up\n"); + raise "fail:hungup"; + } + boxid := string buf; + + # read file + file := "countersigned/"+boxid; + fd := sys->open(file, Sys->OREAD); + if(fd == nil){ + sys->fprint(stderr, "countersigner: can't open %s: %r\n", file); + raise "fail:bad boxid"; + } + blind := kr->getmsg(fd); + if(blind == nil){ + sys->fprint(stderr, "countersigner: can't read %s\n", file); + raise "fail:no blind"; + } + + # answer client + kr->sendmsg(stdout, blind, len blind); +} diff --git a/appl/cmd/auth/createsignerkey.b b/appl/cmd/auth/createsignerkey.b new file mode 100644 index 00000000..90a54b6f --- /dev/null +++ b/appl/cmd/auth/createsignerkey.b @@ -0,0 +1,144 @@ +implement Createsignerkey; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "daytime.m"; + +include "keyring.m"; + kr: Keyring; + +include "arg.m"; + +# signer key never expires +SKexpire: con 0; + +# size in bits of modulus for public keys +PKmodlen: con 512; + +# size in bits of modulus for diffie hellman +DHmodlen: con 512; + +algs := array[] of {"rsa", "elgamal"}; # first entry is default + +Createsignerkey: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +init(nil: ref Draw->Context, argv: list of string) +{ + err: string; + + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + if(kr == nil) + loaderr(Keyring->PATH); + arg := load Arg Arg->PATH; + if(arg == nil) + loaderr(Arg->PATH); + + arg->init(argv); + arg->setusage("createsignerkey [-a algorithm] [-f keyfile] [-e ddmmyyyy] [-b size-in-bits] name-of-owner"); + alg := algs[0]; + filename := "/keydb/signerkey"; + expire := SKexpire; + bits := PKmodlen; + while((c := arg->opt()) != 0){ + case c { + 'a' => + alg = arg->arg(); + if(alg == nil) + arg->usage(); + for(i:=0;; i++){ + if(i >= len algs) + error(sys->sprint("unknown algorithm: %s", alg)); + else if(alg == algs[i]) + break; + } + 'f' or 'k' => + filename = arg->earg(); + 'e' => + s := arg->earg(); + (err, expire) = checkdate(s); + if(err != nil) + error(err); + 'b' => + s := arg->earg(); + bits = int s; + if(bits < 32 || bits > 4096) + error("modulus must be in the range of 32 to 4096 bits"); + * => + arg->usage(); + } + } + argv = arg->argv(); + if(argv == nil) + arg->usage(); + arg = nil; + + owner := hd argv; + + # generate a local key, self-signed + info := ref Keyring->Authinfo; + info.mysk = kr->genSK(alg, owner, bits); + if(info.mysk == nil) + error(sys->sprint("algorithm %s not configured in system", alg)); + info.mypk = kr->sktopk(info.mysk); + info.spk = kr->sktopk(info.mysk); + myPKbuf := array of byte kr->pktostr(info.mypk); + state := kr->sha1(myPKbuf, len myPKbuf, nil, nil); + info.cert = kr->sign(info.mysk, expire, state, "sha1"); + (info.alpha, info.p) = kr->dhparams(DHmodlen); + + if(kr->writeauthinfo(filename, info) < 0) + error(sys->sprint("can't write signerkey file %s: %r", filename)); +} + +loaderr(s: string) +{ + error(sys->sprint("can't load %s: %r", s)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "createsignerkey: %s\n", s); + raise "fail:error"; +} + +checkdate(word: string): (string, int) +{ + if(len word != 8) + return ("!date must be in form ddmmyyyy", 0); + + daytime := load Daytime Daytime->PATH; + if(daytime == nil) + loaderr(Daytime->PATH); + + now := daytime->now(); + + tm := daytime->local(now); + tm.sec = 59; + tm.min = 59; + tm.hour = 24; + + tm.mday = int word[0:2]; + if(tm.mday > 31 || tm.mday < 1) + return ("!bad day of month", 0); + + tm.mon = int word[2:4] - 1; + if(tm.mon > 11 || tm.mday < 0) + return ("!bad month", 0); + + tm.year = int word[4:8] - 1900; + if(tm.year < 70) + return ("!bad year", 0); + + newdate := daytime->tm2epoch(tm); + if(newdate < now) + return ("!expiration date must be in the future", 0); + + return (nil, newdate); +} diff --git a/appl/cmd/auth/factotum/authio.m b/appl/cmd/auth/factotum/authio.m new file mode 100644 index 00000000..7c0565b5 --- /dev/null +++ b/appl/cmd/auth/factotum/authio.m @@ -0,0 +1,80 @@ +Authio: module +{ + + Aattr, Aval, Aquery: con iota; + + Attr: adt { + tag: int; + name: string; + val: string; + + text: fn(a: self ref Attr): string; + }; + + Key: adt { + attrs: list of ref Attr; + secrets: list of ref Attr; + # proto: Authproto; + + mk: fn(attrs: list of ref Attr): ref Key; + text: fn(k: self ref Key): string; + safetext: fn(k: self ref Key): string; + }; + + Fid: adt + { + fid: int; + pid: int; + err: string; + attrs: list of ref Attr; + write: chan of (array of byte, Sys->Rwrite); + read: chan of (int, Sys->Rread); + # proto: Authproto; + done: int; + ai: ref Authinfo; + }; + + Rpc: adt { + r: ref Fid; + cmd: int; + arg: array of byte; + nbytes: int; + rc: chan of (array of byte, string); + }; + + IO: adt { + f: ref Fid; + rpc: ref Rpc; + + findkey: fn(io: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string); + needkey: fn(io: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string); + read: fn(io: self ref IO): array of byte; + readn: fn(io: self ref IO, n: int): array of byte; + write: fn(io: self ref IO, buf: array of byte, n: int): int; + toosmall: fn(io: self ref IO, n: int); + error: fn(io: self ref IO, s: string); + ok: fn(io: self ref IO); + done: fn(io: self ref IO, ai: ref Authinfo); + }; + + # need more ... ? + Authinfo: adt { + cuid: string; # caller id + suid: string; # server id + cap: string; # capability (only valid on server side) + secret: array of byte; + }; + + memrandom: fn(a: array of byte, n: int); + eqbytes: fn(a, b: array of byte): int; + netmkaddr: fn(addr, net, svc: string): string; + user: fn(): string; + lookattrval: fn(a: list of ref Attr, n: string): string; + parseline: fn(s: string): list of ref Attr; +}; + +Authproto: module +{ + init: fn(f: Authio): string; + interaction: fn(attrs: list of ref Authio->Attr, io: ref Authio->IO): string; +}; diff --git a/appl/cmd/auth/factotum/factotum.b b/appl/cmd/auth/factotum/factotum.b new file mode 100644 index 00000000..5f5b02a3 --- /dev/null +++ b/appl/cmd/auth/factotum/factotum.b @@ -0,0 +1,978 @@ +implement Factotum, Authio; + +# +# Copyright © 2003-2004 Vita Nuova Holdings Limited +# + +include "sys.m"; + sys: Sys; + Rread, Rwrite: import Sys; + +include "draw.m"; + +include "string.m"; + str: String; + +include "keyring.m"; + +include "authio.m"; + +include "arg.m"; + +Factotum: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +#confirm, log + +Files: adt { + ctl: ref Sys->FileIO; + rpc: ref Sys->FileIO; + proto: ref Sys->FileIO; + needkey: ref Sys->FileIO; +}; + +Debug: con 0; +debug := Debug; + +files: Files; +authio: Authio; + +keymanc: chan of (list of ref Attr, int, chan of (ref Key, string)); + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + str = load String String->PATH; + authio = load Authio "$self"; + + svcname := "#sfactotum"; + mntpt := "/mnt/factotum"; + arg := load Arg Arg->PATH; + if(arg != nil){ + arg->init(args); + arg->setusage("auth/factotum [-d] [-m /mnt/factotum] [-s factotum]"); + while((o := arg->opt()) != 0) + case o { + 'd' => debug = 1; + 'm' => mntpt = arg->earg(); + 's' => svcname = "#s"+arg->earg(); + * => arg->usage(); + } + args = arg->argv(); + if(args != nil) + arg->usage(); + arg = nil; + } + sys->unmount(nil, mntpt); + if(sys->bind(svcname, mntpt, Sys->MREPL) < 0) + err(sys->sprint("can't bind %s on %s: %r", svcname, mntpt)); + files.ctl = sys->file2chan(mntpt, "ctl"); + files.rpc = sys->file2chan(mntpt, "rpc"); + files.proto = sys->file2chan(mntpt, "proto"); + files.needkey = sys->file2chan(mntpt, "needkey"); + if(files.ctl == nil || files.rpc == nil || files.proto == nil || files.needkey == nil) + err(sys->sprint("can't create %s/*: %r", mntpt)); + keymanc = chan of (list of ref Attr, int, chan of (ref Key, string)); + spawn factotumsrv(); +} + +user(): string +{ + fd := sys->open("/dev/user", Sys->OREAD); + if(fd == nil) + return nil; + b := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, b, len b); + if(n <= 0) + return nil; + return string b[0:n]; +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "factotum: %s\n", s); + raise "fail:error"; +} + +rlist: list of ref Fid; + +factotumsrv() +{ + sys->pctl(Sys->NEWPGRP|Sys->FORKFD|Sys->FORKENV, nil); + if(!Debug) + privacy(); + allkeys := array[0] of ref Key; + pidc := chan of int; + donec := chan of ref Fid; +# keyc := chan of (list of ref Attr, chan of (ref Key, string)); + needfid := -1; + needed, needy: list of (int, list of ref Attr, chan of (ref Key, string)); + needread: Sys->Rread; + needtag := 0; + for(;;) X: alt{ + r := <-donec => + r.pid = 0; + cleanfid(r.fid); + + (off, nbytes, nil, rc) := <-files.ctl.read => + if(rc == nil) + break; + s := ""; + for(i := 0; i < len allkeys; i++) + if((k := allkeys[i]) != nil) + s += k.safetext()+"\n"; + rc <-= reads(s, off, nbytes); + (nil, data, nil, wc) := <-files.ctl.write => + if(wc == nil) + break; + (nf, flds) := sys->tokenize(string data, "\n\r"); + if(nf > 1){ + # compatibility with plan 9; has the advantage you can tell which key is wrong + wc <-= (0, "multiline write not allowed"); + break; + } + s := hd flds; + if(s == nil || s[0] == '#'){ + wc <-= (len data, nil); + break; + } + for(i := 0; i < len s && s[i] != ' '; i++){ + # skip + } + verb := s[0:i]; + if(i < len s) + i++; + s = s[i:]; + case verb { + "key" => + k := Key.mk(parseline(s)); + if(k == nil){ + wc <-= (len data, nil); # ignore it + break; + } + if(lookattrval(k.attrs, "proto") == nil){ + wc <-= (0, "key without proto"); + break; + } + allkeys = addkey(allkeys, k); + wc <-= (len data, nil); + "delkey" => + attrs := parseline(s); + for(al := attrs; al != nil; al = tl al){ + a := hd al; + if(a.name[0] == '!' && (a.val != nil || a.tag != Aquery)){ + wc <-= (0, "cannot specify values for private fields"); + break X; + } + } + if(delkey(allkeys, attrs) == 0) + wc <-= (0, "no matching keys"); + else + wc <-= (len data, nil); + "debug" => + wc <-= (len data, nil); + * => + wc <-= (0, "unknown ctl request"); + } + + (nil, nbytes, fid, rc) := <-files.rpc.read => + if(rc == nil) + break; + r := findfid(fid); + if(r == nil){ + rc <-= (nil, "unknown request"); + break; + } + alt{ + r.read <-= (nbytes, rc) => + ; + * => + rc <-= (nil, "concurrent rpc read not allowed"); + } + (nil, data, fid, wc) := <-files.rpc.write => + if(wc == nil){ + cleanfid(fid); + break; + } + r := findfid(fid); + if(r == nil){ + r = ref Fid(fid, 0, nil, nil, chan[1] of (array of byte, Rwrite), chan[1] of (int, Rread), 0, nil); + spawn request(r, pidc, donec); + r.pid = <-pidc; + rlist = r :: rlist; + } + # this non-blocking write avoids a potential deadlock situation that + # can happen when a proto module calls findkey at the same time + # a client tries to write to the rpc file. this might not be the correct fix! + alt{ + r.write <-= (data, wc) => + ; + * => + wc <-= (-1, "concurrent rpc write not allowed"); + } + + (off, nbytes, nil, rc) := <-files.proto.read => + if(rc == nil) + break; + rc <-= reads("pass\np9any\n", off, nbytes); # TO DO + (nil, nil, nil, wc) := <-files.proto.write => + if(wc != nil) + wc <-= (0, "illegal operation"); + + (nil, nil, fid, rc) := <-files.needkey.read => + if(rc == nil) + break; + if(needfid >= 0 && fid != needfid){ + rc <-= (nil, "file in use"); + break; + } + needfid = fid; + if(needy != nil){ + (tag, attr, kc) := hd needy; + needy = tl needy; + needed = (tag, attr, kc) :: needed; + rc <-= (sys->aprint("needkey tag=%ud %s", tag, attrtext(attr)), nil); + break; + } + if(needread != nil){ + rc <-= (nil, "already reading"); + break; + } + needread = rc; + (nil, data, fid, wc) := <-files.needkey.write => + if(wc == nil){ + if(needfid == fid){ + needfid = -1; # TO DO? give needkey errors back to request + needread = nil; + } + break; + } + if(needfid >= 0 && fid != needfid){ + wc <-= (0, "file in use"); + break; + } + needfid = fid; + tagline := parseline(string data); + if(len tagline != 1 || (t := lookattrval(tagline, "tag")) == nil){ + wc <-= (0, "no tag"); + break; + } + tag := int t; + nl: list of (int, list of ref Attr, chan of (ref Key, string)); + found := 0; + for(l := needed; l != nil; l = tl l){ + (ntag, attrs, kc) := hd l; + if(tag == ntag){ + found = 1; + k := findkey(allkeys, attrs); + if(k != nil) + kc <-= (k, nil); + else + kc <-= (nil, "needkey "+attrtext(attrs)); + while((l = tl l) != nil) + nl = hd l :: nl; + break; + } + nl = hd l :: nl; + } + if(found) + wc <-= (len data, nil); + else + wc <-= (0, "tag not found"); + + (attrs, required, kc) := <-keymanc => + # look for key and reply + k := findkey(allkeys, attrs); + if(k != nil){ + kc <-= (k, nil); + break; + }else if(!required || needfid == -1){ + kc <-= (nil, "needkey "+attrtext(attrs)); + break; + } + # query surrounding environment using needkey + if(needread != nil){ + needed = (needtag, attrs, kc) :: needed; + needread <-= (sys->aprint("needkey tag=%ud %s", needtag, attrtext(attrs)), nil); + needread = nil; + needtag++; + }else + needy = (needtag++, attrs, kc) :: needy; + } +} + +findfid(fid: int): ref Fid +{ + for(rl := rlist; rl != nil; rl = tl rl){ + r := hd rl; + if(r.fid == fid) + return r; + } + return nil; +} + +cleanfid(fid: int) +{ + rl := rlist; + rlist = nil; + for(; rl != nil; rl = tl rl){ + r := hd rl; + if(r.fid != fid) + rlist = r :: rlist; + else if(r.pid) + kill(r.pid); + } +} + +kill(pid: int) +{ + fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "kill"); +} + +privacy() +{ + fd := sys->open("#p/"+string sys->pctl(0, nil)+"/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "private") < 0) + sys->fprint(sys->fildes(2), "factotum: warning: unable to make memory private: %r\n"); +} + +reads(str: string, off, nbytes: int): (array of byte, string) +{ + bstr := array of byte str; + slen := len bstr; + if(off < 0 || off >= slen) + return (nil, nil); + if(off + nbytes > slen) + nbytes = slen - off; + if(nbytes <= 0) + return (nil, nil); + return (bstr[off:off+nbytes], nil); +} + +Ogok, Ostart, Oread, Owrite, Oauthinfo, Oattr: con iota; + +ops := array[] of { + (Ostart, "start"), + (Oread, "read"), + (Owrite, "write"), + (Oauthinfo, "authinfo"), + (Oattr, "attr"), +}; + +request(r: ref Fid, pidc: chan of int, donec: chan of ref Fid) +{ + pidc <-= sys->pctl(0, nil); + rpc := rio(r); + while(rpc != nil){ + if(rpc.cmd == Ostart){ + (proto, attrs, e) := startproto(string rpc.arg); + if(e != nil){ + reply(rpc, "error "+e); + rpc = rio(r); + continue; + } + r.attrs = attrs; # saved for attr request + ok(rpc); + io := ref IO(r, nil); + { + err := proto->interaction(attrs, io); + if(debug && err != nil) + sys->fprint(sys->fildes(2), "factotum: failure: %s\n", err); + if(r.err == nil) + r.err = err; + r.done = 1; + }exception ex{ + "*" => + r.done = 0; + r.err = "exception "+ex; + } + if(r.err != nil) + io.error(r.err); + rpc = finish(r); + r.attrs = nil; + r.err = nil; + r.done = 0; + r.ai = nil; + }else + reply(rpc, "no current protocol"); + } + flushreq(r, donec); +} + +startproto(request: string): (Authproto, list of ref Attr, string) +{ + attrs := parseline(request); + if(Debug) + sys->print("-> %s <-\n", attrtext(attrs)); + p := lookattrval(attrs, "proto"); + if(p == nil) + return (nil, nil, "did not specify protocol"); + if(Debug) + sys->print("proto=%s\n", p); + if(any(p, "./")) # avoid unpleasantness + return (nil, nil, "illegal protocol: "+p); + proto := load Authproto "/dis/auth/proto/"+p+".dis"; + if(proto == nil) + return (nil, nil, sys->sprint("protocol %s: %r", p)); + if(Debug) + sys->print("start %s\n", p); + e: string; + { + e = proto->init(authio); + }exception ex{ + "*" => + e = "exception "+ex; + } + if(e != nil) + return (nil, nil, e); + return (proto, attrs, nil); +} + +finish(r: ref Fid): ref Rpc +{ + while((rpc := rio(r)) != nil) + case rpc.cmd { + Owrite => + phase(rpc, "protocol phase error"); + Oread => + if(r.err != nil) + reply(rpc, "error "+r.err); + else + done(rpc, r.ai); + Oauthinfo => + if(r.done){ + if(r.ai == nil) + reply(rpc, "error no authinfo available"); + else{ + a := packai(r.ai); + if(rpc.nbytes-3 < len a) + reply(rpc, sys->sprint("toosmall %d", len a + 3)); + else + okdata(rpc, a); + } + }else + reply(rpc, "error authentication unfinished"); + Ostart => + return rpc; + * => + reply(rpc, "error unexpected request"); + } + return nil; +} + +flushreq(r: ref Fid, donec: chan of ref Fid) +{ + for(;;) alt{ + donec <-= r => + exit; + (nil, wc) := <-r.write => + wc <-= (0, "write rpc protocol error"); + (nil, rc) := <-r.read => + rc <-= (nil, "read rpc protocol error"); + } +} + +rio(r: ref Fid): ref Rpc +{ + req: array of byte; + for(;;) alt{ + (data, wc) := <-r.write => + if(req != nil){ + wc <-= (0, "rpc pending; read to clear"); + break; + } + req = data; + wc <-= (len data, nil); + + (nbytes, rc) := <-r.read => + if(req == nil){ + rc <-= (nil, "no rpc pending"); + break; + } + (cmd, arg) := op(req, ops); + req = nil; + rpc := ref Rpc(r, cmd, arg, nbytes, rc); + case cmd { + Ogok => + reply(rpc, "error unknown rpc"); + break; + Oattr => + if(r.attrs == nil) + reply(rpc, "error no attributes"); + else + reply(rpc, "ok "+attrtext(r.attrs)); + break; + * => + return rpc; + } + } +} + +ok(rpc: ref Rpc) +{ + reply(rpc, "ok"); +} + +okdata(rpc: ref Rpc, a: array of byte) +{ + b := array[len a + 3] of byte; + b[0] = byte 'o'; + b[1] = byte 'k'; + b[2] = byte ' '; + b[3:] = a; + rpc.rc <-= (b, nil); +} + +done(rpc: ref Rpc, ai: ref Authinfo) +{ + rpc.r.ai = ai; + rpc.r.done = 1; + if(ai != nil) + reply(rpc, "done haveai"); + else + reply(rpc, "done"); +} + +phase(rpc: ref Rpc, s: string) +{ + reply(rpc, "phase "+s); +} + +needkey(rpc: ref Rpc, attrs: list of ref Attr) +{ + reply(rpc, "needkey "+attrtext(attrs)); +} + +reply(rpc: ref Rpc, s: string) +{ + rpc.rc <-= reads(s, 0, rpc.nbytes); +} + +puta(a: array of byte, n: int, v: array of byte): int +{ + if(n < 0) + return -1; + c := len v; + if(n+2+c > len a) + return -1; + a[n++] = byte c; + a[n++] = byte (c>>8); + a[n:] = v; + return n + len v; +} + +packai(ai: ref Authinfo): array of byte +{ + a := array[1024] of byte; + i := puta(a, 0, array of byte ai.cuid); + i = puta(a, i, array of byte ai.suid); + i = puta(a, i, array of byte ai.cap); + i = puta(a, i, ai.secret); + if(i < 0) + return nil; + return a[0:i]; +} + +op(a: array of byte, ops: array of (int, string)): (int, array of byte) +{ + arg: array of byte; + for(i := 0; i < len a; i++) + if(a[i] == byte ' '){ + if(i+1 < len a) + arg = a[i+1:]; + break; + } + s := string a[0:i]; + for(i = 0; i < len ops; i++){ + (cmd, name) := ops[i]; + if(s == name) + return (cmd, arg); + } + return (Ogok, arg); +} + +parseline(s: string): list of ref Attr +{ + fld := str->unquoted(s); + rfld := fld; + for(fld = nil; rfld != nil; rfld = tl rfld) + fld = (hd rfld) :: fld; + attrs: list of ref Attr; + for(; fld != nil; fld = tl fld){ + n := hd fld; + a := ""; + tag := Aattr; + for(i:=0; i<len n; i++) + if(n[i] == '='){ + a = n[i+1:]; + n = n[0:i]; + tag = Aval; + } + if(len n == 0) + continue; + if(tag == Aattr && len n > 1 && n[len n-1] == '?'){ + tag = Aquery; + n = n[0:len n-1]; + } + attrs = ref Attr(tag, n, a) :: attrs; + } + return attrs; +} + +Attr.text(a: self ref Attr): string +{ + case a.tag { + Aattr => + return a.name; + Aval => + return a.name+"="+a.val; + Aquery => + return a.name+"?"; + * => + return "??"; + } +} + +attrtext(attrs: list of ref Attr): string +{ + s := ""; + sp := 0; + for(; attrs != nil; attrs = tl attrs){ + if(sp) + s[len s] = ' '; + sp = 1; + s += (hd attrs).text(); + } + return s; +} + +lookattr(attrs: list of ref Attr, n: string): ref Attr +{ + for(; attrs != nil; attrs = tl attrs) + if((a := hd attrs).tag != Aquery && a.name == n) + return a; + return nil; +} + +lookattrval(attrs: list of ref Attr, n: string): string +{ + if((a := lookattr(attrs, n)) != nil) + return a.val; + return nil; +} + +anyattr(attrs: list of ref Attr, n: string): ref Attr +{ + for(; attrs != nil; attrs = tl attrs) + if((a := hd attrs).name == n) + return a; + return nil; +} + +reverse[T](l: list of T): list of T +{ + r: list of T; + for(; l != nil; l = tl l) + r = hd l :: r; + return r; +} + +setattrs(lv: list of ref Attr, rv: list of ref Attr): list of ref Attr +{ + # new attributes + nl: list of ref Attr; + for(rl := rv; rl != nil; rl = tl rl) + if(anyattr(lv, (hd rl).name) == nil) + nl = ref(*hd rl) :: nl; + + # new values + for(; lv != nil; lv = tl lv){ + a := lookattr(rv, (hd lv).name); # won't take queries + if(a != nil) + nl = ref *a :: nl; + } + + return reverse(nl); +} + +delattrs(lv: list of ref Attr, rv: list of ref Attr): list of ref Attr +{ + nl: list of ref Attr; + for(; lv != nil; lv = tl lv) + if(anyattr(rv, (hd lv).name) == nil) + nl = hd lv :: nl; + return reverse(nl); +} + +matchattr(attrs: list of ref Attr, pat: ref Attr): int +{ + return (b := lookattr(attrs, pat.name)) != nil && (pat.tag == Aquery || b.val == pat.val); +} + +matchattrs(pub: list of ref Attr, secret: list of ref Attr, pats: list of ref Attr): int +{ + for(pl := pats; pl != nil; pl = tl pl) + if(!matchattr(pub, hd pl) && !matchattr(secret, hd pl)) + return 0; + return 1; +} + +sortattrs(attrs: list of ref Attr): list of ref Attr +{ + a := array[len attrs] of ref Attr; + i := 0; + for(l := attrs; l != nil; l = tl l) + a[i++] = hd l; + shellsort(a); + for(i = 0; i < len a; i++) + l = a[i] :: l; + return l; +} + +# sort into decreasing order (we'll reverse the list) +shellsort(a: array of ref Attr) +{ + n := len a; + for(gap := n; gap > 0; ) { + gap /= 2; + max := n-gap; + ex: int; + do{ + ex = 0; + for(i := 0; i < max; i++) { + j := i+gap; + if(a[i].name > a[j].name || a[i].name == nil) { + t := a[i]; a[i] = a[j]; a[j] = t; + ex = 1; + } + } + }while(ex); + } +} + +findkey(keys: array of ref Key, attrs: list of ref Attr): ref Key +{ + if(Debug) + sys->print("findkey %q\n", attrtext(attrs)); + for(i := 0; i < len keys; i++) + if((k := keys[i]) != nil && matchattrs(k.attrs, k.secrets, attrs)) + return k; + return nil; +} + +delkey(keys: array of ref Key, attrs: list of ref Attr): int +{ + nk := 0; + for(i := 0; i < len keys; i++) + if((k := keys[i]) != nil) + if(matchattrs(k.attrs, k.secrets, attrs)){ + nk++; + keys[i] = nil; + } + return nk; +} + +Key.mk(attrs: list of ref Attr): ref Key +{ + k := ref Key; + for(; attrs != nil; attrs = tl attrs){ + a := hd attrs; + if(a.name != nil){ + if(a.name[0] == '!') + k.secrets = a :: k.secrets; + else + k.attrs = a :: k.attrs; + } + } + if(k.attrs != nil || k.secrets != nil) + return k; + return nil; +} + +addkey(keys: array of ref Key, k: ref Key): array of ref Key +{ + for(i := 0; i < len keys; i++) + if(keys[i] == nil){ + keys[i] = k; + return keys; + } + n := array[len keys+1] of ref Key; + n[0:] = keys; + n[len keys] = k; + return n; +} + +Key.text(k: self ref Key): string +{ + s := attrtext(k.attrs); + if(s != nil && k.secrets != nil) + s[len s] = ' '; + return s + attrtext(k.secrets); +} + +Key.safetext(k: self ref Key): string +{ + s := attrtext(sortattrs(k.attrs)); + sp := s != nil; + for(sl := k.secrets; sl != nil; sl = tl sl){ + if(sp) + s[len s] = ' '; + s += sys->sprint("%s?", (hd sl).name); + } + return s; +} + +any(s: string, t: string): int +{ + for(i := 0; i < len s; i++) + for(j := 0; j < len t; j++) + if(s[i] == t[j]) + return 1; + return 0; +} + +IO.findkey(nil: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string) +{ + ea := parseline(extra); + for(; ea != nil; ea = tl ea) + attrs = hd ea :: attrs; + kc := chan of (ref Key, string); + keymanc <-= (attrs, 1, kc); # TO DO: 1 => 0 for not needed + return <-kc; +} + +IO.needkey(nil: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string) +{ + ea := parseline(extra); + for(; ea != nil; ea = tl ea) + attrs = hd ea :: attrs; + kc := chan of (ref Key, string); + keymanc <-= (attrs, 1, kc); + return <-kc; +} + +IO.read(io: self ref IO): array of byte +{ + io.ok(); + while((rpc := rio(io.f)) != nil) + case rpc.cmd { + * => + phase(rpc, "protocol phase error"); + Oauthinfo => + reply(rpc, "error authentication unfinished"); + Owrite => + io.rpc = rpc; + if(rpc.arg == nil) + rpc.arg = array[0] of byte; + return rpc.arg; + } + exit; +} + +IO.readn(io: self ref IO, n: int): array of byte +{ + while((buf := io.read()) != nil && len buf < n) + io.toosmall(n); + return buf; +} + +IO.write(io: self ref IO, buf: array of byte, n: int): int +{ + io.ok(); + while((rpc := rio(io.f)) != nil) + case rpc.cmd { + Oread => + if(rpc.nbytes-3 >= n){ + okdata(rpc, buf[0:n]); + return n; + } + io.toosmall(n+3); + Oauthinfo => + reply(rpc, "error authentication unfinished"); + * => + phase(rpc, "protocol phase error"); + } + exit; +} + +IO.ok(io: self ref IO) +{ + if(io.rpc != nil){ + reply(io.rpc, "ok"); + io.rpc = nil; + } +} + +IO.toosmall(io: self ref IO, n: int) +{ + if(io.rpc != nil){ + reply(io.rpc, sys->sprint("toosmall %d", n)); + io.rpc = nil; + } +} + +IO.error(io: self ref IO, s: string) +{ + if(io.rpc != nil){ + io.rpc.rc <-= (nil, "error "+s); + io.rpc = nil; + } +} + +IO.done(io: self ref IO, ai: ref Authinfo) +{ + io.f.ai = ai; + io.ok(); + while((rpc := rio(io.f)) != nil) + case rpc.cmd { + Oread or Owrite => + done(rpc, ai); + return; + * => + phase(rpc, "protocol phase error"); + } +} + +memrandom(a: array of byte, n: int) +{ + if(0){ + # speed up testing + for(i := 0; i < len a; i++) + a[i] = byte i; + return; + } + fd := sys->open("/dev/notquiterandom", Sys->OREAD); + if(fd == nil) + err("can't open /dev/notquiterandom"); + if(sys->read(fd, a, n) != n) + err("can't read /dev/notquiterandom"); +} + +eqbytes(a, b: array of byte): int +{ + if(len a != len b) + return 0; + for(i := 0; i < len a; i++) + if(a[i] != b[i]) + return 0; + return 1; +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, nil) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} diff --git a/appl/cmd/auth/factotum/feedkey.b b/appl/cmd/auth/factotum/feedkey.b new file mode 100644 index 00000000..606f065a --- /dev/null +++ b/appl/cmd/auth/factotum/feedkey.b @@ -0,0 +1,321 @@ +implement Feedkey; + +# +# Copyright © 2004 Vita Nuova Holdings Limited +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "tk.m"; + tk: Tk; + +include "tkclient.m"; + tkclient: Tkclient; + +include "string.m"; + str: String; + +Feedkey: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +config := array[] of { + "frame .f", + "button .f.done -command {send cmd done} -text {Done}", + "frame .f.key -bg white", + "pack .f.key .f.done .f", + "update" +}; + +Debug: con 0; + +init(ctxt: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + tk = load Tk Tk->PATH; + tkclient = load Tkclient Tkclient->PATH; + str = load String String->PATH; + + needfile := "/mnt/factotum/needkey"; + if(Debug) + needfile = "/dev/null"; + + needs := chan of list of ref Attr; + acks := chan of int; + + sys->pctl(Sys->NEWPGRP|Sys->NEWFD, list of {0, 1, 2}); + + fd := sys->open(needfile, Sys->ORDWR); + if(fd == nil) + err(sys->sprint("can't open %s: %r", needfile)); + spawn needy(fd, needs, acks); + fd = nil; + + ctlfile := "/mnt/factotum/ctl"; + keyfd := sys->open(ctlfile, Sys->ORDWR); + if(keyfd == nil) + err(sys->sprint("can't open %s: %r", ctlfile)); + + tkclient->init(); + + spawn feedkey(ctxt, keyfd, needs, acks); +} + +feedkey(ctxt: ref Draw->Context, keyfd: ref Sys->FD, needs: chan of list of ref Attr, acks: chan of int) +{ + (top, tkctl) := tkclient->toplevel(ctxt, nil, "Need key", Tkclient->Appl); + + cmd := chan of string; + tk->namechan(top, cmd, "cmd"); + + for(i := 0; i < len config; i++) + tkcmd(top, config[i]); + tkclient->startinput(top, "ptr" :: nil); + tkclient->onscreen(top, nil); + if(!Debug) + tkclient->wmctl(top, "task"); + + attrs: list of ref Attr; + for(;;) alt{ + s :=<-tkctl or + s = <-top.ctxt.ctl or + s = <-top.wreq => + tkclient->wmctl(top, s); + p := <-top.ctxt.ptr => + tk->pointer(top, *p); + c := <-top.ctxt.kbd => + tk->keyboard(top, c); + + s := <-cmd => + case s { + "done" => + result := extract(top, ".f.key", attrs); + if(Debug) + sys->print("result: %s\n", attrtext(result)); + if(sys->fprint(keyfd, "key %s", attrtext(result)) < 0) + sys->fprint(sys->fildes(2), "feedkey: can't install key %q: %r\n", attrtext(result)); + acks <-= 0; + tkclient->wmctl(top, "task"); + tk->cmd(top, "pack forget .f.key"); + * => + sys->fprint(sys->fildes(2), "feedkey: odd command: %q\n", s); + } + + attrs = <-needs => + if(attrs == nil) + exit; + tkclient->startinput(top, "kbd" :: nil); + tkcmd(top, "destroy .f.key"); + tkcmd(top, "frame .f.key -bg white"); + populate(top, ".f.key", attrs); + tkcmd(top, "pack forget .f.done"); + tkcmd(top, "pack .f.key .f.done .f"); + tkcmd(top, "update"); + tkclient->wmctl(top, "unhide"); + } +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "feedkey: %s\n", s); + raise "fail:error"; +} + +user(): string +{ + fd := sys->open("/dev/user", Sys->OREAD); + if(fd == nil) + return nil; + b := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, b, len b); + if(n <= 0) + return nil; + return string b[0:n]; +} + +tkcmd(top: ref Tk->Toplevel, cmd: string): string +{ + if(0) + sys->print("tk: %q\n", cmd); + r := tk->cmd(top, cmd); + if(r != nil && r[0] == '!') + sys->fprint(sys->fildes(2), "feedkey: tk: %q on %q\n", r, cmd); + return r; +} + +populate(top: ref Tk->Toplevel, tag: string, attrs: list of ref Attr) +{ + c := 0; + for(al := attrs; al != nil; al = tl al){ + a := hd al; + if(a.name == nil) + tkcmd(top, sys->sprint("entry %s.n%d -bg yellow", tag, c)); + else + tkcmd(top, sys->sprint("label %s.n%d -bg white -text '%s", tag, c, a.name)); + tkcmd(top, sys->sprint("label %s.e%d -bg white -text ' = ", tag, c)); + case a.tag { + Aquery => + show := ""; + if(a.name != nil && a.name[0] == '!') + show = " -show {•}"; + tkcmd(top, sys->sprint("entry %s.v%d%s -bg yellow", tag, c, show)); + if(a.val == nil && a.name == "user") + a.val = user(); + tkcmd(top, sys->sprint("%s.v%d insert 0 '%s", tag, c, a.val)); + tkcmd(top, sys->sprint("grid %s.n%d %s.e%d %s.v%d -in %s -sticky w -pady 1", tag, c, tag, c, tag, c, tag)); + Aval => + if(a.name != nil){ + val := a.val; + if(a.name[0] == '!') + val = "..."; # just in case + tkcmd(top, sys->sprint("label %s.v%d -bg white -text %s", tag, c, val)); + }else + tkcmd(top, sys->sprint("entry %s.v%d -bg yellow", tag, c)); + tkcmd(top, sys->sprint("grid %s.n%d %s.e%d %s.v%d -in %s -sticky w -pady 1", tag, c, tag, c, tag, c, tag)); + Aattr => + tkcmd(top, sys->sprint("grid %s.n%d x x -in %s -sticky w -pady 1", tag, c, tag)); + } + c++; + } +} + +extract(top: ref Tk->Toplevel, tag: string, attrs: list of ref Attr): list of ref Attr +{ + c := 0; + nl: list of ref Attr; + for(al := attrs; al != nil; al = tl al){ + a := ref *hd al; + if(a.tag == Aquery){ + a.val = tkcmd(top, sys->sprint("%s.v%d get", tag, c)); + if(a.name == nil) + a.name = tk->cmd(top, sys->sprint("%s.n%d get", tag, c)); # name might start with `!' + if(a.name != nil){ + a.tag = Aval; + nl = a :: nl; + } + }else + nl = a :: nl; + c++; + } + return nl; +} + +reverse[T](l: list of T): list of T +{ + rl: list of T; + for(; l != nil; l = tl l) + rl = hd l :: rl; + return rl; +} + +needy(fd: ref Sys->FD, needs: chan of list of ref Attr, acks: chan of int) +{ + if(Debug){ + for(;;){ + needs <-= parseline("proto=pass user? server=fred.com service=ftp confirm !password?"); + <-acks; + } + } + + buf := array[512] of byte; + while((n := sys->read(fd, buf, len buf)) > 0){ + s := string buf[0:n]; + for(i := 0; i < len s; i++) + if(s[i] == ' ') + break; + if(i >= len s) + continue; + attrs := parseline(s[i+1:]); + nl: list of ref Attr; + tag: ref Attr; + for(; attrs != nil; attrs = tl attrs){ + a := hd attrs; + if(a.name == "tag") + tag = a; + else + nl = a :: nl; + } + if(nl == nil) + continue; + attrs = reverse(ref Attr(Aquery, nil, nil) :: ref Attr(Aquery, nil, nil) :: nl); # add a few blank + if(attrs != nil && tag != nil && tag.val != nil){ + needs <-= attrs; + <-acks; + sys->fprint(fd, "tag=%d", int tag.val); + } + } + if(n < 0) + sys->fprint(sys->fildes(2), "feedkey: error reading needkey: %r\n"); + needs <-= nil; +} + +# need a library module + +Aattr, Aval, Aquery: con iota; + +Attr: adt { + tag: int; + name: string; + val: string; + + text: fn(a: self ref Attr): string; +}; + +parseline(s: string): list of ref Attr +{ + fld := str->unquoted(s); + rfld := fld; + for(fld = nil; rfld != nil; rfld = tl rfld) + fld = (hd rfld) :: fld; + attrs: list of ref Attr; + for(; fld != nil; fld = tl fld){ + n := hd fld; + a := ""; + tag := Aattr; + for(i:=0; i<len n; i++) + if(n[i] == '='){ + a = n[i+1:]; + n = n[0:i]; + tag = Aval; + } + if(len n == 0) + continue; + if(tag == Aattr && len n > 1 && n[len n-1] == '?'){ + tag = Aquery; + n = n[0:len n-1]; + } + attrs = ref Attr(tag, n, a) :: attrs; + } + return attrs; +} + +Attr.text(a: self ref Attr): string +{ + case a.tag { + Aattr => + return a.name; + Aval => + return sys->sprint("%q=%q", a.name, a.val); + Aquery => + return a.name+"?"; + * => + return "??"; + } +} + +attrtext(attrs: list of ref Attr): string +{ + s := ""; + sp := 0; + for(; attrs != nil; attrs = tl attrs){ + if(sp) + s[len s] = ' '; + sp = 1; + s += (hd attrs).text(); + } + return s; +} diff --git a/appl/cmd/auth/factotum/mkfile b/appl/cmd/auth/factotum/mkfile new file mode 100644 index 00000000..1979a14c --- /dev/null +++ b/appl/cmd/auth/factotum/mkfile @@ -0,0 +1,27 @@ +<../../../../mkconfig + +DIRS=\ + proto\ + +TARG=\ + factotum.dis\ + feedkey.dis\ + rpc.dis\ + +SYSMODULES=\ + arg.m\ + keyring.m\ + security.m\ + rand.m\ + sys.m\ + draw.m\ + bufio.m\ + string.m\ + +MODULES=\ + authio.m\ + +DISBIN=$ROOT/dis/auth + +<$ROOT/mkfiles/mkdis +<$ROOT/mkfiles/mksubdirs diff --git a/appl/cmd/auth/factotum/proto/infauth.b b/appl/cmd/auth/factotum/proto/infauth.b new file mode 100644 index 00000000..244979bc --- /dev/null +++ b/appl/cmd/auth/factotum/proto/infauth.b @@ -0,0 +1,362 @@ +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, "missing your authentication data"); + x: string = e; + return "remote: "+x; + } + + { + while(string getmsg(io) != "OK") + ; + }exception e{ + Error0 => + return e; + Error1 => + x: string = e; + return "remote: "+x; + } + if(err != nil) + return err; + + return negotiatecrypto(io, key, ai, rattrs); +} + +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 = "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(); + + io.done(ai); + return nil; +} + +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] == '!'){ +sys->print("got remote error: %s, 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); +} + +keytoauthinfo(key:ref Key): (ref Keyring->Authinfo, string) +{ + if((s := authio->lookattrval(key.secrets, "!authinfo")) == nil){ + # XXX could look up authinfo by hash at this point + return (nil, "no authinfo attribute"); + } + + return strtoauthinfo(s); +} + +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); +} diff --git a/appl/cmd/auth/factotum/proto/keyreps.b b/appl/cmd/auth/factotum/proto/keyreps.b new file mode 100644 index 00000000..5fdac2c0 --- /dev/null +++ b/appl/cmd/auth/factotum/proto/keyreps.b @@ -0,0 +1,173 @@ +implement Keyreps; +include "sys.m"; + sys: Sys; +include "keyring.m"; + kr: Keyring; + IPint: import kr; +include "sexprs.m"; +include "spki.m"; +include "encoding.m"; + base64: Encoding; +include "keyreps.m"; + +init() +{ + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + base64 = load Encoding Encoding->BASE64PATH; +} + +keyextract(flds: list of string, names: list of (string, int)): list of (string, ref IPint) +{ + a := array[len flds] of ref IPint; + for(i := 0; i < len a; i++){ + a[i] = IPint.b64toip(hd flds); + flds = tl flds; + } + rl: list of (string, ref IPint); + for(; names != nil; names = tl names){ + (n, p) := hd names; + if(p < len a) + rl = (n, a[p]) :: rl; + } + return revt(rl); +} + +Keyrep.pk(pk: ref Keyring->PK): ref Keyrep.PK +{ + s := kr->pktostr(pk); + (nf, flds) := sys->tokenize(s, "\n"); + if((nf -= 2) < 0) + return nil; + case hd flds { + "rsa" => + return ref Keyrep.PK(hd flds, hd tl flds, + keyextract(tl tl flds, list of {("e",1), ("n",0)})); + "elgamal" or "dsa" => + return ref Keyrep.PK(hd flds, hd tl flds, + keyextract(tl tl flds, list of {("p",0), ("alpha",1), ("key",2)})); + * => + return nil; + } +} + +Keyrep.sk(pk: ref Keyring->SK): ref Keyrep.SK +{ + s := kr->pktostr(pk); + (nf, flds) := sys->tokenize(s, "\n"); + if((nf -= 2) < 0) + return nil; + case hd flds { + "rsa" => + return ref Keyrep.SK(hd flds, hd tl flds, + keyextract(tl tl flds,list of {("e",1), ("n",0), ("!dk",2), ("!p",3), ("!q",4), ("!kp",5), ("!kq",6), ("!c2",7)})); + "elgamal" or "dsa" => + return ref Keyrep.SK(hd flds, hd tl flds, + keyextract(tl tl flds, list of {("p",0), ("alpha",1), ("key",2), ("!secret",3)})); + * => + return nil; + } +} + +Keyrep.get(k: self ref Keyrep, n: string): ref IPint +{ + for(el := k.els; el != nil; el = tl el) + if((hd el).t0 == n) + return (hd el).t1; + return nil; +} + +Keyrep.getb(k: self ref Keyrep, n: string): array of byte +{ + v := k.get(n); + if(v == nil) + return nil; + return pre0(v.iptobebytes()); +} + +pre0(a: array of byte): array of byte +{ + for(i:=0; i<len a-1; i++) + if(a[i] != a[i+1] && (a[i] != byte 0 || (int a[i+1] & 16r80) != 0)) + break; + if(i > 0) + a = a[i:]; + if(len a < 1 || (int a[0] & 16r80) == 0) + return a; + b := array[len a + 1] of byte; + b[0] = byte 0; + b[1:] = a; + return b; +} + +Keyrep.mkpk(k: self ref Keyrep): (ref Keyring->PK, int) +{ + case k.alg { + "rsa" => + e := k.get("e"); + n := k.get("n"); + return (kr->strtopk(sys->sprint("rsa\n%s\n%s\n%s\n", k.owner, n.iptob64(), e.iptob64())), n.bits()); + * => + raise "Keyrep: unknown algorithm" + k.alg; + } +} + +Keyrep.mksk(k: self ref Keyrep): ref Keyring->SK +{ + case k.alg { + "rsa" => + e := k.get("e"); + n := k.get("n"); + dk := k.get("!dk"); + p := k.get("!p"); + q := k.get("!q"); + kp := k.get("!kp"); + kq := k.get("!kq"); + c12 := k.get("!c2"); + return kr->strtosk(sys->sprint("rsa\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n", + k.owner, n.iptob64(), e.iptob64(), dk.iptob64(), p.iptob64(), q.iptob64(), + kp.iptob64(), kq.iptob64(), c12.iptob64())); + * => + raise "Keyrep: unknown algorithm"; + } +} + +Keyrep.eq(k1: self ref Keyrep, k2: ref Keyrep): int +{ + # n but n is small + for(l1 := k1.els; l1 != nil; l1 = tl l1){ + (n, v1) := hd l1; + v2 := k2.get(n); + if(v2 == nil || !v1.eq(v2)) + return 0; + } + for(l2 := k2.els; l2 != nil; l2 = tl l2) + if(k1.get((hd l2).t0) == nil) + return 0; + return 1; +} + +Keyrep.mkkey(kr: self ref Keyrep): ref SPKI->Key +{ + k := ref SPKI->Key; + (k.pk, k.nbits) = kr.mkpk(); + k.sk = kr.mksk(); + return k; +} + +sig2icert(sig: ref SPKI->Signature, signer: string, exp: int): ref Keyring->Certificate +{ + if(sig.sig == nil) + return nil; + s := sys->sprint("%s\n%s\n%s\n%d\n%s\n", "rsa", sig.hash.alg, signer, exp, base64->enc((hd sig.sig).t1)); +#sys->print("alg %s *** %s\n", sig.sa, base64->enc((hd sig.sig).t1)); + return kr->strtocert(s); +} + +revt[S,T](l: list of (S,T)): list of (S,T) +{ + rl: list of (S,T); + for(; l != nil; l = tl l) + rl = hd l :: rl; + return rl; +} diff --git a/appl/cmd/auth/factotum/proto/keyreps.m b/appl/cmd/auth/factotum/proto/keyreps.m new file mode 100644 index 00000000..ddfd7f0d --- /dev/null +++ b/appl/cmd/auth/factotum/proto/keyreps.m @@ -0,0 +1,23 @@ +Keyreps: module +{ + PATH: con "/dis/lib/spki/keyreps.dis"; + init: fn(); + Keyrep: adt { + alg: string; + owner: string; + els: list of (string, ref Keyring->IPint); + pick{ # keeps a type distance between public and private keys + PK => + SK => + } + + pk: fn(pk: ref Keyring->PK): ref Keyrep.PK; + sk: fn(sk: ref Keyring->SK): ref Keyrep.SK; + mkpk: fn(k: self ref Keyrep): (ref Keyring->PK, int); + mksk: fn(k: self ref Keyrep): ref Keyring->SK; + get: fn(k: self ref Keyrep, n: string): ref Keyring->IPint; + getb: fn(k: self ref Keyrep, n: string): array of byte; + eq: fn(k1: self ref Keyrep, k2: ref Keyrep): int; + mkkey: fn(k: self ref Keyrep): ref SPKI->Key; + }; +}; diff --git a/appl/cmd/auth/factotum/proto/mkfile b/appl/cmd/auth/factotum/proto/mkfile new file mode 100644 index 00000000..efdd73da --- /dev/null +++ b/appl/cmd/auth/factotum/proto/mkfile @@ -0,0 +1,22 @@ +<../../../../../mkconfig + +TARG=\ + p9any.dis\ + pass.dis\ + +SYSMODULES=\ + factotum.m\ + keyring.m\ + security.m\ + rand.m\ + sys.m\ + draw.m\ + bufio.m\ + string.m\ + +MODULES=\ + ../authio.m\ + +DISBIN=$ROOT/dis/auth/proto + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/auth/factotum/proto/p9any.b b/appl/cmd/auth/factotum/proto/p9any.b new file mode 100644 index 00000000..1668a701 --- /dev/null +++ b/appl/cmd/auth/factotum/proto/p9any.b @@ -0,0 +1,232 @@ +implement Authproto; + +# currently includes p9sk1 + +include "sys.m"; + sys: Sys; + Rread, Rwrite: import Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + +include "auth9.m"; + auth9: Auth9; + ANAMELEN, AERRLEN, DOMLEN, DESKEYLEN, CHALLEN, SECRETLEN: import Auth9; + TICKREQLEN, TICKETLEN, AUTHENTLEN: import Auth9; + Ticketreq, Ticket, Authenticator: import auth9; + +include "../authio.m"; + authio: Authio; + Aattr, Aval, Aquery: import Authio; + Attr, IO, Key, Authinfo: import authio; + netmkaddr, eqbytes, memrandom: import authio; + +include "encoding.m"; + base16: Encoding; + +Debug: con 0; + +# init, addkey, closekey, write, read, close, keyprompt + +init(f: Authio): string +{ + authio = f; + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + auth9 = load Auth9 Auth9->PATH; + auth9->init(); + base16 = load Encoding Encoding->BASE16PATH; + return nil; +} + +version := 1; + +interaction(attrs: list of ref Attr, io: ref IO): string +{ + return p9any(io); +} + +p9any(io: ref IO): string +{ + while((buf := io.read()) == nil || (n := len buf) == 0 || buf[n-1] != byte 0) + io.toosmall(2048); + s := string buf[0:n-1]; + if(Debug) + sys->print("s: %q\n", s); + (nil, flds) := sys->tokenize(s, " \t"); + if(flds != nil && len hd flds >= 2 && (hd flds)[0:2] == "v."){ + if(hd flds == "v.2"){ + version = 2; + flds = tl flds; + if(Debug) + sys->print("version 2\n"); + }else + return "p9any: unknown version"; + } + doms: list of string; + for(; flds != nil; flds = tl flds){ + (nf, subf) := sys->tokenize(hd flds, "@"); + if(nf == 2 && hd subf == "p9sk1") + doms = hd tl subf :: doms; + } + if(doms == nil) + return "p9any: unsupported protocol"; + if(Debug){ + for(l := doms; l != nil; l = tl l) + sys->print("dom: %q\n", hd l); + } + r := array of byte ("p9sk1 "+hd doms); + buf[0:] = r; + buf[len r] = byte 0; + io.write(buf, len r + 1); + if(version == 2){ + b := io.readn(3); + if(b == nil || b[0] != byte 'O' || b[1] != byte 'K' || b[2] != byte 0) + return "p9any: AS protocol botch: not OK"; + if(Debug) + sys->print("OK\n"); + } + return p9sk1client(io, hd doms); +} + +#p9sk1: +# C->S: nonce-C +# S->C: nonce-S, uid-S, domain-S +# C->A: nonce-S, uid-S, domain-S, uid-C, factotum-C +# A->C: Kc{nonce-S, uid-C, uid-S, Kn}, Ks{nonce-S, uid-C, uid-S, K-n} +# C->S: Ks{nonce-S, uid-C, uid-S, K-n}, Kn{nonce-S, counter} +# S->C: Kn{nonce-C, counter} + +#asserts that uid-S and uid-C share new secret Kn +#increment the counter to reuse the ticket. + +p9sk1client(io: ref IO, udom: string): string +{ + + # C->S: nonce-C + cchal := array[CHALLEN] of byte; + memrandom(cchal, CHALLEN); + if(io.write(cchal, len cchal) != len cchal) + return sys->sprint("p9sk1: can't write cchal: %r"); + + # S->C: nonce-S, uid-S, domain-S + trbuf := io.readn(TICKREQLEN); + if(trbuf == nil) + return sys->sprint("p9sk1: can't read ticketreq: %r"); + + (nil, tr) := Ticketreq.unpack(trbuf); + if(tr == nil) + return "p9sk1: can't unpack ticket request"; + if(Debug) + sys->print("ticketreq: type=%d authid=%q authdom=%q chal= hostid=%q uid=%q\n", + tr.rtype, tr.authid, tr.authdom, tr.hostid, tr.uid); + + (mykey, diag) := io.findkey(nil, sys->sprint("dom=%q proto=p9sk1 user? !password?", udom)); + if(mykey == nil) + return "can't find key: "+diag; + ukey: array of byte; + if((a := authio->lookattrval(mykey.secrets, "!hex")) != nil){ + ukey = base16->dec(a); + if(len ukey != DESKEYLEN) + return "p9sk1: invalid !hex key"; + }else if((a = authio->lookattrval(mykey.secrets, "!password")) != nil) + ukey = auth9->passtokey(a); + else + return "no !password (or !hex) in key"; + + # A->C: Kc{nonce-S, uid-C, uid-S, Kn}, Ks{nonce-S, uid-C, uid-S, K-n} + user := authio->lookattrval(mykey.attrs, "user"); + if(user == nil) + user = authio->user(); # shouldn't happen + tr.rtype = Auth9->AuthTreq; + tr.hostid = user; + tr.uid = tr.hostid; # not speaking for anyone else + (tick, serverbits) := getastickets(tr, ukey); + if(tick == nil) + return sys->sprint("p9sk1: getasticket failed: %r"); + if(tick.num != Auth9->AuthTc) + return "p9sk1: getasticket: failed: wrong key?"; + if(Debug) + sys->print("ticket: num=%d chal= cuid=%q suid=%q key=\n", tick.num, tick.cuid, tick.suid); + + # C->S: Ks{nonce-S, uid-C, uid-S, K-n}, Kn{nonce-S, counter} + ar := ref Authenticator; + ar.num = Auth9->AuthAc; + ar.chal = tick.chal; + ar.id = 0; + obuf := array[TICKETLEN+AUTHENTLEN] of byte; + obuf[0:] = serverbits; + obuf[TICKETLEN:] = ar.pack(tick.key); + if(io.write(obuf, len obuf) != len obuf) + return "p9sk1: error writing authenticator: %r"; + + # S->C: Kn{nonce-C, counter} + sbuf := io.readn(AUTHENTLEN); + if(sbuf == nil) + return sys->sprint("p9sk1: can't read server's authenticator: %r"); + (nil, ar) = Authenticator.unpack(sbuf, tick.key); + if(ar.num != Auth9->AuthAs || !eqbytes(ar.chal, cchal) || ar.id != 0) + return "invalid authenticator from server"; + + ai := ref Authinfo(tick.cuid, tick.suid, nil, auth9->des56to64(tick.key)); + io.done(ai); + + return nil; +} + +getastickets(tr: ref Ticketreq, key: array of byte): (ref Ticket, array of byte) +{ + afd := authdial(nil, tr.authdom); + if(afd == nil) + return (nil, nil); + return auth9->_asgetticket(afd, tr, key); +} + +# +# where to put the following functions? +# + +csgetvalue(netroot: string, keytag: string, keyval: string, needtag: string): string +{ + cs := "/net/cs"; + if(netroot != nil) + cs = netroot+"/cs"; + fd := sys->open(cs, Sys->ORDWR); # TO DO: choice of root + if(fd == nil) + return nil; + if(sys->fprint(fd, "!%s=%s %s=*", keytag, keyval, needtag) < 0) + return nil; + sys->seek(fd, big 0, 0); + buf := array[1024] of byte; + while((n := sys->read(fd, buf, len buf)) > 0){ + al := authio->parseline(string buf[0:n]); # assume the conventions match factotum's + for(; al != nil; al = tl al) + if((hd al).name == needtag) + return (hd al).val; + } + return nil; +} + +authdial(netroot: string, dom: string): ref Sys->FD +{ + p: string; + if(dom != nil){ + # look up an auth server in an authentication domain + p = csgetvalue(netroot, "authdom", dom, "auth"); + + # if that didn't work, just try the IP domain + if(p == nil) + p = csgetvalue(netroot, "dom", dom, "auth"); + if(p == nil) + p = "$auth"; # temporary ... + if(p == nil){ + sys->werrstr("no auth server found for "+dom); + return nil; + } + }else + p = "$auth"; # look for one relative to my machine + (nil, conn) := sys->dial(netmkaddr(p, netroot, "ticket"), nil); + return conn.dfd; +} diff --git a/appl/cmd/auth/factotum/proto/pass.b b/appl/cmd/auth/factotum/proto/pass.b new file mode 100644 index 00000000..9c4462b3 --- /dev/null +++ b/appl/cmd/auth/factotum/proto/pass.b @@ -0,0 +1,29 @@ +implement Authproto; + +include "sys.m"; + sys: Sys; + +include "../authio.m"; + authio: Authio; + Attr, IO: import authio; + +init(f: Authio): string +{ + sys = load Sys Sys->PATH; + authio = f; + return nil; +} + +interaction(attrs: list of ref Attr, io: ref Authio->IO): string +{ + (key, err) := io.findkey(attrs, "user? !password?"); + if(key == nil) + return err; + user := authio->lookattrval(key.attrs, "user"); + if(user == nil) + return "unknown user"; + pass := authio->lookattrval(key.secrets, "!password"); + a := sys->aprint("%q %q", user, pass); + io.write(a, len a); + return nil; +} diff --git a/appl/cmd/auth/factotum/rpc.b b/appl/cmd/auth/factotum/rpc.b new file mode 100644 index 00000000..220980a8 --- /dev/null +++ b/appl/cmd/auth/factotum/rpc.b @@ -0,0 +1,68 @@ +implement Rpcio; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "arg.m"; + +Rpcio: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: rpc\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + cantload(Bufio->PATH); + + file := "/mnt/factotum/rpc"; + if(len args > 1) + file = hd tl args; + rfd := sys->open(file, Sys->ORDWR); + if(rfd == nil){ + sys->fprint(sys->fildes(2), "rpc: can't open %s: %r\n", file); + raise "fail:load"; + } + f := bufio->fopen(sys->fildes(0), Sys->OREAD); + for(;;){ + sys->print("> "); + s := f.gets('\n'); + if(s == nil) + break; + rpc(rfd, s[0:len s-1]); + } +} + +cantload(s: string) +{ + sys->fprint(sys->fildes(2), "csquery: can't load %s: %r\n", s); + raise "fail:load"; +} + +rpc(f: ref Sys->FD, addr: string) +{ + b := array of byte addr; + if(sys->write(f, b, len b) > 0){ + sys->seek(f, big 0, Sys->SEEKSTART); + buf := array[256] of byte; + if((n := sys->read(f, buf, len buf)) > 0) + sys->print("%s\n", string buf[0:n]); + if(n >= 0) + return; + } + sys->print("!%r\n"); +} diff --git a/appl/cmd/auth/getpk.b b/appl/cmd/auth/getpk.b new file mode 100644 index 00000000..24283340 --- /dev/null +++ b/appl/cmd/auth/getpk.b @@ -0,0 +1,83 @@ +implement Getpk; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "arg.m"; +include "keyring.m"; + keyring: Keyring; + +Getpk: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +badmodule(p: string) +{ + sys->fprint(sys->fildes(2), "getpk: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + keyring = load Keyring Keyring->PATH; + if(keyring == nil) + badmodule(Keyring->PATH); + arg := load Arg Arg->PATH; + if(arg == nil) + badmodule(Arg->PATH); + arg->init(argv); + arg->setusage("usage: getpk [-asu] file..."); + aflag := 0; + sflag := 0; + uflag := 0; + while((opt := arg->opt()) != 0){ + case opt { + 's' => + sflag++; + 'a' => + aflag++; + 'u' => + uflag++; + * => + arg->usage(); + } + } + argv = arg->argv(); + if(argv == nil) + arg->usage(); + multi := len argv > 1; + for(; argv != nil; argv = tl argv){ + info := keyring->readauthinfo(hd argv); + if(info == nil){ + sys->fprint(sys->fildes(2), "getpk: cannot read %s: %r\n", hd argv); + continue; + } + pk := info.mypk; + if(sflag) + pk = info.spk; + s := keyring->pktostr(pk); + if(!aflag) + s = hex(hash(s)); + if(multi) + s = hd argv + ": " + s; + if(uflag) + s += " " + pk.owner; + sys->print("%s\n", s); + } +} + +hash(s: string): array of byte +{ + d := array of byte s; + digest := array[Keyring->SHA1dlen] of byte; + keyring->sha1(d, len d, digest, nil); + return digest; +} + +hex(a: array of byte): string +{ + s := ""; + for(i := 0; i < len a; i++) + s += sys->sprint("%2.2ux", int a[i]); + return s; +} diff --git a/appl/cmd/auth/keyfs.b b/appl/cmd/auth/keyfs.b new file mode 100644 index 00000000..f81c3ee7 --- /dev/null +++ b/appl/cmd/auth/keyfs.b @@ -0,0 +1,806 @@ +implement Keyfs; + +# +# Copyright © 2002,2003 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + Qid: import Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + AESbsize, AESstate: import kr; + +include "rand.m"; + rand: Rand; + +include "styx.m"; + styx: Styx; + Tmsg, Rmsg: import styx; + +include "styxservers.m"; + styxservers: Styxservers; + Fid, Styxserver, Navigator, Navop: import styxservers; + Enotfound, Eperm, Ebadarg, Edot: import styxservers; + +include "arg.m"; + +Keyfs: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +User: adt +{ + x: int; # table index + name: string; + secret: array of byte; # eg, password hashed by SHA1 + expire: int; # expiration time (epoch seconds) + status: int; + failed: int; # count of failed attempts + path: big; +}; + +Qroot, Quser, Qsecret, Qlog, Qstatus, Qexpire: con iota; +files := array[] of { + (Qsecret, "secret"), + (Qlog, "log"), + (Qstatus, "status"), + (Qexpire, "expire") +}; + +Maxsecret: con 255; +Maxname: con 255; +Maxfail: con 50; +users: array of ref User; +Sok, Sdisabled: con iota; +status := array[] of {Sok => "ok", Sdisabled => "disabled" }; +Never: con 0; # expiry time + +Eremoved: con "user has been removed"; + +pathgen := 0; +keyversion := 0; +user: string; +now: int; + +usage() +{ + sys->fprint(sys->fildes(2), "Usage: keyfs [-D] [-m mountpoint] [keyfile]\n"); + raise "fail:usage"; +} + +nomod(s: string) +{ + sys->fprint(sys->fildes(2), "keyfs: can't load %s: %r\n", s); + raise "fail:load"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + sys->pctl(Sys->NEWPGRP, nil); + kr = load Keyring Keyring->PATH; + if(kr == nil) + nomod(Keyring->PATH); + styx = load Styx Styx->PATH; + if(styx == nil) + nomod(Styx->PATH); + styxservers = load Styxservers Styxservers->PATH; + if(styxservers == nil) + nomod(Styxservers->PATH); + rand = load Rand Rand->PATH; + if(rand == nil) + nomod(Rand->PATH); + + styx->init(); + styxservers->init(styx); + rand->init(sys->millisec()); + + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + arg->init(args); + arg->setusage("keyfs [-m mntpt] [-D] [-n nvramfile] [keyfile]"); + mountpt := "/mnt/keys"; + keyfile := "/keydb/keys"; + nvram: string; + while((o := arg->opt()) != 0) + case o { + 'm' => + mountpt = arg->earg(); + 'D' => + styxservers->traceset(1); + 'n' => + nvram = arg->earg(); + * => + usage(); + } + args = arg->argv(); + arg = nil; + + if(args != nil) + keyfile = hd args; + + pwd, err: string; + if(nvram != nil){ + pwd = rf(nvram); + if(pwd == nil) + error(sys->sprint("can't read %s: %r", nvram)); + } + if(pwd == nil){ + (pwd, err) = readconsline("Key: ", 1); + if(pwd == nil || err == "exit") + exit; + if(err != nil) + error(sys->sprint("couldn't get key: %s", err)); + (rc, d) := sys->stat(keyfile); + if(rc == -1 || d.length == big 0){ + pwd0 := pwd; + (pwd, err) = readconsline("Confirm key: ", 1); + if(pwd == nil || err == "exit") + exit; + if(pwd != pwd0) + error("key mismatch"); + for(i := 0; i < len pwd0; i++) + pwd0[i] = ' '; # clear it out + } + } + + thekey = hashkey(pwd); + for(i:=0; i<len pwd; i++) + pwd[i] = ' '; # clear it out + + sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil); # immediately avoid sharing keyfd + + readkeys(keyfile); + + user = rf("/dev/user"); + if(user == nil) + user = "keyfs"; + + fds := array[2] of ref Sys->FD; + if(sys->pipe(fds) < 0) + error(sys->sprint("can't create pipe: %r")); + + navops := chan of ref Navop; + spawn navigator(navops); + + (tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big Qroot); + fds[0] = nil; + + pidc := chan of int; + spawn serveloop(tchan, srv, pidc, navops, keyfile); + <-pidc; + + if(sys->mount(fds[1], nil, mountpt, Sys->MREPL|Sys->MCREATE, nil) < 0) + error(sys->sprint("mount on %s failed: %r", mountpt)); +} + +rf(f: string): string +{ + fd := sys->open(f, Sys->OREAD); + if(fd == nil) + return nil; + b := array[256] of byte; + n := sys->read(fd, b, len b); + if(n < 0) + return nil; + return string b[0:n]; +} + +quit(err: string) +{ + fd := sys->open("/prog/"+string sys->pctl(0, nil)+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "killgrp"); + if(err != nil) + raise "fail:"+err; + exit; +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "keyfs: %s\n", s); + quit("error"); +} + +thekey: array of byte; + +hashkey(s: string): array of byte +{ + key := array of byte s; + skey := array[Keyring->SHA1dlen] of byte; + sha := kr->sha1(array of byte "aescbc file", 11, nil, nil); + kr->sha1(key, len key, skey, sha); + for(i:=0; i<len key; i++) + key[i] = byte 0; # clear it out +#{sys->print("HEX="); for(i:=0;i<len skey&&i<AESbsize; i++)sys->print("%.2ux", int skey[i]);sys->print("\n");} + return skey[0:AESbsize]; +} + +readconsline(prompt: string, raw: int): (string, string) +{ + fd := sys->open("/dev/cons", Sys->ORDWR); + if(fd == nil) + return (nil, sys->sprint("can't open cons: %r")); + sys->fprint(fd, "%s", prompt); + fdctl: ref Sys->FD; + if(raw){ + fdctl = sys->open("/dev/consctl", sys->OWRITE); + if(fdctl == nil || sys->fprint(fdctl, "rawon") < 0) + return (nil, sys->sprint("can't open consctl: %r")); + } + line := array[256] of byte; + o := 0; + err: string; + buf := array[1] of byte; + Read: + while((r := sys->read(fd, buf, len buf)) > 0){ + c := int buf[0]; + case c { + 16r7F => + err = "interrupt"; + break Read; + '\b' => + if(o > 0) + o--; + '\n' or '\r' or 16r4 => + break Read; + * => + if(o > len line){ + err = "line too long"; + break Read; + } + line[o++] = byte c; + } + } + sys->fprint(fd, "\n"); + if(r < 0) + err = sys->sprint("can't read cons: %r"); + if(raw) + sys->fprint(fdctl, "rawoff"); + if(err != nil) + return (nil, err); + return (string line[0:o], err); +} + +serveloop(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop, keyfile: string) +{ + pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, 1::2::srv.fd.fd::nil); + while((gm := <-tchan) != nil){ + now = time(); + pick m := gm { + Readerror => + error(sys->sprint("mount read error: %s", m.error)); + Create => + (c, mode, nil, err) := srv.cancreate(m); + if(c == nil){ + srv.reply(ref Rmsg.Error(m.tag, err)); + break; + } + case TYPE(c.path) { # parent + Qroot => + if((m.perm & Sys->DMDIR) == 0){ + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + break; + } + u := findusername(m.name); + if(u != nil){ + srv.reply(ref Rmsg.Error(m.tag, "user already exists")); + continue; + } + if(len m.name > Maxname){ + srv.reply(ref Rmsg.Error(m.tag, "user name too long")); + continue; + } + u = newuser(m.name, nil); + qid := Qid((u.path | big Quser), 0, Sys->QTDIR); + c.open(mode, qid); + writekeys(keyfile); + srv.reply(ref Rmsg.Create(m.tag, qid, srv.iounit())); + * => + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + break; + } + Read => + (c, err) := srv.canread(m); + if(c == nil){ + srv.reply(ref Rmsg.Error(m.tag, err)); + break; + } + if(c.qtype & Sys->QTDIR){ + srv.read(m); # does readdir + break; + } + u := finduserpath(c.path); + if(u == nil){ + srv.reply(ref Rmsg.Error(m.tag, Eremoved)); + break; + } + case TYPE(c.path) { + Qsecret => + if(u.status != Sok){ + srv.reply(ref Rmsg.Error(m.tag, "user disabled")); + break; + } + if(u.expire < now && u.expire != Never){ + srv.reply(ref Rmsg.Error(m.tag, "user expired")); + break; + } + srv.reply(styxservers->readbytes(m, u.secret)); + Qlog => + srv.reply(styxservers->readstr(m, sys->sprint("%d", u.failed))); + Qstatus => + s := status[u.status]; + if(u.status == Sok && u.expire != Never && u.expire < now) + s = "expired"; + srv.reply(styxservers->readstr(m, s)); + Qexpire => + s: string; + if(u.expire != Never) + s = sys->sprint("%ud", u.expire); + else + s = "never"; + srv.reply(styxservers->readstr(m, s)); + * => + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + } + Write => + (c, merr) := srv.canwrite(m); + if(c == nil){ + srv.reply(ref Rmsg.Error(m.tag, merr)); + break; + } + u := finduserpath(c.path); + if(u == nil){ + srv.reply(ref Rmsg.Error(m.tag, Eremoved)); + break; + } + Case: + case TYPE(c.path) { + Qsecret => + if(m.offset != big 0 || len m.data > Maxsecret){ + srv.reply(ref Rmsg.Error(m.tag, "illegal write")); + break; + } + u.secret = m.data; + writekeys(keyfile); + srv.reply(ref Rmsg.Write(m.tag, len m.data)); + Qexpire => + s := trim(string m.data); + if(s != "never"){ + if(!isnumeric(s)){ + srv.reply(ref Rmsg.Error(m.tag, "illegal expiry time")); + break; + } + u.expire = int s; + }else + u.expire = Never; + u.failed = 0; + writekeys(keyfile); + srv.reply(ref Rmsg.Write(m.tag, len m.data)); + Qstatus => + s := trim(string m.data); + for(i := 0; i < len status; i++) + if(s == status[i]){ + u.status = i; + if(i == Sok) + u.failed = 0; + writekeys(keyfile); + srv.reply(ref Rmsg.Write(m.tag, len m.data)); + break Case; + } + srv.reply(ref Rmsg.Error(m.tag, "unknown status")); + Qlog => + s := trim(string m.data); + if(s != "good" && s != "ok"){ + if(++u.failed >= Maxfail) + u.status = Sdisabled; + }else + u.failed = 0; + writekeys(keyfile); + srv.reply(ref Rmsg.Write(m.tag, len m.data)); + * => + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + } + Remove => + c := srv.getfid(m.fid); + if(c == nil){ + srv.remove(m); # let it diagnose the errors + break; + } + case TYPE(c.path) { + Quser => + u := finduserpath(c.path); + if(u == nil){ + srv.reply(ref Rmsg.Error(m.tag, Eremoved)); + break; + } + removeuser(u); + writekeys(keyfile); + srv.delfid(c); + srv.reply(ref Rmsg.Remove(m.tag)); + Qsecret => + u := finduserpath(c.path); + if(u == nil){ + srv.reply(ref Rmsg.Error(m.tag, Eremoved)); + break; + } + u.secret = nil; + writekeys(keyfile); + srv.delfid(c); + srv.reply(ref Rmsg.Remove(m.tag)); + * => + srv.remove(m); # let it reject it + } + Wstat => + # rename user + c := srv.getfid(m.fid); + if(c == nil || TYPE(c.path) != Quser){ + srv.default(gm); # let it reject it + break; + } + u := finduserpath(c.path); + if(u == nil){ + srv.reply(ref Rmsg.Error(m.tag, Eremoved)); + break; + } + if((new := m.stat.name) == nil){ + srv.default(gm); + break; + } + if(new == "." || new == ".."){ + srv.reply(ref Rmsg.Error(m.tag, Edot)); + break; + } + if(findusername(new) != nil){ + srv.reply(ref Rmsg.Error(m.tag, "user already exists")); + break; + } + # unhashuser(u); + u.name = new; + # hashuser(u); + writekeys(keyfile); + srv.reply(ref Rmsg.Wstat(m.tag)); + * => + srv.default(gm); + } + } + navops <-= nil; # shut down navigator +} + +trim(s: string): string +{ + (nf, flds) := sys->tokenize(s, " \t\n"); + if(nf == 0) + return nil; + return hd flds; +} + +isnumeric(s: string): int +{ + for(i:=0; i<len s; i++) + if(!(s[i]>='0' && s[i]<='9')) + return 0; + return i>0; +} + +TYPE(path: big): int +{ + return int path & 16rF; +} + +INDEX(path: big): int +{ + return (int path & 16rFFFF) >> 4; +} + +finduserpath(path: big): ref User +{ + i := INDEX(path); + if(i >= len users || (u := users[i]) == nil || u.path != (path & ~big 16rF)) + return nil; + return u; +} + +findusername(name: string): ref User +{ + for(i := 0; i < len users; i++) + if((u := users[i]) != nil && u.name == name) + return u; + return nil; +} + +newuser(name: string, u: ref User): ref User +{ + for(i := 0; i < len users; i++) + if(users[i] == nil) + break; + if(i >= len users) + users = (array[i+16] of ref User)[0:] = users; + path := big ((pathgen++ << 16) | (i<<4)); + if(u == nil) + u = ref User(i, name, nil, Never, Sok, 0, path); + else{ + u.x = i; + u.path = path; + } + users[i] = u; + return u; +} + +removeuser(u: ref User) +{ + if(u != nil) + users[u.x] = nil; +} + +dirslot(n: int): int +{ + for(i := 0; i < len users; i++){ + u := users[i]; + if(u != nil){ + if(n == 0) + break; + n--; + } + } + return i; +} + +dir(qid: Sys->Qid, name: string, length: big, perm: int): ref Sys->Dir +{ + d := ref sys->zerodir; + d.qid = qid; + if(qid.qtype & Sys->QTDIR) + perm |= Sys->DMDIR; + d.mode = perm; + d.name = name; + d.uid = user; + d.gid = user; + d.length = length; + d.atime = now; + d.mtime = now; + return d; +} + +dirgen(p: big, name: string, u: ref User): (ref Sys->Dir, string) +{ + case t := TYPE(p) { + Qroot => + return (dir(Qid(big Qroot, keyversion,Sys->QTDIR), "/", big 0, 8r755), nil); + Quser => + if(name == nil){ + if(u == nil){ + u = finduserpath(p); + if(u == nil) + return (nil, Enotfound); + } + name = u.name; + } + return (dir(Qid(p,0,Sys->QTDIR), name, big 0, 8r500), nil); # note: unwritable + * => + l := 0; + if(t == Qsecret){ + if(u == nil) + u = finduserpath(p); + if(u != nil) + l = len u.secret; + } + return (dir(Qid(p,0,Sys->QTFILE), name, big l, 8r600), nil); + } +} + +navigator(navops: chan of ref Navop) +{ + while((m := <-navops) != nil){ + Pick: + pick n := m { + Stat => + n.reply <-= dirgen(n.path, nil, nil); + Walk => + case TYPE(n.path) { + Qroot => + if(n.name == ".."){ + n.reply <-= dirgen(n.path, nil, nil); + break; + } + u := findusername(n.name); + if(u == nil){ + n.reply <-= (nil, Enotfound); + break; + } + n.reply <-= dirgen(u.path | big Quser, u.name, u); + Quser => + if(n.name == ".."){ + n.reply <-= dirgen(big Qroot, nil, nil); + break; + } + for(j := 0; j < len files; j++){ + (ftype, name) := files[j]; + if(n.name == name){ + n.reply <-= dirgen((n.path & ~big 16rF) | big ftype, name, nil); + break Pick; + } + } + n.reply <-= (nil, Enotfound); + * => + if(n.name != ".."){ + n.reply <-= (nil, Enotfound); + break; + } + n.reply <-= dirgen((n.path & ~big 16rF) | big Quser, nil, nil); # parent directory + } + Readdir => + case TYPE(n.path) { + Qroot => + for(j := dirslot(n.offset); --n.count >= 0 && j < len users; j++) + if((u := users[j]) != nil) + n.reply <-= dirgen(u.path | big Quser, u.name, u); + n.reply <-= (nil, nil); + Quser => + u := finduserpath(n.path); + if(u == nil){ + n.reply <-= (nil, Eremoved); + break; + } + for(j := n.offset; --n.count >= 0 && j < len files; j++){ + (ftype, name) := files[j]; + n.reply <-= dirgen((n.path & ~big 16rF)|big ftype, name, u); + } + n.reply <-= (nil, nil); + } + } + } +} + +timefd: ref Sys->FD; + +time(): int +{ + if(timefd == nil){ + timefd = sys->open("/dev/time", Sys->OREAD); + if(timefd == nil) + return 0; + } + buf := array[128] of byte; + sys->seek(timefd, big 0, 0); + n := sys->read(timefd, buf, len buf); + if(n < 0) + return 0; + t := (big string buf[0:n]) / big 1000000; + return int t; +} + +Checkpat: con "XXXXXXXXXXXXXXXX"; # it's what Plan 9's aescbc uses +Checklen: con len Checkpat; + +Hdrlen: con 1+1+4; + +packedsize(u: ref User): int +{ + return Hdrlen+(1+len array of byte u.name)+(1+len u.secret); +} + +pack(u: ref User): array of byte +{ + a := array[packedsize(u)] of byte; + a[0] = byte u.status; + a[1] = byte u.failed; + a[2] = byte u.expire; + a[3] = byte (u.expire>>8); + a[4] = byte (u.expire>>16); + a[5] = byte (u.expire>>24); + bn := array of byte u.name; + n := len bn; + if(n > 255) + error(sys->sprint("overlong user name: %s", u.name)); # shouldn't happen + a[6] = byte n; + a[7:] = bn; + n += 7; + a[n] = byte len u.secret; + a[n+1:] = u.secret; + return a; +} + +unpack(a: array of byte): (ref User, int) +{ + if(len a < Hdrlen+2) + return (nil, 0); + u := ref User; + u.status = int a[0]; + u.failed = int a[1]; + u.expire = (int a[5] << 24) | (int a[4] << 16) | (int a[3] << 8) | int a[2]; + n := int a[6]; + j := 7+n; + if(j > len a) + return (nil, 0); + u.name = string a[7:j]; + if(j >= len a) + return (nil, 0); + n = int a[j++]; + if(j+n > len a) + return (nil, 0); + if(n > 0){ + u.secret = array[n] of byte; + u.secret[0:] = a[j:j+n]; + } + return (u, j+n); +} + +corrupt(keyfile: string) +{ + error(sys->sprint("%s: incorrect key or corrupt/damaged keyfile", keyfile)); +} + +readkeys(keyfile: string) +{ + fd := sys->open(keyfile, Sys->OREAD); + if(fd == nil) + error(sys->sprint("can't open %s: %r", keyfile)); + (rc, d) := sys->fstat(fd); + if(rc < 0) + error(sys->sprint("can't get status of %s: %r", keyfile)); + length := int d.length; + if(length == 0) + return; + if(length < AESbsize+Checklen) + corrupt(keyfile); + buf := array[length] of byte; + if(sys->read(fd, buf, len buf) != len buf) + error(sys->sprint("can't read %s: %r", keyfile)); + state := kr->aessetup(thekey, buf[0:AESbsize]); + if(state == nil) + error("can't initialise AES"); + kr->aescbc(state, buf[AESbsize:], length-AESbsize, Keyring->Decrypt); + if(string buf[length-Checklen:] != Checkpat) + corrupt(keyfile); + length -= Checklen; + for(i := AESbsize; i < length;){ + (u, n) := unpack(buf[i:]); + if(u == nil) + corrupt(keyfile); + newuser(u.name, u); + i += n; + } +} + +writekeys(keyfile: string) +{ + length := 0; + for(i := 0; i < len users; i++) + if((u := users[i]) != nil) + length += packedsize(u); + if(length == 0){ + # leave it empty for clarity + fd := sys->create(keyfile, Sys->OWRITE, 8r600); + if(fd == nil) + error(sys->sprint("can't create %s: %r", keyfile)); + return; + } + length += AESbsize+Checklen; + buf := array[length] of byte; + for(i=0; i<AESbsize; i++) + buf[i] = byte rand->rand(256); + j := AESbsize; + for(i = 0; i < len users; i++) + if((u = users[i]) != nil){ + a := pack(u); + buf[j:] = a; + j += len a; + } + buf[length-Checklen:] = array of byte Checkpat; + state := kr->aessetup(thekey, buf[0:AESbsize]); + if(state == nil) + error("can't initialise AES"); + kr->aescbc(state, buf[AESbsize:], length-AESbsize, Keyring->Encrypt); + fd := sys->create(keyfile, Sys->OWRITE, 8r600); + if(fd == nil) + error(sys->sprint("can't create %s: %r", keyfile)); + if(sys->write(fd, buf, len buf) != len buf) + error(sys->sprint("error writing to %s: %r", keyfile)); +} diff --git a/appl/cmd/auth/keysrv.b b/appl/cmd/auth/keysrv.b new file mode 100644 index 00000000..c7144256 --- /dev/null +++ b/appl/cmd/auth/keysrv.b @@ -0,0 +1,199 @@ +implement Keysrv; + +# +# remote access to keys (currently only to change secret) +# +# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + +include "security.m"; + auth: Auth; + +include "arg.m"; + +keydb := "/mnt/keys"; + +Keysrv: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: keysrv\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + if(sys->pctl(Sys->FORKNS|Sys->NEWPGRP, nil) < 0) + err(sys->sprint("can't fork name space: %r")); + + keyfile := "/usr/"+user()+"/keyring/default"; + + arg := load Arg Arg->PATH; + if(arg == nil) + err("can't load Arg"); + arg->init(args); + while((o := arg->opt()) != 0) + case o { + 'k' => + keyfile = arg->arg(); + * => + usage(); + } + args = arg->argv(); + arg = nil; + + kr = load Keyring Keyring->PATH; + if(kr == nil) + err("can't load Keyring"); + + auth = load Auth Auth->PATH; + if(auth == nil) + err("can't load Auth"); + auth->init(); + + ai := kr->readauthinfo(keyfile); + if(ai == nil) + err(sys->sprint("can't read server key file %s: %r", keyfile)); + + (fd, id_or_err) := auth->server("sha1" :: "rc4_256" :: nil, ai, sys->fildes(0), 0); + if(fd == nil) + err(sys->sprint("can't authenticate: %s", id_or_err)); + + if(sys->bind("#s", "/mnt/keysrv", Sys->MREPL) < 0) + err(sys->sprint("can't bind #s on /mnt/keysrv: %r")); + srv := sys->file2chan("/mnt/keysrv", "secret"); + if(srv == nil) + err(sys->sprint("can't create file2chan on /mnt/keysrv: %r")); + exitc := chan of int; + spawn worker(srv, id_or_err, exitc); + if(sys->export(fd, "/mnt/keysrv", Sys->EXPWAIT) < 0){ + exitc <-= 1; + err(sys->sprint("can't export %s: %r", "/mnt/keysrv")); + } + exitc <-= 1; +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "keysrv: %s\n", s); + raise "fail:error"; +} + +user(): string +{ + fd := sys->open("/dev/user", Sys->OREAD); + if(fd == nil) + err(sys->sprint("can't open /dev/user: %r")); + + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) + err(sys->sprint("error reading /dev/user: %r")); + + return string buf[0:n]; +} + +worker(file: ref Sys->FileIO, user: string, exitc: chan of int) +{ + (keydir, secret, err) := getuser(user); + if(keydir == nil || secret == nil){ + if(err == nil) + err = "no existing secret"; # can't change it remotely until set + } + (nil, hash) := hashkey(secret); + for(;;)alt{ + <-exitc => + exit; + (nil, nbytes, fid, rc) := <-file.read => + if(rc == nil) + break; + if(err != nil){ + rc <-= (nil, err); + break; + } + rc <-= (nil, nil); + (nil, data, fid, wc) := <-file.write => + if(wc == nil) + break; + if(err != nil){ + wc <-= (0, err); + break; + } + for(i := 0; i < len data; i++) + if(data[i] == byte ' ') + break; + if(string data[0:i] != hash){ + wc <-= (0, "wrong secret"); + break; + } + if(++i >= len data){ + wc <-= (0, nil); + break; + } + if(len data - i < 8){ + wc <-= (0, "unacceptable secret"); + break; + } + if(putsecret(keydir, data[i:]) < 0){ + wc <-= (0, sys->sprint("can't update secret: %r")); + break; + } + wc <-= (len data, nil); + } +} + +hashkey(a: array of byte): (array of byte, string) +{ + hash := array[Keyring->SHA1dlen] of byte; + kr->sha1(a, len a, hash, nil); + s := ""; + for(i := 0; i < len hash; i++) + s += sys->sprint("%2.2ux", int hash[i]); + return (hash, s); +} + +getuser(id: string): (string, array of byte, string) +{ + (ok, nil) := sys->stat(keydb); + if(ok < 0) + return (nil, nil, sys->sprint("can't stat %s: %r", id)); + dbdir := keydb+"/"+id; + (ok, nil) = sys->stat(dbdir); + if(ok < 0) + return (nil, nil, sys->sprint("user not registered: %s", id)); + fd := sys->open(dbdir+"/secret", Sys->OREAD); + if(fd == nil) + return (nil, nil, sys->sprint("can't open %s/secret: %r", id)); + d: Sys->Dir; + (ok, d) = sys->fstat(fd); + if(ok < 0) + return (nil, nil, sys->sprint("can't stat %s/secret: %r", id)); + l := int d.length; + secret: array of byte; + if(l > 0){ + secret = array[l] of byte; + if(sys->read(fd, secret, len secret) != len secret) + return (nil, nil, sys->sprint("error reading %s/secret: %r", id)); + } + return (dbdir, secret, nil); +} + +putsecret(dir: string, secret: array of byte): int +{ + fd := sys->create(dir+"/secret", Sys->OWRITE, 8r600); + if(fd == nil) + return -1; + return sys->write(fd, secret, len secret); +} diff --git a/appl/cmd/auth/logind.b b/appl/cmd/auth/logind.b new file mode 100644 index 00000000..f9d14616 --- /dev/null +++ b/appl/cmd/auth/logind.b @@ -0,0 +1,244 @@ +implement Logind; + +# +# certification service (signer) +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + IPint: import kr; + +include "security.m"; + ssl: SSL; + +include "daytime.m"; + daytime: Daytime; + +Logind: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +TimeLimit: con 5*60*1000; # five minutes +keydb := "/mnt/keys"; + +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->open("/dev/cons", sys->OWRITE); + + kr = load Keyring Keyring->PATH; + + ssl = load SSL SSL->PATH; + if(ssl == nil) + nomod(SSL->PATH); + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + nomod(Daytime->PATH); + + (err, c) := ssl->connect(sys->fildes(0)); + if(c == nil) + fatal("pushing ssl: " + err); + + # impose time out to ensure dead network connections recovered well before TCP/IP's long time out + + grpid := sys->pctl(Sys->NEWPGRP,nil); + pidc := chan of int; + spawn stalker(pidc, grpid); + tpid := <-pidc; + err = dologin(c); + if(err != nil){ + sys->fprint(stderr, "logind: %s\n", err); + kr->puterror(c.dfd, err); + } + kill(tpid, "kill"); +} + +dologin(c: ref Sys->Connection): string +{ + ivec: array of byte; + + (info, err) := signerkey("/keydb/signerkey"); + if(info == nil) + return "can't read signer's own key: "+err; + + # get user name; ack + s: string; + (s, err) = kr->getstring(c.dfd); + if(err != nil) + return err; + name := s; + kr->putstring(c.dfd, name); + + # get initialization vector + (ivec, err) = kr->getbytearray(c.dfd); + if(err != nil) + return "can't get initialization vector: "+err; + + # lookup password + pw := getsecret(s); + if(pw == nil) + return sys->sprint("no password entry for %s: %r", s); + if(len pw < Keyring->SHA1dlen) + return "bad password for "+s+": not SHA1 hashed?"; + userexp := getexpiry(s); + if(userexp < 0) + return sys->sprint("expiry time for %s: %r", s); + + # generate our random diffie hellman part + bits := info.p.bits(); + r0 := kr->IPint.random(bits/4, bits); + + # generate alpha0 = alpha**r0 mod p + alphar0 := info.alpha.expmod(r0, info.p); + + # start encrypting + pwbuf := array[8] of byte; + for(i := 0; i < 8; i++) + pwbuf[i] = pw[i] ^ pw[8+i]; + for(i = 0; i < 4; i++) + pwbuf[i] ^= pw[16+i]; + for(i = 0; i < 8; i++) + pwbuf[i] ^= ivec[i]; + err = ssl->secret(c, pwbuf, pwbuf); + if(err != nil) + return "can't set ssl secret: "+err; + + if(sys->fprint(c.cfd, "alg rc4") < 0) + return sys->sprint("can't push alg rc4: %r"); + + # send P(alpha**r0 mod p) + if(kr->putstring(c.dfd, alphar0.iptob64()) < 0) + return sys->sprint("can't send (alpha**r0 mod p): %r"); + + # stop encrypting + if(sys->fprint(c.cfd, "alg clear") < 0) + return sys->sprint("can't clear alg: %r"); + + # send alpha, p + if(kr->putstring(c.dfd, info.alpha.iptob64()) < 0 || + kr->putstring(c.dfd, info.p.iptob64()) < 0) + return sys->sprint("can't send alpha, p: %r"); + + # get alpha**r1 mod p + (s, err) = kr->getstring(c.dfd); + if(err != nil) + return "can't get alpha**r1 mod p:"+err; + alphar1 := kr->IPint.b64toip(s); + + # compute alpha**(r0*r1) mod p + alphar0r1 := alphar1.expmod(r0, info.p); + + # turn on digesting + secret := alphar0r1.iptobytes(); + err = ssl->secret(c, secret, secret); + if(err != nil) + return "can't set digest secret: "+err; + if(sys->fprint(c.cfd, "alg sha1") < 0) + return sys->sprint("can't push alg sha1: %r"); + + # send our public key + if(kr->putstring(c.dfd, kr->pktostr(kr->sktopk(info.mysk))) < 0) + return sys->sprint("can't send signer's public key: %r"); + + # get his public key + (s, err) = kr->getstring(c.dfd); + if(err != nil) + return "client public key: "+err; + hisPKbuf := array of byte s; + hisPK := kr->strtopk(s); + if(hisPK.owner != name) + return "pk name doesn't match user name"; + + # sign and return + state := kr->sha1(hisPKbuf, len hisPKbuf, nil, nil); + cert := kr->sign(info.mysk, userexp, state, "sha1"); + + if(kr->putstring(c.dfd, kr->certtostr(cert)) < 0) + return sys->sprint("can't send certificate: %r"); + + return nil; +} + +nomod(mod: string) +{ + fatal(sys->sprint("can't load %s: %r",mod)); +} + +fatal(msg: string) +{ + sys->fprint(stderr, "logind: %s\n", msg); + exit; +} + +signerkey(filename: string): (ref Keyring->Authinfo, string) +{ + + info := kr->readauthinfo(filename); + if(info == nil) + return (nil, sys->sprint("readauthinfo %r")); + + # validate signer key + now := daytime->now(); + if(info.cert.exp != 0 && info.cert.exp < now) + return (nil, sys->sprint("signer key expired")); + + return (info, nil); +} + +getsecret(id: string): array of byte +{ + fd := sys->open(sys->sprint("%s/%s/secret", keydb, id), Sys->OREAD); + if(fd == nil) + return nil; + (ok, d) := sys->fstat(fd); + if(ok < 0) + return nil; + a := array[int d.length] of byte; + n := sys->read(fd, a, len a); + if(n < 0) + return nil; + return a[0:n]; +} + +getexpiry(id: string): int +{ + fd := sys->open(sys->sprint("%s/%s/expire", keydb, id), Sys->OREAD); + if(fd == nil) + return -1; + a := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, a, len a); + if(n < 0) + return -1; + s := string a[0:n]; + if(s == "never") + return 0; + if(s == "expired"){ + sys->werrstr(sys->sprint("entry for %s expired", id)); + return -1; + } + return int s; +} + +stalker(pidc: chan of int, killpid: int) +{ + pidc <-= sys->pctl(0, nil); + sys->sleep(TimeLimit); + sys->fprint(stderr, "logind: login timed out\n"); + kill(killpid, "killgrp"); +} + +kill(pid: int, how: string) +{ + fd := sys->open("#p/" + string pid + "/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "%s", how) < 0) + sys->fprint(stderr, "logind: can't %s %d: %r\n", how, pid); +} diff --git a/appl/cmd/auth/mkauthinfo.b b/appl/cmd/auth/mkauthinfo.b new file mode 100644 index 00000000..33feffbb --- /dev/null +++ b/appl/cmd/auth/mkauthinfo.b @@ -0,0 +1,125 @@ +implement Mkauthinfo; + +# +# sign a new key to produce a certificate +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + IPint: import kr; + +include "security.m"; + auth: Auth; + +include "daytime.m"; + daytime: Daytime; + +include "arg.m"; + +Mkauthinfo: module{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->open("/dev/cons", sys->OWRITE); + + kr = load Keyring Keyring->PATH; + + auth = load Auth Auth->PATH; + if(auth == nil) + nomod(Auth->PATH); + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + nomod(Daytime->PATH); + + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + arg->init(args); + arg->setusage("auth/mkauthinfo [-k keyspec] [-e ddmmyyyy] user [keyfile]"); + keyspec := "key=default"; + expiry := 0; + while((o := arg->opt()) != 0) + case o { + 'k' => + keyspec = arg->earg(); + 'e' => + expiry = parsedate(arg->earg()); + * => + arg->usage(); + } + args = arg->argv(); + if(args == nil) + arg->usage(); + user := hd args; + args = tl args; + dstfile := "/fd/1"; + if(args != nil) + dstfile = hd args; + arg = nil; + + sai := auth->key(keyspec); + if(sai == nil){ + sys->fprint(stderr, "sign: can't find key matching %q: %r\n", keyspec); + raise "fail:no key"; + } + + info := ref Keyring->Authinfo; + info.alpha = sai.alpha; + info.p = sai.p; + info.mysk = kr->genSKfromPK(sai.spk, user); + info.mypk = kr->sktopk(info.mysk); + info.spk = sai.mypk; + pkbuf := array of byte kr->pktostr(info.mypk); + state := kr->sha1(pkbuf, len pkbuf, nil, nil); + info.cert = kr->sign(sai.mysk, expiry, state, "sha1"); + if(kr->writeauthinfo("/fd/1", info) < 0){ + sys->fprint(stderr, "sign: error writing certificate: %r\n"); + raise "fail:write error"; + } +} + +parsedate(s: string): int +{ + now := daytime->now(); + tm := daytime->local(now); + if(s == "permanent") + return 0; + if(len s != 8) + fatal("bad date format "+s+" (expected DDMMYYYY)"); + tm.mday = int s[0:2]; + if(tm.mday > 31 || tm.mday < 1) + fatal(sys->sprint("bad day of month %d", tm.mday)); + tm.mon = int s[2:4] - 1; + if(tm.mon > 11 || tm.mday < 0) + fatal(sys->sprint("bad month %d\n", tm.mon + 1)); + tm.year = int s[4:8] - 1900; + if(tm.year < 70) + fatal(sys->sprint("bad year %d (year may be no earlier than 1970)", tm.year + 1900)); + expiry := daytime->tm2epoch(tm); + expiry += 60; + if(expiry <= now) + fatal("expiry date has already passed"); + return expiry; +} + +nomod(mod: string) +{ + fatal(sys->sprint("can't load %s: %r",mod)); +} + +fatal(msg: string) +{ + sys->fprint(stderr, "mkauthinfo: %s\n", msg); + raise "fail:error"; +} diff --git a/appl/cmd/auth/mkfile b/appl/cmd/auth/mkfile new file mode 100644 index 00000000..112ba66a --- /dev/null +++ b/appl/cmd/auth/mkfile @@ -0,0 +1,38 @@ +<../../../mkconfig + +DIRS=\ + factotum\ + +TARG=\ + aescbc.dis\ + changelogin.dis\ + countersigner.dis\ + convpasswd.dis\ + createsignerkey.dis\ + keyfs.dis\ + keysrv.dis\ + getpk.dis\ + logind.dis\ + mkauthinfo.dis\ + passwd.dis\ + secstore.dis\ + signer.dis\ + verify.dis\ + +SYSMODULES=\ + arg.m\ + keyring.m\ + security.m\ + rand.m\ + sys.m\ + draw.m\ + bufio.m\ + secstore.m\ + string.m\ + styx.m\ + styxservers.m\ + +DISBIN=$ROOT/dis/auth + +<$ROOT/mkfiles/mkdis +<$ROOT/mkfiles/mksubdirs diff --git a/appl/cmd/auth/passwd.b b/appl/cmd/auth/passwd.b new file mode 100644 index 00000000..d10b5c95 --- /dev/null +++ b/appl/cmd/auth/passwd.b @@ -0,0 +1,290 @@ +implement Passwd; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + +include "security.m"; + auth: Auth; + +include "arg.m"; + +Passwd: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr, stdin, stdout: ref Sys->FD; +keysrv := "/mnt/keysrv"; +signer := "$SIGNER"; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: passwd [-u user] [-s signer] [keyfile]\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + stdin = sys->fildes(0); + stdout = sys->fildes(1); + stderr = sys->fildes(2); + + kr = load Keyring Keyring->PATH; + if(kr == nil) + noload(Keyring->PATH); + auth = load Auth Auth->PATH; + if(auth == nil) + noload(Auth->PATH); + auth->init(); + + keyfile, id: string; + arg := load Arg Arg->PATH; + if(arg == nil) + noload(Arg->PATH); + arg->init(args); + while((o := arg->opt()) != 0) + case o { + 's' => + signer = arg->arg(); + 'u' => + id = arg->arg(); + * => + usage(); + } + args = arg->argv(); + arg = nil; + + if(args == nil) + args = "default" :: nil; + + if(id == nil) + id= user(); + + if(args != nil) + keyfile = hd args; + else + keyfile = "default"; + if(len keyfile > 0 && keyfile[0] != '/') + keyfile = "/usr/" + id + "/keyring/" + keyfile; + + ai := kr->readauthinfo(keyfile); + if(ai == nil) + err(sys->sprint("can't read certificate from %s: %r", keyfile)); +sys->print("key owner: %s\n", ai.mypk.owner); + + sys->pctl(Sys->FORKNS|Sys->FORKFD, nil); + remid := mountsrv(ai); + + # get password + ok: int; + secret: array of byte; + oldhash: array of byte; + word: string; + for(;;){ + sys->print("Inferno secret: "); + (ok, word) = readline(stdin, "rawon"); + if(!ok || word == nil) + exit; + secret = array of byte word; + (nil, s) := hashkey(secret); + for(i := 0; i < len word; i++) + word[i] = ' '; + oldhash = array of byte s; + e := putsecret(oldhash, nil); + if(e != "wrong secret"){ + if(e == nil) + break; + err(e); + } + sys->fprint(stderr, "!wrong secret\n"); + } + newsecret: array of byte; + for(;;){ + for(;;){ + sys->print("new secret [default = don't change]: "); + (ok, word) = readline(stdin, "rawon"); + if(!ok) + exit; + if(word == "" && secret != nil) + break; + if(len word >= 8) + break; + sys->print("!secret must be at least 8 characters\n"); + } + if(word != ""){ + # confirm password change + word1 := word; + sys->print("confirm: "); + (ok, word) = readline(stdin, "rawon"); + if(!ok || word != word1){ + sys->fprint(stderr, "!entries didn't match\n"); + continue; + } + # TO DO... + #pwbuf := array of byte word; + #newsecret = array[Keyring->SHA1dlen] of byte; + #kr->sha1(pwbuf, len pwbuf, newsecret, nil); + newsecret = array of byte word; + } + if(!eq(newsecret, secret)){ + if((e := putsecret(oldhash, newsecret)) != nil){ + sys->fprint(stderr, "passwd: can't update secret for %s: %s\n", id, e); + continue; + } + } + break; + } +} + +noload(s: string) +{ + err(sys->sprint("can't load %s: %r", s)); +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "passwd: %s\n", s); + raise "fail:error"; +} + +mountsrv(ai: ref Keyring->Authinfo): string +{ + (rc, c) := sys->dial(netmkaddr(signer, "net", "infkey"), nil); + if(rc < 0) + err(sys->sprint("can't dial %s: %r", signer)); + (fd, id_or_err) := auth->client("sha1/rc4_256", ai, c.dfd); + if(fd == nil) + err(sys->sprint("can't authenticate with %s: %r", signer)); + if(sys->mount(fd, nil, keysrv, Sys->MREPL, nil) < 0) + err(sys->sprint("can't mount %s on %s: %r", signer, keysrv)); + return id_or_err; +} + +user(): string +{ + fd := sys->open("/dev/user", Sys->OREAD); + if(fd == nil) + err(sys->sprint("can't open /dev/user: %r")); + + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) + err(sys->sprint("error reading /dev/user: %r")); + + return string buf[0:n]; +} + +eq(a, b: array of byte): int +{ + if(len a != len b) + return 0; + for(i := 0; i < len a; i++) + if(a[i] != b[i]) + return 0; + return 1; +} + +hashkey(a: array of byte): (array of byte, string) +{ + hash := array[Keyring->SHA1dlen] of byte; + kr->sha1(a, len a, hash, nil); + s := ""; + for(i := 0; i < len hash; i++) + s += sys->sprint("%2.2ux", int hash[i]); + return (hash, s); +} + +putsecret(oldhash: array of byte, secret: array of byte): string +{ + fd := sys->create(keysrv+"/secret", Sys->OWRITE, 8r600); + if(fd == nil) + return sys->sprint("%r"); + n := len oldhash; + if(secret != nil) + n += 1 + len secret; + buf := array[n] of byte; + buf[0:] = oldhash; + if(secret != nil){ + buf[len oldhash] = byte ' '; + buf[len oldhash+1:] = secret; + } + if(sys->write(fd, buf, len buf) < 0) + return sys->sprint("%r"); + return nil; +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, l) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} + +readline(io: ref Sys->FD, mode: string): (int, string) +{ + r : int; + line : string; + buf := array[8192] of byte; + fdctl : ref Sys->FD; + rawoff := array of byte "rawoff"; + + if(mode == "rawon"){ + fdctl = sys->open("/dev/consctl", sys->OWRITE); + if(fdctl == nil || sys->write(fdctl,array of byte mode,len mode) != len mode){ + sys->fprint(stderr, "unable to change console mode"); + return (0,nil); + } + } + + line = ""; + for(;;) { + r = sys->read(io, buf, len buf); + if(r <= 0){ + sys->fprint(stderr, "error read from console mode"); + if(mode == "rawon") + sys->write(fdctl,rawoff,6); + return (0, nil); + } + + line += string buf[0:r]; + if ((len line >= 1) && (line[(len line)-1] == '\n')){ + if(mode == "rawon"){ + r = sys->write(stdout,array of byte "\n",1); + if(r <= 0) { + sys->write(fdctl,rawoff,6); + return (0, nil); + } + } + break; + } + else { + if(mode == "rawon"){ + #r = sys->write(stdout, array of byte "*",1); + if(r <= 0) { + sys->write(fdctl,rawoff,6); + return (0, nil); + } + } + } + } + + if(mode == "rawon") + sys->write(fdctl,rawoff,6); + + return (1, line[0:len line - 1]); +} diff --git a/appl/cmd/auth/secstore.b b/appl/cmd/auth/secstore.b new file mode 100644 index 00000000..5a63b78d --- /dev/null +++ b/appl/cmd/auth/secstore.b @@ -0,0 +1,317 @@ +implement Secstorec; + +# +# interact with the Plan 9 secstore +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "secstore.m"; + secstore: Secstore; + +include "arg.m"; + +Secstorec: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Maxfilesize: con 128*1024; + +stderr: ref Sys->FD; +conn: ref Sys->Connection; +seckey: array of byte; +filekey: array of byte; +file: array of byte; +verbose := 0; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + secstore = load Secstore Secstore->PATH; + + sys->pctl(Sys->FORKFD, nil); + stderr = sys->fildes(2); + secstore->init(); + secstore->privacy(); + + addr := "net!$auth!secstore"; + user := readfile("/dev/user"); + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("auth/secstore [-iv] [-k key] [-p pin] [-s net!server!secstore] [-u user] [{drptx} file ...]"); + iflag := 0; + pass, pin: string; + while((o := arg->opt()) != 0) + case o { + 'i' => iflag = 1; + 'k' => pass = arg->earg(); + 'v' => verbose = 1; + 's' => addr = arg->earg(); + 'u' => user = arg->earg(); + 'p' => pin = arg->earg(); + * => + arg->usage(); + } + args = arg->argv(); + op := -1; + if(args != nil){ + if(len hd args != 1) + arg->usage(); + op = (hd args)[0]; + args = tl args; + case op { + 'd' or 'r' or 'p' or 'x' => + if(args == nil) + arg->usage(); + 't' => + ; + * => + arg->usage(); + } + } + arg = nil; + + if(iflag){ + buf := array[Secstore->Maxmsg] of byte; + stdin := sys->fildes(0); + for(nr := 0; nr < len buf && (n := sys->read(stdin, buf, len buf-nr)) > 0;) + nr += n; + s := string buf[0:nr]; + secstore->erasekey(buf[0:nr]); + (nf, flds) := sys->tokenize(s, "\n"); + for(i := 0; i < len s; i++) + s[i] = 0; + if(nf < 1) + error("no password on standard input"); + pass = hd flds; + if(nf > 1) + pin = hd tl flds; + } + conn: ref Sys->Connection; +Auth: + for(;;){ + if(!iflag) + pass = readpassword("secstore password"); + if(pass == nil) + exit; + erase(); + seckey = secstore->mkseckey(pass); + filekey = secstore->mkfilekey(pass); + for(i := 0; i < len pass; i++) + pass[i] = 0; # clear it + conn = secstore->dial(netmkaddr(addr, "net", "secstore")); + if(conn == nil) + error(sys->sprint("can't connect to secstore: %r")); + (srvname, diag) := secstore->auth(conn, user, seckey); + if(srvname == nil){ + secstore->bye(conn); + sys->fprint(stderr, "secstore: authentication failed: %s\n", diag); + if(iflag) + raise "fail:auth"; + continue; + } + case diag { + "" => + if(verbose) + sys->fprint(stderr, "server: %s\n", srvname); + secstore->erasekey(seckey); + seckey = nil; + break Auth; + "need pin" => + if(!iflag){ + pin = readpassword("STA PIN+SecureID"); + if(len pin == 0){ + sys->fprint(stderr, "cancelled"); + exit; + } + }else if(pin == nil) + raise "fail:no pin"; + if(secstore->sendpin(conn, pin) < 0){ + sys->fprint(stderr, "secstore: pin rejected: %r\n"); + if(iflag) + raise "fail:bad pin"; + continue; + } + } + } + if(op == 't'){ + erase(); # no longer need the keys + entries := secstore->files(conn); + for(; entries != nil; entries = tl entries){ + (name, size, date, hash, nil) := hd entries; + if(args != nil){ + for(l := args; l != nil; l = tl l) + if((hd args) == name) + break; + if(args == nil) + continue; + } + if(verbose) + sys->print("%-14q %10d %s %s\n", name, size, date, hash); + else + sys->print("%q\n", name); + } + exit; + } + for(; args != nil; args = tl args){ + fname := hd args; + case op { + 'd' => + checkname(fname, 1); + if(secstore->remove(conn, fname) < 0) + error(sys->sprint("can't remove %q: %r", fname)); + verb('d', fname); + 'p' => + checkname(fname, 1); + file = getfile(conn, fname, filekey); + lines := secstore->lines(file); + lno := 1; + for(; lines != nil; lines = tl lines){ + l := hd lines; + if(sys->write(sys->fildes(1), l, len l) != len l) + sys->fprint(sys->fildes(2), "secstore (%s:%d): %r\n", fname, lno); + lno++; + } + secstore->erasekey(file); + file = nil; + verb('p', fname); + 'x' => + checkname(fname, 1); + file = getfile(conn, fname, filekey); + ofd := sys->create(fname, Sys->OWRITE, 8r600); + if(ofd == nil) + error(sys->sprint("can't create %q: %r", fname)); + if(sys->write(ofd, file, len file) != len file) + error(sys->sprint("error writing to %q: %r", fname)); + secstore->erasekey(file); + file = nil; + verb('x', fname); + 'r' or * => + error(sys->sprint("op %c not implemented", op)); + } + } + erase(); +} + +checkname(s: string, noslash: int): string +{ + tail := s; + for(i := 0; i < len s; i++){ + if(s[i] == '/'){ + if(noslash) + break; + tail = s[i+1:]; + } + if(s[i] == '\n' || s[i] <= ' ') + break; + } + if(s == nil || tail == nil || i < len s || s == "..") + error(sys->sprint("can't use %q as a secstore file name", s)); # server checks as well, of course + return tail; +} + +verb(op: int, n: string) +{ + if(verbose) + sys->fprint(stderr, "%c %q\n", op, n); +} + +getfile(conn: ref Sys->Connection, fname: string, key: array of byte): array of byte +{ + f := secstore->getfile(conn, fname, 0); + if(f == nil) + error(sys->sprint("can't fetch %q: %r", fname)); + if(fname != "."){ + f = secstore->decrypt(f, key); + if(f == nil) + error(sys->sprint("can't decrypt %q: %r", fname)); + } + return f; +} + +erase() +{ + if(secstore != nil){ + secstore->erasekey(seckey); + secstore->erasekey(filekey); + secstore->erasekey(file); + } +} + +error(s: string) +{ + erase(); + sys->fprint(stderr, "secstore: %s\n", s); + raise "fail:error"; +} + +readpassword(prompt: string): string +{ + cons := sys->open("/dev/cons", Sys->ORDWR); + if(cons == nil) + return nil; + stdin := bufio->fopen(cons, Sys->OREAD); + if(stdin == nil) + return nil; + cfd := sys->open("/dev/consctl", Sys->OWRITE); + if (cfd == nil || sys->fprint(cfd, "rawon") <= 0) + sys->fprint(stderr, "secstore: warning: cannot hide typed password\n"); +L: + for(;;){ + sys->fprint(cons, "%s: ", prompt); + s := ""; + while ((c := stdin.getc()) >= 0){ + case c { + '\n' or ('d'&8r037) => + sys->fprint(cons, "\n"); + return s; + '\b' or 8r177 => + if(len s > 0) + s = s[0:len s - 1]; + 'u' & 8r037 => + sys->fprint(cons, "\n"); + continue L; + * => + s[len s] = c; + } + } + break; + } + return nil; +} + +readfile(f: string): string +{ + fd := sys->open(f, Sys->OREAD); + if(fd == nil) + return ""; + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) + return ""; + return string buf[0:n]; +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, nil) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} diff --git a/appl/cmd/auth/signer.b b/appl/cmd/auth/signer.b new file mode 100644 index 00000000..b3f4669d --- /dev/null +++ b/appl/cmd/auth/signer.b @@ -0,0 +1,132 @@ +implement Signer; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + IPint: import kr; + +include "security.m"; + random: Random; + +Signer: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +# size in bits of modulus for public keys +PKmodlen: con 512; + +# size in bits of modulus for diffie hellman +DHmodlen: con 512; + +stderr, stdin, stdout: ref Sys->FD; + +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + random = load Random Random->PATH; + kr = load Keyring Keyring->PATH; + + stdin = sys->fildes(0); + stdout = sys->fildes(1); + stderr = sys->fildes(2); + + sys->pctl(Sys->FORKNS, nil); + if(sys->chdir("/keydb") < 0){ + sys->fprint(stderr, "signer: no key database\n"); + raise "fail:no keydb"; + } + + err := sign(); + if(err != nil){ + sys->fprint(stderr, "signer: %s\n", err); + raise "fail:error"; + } +} + +sign(): string +{ + info := signerkey("signerkey"); + if(info == nil) + return "can't read key"; + + # send public part to client + mypkbuf := array of byte kr->pktostr(kr->sktopk(info.mysk)); + kr->sendmsg(stdout, mypkbuf, len mypkbuf); + alphabuf := array of byte info.alpha.iptob64(); + kr->sendmsg(stdout, alphabuf, len alphabuf); + pbuf := array of byte info.p.iptob64(); + kr->sendmsg(stdout, pbuf, len pbuf); + + # get client's public key + hisPKbuf := kr->getmsg(stdin); + if(hisPKbuf == nil) + return "caller hung up"; + hisPK := kr->strtopk(string hisPKbuf); + if(hisPK == nil) + return "illegal caller PK"; + + # hash, sign, and blind + state := kr->sha1(hisPKbuf, len hisPKbuf, nil, nil); + cert := kr->sign(info.mysk, 0, state, "sha1"); + + # sanity clause + state = kr->sha1(hisPKbuf, len hisPKbuf, nil, nil); + if(kr->verify(info.mypk, cert, state) == 0) + return "bad signer certificate"; + + certbuf := array of byte kr->certtostr(cert); + blind := random->randombuf(random->ReallyRandom, len certbuf); + for(i := 0; i < len blind; i++) + certbuf[i] = certbuf[i] ^ blind[i]; + + # sum PKs and blinded certificate + state = kr->md5(mypkbuf, len mypkbuf, nil, nil); + kr->md5(hisPKbuf, len hisPKbuf, nil, state); + digest := array[Keyring->MD5dlen] of byte; + kr->md5(certbuf, len certbuf, digest, state); + + # save sum and blinded cert in a file + file := "signed/"+hisPK.owner; + fd := sys->create(file, Sys->OWRITE, 8r600); + if(fd == nil) + return "can't create "+file+sys->sprint(": %r"); + if(kr->sendmsg(fd, blind, len blind) < 0 || + kr->sendmsg(fd, digest, len digest) < 0){ + sys->remove(file); + return "can't write "+file+sys->sprint(": %r"); + } + + # send blinded cert to client + kr->sendmsg(stdout, certbuf, len certbuf); + + return nil; +} + +signerkey(filename: string): ref Keyring->Authinfo +{ + info := kr->readauthinfo(filename); + if(info != nil) + return info; + + # generate a local key + info = ref Keyring->Authinfo; + info.mysk = kr->genSK("elgamal", "*", PKmodlen); + info.mypk = kr->sktopk(info.mysk); + info.spk = kr->sktopk(info.mysk); + myPKbuf := array of byte kr->pktostr(info.mypk); + state := kr->sha1(myPKbuf, len myPKbuf, nil, nil); + info.cert = kr->sign(info.mysk, 0, state, "sha1"); + (info.alpha, info.p) = kr->dhparams(DHmodlen); + + if(kr->writeauthinfo(filename, info) < 0){ + sys->fprint(stderr, "can't write signerkey file: %r\n"); + return nil; + } + + return info; +} diff --git a/appl/cmd/auth/verify.b b/appl/cmd/auth/verify.b new file mode 100644 index 00000000..d829a76c --- /dev/null +++ b/appl/cmd/auth/verify.b @@ -0,0 +1,85 @@ +implement Verify; + +include "sys.m"; + sys: Sys; + +include "keyring.m"; + kr: Keyring; + +include "draw.m"; + +Verify: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr, stdin: ref Sys->FD; + +pro := array[] of { + "alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", + "hotel", "india", "juliet", "kilo", "lima", "mike", "nancy", "oscar", + "papa", "quebec", "romeo", "sierra", "tango", "uniform", + "victor", "whisky", "xray", "yankee", "zulu" +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + + stdin = sys->fildes(0); + stderr = sys->fildes(2); + + if(args != nil) + args = tl args; + if(args == nil){ + sys->fprint(stderr, "usage: verify boxid\n"); + raise "fail:usage"; + } + + sys->pctl(Sys->FORKNS, nil); + if(sys->chdir("/keydb") < 0){ + sys->fprint(stderr, "signer: no key database\n"); + raise "fail:no keydb"; + } + + boxid := hd args; + file := "signed/"+boxid; + fd := sys->open(file, Sys->OREAD); + if(fd == nil){ + sys->fprint(stderr, "signer: can't open %s: %r\n", file); + raise "fail:no certificate"; + } + certbuf := kr->getmsg(fd); + digest := kr->getmsg(fd); + if(digest == nil || certbuf == nil){ + sys->fprint(stderr, "signer: can't read %s: %r\n", file); + raise "fail:bad certificate"; + } + + s: string; + for(i := 0; i < len digest; i++){ + s = s + (string (2*i)) + ": " + pro[((int digest[i])>>4)%len pro] + "\t"; + s = s + (string (2*i+1)) + ": " + pro[(int digest[i])%len pro] + "\n"; + } + + sys->print("%s\naccept (y or n)? ", s); + buf := array[5] of byte; + n := sys->read(stdin, buf, len buf); + if(n < 1 || buf[0] != byte 'y'){ + sys->print("\nrejected\n"); + raise "fail:rejected"; + } + sys->print("\naccepted\n"); + + nfile := "countersigned/"+boxid; + fd = sys->create(nfile, Sys->OWRITE, 8r600); + if(fd == nil){ + sys->fprint(stderr, "signer: can't create %s: %r\n", nfile); + raise "fail:create"; + } + if(kr->sendmsg(fd, certbuf, len certbuf) < 0){ + sys->fprint(stderr, "signer: can't write %s: %r\n", nfile); + raise "fail:write"; + } +} |
