xref: /plan9/sys/src/cmd/auth/secstore/secstore.c (revision 2e54da428eebf47f42ea14de775e10f38b9f44a1)
1 /* secstore - network login client */
2 #include <u.h>
3 #include <libc.h>
4 #include <mp.h>
5 #include <libsec.h>
6 #include <authsrv.h>
7 #include "SConn.h"
8 #include "secstore.h"
9 
10 enum{ CHK = 16, MAXFILES = 100 };
11 
12 typedef struct AuthConn{
13 	SConn	*conn;
14 	char	pass[64];
15 	int	passlen;
16 } AuthConn;
17 
18 int verbose;
19 Nvrsafe nvr;
20 
21 void
usage(void)22 usage(void)
23 {
24 	fprint(2, "usage: secstore [-cinv] [-[gG] getfile] [-p putfile] "
25 		"[-r rmfile] [-s tcp!server!5356] [-u user]\n");
26 	exits("usage");
27 }
28 
29 static int
getfile(SConn * conn,char * gf,uchar ** buf,ulong * buflen,uchar * key,int nkey)30 getfile(SConn *conn, char *gf, uchar **buf, ulong *buflen, uchar *key, int nkey)
31 {
32 	int fd = -1, i, n, nr, nw, len;
33 	char s[Maxmsg+1];
34 	uchar skey[SHA1dlen], ib[Maxmsg+CHK], *ibr, *ibw, *bufw, *bufe;
35 	AESstate aes;
36 	DigestState *sha;
37 
38 	memset(&aes, 0, sizeof aes);
39 
40 	snprint(s, Maxmsg, "GET %s", gf);
41 	conn->write(conn, (uchar*)s, strlen(s));
42 
43 	/* get file size */
44 	s[0] = '\0';
45 	bufw = bufe = nil;
46 	if(readstr(conn, s) < 0){
47 		fprint(2, "secstore: remote: %s\n", s);
48 		return -1;
49 	}
50 	len = atoi(s);
51 	if(len == -1){
52 		fprint(2, "secstore: remote file %s does not exist\n", gf);
53 		return -1;
54 	}else if(len == -3){
55 		fprint(2, "secstore: implausible filesize for %s\n", gf);
56 		return -1;
57 	}else if(len < 0){
58 		fprint(2, "secstore: GET refused for %s\n", gf);
59 		return -1;
60 	}
61 	if(buf != nil){
62 		*buflen = len - AESbsize - CHK;
63 		*buf = bufw = emalloc(len);
64 		bufe = bufw + len;
65 	}
66 
67 	/* directory listing */
68 	if(strcmp(gf,".")==0){
69 		if(buf != nil)
70 			*buflen = len;
71 		for(i=0; i < len; i += n){
72 			if((n = conn->read(conn, (uchar*)s, Maxmsg)) <= 0){
73 				fprint(2, "secstore: empty file chunk\n");
74 				return -1;
75 			}
76 			if(buf == nil)
77 				write(1, s, n);
78 			else
79 				memmove(*buf + i, s, n);
80 		}
81 		return 0;
82 	}
83 
84 	/*
85 	 * conn is already encrypted against wiretappers, but gf is also
86 	 * encrypted against server breakin.
87 	 */
88 	if(buf == nil && (fd = create(gf, OWRITE, 0600)) < 0){
89 		fprint(2, "secstore: can't open %s: %r\n", gf);
90 		return -1;
91 	}
92 
93 	ibr = ibw = ib;
94 	for(nr=0; nr < len;){
95 		if((n = conn->read(conn, ibw, Maxmsg)) <= 0){
96 			fprint(2, "secstore: empty file chunk n=%d nr=%d len=%d: %r\n",
97 				n, nr, len);
98 			return -1;
99 		}
100 		nr += n;
101 		ibw += n;
102 		if(!aes.setup){		/* first time, read 16 byte IV */
103 			if(n < AESbsize){
104 				fprint(2, "secstore: no IV in file\n");
105 				return -1;
106 			}
107 			sha = sha1((uchar*)"aescbc file", 11, nil, nil);
108 			sha1(key, nkey, skey, sha);
109 			setupAESstate(&aes, skey, AESbsize, ibr);
110 			memset(skey, 0, sizeof skey);
111 			ibr += AESbsize;
112 			n   -= AESbsize;
113 		}
114 		aesCBCdecrypt(ibw-n, n, &aes);
115 		n = ibw - ibr - CHK;
116 		if(n > 0){
117 			if(buf == nil){
118 				nw = write(fd, ibr, n);
119 				if(nw != n){
120 					fprint(2, "secstore: write error on %s", gf);
121 					return -1;
122 				}
123 			}else{
124 				assert(bufw + n <= bufe);
125 				memmove(bufw, ibr, n);
126 				bufw += n;
127 			}
128 			ibr += n;
129 		}
130 		memmove(ib, ibr, ibw-ibr);
131 		ibw = ib + (ibw-ibr);
132 		ibr = ib;
133 	}
134 	if(buf == nil)
135 		close(fd);
136 	n = ibw-ibr;
137 	if(n != CHK || memcmp(ib, "XXXXXXXXXXXXXXXX", CHK) != 0){
138 		fprint(2, "secstore: decrypted file failed to authenticate!\n");
139 		return -1;
140 	}
141 	return 0;
142 }
143 
144 /*
145  * This sends a file to the secstore disk that can, in an emergency, be
146  * decrypted by the program aescbc.c.
147  */
148 static int
putfile(SConn * conn,char * pf,uchar * buf,ulong len,uchar * key,int nkey)149 putfile(SConn *conn, char *pf, uchar *buf, ulong len, uchar *key, int nkey)
150 {
151 	int i, n, fd, ivo, bufi, done;
152 	char s[Maxmsg];
153 	uchar skey[SHA1dlen], b[CHK+Maxmsg], IV[AESbsize];
154 	AESstate aes;
155 	DigestState *sha;
156 
157 	/* create initialization vector */
158 	srand(time(0));			/* doesn't need to be unpredictable */
159 	for(i=0; i<AESbsize; i++)
160 		IV[i] = 0xff & rand();
161 	sha = sha1((uchar*)"aescbc file", 11, nil, nil);
162 	sha1(key, nkey, skey, sha);
163 	setupAESstate(&aes, skey, AESbsize, IV);
164 	memset(skey, 0, sizeof skey);
165 
166 	snprint(s, Maxmsg, "PUT %s", pf);
167 	conn->write(conn, (uchar*)s, strlen(s));
168 
169 	if(buf == nil){
170 		/* get file size */
171 		if((fd = open(pf, OREAD)) < 0){
172 			fprint(2, "secstore: can't open %s: %r\n", pf);
173 			return -1;
174 		}
175 		len = seek(fd, 0, 2);
176 		seek(fd, 0, 0);
177 	} else
178 		fd = -1;
179 	if(len > MAXFILESIZE){
180 		fprint(2, "secstore: implausible filesize %ld for %s\n",
181 			len, pf);
182 		return -1;
183 	}
184 
185 	/* send file size */
186 	snprint(s, Maxmsg, "%ld", len + AESbsize + CHK);
187 	conn->write(conn, (uchar*)s, strlen(s));
188 
189 	/* send IV and file+XXXXX in Maxmsg chunks */
190 	ivo = AESbsize;
191 	bufi = 0;
192 	memcpy(b, IV, ivo);
193 	for(done = 0; !done; ){
194 		if(buf == nil){
195 			n = read(fd, b+ivo, Maxmsg-ivo);
196 			if(n < 0){
197 				fprint(2, "secstore: read error on %s: %r\n",
198 					pf);
199 				return -1;
200 			}
201 		}else{
202 			if((n = len - bufi) > Maxmsg-ivo)
203 				n = Maxmsg-ivo;
204 			memcpy(b+ivo, buf+bufi, n);
205 			bufi += n;
206 		}
207 		n += ivo;
208 		ivo = 0;
209 		if(n < Maxmsg){		/* EOF on input; append XX... */
210 			memset(b+n, 'X', CHK);
211 			n += CHK;	/* might push n>Maxmsg */
212 			done = 1;
213 		}
214 		aesCBCencrypt(b, n, &aes);
215 		if(n > Maxmsg){
216 			assert(done==1);
217 			conn->write(conn, b, Maxmsg);
218 			n -= Maxmsg;
219 			memmove(b, b+Maxmsg, n);
220 		}
221 		conn->write(conn, b, n);
222 	}
223 
224 	if(buf == nil)
225 		close(fd);
226 	fprint(2, "secstore: saved %ld bytes\n", len);
227 
228 	return 0;
229 }
230 
231 static int
removefile(SConn * conn,char * rf)232 removefile(SConn *conn, char *rf)
233 {
234 	char buf[Maxmsg];
235 
236 	if(strchr(rf, '/') != nil){
237 		fprint(2, "secstore: simple filenames, not paths like %s\n", rf);
238 		return -1;
239 	}
240 
241 	snprint(buf, Maxmsg, "RM %s", rf);
242 	conn->write(conn, (uchar*)buf, strlen(buf));
243 
244 	return 0;
245 }
246 
247 static int
cmd(AuthConn * c,char ** gf,int * Gflag,char ** pf,char ** rf)248 cmd(AuthConn *c, char **gf, int *Gflag, char **pf, char **rf)
249 {
250 	ulong len;
251 	int rv = -1;
252 	uchar *memfile, *memcur, *memnext;
253 
254 	while(*gf != nil){
255 		if(verbose)
256 			fprint(2, "get %s\n", *gf);
257 		if(getfile(c->conn, *gf, *Gflag? &memfile: nil, &len,
258 		    (uchar*)c->pass, c->passlen) < 0)
259 			goto Out;
260 		if(*Gflag){
261 			/* write 1 line at a time, as required by /mnt/factotum/ctl */
262 			memcur = memfile;
263 			while(len>0){
264 				memnext = (uchar*)strchr((char*)memcur, '\n');
265 				if(memnext){
266 					write(1, memcur, memnext-memcur+1);
267 					len -= memnext-memcur+1;
268 					memcur = memnext+1;
269 				}else{
270 					write(1, memcur, len);
271 					break;
272 				}
273 			}
274 			free(memfile);
275 		}
276 		gf++;
277 		Gflag++;
278 	}
279 	while(*pf != nil){
280 		if(verbose)
281 			fprint(2, "put %s\n", *pf);
282 		if(putfile(c->conn, *pf, nil, 0, (uchar*)c->pass, c->passlen) < 0)
283 			goto Out;
284 		pf++;
285 	}
286 	while(*rf != nil){
287 		if(verbose)
288 			fprint(2, "rm  %s\n", *rf);
289 		if(removefile(c->conn, *rf) < 0)
290 			goto Out;
291 		rf++;
292 	}
293 
294 	c->conn->write(c->conn, (uchar*)"BYE", 3);
295 	rv = 0;
296 
297 Out:
298 	c->conn->free(c->conn);
299 	return rv;
300 }
301 
302 static int
chpasswd(AuthConn * c,char * id)303 chpasswd(AuthConn *c, char *id)
304 {
305 	int rv = -1, newpasslen = 0;
306 	ulong len;
307 	uchar *memfile;
308 	char *newpass, *passck, *list, *cur, *next, *hexHi;
309 	char *f[8], prompt[128];
310 	mpint *H, *Hi;
311 
312 	H = mpnew(0);
313 	Hi = mpnew(0);
314 	/* changing our password is vulnerable to connection failure */
315 	for(;;){
316 		snprint(prompt, sizeof(prompt), "new password for %s: ", id);
317 		newpass = getpassm(prompt);
318 		if(newpass == nil)
319 			goto Out;
320 		if(strlen(newpass) >= 7)
321 			break;
322 		else if(strlen(newpass) == 0){
323 			fprint(2, "!password change aborted\n");
324 			goto Out;
325 		}
326 		print("!password must be at least 7 characters\n");
327 	}
328 	newpasslen = strlen(newpass);
329 	snprint(prompt, sizeof(prompt), "retype password: ");
330 	passck = getpassm(prompt);
331 	if(passck == nil){
332 		fprint(2, "secstore: getpassm failed\n");
333 		goto Out;
334 	}
335 	if(strcmp(passck, newpass) != 0){
336 		fprint(2, "secstore: passwords didn't match\n");
337 		goto Out;
338 	}
339 
340 	c->conn->write(c->conn, (uchar*)"CHPASS", strlen("CHPASS"));
341 	hexHi = PAK_Hi(id, newpass, H, Hi);
342 	c->conn->write(c->conn, (uchar*)hexHi, strlen(hexHi));
343 	free(hexHi);
344 	mpfree(H);
345 	mpfree(Hi);
346 
347 	if(getfile(c->conn, ".", (uchar **) &list, &len, nil, 0) < 0){
348 		fprint(2, "secstore: directory listing failed.\n");
349 		goto Out;
350 	}
351 
352 	/* Loop over files and reencrypt them; try to keep going after error */
353 	for(cur=list; (next=strchr(cur, '\n')) != nil; cur=next+1){
354 		*next = '\0';
355 		if(tokenize(cur, f, nelem(f))< 1)
356 			break;
357 		fprint(2, "secstore: reencrypting '%s'\n", f[0]);
358 		if(getfile(c->conn, f[0], &memfile, &len, (uchar*)c->pass,
359 		    c->passlen) < 0){
360 			fprint(2, "secstore: getfile of '%s' failed\n", f[0]);
361 			continue;
362 		}
363 		if(putfile(c->conn, f[0], memfile, len, (uchar*)newpass,
364 		    newpasslen) < 0)
365 			fprint(2, "secstore: putfile of '%s' failed\n", f[0]);
366 		free(memfile);
367 	}
368 	free(list);
369 	c->conn->write(c->conn, (uchar*)"BYE", 3);
370 	rv = 0;
371 
372 Out:
373 	if(newpass != nil){
374 		memset(newpass, 0, newpasslen);
375 		free(newpass);
376 	}
377 	c->conn->free(c->conn);
378 	return rv;
379 }
380 
381 static AuthConn*
login(char * id,char * dest,int pass_stdin,int pass_nvram)382 login(char *id, char *dest, int pass_stdin, int pass_nvram)
383 {
384 	int fd, n, ntry = 0;
385 	char *S, *PINSTA = nil, *nl, s[Maxmsg+1], *pass;
386 	AuthConn *c;
387 
388 	if(dest == nil)
389 		sysfatal("tried to login with nil dest");
390 	c = emalloc(sizeof(*c));
391 	if(pass_nvram){
392 		if(readnvram(&nvr, 0) < 0)
393 			exits("readnvram: %r");
394 		strecpy(c->pass, c->pass+sizeof c->pass, nvr.config);
395 	}
396 	if(pass_stdin){
397 		n = readn(0, s, Maxmsg-2);	/* so len(PINSTA)<Maxmsg-3 */
398 		if(n < 1)
399 			exits("no password on standard input");
400 		s[n] = 0;
401 		nl = strchr(s, '\n');
402 		if(nl){
403 			*nl++ = 0;
404 			PINSTA = estrdup(nl);
405 			nl = strchr(PINSTA, '\n');
406 			if(nl)
407 				*nl = 0;
408 		}
409 		strecpy(c->pass, c->pass+sizeof c->pass, s);
410 	}
411 	for(;;){
412 		if(verbose)
413 			fprint(2, "dialing %s\n", dest);
414 		if((fd = dial(dest, nil, nil, nil)) < 0){
415 			fprint(2, "secstore: can't dial %s\n", dest);
416 			free(c);
417 			return nil;
418 		}
419 		if((c->conn = newSConn(fd)) == nil){
420 			free(c);
421 			return nil;
422 		}
423 		ntry++;
424 		if(!pass_stdin && !pass_nvram){
425 			pass = getpassm("secstore password: ");
426 			if(strlen(pass) >= sizeof c->pass){
427 				fprint(2, "secstore: password too long, skipping secstore login\n");
428 				exits("password too long");
429 			}
430 			strcpy(c->pass, pass);
431 			memset(pass, 0, strlen(pass));
432 			free(pass);
433 		}
434 		if(c->pass[0]==0){
435 			fprint(2, "secstore: null password, skipping secstore login\n");
436 			exits("no password");
437 		}
438 		if(PAKclient(c->conn, id, c->pass, &S) >= 0)
439 			break;
440 		c->conn->free(c->conn);
441 		if(pass_stdin)
442 			exits("invalid password on standard input");
443 		if(pass_nvram)
444 			exits("invalid password in nvram");
445 		/* and let user try retyping the password */
446 		if(ntry==3)
447 			fprint(2, "Enter an empty password to quit.\n");
448 	}
449 	c->passlen = strlen(c->pass);
450 	fprint(2, "%s\n", S);
451 	free(S);
452 	if(readstr(c->conn, s) < 0){
453 		c->conn->free(c->conn);
454 		free(c);
455 		return nil;
456 	}
457 	if(strcmp(s, "STA") == 0){
458 		long sn;
459 
460 		if(pass_stdin){
461 			if(PINSTA)
462 				strncpy(s+3, PINSTA, sizeof s - 3);
463 			else
464 				exits("missing PIN+SecureID on standard input");
465 			free(PINSTA);
466 		}else{
467 			pass = getpassm("STA PIN+SecureID: ");
468 			strncpy(s+3, pass, sizeof s - 4);
469 			memset(pass, 0, strlen(pass));
470 			free(pass);
471 		}
472 		sn = strlen(s+3);
473 		if(verbose)
474 			fprint(2, "%ld\n", sn);
475 		c->conn->write(c->conn, (uchar*)s, sn+3);
476 		readstr(c->conn, s);	/* TODO: check for error? */
477 	}
478 	if(strcmp(s, "OK") != 0){
479 		fprint(2, "%s: %s\n", argv0, s);
480 		c->conn->free(c->conn);
481 		free(c);
482 		return nil;
483 	}
484 	return c;
485 }
486 
487 void
main(int argc,char ** argv)488 main(int argc, char **argv)
489 {
490 	int chpass = 0, pass_stdin = 0, pass_nvram = 0, rc;
491 	int ngfile = 0, npfile = 0, nrfile = 0, Gflag[MAXFILES+1];
492 	char *serve, *tcpserve, *user;
493 	char *gfile[MAXFILES], *pfile[MAXFILES], *rfile[MAXFILES];
494 	AuthConn *c;
495 
496 	serve = "$auth";
497 	user = getuser();
498 	memset(Gflag, 0, sizeof Gflag);
499 
500 	ARGBEGIN{
501 	case 'c':
502 		chpass = 1;
503 		break;
504 	case 'G':
505 		Gflag[ngfile]++;
506 		/* fall through */
507 	case 'g':
508 		if(ngfile >= MAXFILES)
509 			exits("too many gfiles");
510 		gfile[ngfile++] = EARGF(usage());
511 		break;
512 	case 'i':
513 		pass_stdin = 1;
514 		break;
515 	case 'n':
516 		pass_nvram = 1;
517 		break;
518 	case 'p':
519 		if(npfile >= MAXFILES)
520 			exits("too many pfiles");
521 		pfile[npfile++] = EARGF(usage());
522 		break;
523 	case 'r':
524 		if(nrfile >= MAXFILES)
525 			exits("too many rfiles");
526 		rfile[nrfile++] = EARGF(usage());
527 		break;
528 	case 's':
529 		serve = EARGF(usage());
530 		break;
531 	case 'u':
532 		user = EARGF(usage());
533 		break;
534 	case 'v':
535 		verbose++;
536 		break;
537 	default:
538 		usage();
539 		break;
540 	}ARGEND;
541 	gfile[ngfile] = nil;
542 	pfile[npfile] = nil;
543 	rfile[nrfile] = nil;
544 
545 	if(argc!=0 || user==nil)
546 		usage();
547 
548 	if(chpass && (ngfile || npfile || nrfile)){
549 		fprint(2, "secstore: Get, put, and remove invalid with password change.\n");
550 		exits("usage");
551 	}
552 
553 	rc = strlen(serve) + sizeof "tcp!!99990";
554 	tcpserve = emalloc(rc);
555 	if(strchr(serve,'!'))
556 		strcpy(tcpserve, serve);
557 	else
558 		snprint(tcpserve, rc, "tcp!%s!5356", serve);
559 	c = login(user, tcpserve, pass_stdin, pass_nvram);
560 	free(tcpserve);
561 	if(c == nil)
562 		sysfatal("secstore authentication failed");
563 	if(chpass)
564 		rc = chpasswd(c, user);
565 	else
566 		rc = cmd(c, gfile, Gflag, pfile, rfile);
567 	if(rc < 0)
568 		sysfatal("secstore cmd failed");
569 	exits("");
570 }
571