xref: /inferno-os/appl/cmd/crypt.b (revision 66f5808b81b1df84bc57c4f7b9d487201bc162fb)
1implement Crypt;
2
3# encrypt/decrypt from stdin to stdout
4
5include "sys.m";
6	sys: Sys;
7	stderr: ref Sys->FD;
8include "draw.m";
9include "keyring.m";
10	keyring: Keyring;
11include "security.m";
12	ssl: SSL;
13include "bufio.m";
14include "msgio.m";
15	msgio: Msgio;
16include "arg.m";
17
18Crypt: module {
19	init: fn(nil: ref Draw->Context, argv: list of string);
20};
21
22Ehungup: con "i/o on hungup channel";
23
24ALGSTR: con "alg ";
25DEFAULTALG: con "md5/ideacbc";
26usage()
27{
28	sys->fprint(stderr, "usage: crypt [-?] [-d] [-k secret] [-f secretfile] [-a alg[/alg]]\n");
29	sys->fprint(stderr, "available algorithms:\n");
30	showalgs(stderr);
31	fail("bad usage");
32}
33
34badmodule(m: string)
35{
36	sys->fprint(stderr, "crypt: cannot load %s: %r\n", m);
37	fail("bad module");
38}
39
40headers: con 1;
41verbose := 0;
42
43init(nil: ref Draw->Context, argv: list of string)
44{
45	sys = load Sys Sys->PATH;
46	stderr = sys->fildes(2);
47	ssl = load SSL SSL->PATH;
48	if (ssl == nil)
49		badmodule(SSL->PATH);
50	keyring = load Keyring Keyring->PATH;
51	if (keyring == nil)
52		badmodule(SSL->PATH);
53	msgio = load Msgio Msgio->PATH;
54	msgio->init();
55
56	arg := load Arg Arg->PATH;
57	if (arg == nil)
58		badmodule(SSL->PATH);
59
60	decrypt := 0;
61	secret: array of byte;
62	alg := DEFAULTALG;
63
64	arg->init(argv);
65	while ((opt := arg->opt()) != 0) {
66		case opt {
67		'd' =>
68			decrypt = 1;
69		'k' =>
70			if ((s := arg->arg()) == nil)
71				usage();
72			secret = array of byte s;
73		'f' =>
74			if ((f := arg->arg()) == nil)
75				usage();
76			secret = readfile(f);
77		'a' =>
78			if ((alg = arg->arg()) == nil)
79				usage();
80		'?' =>
81			showalgs(sys->fildes(1));
82			return;
83		'v' =>
84			verbose = 1;
85		* =>
86			usage();
87		}
88	}
89	argv = arg->argv();
90	if (argv != nil)
91		usage();
92	if(secret == nil)
93		secret = array of byte readpassword();
94	sk := array[Keyring->SHA1dlen] of byte;
95	keyring->sha1(secret, len secret, sk, nil);
96	if (headers) {
97		# deal with header - the header encodes the algorithm along with the data.
98		if (decrypt) {
99			msg := msgio->getmsg(sys->fildes(0));
100			if (msg != nil)
101				alg = string msg;
102			if (msg == nil || len alg < len ALGSTR || alg[0:len ALGSTR] != ALGSTR)
103				error("couldn't get decrypt algorithm");
104			alg = alg[len ALGSTR:];
105		} else {
106			msg := array of byte ("alg " + alg);
107			e := msgio->sendmsg(sys->fildes(1),  msg, len msg);
108			if (e == -1)
109				error("couldn't write algorithm string");
110		}
111	}
112	fd := docrypt(decrypt, alg, sk);
113	if (decrypt) {
114		# if decrypting, don't use stream, as we want to catch
115		# decryption or checksum errors when they happen.
116		buf := array[Sys->ATOMICIO] of byte;
117		stdout := sys->fildes(1);
118		while ((n := sys->read(fd, buf, len buf)) > 0)
119			sys->write(stdout, buf, n);
120
121		if (n == -1) {
122			err := sys->sprint("%r");
123			if (err != Ehungup)
124				error("decryption failed: " + err);
125		}
126	} else {
127		stream(fd, sys->fildes(1), Sys->ATOMICIO);
128	}
129}
130
131docrypt(decrypt: int, alg: string, sk: array of byte): ref Sys->FD
132{
133	if (verbose)
134		sys->fprint(stderr, "%scrypting with alg %s\n", (array[] of {"en", "de"})[decrypt!=0], alg);
135	(err, fds, nil, nil) := cryptpipe(decrypt, alg, sk);
136	if (err != nil)
137		error(err);
138
139	spawn stream(sys->fildes(0), fds[1], Sys->ATOMICIO);
140	return fds[0];
141}
142
143# set up an encrypt/decrypt session; if decrypt is non-zero, then
144# decrypt, else encrypt. alg is the algorithm to use; sk is the
145# used as the secret key.
146# returns tuple (err, fds, cfd, dir)
147# where err is non-nil on failure;
148# otherwise fds is an array of two fds; writing to fds[1] will make
149# crypted/decrypted data available to be read on fds[0].
150# dir is the ssl directory in question.
151cryptpipe(decrypt: int, alg: string, sk: array of byte): (string, array of ref Sys->FD, ref Sys->FD, string)
152{
153	pfd := array[2] of ref Sys->FD;
154	if (sys->pipe(pfd) == -1)
155		return ("pipe failed", nil, nil, nil);
156
157	(err, c) := ssl->connect(pfd[1]);
158	if (err != nil)
159		return ("could not connect ssl: "+err, nil, nil, nil);
160	pfd[1] = nil;
161	err = ssl->secret(c, sk, sk);
162	if (err != nil)
163		return ("could not write secret: "+err, nil, nil, nil);
164
165	if (alg != nil)
166		if (sys->fprint(c.cfd, "alg %s", alg) == -1)
167			return (sys->sprint("bad algorithm %s: %r", alg), nil, nil, nil);
168
169	fds := array[2] of ref Sys->FD;
170	if (decrypt) {
171		fds[1] = pfd[0];
172		fds[0] = c.dfd;
173	} else {
174		fds[1] = c.dfd;
175		fds[0] = pfd[0];
176	}
177	return (nil, fds, c.cfd, c.dir);
178}
179
180algnames := array[] of {("crypt", "encalgs"), ("hash", "hashalgs")};
181
182# find available algorithms and return as tuple of two lists:
183# (err, hashalgs, cryptalgs)
184algs(): (string, array of list of string)
185{
186	(err, nil, nil, dir) := cryptpipe(0, nil, array[100] of byte);
187	if (err != nil)
188		return (err, nil);
189	alglists := array[len algnames] of list of string;
190	for (i := 0; i < len algnames; i++) {
191		(nil, f) := algnames[i];
192		(nil, alglists[i]) = sys->tokenize(string readfile(dir + "/"  + f), " ");
193	}
194	return (nil, alglists);
195}
196
197showalgs(fd: ref Sys->FD)
198{
199	(err, alglists) := algs();
200	if (err != nil)
201		error("cannot get algorithms: " + err);
202	for (j := 0; j < len alglists; j++) {
203		(name, nil) := algnames[j];
204		sys->fprint(fd, "%s:", name);
205		for (l := alglists[j]; l != nil; l = tl l)
206			sys->fprint(fd, " %s", hd l);
207		sys->fprint(fd, "\n");
208	}
209}
210
211readpassword(): string
212{
213	bufio := load Bufio Bufio->PATH;
214	Iobuf: import bufio;
215	stdin := bufio->open("/dev/cons", Sys->OREAD);
216
217	cfd := sys->open("/dev/consctl", Sys->OWRITE);
218	if (cfd == nil || sys->fprint(cfd, "rawon") <= 0)
219		sys->fprint(stderr, "crypt: warning: cannot hide typed password\n");
220	sys->fprint(stderr, "password: ");
221	s := "";
222	while ((c := stdin.getc()) >= 0 && c != '\n'){
223		case c {
224		'\b' =>
225			if (len s > 0)
226				s = s[0:len s - 1];
227		8r25 =>		# ^U
228			s = nil;
229		* =>
230			s[len s] = c;
231		}
232	}
233	sys->fprint(stderr, "\n");
234	return s;
235}
236
237stream(src, dst: ref Sys->FD, bufsize: int)
238{
239	sys->stream(src, dst, bufsize);
240}
241
242readfile(f: string): array of byte
243{
244	fd := sys->open(f, Sys->OREAD);
245	if (fd == nil)
246		error(sys->sprint("cannot read %s: %r", f));
247	buf := array[8192] of byte;	# >8K key? get real!
248	n := sys->read(fd, buf, len buf);
249	if (n <= 0)
250		return nil;
251	return buf[0:n];
252}
253
254error(s: string)
255{
256	sys->fprint(stderr, "crypt: %s\n", s);
257	fail("error");
258}
259
260fail(e: string)
261{
262	raise "fail: "+e;
263}
264