implement Crypt; # encrypt/decrypt from stdin to stdout include "sys.m"; sys: Sys; stderr: ref Sys->FD; include "draw.m"; include "keyring.m"; keyring: Keyring; include "security.m"; ssl: SSL; include "bufio.m"; include "msgio.m"; msgio: Msgio; include "arg.m"; Crypt: module { init: fn(nil: ref Draw->Context, argv: list of string); }; Ehungup: con "i/o on hungup channel"; ALGSTR: con "alg "; DEFAULTALG: con "md5/ideacbc"; usage() { sys->fprint(stderr, "usage: crypt [-?] [-d] [-k secret] [-f secretfile] [-a alg[/alg]]\n"); sys->fprint(stderr, "available algorithms:\n"); showalgs(stderr); fail("bad usage"); } badmodule(m: string) { sys->fprint(stderr, "crypt: cannot load %s: %r\n", m); fail("bad module"); } headers: con 1; verbose := 0; init(nil: ref Draw->Context, argv: list of string) { sys = load Sys Sys->PATH; stderr = sys->fildes(2); ssl = load SSL SSL->PATH; if (ssl == nil) badmodule(SSL->PATH); keyring = load Keyring Keyring->PATH; if (keyring == nil) badmodule(SSL->PATH); msgio = load Msgio Msgio->PATH; msgio->init(); arg := load Arg Arg->PATH; if (arg == nil) badmodule(SSL->PATH); decrypt := 0; secret: array of byte; alg := DEFAULTALG; arg->init(argv); while ((opt := arg->opt()) != 0) { case opt { 'd' => decrypt = 1; 'k' => if ((s := arg->arg()) == nil) usage(); secret = array of byte s; 'f' => if ((f := arg->arg()) == nil) usage(); secret = readfile(f); 'a' => if ((alg = arg->arg()) == nil) usage(); '?' => showalgs(sys->fildes(1)); return; 'v' => verbose = 1; * => usage(); } } argv = arg->argv(); if (argv != nil) usage(); if(secret == nil) secret = array of byte readpassword(); sk := array[Keyring->SHA1dlen] of byte; keyring->sha1(secret, len secret, sk, nil); if (headers) { # deal with header - the header encodes the algorithm along with the data. if (decrypt) { msg := msgio->getmsg(sys->fildes(0)); if (msg != nil) alg = string msg; if (msg == nil || len alg < len ALGSTR || alg[0:len ALGSTR] != ALGSTR) error("couldn't get decrypt algorithm"); alg = alg[len ALGSTR:]; } else { msg := array of byte ("alg " + alg); e := msgio->sendmsg(sys->fildes(1), msg, len msg); if (e == -1) error("couldn't write algorithm string"); } } fd := docrypt(decrypt, alg, sk); if (decrypt) { # if decrypting, don't use stream, as we want to catch # decryption or checksum errors when they happen. buf := array[Sys->ATOMICIO] of byte; stdout := sys->fildes(1); while ((n := sys->read(fd, buf, len buf)) > 0) sys->write(stdout, buf, n); if (n == -1) { err := sys->sprint("%r"); if (err != Ehungup) error("decryption failed: " + err); } } else { stream(fd, sys->fildes(1), Sys->ATOMICIO); } } docrypt(decrypt: int, alg: string, sk: array of byte): ref Sys->FD { if (verbose) sys->fprint(stderr, "%scrypting with alg %s\n", (array[] of {"en", "de"})[decrypt!=0], alg); (err, fds, nil, nil) := cryptpipe(decrypt, alg, sk); if (err != nil) error(err); spawn stream(sys->fildes(0), fds[1], Sys->ATOMICIO); return fds[0]; } # set up an encrypt/decrypt session; if decrypt is non-zero, then # decrypt, else encrypt. alg is the algorithm to use; sk is the # used as the secret key. # returns tuple (err, fds, cfd, dir) # where err is non-nil on failure; # otherwise fds is an array of two fds; writing to fds[1] will make # crypted/decrypted data available to be read on fds[0]. # dir is the ssl directory in question. cryptpipe(decrypt: int, alg: string, sk: array of byte): (string, array of ref Sys->FD, ref Sys->FD, string) { pfd := array[2] of ref Sys->FD; if (sys->pipe(pfd) == -1) return ("pipe failed", nil, nil, nil); (err, c) := ssl->connect(pfd[1]); if (err != nil) return ("could not connect ssl: "+err, nil, nil, nil); pfd[1] = nil; err = ssl->secret(c, sk, sk); if (err != nil) return ("could not write secret: "+err, nil, nil, nil); if (alg != nil) if (sys->fprint(c.cfd, "alg %s", alg) == -1) return (sys->sprint("bad algorithm %s: %r", alg), nil, nil, nil); fds := array[2] of ref Sys->FD; if (decrypt) { fds[1] = pfd[0]; fds[0] = c.dfd; } else { fds[1] = c.dfd; fds[0] = pfd[0]; } return (nil, fds, c.cfd, c.dir); } algnames := array[] of {("crypt", "encalgs"), ("hash", "hashalgs")}; # find available algorithms and return as tuple of two lists: # (err, hashalgs, cryptalgs) algs(): (string, array of list of string) { (err, nil, nil, dir) := cryptpipe(0, nil, array[100] of byte); if (err != nil) return (err, nil); alglists := array[len algnames] of list of string; for (i := 0; i < len algnames; i++) { (nil, f) := algnames[i]; (nil, alglists[i]) = sys->tokenize(string readfile(dir + "/" + f), " "); } return (nil, alglists); } showalgs(fd: ref Sys->FD) { (err, alglists) := algs(); if (err != nil) error("cannot get algorithms: " + err); for (j := 0; j < len alglists; j++) { (name, nil) := algnames[j]; sys->fprint(fd, "%s:", name); for (l := alglists[j]; l != nil; l = tl l) sys->fprint(fd, " %s", hd l); sys->fprint(fd, "\n"); } } readpassword(): string { bufio := load Bufio Bufio->PATH; Iobuf: import bufio; stdin := bufio->open("/dev/cons", Sys->OREAD); cfd := sys->open("/dev/consctl", Sys->OWRITE); if (cfd == nil || sys->fprint(cfd, "rawon") <= 0) sys->fprint(stderr, "crypt: warning: cannot hide typed password\n"); sys->fprint(stderr, "password: "); s := ""; while ((c := stdin.getc()) >= 0 && c != '\n'){ case c { '\b' => if (len s > 0) s = s[0:len s - 1]; 8r25 => # ^U s = nil; * => s[len s] = c; } } sys->fprint(stderr, "\n"); return s; } stream(src, dst: ref Sys->FD, bufsize: int) { sys->stream(src, dst, bufsize); } readfile(f: string): array of byte { fd := sys->open(f, Sys->OREAD); if (fd == nil) error(sys->sprint("cannot read %s: %r", f)); buf := array[8192] of byte; # >8K key? get real! n := sys->read(fd, buf, len buf); if (n <= 0) return nil; return buf[0:n]; } error(s: string) { sys->fprint(stderr, "crypt: %s\n", s); fail("error"); } fail(e: string) { raise "fail: "+e; }