xref: /inferno-os/appl/cmd/auth/aescbc.b (revision a6011949be081a8fe1bec0713ce60c36beb3a351)
1implement Aescbc;
2
3#
4# broadly transliterated from the Plan 9 command
5#
6
7include "sys.m";
8	sys: Sys;
9
10include "draw.m";
11
12include "bufio.m";
13	bufio: Bufio;
14	Iobuf: import bufio;
15
16include "keyring.m";
17	kr: Keyring;
18	AESbsize, MD5dlen, SHA1dlen: import Keyring;
19
20include "arg.m";
21
22Aescbc: module
23{
24	init:	fn(nil: ref Draw->Context, nil: list of string);
25};
26
27#
28# encrypted file: v2hdr, 16 byte IV, AES-CBC(key, random || file), HMAC_SHA1(md5(key), AES-CBC(random || file))
29#
30
31Checkpat: con "XXXXXXXXXXXXXXXX";
32Checklen: con len Checkpat;
33Bufsize: con 4096;
34AESmaxkey: con 32;
35
36V2hdr: con "AES CBC SHA1  2\n";
37
38bin: ref Iobuf;
39bout: ref Iobuf;
40stderr: ref Sys->FD;
41
42init(nil: ref Draw->Context, args: list of string)
43{
44	sys = load Sys Sys->PATH;
45	kr = load Keyring Keyring->PATH;
46	bufio = load Bufio Bufio->PATH;
47
48	sys->pctl(Sys->FORKFD, nil);
49	stderr = sys->fildes(2);
50	arg := load Arg Arg->PATH;
51	arg->init(args);
52	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");
53	encrypt := -1;
54	keyfile: string;
55	pass: string;
56	while((o := arg->opt()) != 0)
57		case o {
58		'd' or 'e' =>
59			if(encrypt >= 0)
60				arg->usage();
61			encrypt = o == 'e';
62		'f' =>
63			keyfile = arg->earg();
64		'k' =>
65			pass = arg->earg();
66		* =>
67			arg->usage();
68		}
69	args = arg->argv();
70	if(args != nil || encrypt < 0)
71		arg->usage();
72	arg = nil;
73
74	bin = bufio->fopen(sys->fildes(0), Bufio->OREAD);
75	bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE);
76
77	buf := array[Bufsize+SHA1dlen] of byte;	# Checklen <= SHA1dlen
78
79	pwd: array of byte;
80	if(keyfile != nil){
81		fd := sys->open(keyfile, Sys->OREAD);
82		if(fd == nil)
83			error(sys->sprint("can't open %q: %r", keyfile), "keyfile");
84		n := sys->readn(fd, buf, len buf);
85		while(n > 0 && buf[n-1] == byte '\n')
86			n--;
87		if(n <= 0)
88			error("no key", "no key");
89		pwd = buf[0:n];
90	}else{
91		if(pass == nil)
92			pass = readpassword("password");
93		if(pass == nil)
94			error("no key", "no key");
95		pwd = array of byte pass;
96		for(i := 0;  i < len pass; i++)
97			pass[i] = 0;
98	}
99	key := array[AESmaxkey] of byte;
100	key2 := array[SHA1dlen] of byte;
101	dstate := kr->sha1(array of byte "aescbc file", 11, nil, nil);
102	kr->sha1(pwd, len pwd, key2, dstate);
103	for(i := 0; i < len pwd; i++)
104		pwd[i] = byte 0;
105	key[0:] = key2[0:MD5dlen];
106	nkey := MD5dlen;
107	kr->md5(key, nkey, key2, nil);	# protect key even if HMAC_SHA1 is broken
108	key2 = key2[0:MD5dlen];
109
110	if(encrypt){
111		Write(array of byte V2hdr, AESbsize);
112		genrandom(buf, 2*AESbsize); # CBC is semantically secure if IV is unpredictable.
113		aes := kr->aessetup(key[0:nkey], buf);  # use first AESbsize bytes as IV
114		kr->aescbc(aes, buf[AESbsize:], AESbsize, Keyring->Encrypt);  # use second AESbsize bytes as initial plaintext
115		Write(buf, 2*AESbsize);
116		dstate = kr->hmac_sha1(buf[AESbsize:], AESbsize, key2, nil, nil);
117		while((n := bin.read(buf, Bufsize)) > 0){
118			kr->aescbc(aes, buf, n, Keyring->Encrypt);
119			Write(buf, n);
120			dstate = kr->hmac_sha1(buf, n, key2, nil, dstate);
121			if(n < Bufsize)
122				break;
123		}
124		if(n < 0)
125			error(sys->sprint("read error: %r"), "read error");
126		kr->hmac_sha1(nil, 0, key2, buf, dstate);
127		Write(buf, SHA1dlen);
128	}else{	# decrypt
129		Read(buf, AESbsize);
130		if(string buf[0:AESbsize] == V2hdr){
131			Read(buf, 2*AESbsize);	# read IV and random initial plaintext
132			aes := kr->aessetup(key[0:nkey], buf);
133			dstate = kr->hmac_sha1(buf[AESbsize:], AESbsize, key2, nil, nil);
134			kr->aescbc(aes, buf[AESbsize:], AESbsize, Keyring->Decrypt);
135			Read(buf, SHA1dlen);
136			while((n := bin.read(buf[SHA1dlen:], Bufsize)) > 0){
137				dstate = kr->hmac_sha1(buf, n, key2, nil, dstate);
138				kr->aescbc(aes, buf, n, Keyring->Decrypt);
139				Write(buf, n);
140				buf[0:] = buf[n:n+SHA1dlen];	# these bytes are not yet decrypted
141			}
142			kr->hmac_sha1(nil, 0, key2, buf[SHA1dlen:], dstate);
143			if(!eqbytes(buf, buf[SHA1dlen:], SHA1dlen))
144				error("decrypted file failed to authenticate", "failed to authenticate");
145		}else{	# compatibility with past mistake; assume we're decrypting secstore files
146			aes := kr->aessetup(key[0:AESbsize], buf);
147			Read(buf, Checklen);
148			kr->aescbc(aes, buf, Checklen, Keyring->Decrypt);
149			while((n := bin.read(buf[Checklen:], Bufsize)) > 0){
150				kr->aescbc(aes, buf[Checklen:], n, Keyring->Decrypt);
151				Write(buf, n);
152				buf[0:] = buf[n:n+Checklen];
153			}
154			if(string buf[0:Checklen] != Checkpat)
155				error("decrypted file failed to authenticate", "failed to authenticate");
156		}
157	}
158	bout.flush();
159}
160
161error(s: string, why: string)
162{
163	bout.flush();
164	sys->fprint(stderr, "aescbc: %s\n", s);
165	raise "fail:"+why;
166}
167
168eqbytes(a: array of byte, b: array of byte, n: int): int
169{
170	if(len a < n || len b < n)
171		return 0;
172	for(i := 0; i < n; i++)
173		if(a[i] != b[i])
174			return 0;
175	return 1;
176}
177
178Read(buf: array of byte, n: int)
179{
180	if(bin.read(buf, n) != n){
181		sys->fprint(sys->fildes(2), "aescbc: unexpectedly short read\n");
182		raise "fail:read error";
183	}
184}
185
186Write(buf: array of byte, n: int)
187{
188	if(bout.write(buf,  n) != n){
189		sys->fprint(sys->fildes(2), "aescbc: write error: %r\n");
190		raise "fail:write error";
191	}
192}
193
194readpassword(prompt: string): string
195{
196	cons := sys->open("/dev/cons", Sys->ORDWR);
197	if(cons == nil)
198		return nil;
199	stdin := bufio->fopen(cons, Sys->OREAD);
200	if(stdin == nil)
201		return nil;
202	cfd := sys->open("/dev/consctl", Sys->OWRITE);
203	if (cfd == nil || sys->fprint(cfd, "rawon") <= 0)
204		sys->fprint(stderr, "aescbc: warning: cannot hide typed password\n");
205	s: string;
206L:
207	for(;;){
208		sys->fprint(cons, "%s: ", prompt);
209		s = "";
210		while ((c := stdin.getc()) >= 0){
211			case c {
212			'\n' =>
213				break L;
214			'\b' or 8r177 =>
215				if(len s > 0)
216					s = s[0:len s - 1];
217			'u' & 8r037 =>
218				sys->fprint(cons, "\n");
219				continue L;
220			* =>
221				s[len s] = c;
222			}
223		}
224	}
225	sys->fprint(cons, "\n");
226	return s;
227}
228
229genrandom(b: array of byte, n: int)
230{
231	fd := sys->open("/dev/notquiterandom", Sys->OREAD);
232	if(fd == nil){
233		sys->fprint(stderr, "aescbc: can't open /dev/notquiterandom: %r\n");
234		raise "fail:random";
235	}
236	if(sys->read(fd, b, n) != n){
237		sys->fprint(stderr, "aescbc: can't read random numbers: %r\n");
238		raise "fail:read random";
239	}
240}
241