xref: /inferno-os/appl/lib/secstore.b (revision 728860af799ffd5aa8b3b90576ae582b11b7f5a5)
1implement Secstore;
2
3#
4# interact with the Plan 9 secstore
5#
6
7include "sys.m";
8	sys: Sys;
9
10include "dial.m";
11	dialler: Dial;
12
13include "keyring.m";
14	kr: Keyring;
15	DigestState, IPint: import kr;
16	AESbsize, AESstate: import kr;
17
18include "security.m";
19	ssl: SSL;
20	random: Random;
21
22include "encoding.m";
23	base64: Encoding;
24
25include "secstore.m";
26
27
28init()
29{
30	sys = load Sys Sys->PATH;
31	kr = load Keyring Keyring->PATH;
32	ssl = load SSL SSL->PATH;
33	random = load Random Random->PATH;
34	base64 = load Encoding Encoding->BASE64PATH;
35	dialler = load Dial Dial->PATH;
36	initPAKparams();
37}
38
39privacy(): int
40{
41	fd := sys->open("#p/"+string sys->pctl(0, nil)+"/ctl", Sys->OWRITE);
42	if(fd == nil || sys->fprint(fd, "private") < 0)
43		return 0;
44	return 1;
45}
46
47connect(addr: string, user: string, pwhash: array of byte): (ref Dial->Connection, string, string)
48{
49	conn := dial(addr);
50	if(conn == nil){
51		sys->werrstr(sys->sprint("can't dial %s: %r", addr));
52		return (nil, nil, sys->sprint("%r"));
53	}
54	(sname, diag) := auth(conn, user, pwhash);
55	if(sname == nil){
56		sys->werrstr(sys->sprint("can't authenticate: %s", diag));
57		return (nil, nil, sys->sprint("%r"));
58	}
59	return (conn, sname, diag);
60}
61
62dial(netaddr: string): ref Dial->Connection
63{
64	if(netaddr == nil)
65		netaddr = "net!$auth!secstore";
66	conn := dialler->dial(netaddr, nil);
67	if(conn == nil)
68		return nil;
69	(err, sslconn) := ssl->connect(conn.dfd);
70	if(err != nil)
71		sys->werrstr(err);
72	return sslconn;
73}
74
75auth(conn: ref Dial->Connection, user: string, pwhash: array of byte): (string, string)
76{
77	sname := PAKclient(conn, user, pwhash);
78	if(sname == nil)
79		return (nil, sys->sprint("%r"));
80	s := readstr(conn.dfd);
81	if(s == "STA")
82		return (sname, "need pin");
83	if(s != "OK"){
84		if(s != nil)
85			sys->werrstr(s);
86		return (nil, sys->sprint("%r"));
87	}
88	return (sname, nil);
89}
90
91cansecstore(netaddr: string, user: string): int
92{
93	conn := dial(netaddr);
94	if(conn == nil)
95		return 0;
96	if(sys->fprint(conn.dfd, "secstore\tPAK\nC=%s\nm=0\n", user) < 0)
97		return 0;
98	buf := array[128] of byte;
99	n := sys->read(conn.dfd, buf, len buf);
100	if(n <= 0)
101		return 0;
102	return string buf[0:n] == "!account exists";
103}
104
105sendpin(conn: ref Dial->Connection, pin: string): int
106{
107	if(sys->fprint(conn.dfd, "STA%s", pin) < 0)
108		return -1;
109	s := readstr(conn.dfd);
110	if(s != "OK"){
111		if(s != nil)
112			sys->werrstr(s);
113		return -1;
114	}
115	return 0;
116}
117
118files(conn: ref Dial->Connection): list of (string, int, string, string, array of byte)
119{
120	file := getfile(conn, ".", 0);
121	if(file == nil)
122		return nil;
123	rl: list of (string, int, string, string, array of byte);
124	for(linelist := lines(file); linelist != nil; linelist = tl linelist){
125		s := string hd linelist;
126		# factotum\t2552 Dec  9 13:04:49 GMT 2005 n9wSk45SPDxgljOIflGQoXjOkjs=
127		for(i := 0; i < len s && s[i] != '\t' && s[i] != ' '; i++){}	# can be trailing spaces
128		name := s[0:i];
129		for(; i < len s && (s[i] == ' ' || s[i] == '\t'); i++){}
130		for(j := i; j  < len s && s[j] != ' '; j++){}
131		size := int s[i+1:j];
132		for(i = j; i < len s && s[i] == ' '; i++){}
133		date := s[i:i+24];
134		i += 24+1;
135		for(j = i; j < len s && s[j] != '\n'; j++){}
136		sha1 := s[i:j];
137		rl = (name, int size, date, sha1, base64->dec(sha1)) :: rl;
138	}
139	l: list of (string, int, string, string, array of byte);
140	for(; rl != nil; rl = tl rl)
141		l = hd rl :: l;
142	return l;
143}
144
145getfile(conn: ref Dial->Connection, name: string, maxsize: int): array of byte
146{
147	fd := conn.dfd;
148	if(maxsize <= 0)
149		maxsize = Maxfilesize;
150	if(sys->fprint(fd, "GET %s\n", name) < 0 ||
151	   (s := readstr(fd)) == nil){
152		sys->werrstr(sys->sprint("can't get %q: %r", name));
153		return nil;
154	}
155	nb := int s;
156	if(nb == -1){
157		sys->werrstr(sys->sprint("remote file %q does not exist", name));
158		return nil;
159	}
160	if(nb < 0 || nb > maxsize){
161		sys->werrstr(sys->sprint("implausible file size %d for %q", nb, name));
162		return nil;
163	}
164	file := array[nb] of byte;
165	for(nr := 0; nr < nb;){
166		n :=  sys->read(fd, file[nr:], nb-nr);
167		if(n < 0){
168			sys->werrstr(sys->sprint("error reading %q: %r", name));
169			return nil;
170		}
171		if(n == 0){
172			sys->werrstr(sys->sprint("empty file chunk reading %q at offset %d", name, nr));
173			return nil;
174		}
175		nr += n;
176	}
177	return file;
178}
179
180remove(conn: ref Dial->Connection, name: string): int
181{
182	if(sys->fprint(conn.dfd, "RM %s\n", name) < 0)
183		return -1;
184
185	return 0;
186}
187
188putfile(conn: ref Dial->Connection, name: string, data: array of byte): int
189{
190	if(len data > Maxfilesize){
191		sys->werrstr("file too long");
192		return -1;
193	}
194	fd := conn.dfd;
195	if(sys->fprint(fd, "PUT %s\n", name) < 0)
196		return -1;
197	if(sys->fprint(fd, "%d", len data) < 0)
198		return -1;
199	for(o := 0; o < len data;){
200		n := len data-o;
201		if(n > Maxmsg)
202			n = Maxmsg;
203		if(sys->write(fd, data[o:o+n], n) != n)
204			return -1;
205		o += n;
206	}
207	return 0;
208}
209
210bye(conn: ref Dial->Connection)
211{
212	if(conn != nil){
213		if(conn.dfd != nil)
214			sys->fprint(conn.dfd, "BYE");
215		conn.dfd = nil;
216		conn.cfd = nil;
217	}
218}
219
220mkseckey(s: string): array of byte
221{
222	key := array of byte s;
223	skey := array[Keyring->SHA1dlen] of byte;
224	kr->sha1(key, len key, skey, nil);
225	erasekey(key);
226	return skey;
227}
228
229Checkpat: con "XXXXXXXXXXXXXXXX";	# it's what Plan 9's aescbc uses
230Checklen: con len Checkpat;
231
232mkfilekey(s: string): array of byte
233{
234	key := array of byte s;
235	skey := array[Keyring->SHA1dlen] of byte;
236	sha := kr->sha1(array of byte "aescbc file", 11, nil, nil);
237	kr->sha1(key, len key, skey, sha);
238	erasekey(key);
239	erasekey(skey[AESbsize:]);
240	return skey[0:AESbsize];
241}
242
243decrypt(file: array of byte, key: array of byte): array of byte
244{
245	length := len file;
246	if(length == 0)
247		return file;
248	if(length < AESbsize+Checklen)
249		return nil;
250	state := kr->aessetup(key, file[0:AESbsize]);
251	if(state == nil){
252		sys->werrstr("can't set AES state");
253		return nil;
254	}
255	kr->aescbc(state, file[AESbsize:], length-AESbsize, Keyring->Decrypt);
256	if(string file[length-Checklen:] != Checkpat){
257		sys->werrstr("file did not decrypt correctly");
258		return nil;
259	}
260	return file[AESbsize: length-Checklen];
261}
262
263encrypt(file: array of byte, key: array of byte): array of byte
264{
265	dat := array[AESbsize+len file+Checklen] of byte;
266	iv := random->randombuf(random->NotQuiteRandom, AESbsize);
267	if(len iv != AESbsize)
268		return nil;
269	dat[:] = iv;
270	dat[len iv:] = file;
271	dat[len iv+len file:] = array of byte Checkpat;
272	state := kr->aessetup(key, iv);
273	if(state == nil){
274		sys->werrstr("can't set AES state");
275		return nil;
276	}
277	kr->aescbc(state, dat[AESbsize:], len dat-AESbsize, Keyring->Encrypt);
278	return dat;
279}
280
281lines(file: array of byte): list of array of byte
282{
283	rl: list of array of byte;
284	for(i := 0; i < len file;){
285		for(j := i; j < len file; j++)
286			if(file[j] == byte '\n'){
287				j++;
288				break;
289			}
290		rl = file[i:j] :: rl;
291		i = j;
292	}
293	l: list of array of byte;
294	for(; rl != nil; rl = tl rl)
295		l = (hd rl) :: l;
296	return l;
297}
298
299readstr(fd: ref Sys->FD): string
300{
301	buf := array[500] of byte;
302	n := sys->read(fd, buf, len buf);
303	if(n < 0)
304		return nil;
305	s := string buf[0:n];
306	if(s[0] == '!'){
307		sys->werrstr(s[1:]);
308		return nil;
309	}
310	return s;
311}
312
313writerr(fd: ref Sys->FD, s: string)
314{
315	sys->fprint(fd, "!%s", s);
316	sys->werrstr(s);
317}
318
319setsecret(conn: ref Dial->Connection, sigma: array of byte, direction: int): string
320{
321	secretin := array[Keyring->SHA1dlen] of byte;
322	secretout := array[Keyring->SHA1dlen] of byte;
323	if(direction != 0){
324		kr->hmac_sha1(sigma, len sigma, array of byte "one", secretout, nil);
325		kr->hmac_sha1(sigma, len sigma, array of byte "two", secretin, nil);
326	}else{
327		kr->hmac_sha1(sigma, len sigma, array of byte "two", secretout, nil);
328		kr->hmac_sha1(sigma, len sigma, array of byte "one", secretin, nil);
329	}
330	return ssl->secret(conn, secretin, secretout);
331}
332
333erasekey(a: array of byte)
334{
335	for(i := 0; i < len a; i++)
336		a[i] = byte 0;
337}
338
339#
340# the following must only be used to talk to a Plan 9 secstore
341#
342
343VERSION: con "secstore";
344
345PAKparams: adt {
346	q:	ref IPint;
347	p:	ref IPint;
348	r:	ref IPint;
349	g:	ref IPint;
350};
351
352pak: ref PAKparams;
353
354# from seed EB7B6E35F7CD37B511D96C67D6688CC4DD440E1E
355
356initPAKparams()
357{
358	if(pak != nil)
359		return;
360	lpak := ref PAKparams;
361	lpak.q = IPint.strtoip("E0F0EF284E10796C5A2A511E94748BA03C795C13", 16);
362	lpak.p = IPint.strtoip("C41CFBE4D4846F67A3DF7DE9921A49D3B42DC33728427AB159CEC8CBB"+
363		"DB12B5F0C244F1A734AEB9840804EA3C25036AD1B61AFF3ABBC247CD4B384224567A86"+
364		"3A6F020E7EE9795554BCD08ABAD7321AF27E1E92E3DB1C6E7E94FAAE590AE9C48F96D9"+
365		"3D178E809401ABE8A534A1EC44359733475A36A70C7B425125062B1142D", 16);
366	lpak.r = IPint.strtoip("DF310F4E54A5FEC5D86D3E14863921E834113E060F90052AD332B3241"+
367		"CEF2497EFA0303D6344F7C819691A0F9C4A773815AF8EAECFB7EC1D98F039F17A32A7E"+
368		"887D97251A927D093F44A55577F4D70444AEBD06B9B45695EC23962B175F266895C67D"+
369		"21C4656848614D888A4", 16);
370	lpak.g = IPint.strtoip("2F1C308DC46B9A44B52DF7DACCE1208CCEF72F69C743ADD4D23271734"+
371		"44ED6E65E074694246E07F9FD4AE26E0FDDD9F54F813C40CB9BCD4338EA6F242AB94CD"+
372		"410E676C290368A16B1A3594877437E516C53A6EEE5493A038A017E955E218E7819734"+
373		"E3E2A6E0BAE08B14258F8C03CC1B30E0DDADFCF7CEDF0727684D3D255F1", 16);
374	pak = lpak;	# atomic store
375}
376
377# H = (sha(ver,C,sha(passphrase)))^r mod p,
378# a hash function expensive to attack by brute force.
379
380longhash(ver: string, C: string, passwd: array of byte): ref IPint
381{
382	aver := array of byte ver;
383	aC := array of byte C;
384	Cp := array[len aver + len aC + len passwd] of byte;
385	Cp[0:] = aver;
386	Cp[len aver:] = aC;
387	Cp[len aver+len aC:] = passwd;
388	buf := array[7*Keyring->SHA1dlen] of byte;
389	for(i := 0; i < 7; i++){
390		key := array[] of { byte('A'+i) };
391		kr->hmac_sha1(Cp, len Cp, key, buf[i*Keyring->SHA1dlen:], nil);
392	}
393	erasekey(Cp);
394	return mod(IPint.bebytestoip(buf), pak.p).expmod(pak.r, pak.p);	# H
395}
396
397mod(a, b: ref IPint): ref IPint
398{
399	return a.div(b).t1;
400}
401
402shaz(s: string, digest: array of byte, state: ref DigestState): ref DigestState
403{
404	a := array of byte s;
405	state = kr->sha1(a, len a, digest, state);
406	erasekey(a);
407	return state;
408}
409
410# Hi = H^-1 mod p
411PAK_Hi(C: string, passhash: array of byte): (string, ref IPint, ref IPint)
412{
413	H := longhash(VERSION, C, passhash);
414	Hi := H.invert(pak.p);
415	return (Hi.iptostr(64), H, Hi);
416}
417
418# another, faster, hash function for each party to
419# confirm that the other has the right secrets.
420
421shorthash(mess: string, C: string, S: string, m: string, mu: string, sigma: string, Hi: string): array of byte
422{
423	state := shaz(mess, nil, nil);
424	state = shaz(C, nil, state);
425	state = shaz(S, nil, state);
426	state = shaz(m, nil, state);
427	state = shaz(mu, nil, state);
428	state = shaz(sigma, nil, state);
429	state = shaz(Hi, nil, state);
430	state = shaz(mess, nil, state);
431	state = shaz(C, nil, state);
432	state = shaz(S, nil, state);
433	state = shaz(m, nil, state);
434	state = shaz(mu, nil, state);
435	state = shaz(sigma, nil, state);
436	digest := array[Keyring->SHA1dlen] of byte;
437	shaz(Hi, digest, state);
438	return digest;
439}
440
441#
442# On input, conn provides an open channel to the server;
443#	C is the name this client calls itself;
444#	pass is the user's passphrase
445# On output, session secret has been set in conn
446#	(unless return code is negative, which means failure).
447#
448PAKclient(conn: ref Dial->Connection, C: string, pwhash: array of byte): string
449{
450	dfd := conn.dfd;
451
452	(hexHi, H, nil) := PAK_Hi(C, pwhash);
453
454	# random 1<=x<=q-1; send C, m=g**x H
455	x := mod(IPint.random(240, 240), pak.q);
456	if(x.eq(IPint.inttoip(0)))
457		x = IPint.inttoip(1);
458	m := mod(pak.g.expmod(x, pak.p).mul(H), pak.p);
459	hexm := m.iptostr(64);
460
461	if(sys->fprint(dfd, "%s\tPAK\nC=%s\nm=%s\n", VERSION, C, hexm) < 0)
462		return nil;
463
464	# recv g**y, S, check hash1(g**xy)
465	s := readstr(dfd);
466	if(s == nil){
467		e := sys->sprint("%r");
468		writerr(dfd, "couldn't read g**y");
469		sys->werrstr(e);
470		return nil;
471	}
472	# should be: "mu=%s\nk=%s\nS=%s\n"
473	(nf, flds) := sys->tokenize(s, "\n");
474	if(nf != 3){
475		writerr(dfd, "verifier syntax  error");
476		return nil;
477	}
478	hexmu := ex("mu=", hd flds); flds = tl flds;
479	ks := ex("k=", hd flds); flds = tl flds;
480	S := ex("S=", hd flds);
481	if(hexmu == nil || ks == nil || S == nil){
482		writerr(dfd, "verifier syntax error");
483		return nil;
484	}
485	mu := IPint.strtoip(hexmu, 64);
486	sigma := mu.expmod(x, pak.p);
487	hexsigma := sigma.iptostr(64);
488	digest := shorthash("server", C, S, hexm, hexmu, hexsigma, hexHi);
489	kc := base64->enc(digest);
490	if(ks != kc){
491		writerr(dfd, "verifier didn't match");
492		return nil;
493	}
494
495	# send hash2(g**xy)
496	digest = shorthash("client", C, S, hexm, hexmu, hexsigma, hexHi);
497	kc = base64->enc(digest);
498	if(sys->fprint(dfd, "k'=%s\n", kc) < 0)
499		return nil;
500
501	# set session key
502	digest = shorthash("session", C, S, hexm, hexmu, hexsigma, hexHi);
503	for(i := 0; i < len hexsigma; i++)
504		hexsigma[i] = 0;
505
506	err := setsecret(conn, digest, 0);
507	if(err != nil)
508		return nil;
509	erasekey(digest);
510	if(sys->fprint(conn.cfd, "alg sha1 rc4_128") < 0)
511		return nil;
512	return S;
513}
514
515ex(tag: string, s: string): string
516{
517	if(len s < len tag || s[0:len tag] != tag)
518		return nil;
519	return s[len tag:];
520}
521