summaryrefslogtreecommitdiff
path: root/appl/cmd/auth
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /appl/cmd/auth
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/cmd/auth')
-rw-r--r--appl/cmd/auth/aescbc.b254
-rw-r--r--appl/cmd/auth/changelogin.b305
-rw-r--r--appl/cmd/auth/convpasswd.b120
-rw-r--r--appl/cmd/auth/countersigner.b59
-rw-r--r--appl/cmd/auth/createsignerkey.b144
-rw-r--r--appl/cmd/auth/factotum/authio.m80
-rw-r--r--appl/cmd/auth/factotum/factotum.b978
-rw-r--r--appl/cmd/auth/factotum/feedkey.b321
-rw-r--r--appl/cmd/auth/factotum/mkfile27
-rw-r--r--appl/cmd/auth/factotum/proto/infauth.b362
-rw-r--r--appl/cmd/auth/factotum/proto/keyreps.b173
-rw-r--r--appl/cmd/auth/factotum/proto/keyreps.m23
-rw-r--r--appl/cmd/auth/factotum/proto/mkfile22
-rw-r--r--appl/cmd/auth/factotum/proto/p9any.b232
-rw-r--r--appl/cmd/auth/factotum/proto/pass.b29
-rw-r--r--appl/cmd/auth/factotum/rpc.b68
-rw-r--r--appl/cmd/auth/getpk.b83
-rw-r--r--appl/cmd/auth/keyfs.b806
-rw-r--r--appl/cmd/auth/keysrv.b199
-rw-r--r--appl/cmd/auth/logind.b244
-rw-r--r--appl/cmd/auth/mkauthinfo.b125
-rw-r--r--appl/cmd/auth/mkfile38
-rw-r--r--appl/cmd/auth/passwd.b290
-rw-r--r--appl/cmd/auth/secstore.b317
-rw-r--r--appl/cmd/auth/signer.b132
-rw-r--r--appl/cmd/auth/verify.b85
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";
+ }
+}