1 /* secstore - network login client */
10 enum{ CHK = 16, MAXFILES = 100 };
12 typedef struct AuthConn{
24 fprint(2, "usage: secstore [-cinv] [-[gG] getfile] [-p putfile] "
25 "[-r rmfile] [-s tcp!server!5356] [-u user]\n");
30 getfile(SConn *conn, char *gf, uchar **buf, ulong *buflen, uchar *key, int nkey)
32 int fd = -1, i, n, nr, nw, len;
34 uchar skey[SHA1dlen], ib[Maxmsg+CHK], *ibr, *ibw, *bufw, *bufe;
38 memset(&aes, 0, sizeof aes);
40 snprint(s, Maxmsg, "GET %s", gf);
41 conn->write(conn, (uchar*)s, strlen(s));
46 if(readstr(conn, s) < 0){
47 fprint(2, "secstore: remote: %s\n", s);
52 fprint(2, "secstore: remote file %s does not exist\n", gf);
55 fprint(2, "secstore: implausible filesize for %s\n", gf);
58 fprint(2, "secstore: GET refused for %s\n", gf);
62 *buflen = len - AESbsize - CHK;
63 *buf = bufw = emalloc(len);
67 /* directory listing */
68 if(strcmp(gf,".")==0){
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");
79 memmove(*buf + i, s, n);
85 * conn is already encrypted against wiretappers, but gf is also
86 * encrypted against server breakin.
88 if(buf == nil && (fd = create(gf, OWRITE, 0600)) < 0){
89 fprint(2, "secstore: can't open %s: %r\n", gf);
95 if((n = conn->read(conn, ibw, Maxmsg)) <= 0){
96 fprint(2, "secstore: empty file chunk n=%d nr=%d len=%d: %r\n",
102 if(!aes.setup){ /* first time, read 16 byte IV */
104 fprint(2, "secstore: no IV in file\n");
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);
114 aesCBCdecrypt(ibw-n, n, &aes);
118 nw = write(fd, ibr, n);
120 fprint(2, "secstore: write error on %s", gf);
124 assert(bufw + n <= bufe);
125 memmove(bufw, ibr, n);
130 memmove(ib, ibr, ibw-ibr);
131 ibw = ib + (ibw-ibr);
137 if(n != CHK || memcmp(ib, "XXXXXXXXXXXXXXXX", CHK) != 0){
138 fprint(2, "secstore: decrypted file failed to authenticate!\n");
145 * This sends a file to the secstore disk that can, in an emergency, be
146 * decrypted by the program aescbc.c.
149 putfile(SConn *conn, char *pf, uchar *buf, ulong len, uchar *key, int nkey)
151 int i, n, fd, ivo, bufi, done;
153 uchar skey[SHA1dlen], b[CHK+Maxmsg], IV[AESbsize];
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);
166 snprint(s, Maxmsg, "PUT %s", pf);
167 conn->write(conn, (uchar*)s, strlen(s));
171 if((fd = open(pf, OREAD)) < 0){
172 fprint(2, "secstore: can't open %s: %r\n", pf);
175 len = seek(fd, 0, 2);
179 if(len > MAXFILESIZE){
180 fprint(2, "secstore: implausible filesize %ld for %s\n",
186 snprint(s, Maxmsg, "%ld", len + AESbsize + CHK);
187 conn->write(conn, (uchar*)s, strlen(s));
189 /* send IV and file+XXXXX in Maxmsg chunks */
193 for(done = 0; !done; ){
195 n = read(fd, b+ivo, Maxmsg-ivo);
197 fprint(2, "secstore: read error on %s: %r\n",
202 if((n = len - bufi) > Maxmsg-ivo)
204 memcpy(b+ivo, buf+bufi, n);
209 if(n < Maxmsg){ /* EOF on input; append XX... */
210 memset(b+n, 'X', CHK);
211 n += CHK; /* might push n>Maxmsg */
214 aesCBCencrypt(b, n, &aes);
217 conn->write(conn, b, Maxmsg);
219 memmove(b, b+Maxmsg, n);
221 conn->write(conn, b, n);
226 fprint(2, "secstore: saved %ld bytes\n", len);
232 removefile(SConn *conn, char *rf)
236 if(strchr(rf, '/') != nil){
237 fprint(2, "secstore: simple filenames, not paths like %s\n", rf);
241 snprint(buf, Maxmsg, "RM %s", rf);
242 conn->write(conn, (uchar*)buf, strlen(buf));
248 cmd(AuthConn *c, char **gf, int *Gflag, char **pf, char **rf)
252 uchar *memfile, *memcur, *memnext;
256 fprint(2, "get %s\n", *gf);
257 if(getfile(c->conn, *gf, *Gflag? &memfile: nil, &len,
258 (uchar*)c->pass, c->passlen) < 0)
261 /* write 1 line at a time, as required by /mnt/factotum/ctl */
264 memnext = (uchar*)strchr((char*)memcur, '\n');
266 write(1, memcur, memnext-memcur+1);
267 len -= memnext-memcur+1;
270 write(1, memcur, len);
281 fprint(2, "put %s\n", *pf);
282 if(putfile(c->conn, *pf, nil, 0, (uchar*)c->pass, c->passlen) < 0)
288 fprint(2, "rm %s\n", *rf);
289 if(removefile(c->conn, *rf) < 0)
294 c->conn->write(c->conn, (uchar*)"BYE", 3);
298 c->conn->free(c->conn);
303 chpasswd(AuthConn *c, char *id)
305 int rv = -1, newpasslen = 0;
308 char *newpass, *passck, *list, *cur, *next, *hexHi;
309 char *f[8], prompt[128];
314 /* changing our password is vulnerable to connection failure */
316 snprint(prompt, sizeof(prompt), "new password for %s: ", id);
317 newpass = getpassm(prompt);
320 if(strlen(newpass) >= 7)
322 else if(strlen(newpass) == 0){
323 fprint(2, "!password change aborted\n");
326 print("!password must be at least 7 characters\n");
328 newpasslen = strlen(newpass);
329 snprint(prompt, sizeof(prompt), "retype password: ");
330 passck = getpassm(prompt);
332 fprint(2, "secstore: getpassm failed\n");
335 if(strcmp(passck, newpass) != 0){
336 fprint(2, "secstore: passwords didn't match\n");
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));
347 if(getfile(c->conn, ".", (uchar **) &list, &len, nil, 0) < 0){
348 fprint(2, "secstore: directory listing failed.\n");
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){
355 if(tokenize(cur, f, nelem(f))< 1)
357 fprint(2, "secstore: reencrypting '%s'\n", f[0]);
358 if(getfile(c->conn, f[0], &memfile, &len, (uchar*)c->pass,
360 fprint(2, "secstore: getfile of '%s' failed\n", f[0]);
363 if(putfile(c->conn, f[0], memfile, len, (uchar*)newpass,
365 fprint(2, "secstore: putfile of '%s' failed\n", f[0]);
369 c->conn->write(c->conn, (uchar*)"BYE", 3);
374 memset(newpass, 0, newpasslen);
377 c->conn->free(c->conn);
382 login(char *id, char *dest, int pass_stdin, int pass_nvram)
385 char *S, *PINSTA = nil, *nl, s[Maxmsg+1], *pass;
389 sysfatal("tried to login with nil dest");
390 c = emalloc(sizeof(*c));
392 if(readnvram(&nvr, 0) < 0){
393 fprint(2, "secstore: readnvram: %r\n");
394 exits("readnvram failed");
396 strecpy(c->pass, c->pass+sizeof c->pass, nvr.config);
399 n = readn(0, s, Maxmsg-2); /* so len(PINSTA)<Maxmsg-3 */
401 exits("no password on standard input");
403 nl = strchr(s, '\n');
406 PINSTA = estrdup(nl);
407 nl = strchr(PINSTA, '\n');
411 strecpy(c->pass, c->pass+sizeof c->pass, s);
415 fprint(2, "dialing %s\n", dest);
416 if((fd = dial(dest, nil, nil, nil)) < 0){
417 fprint(2, "secstore: can't dial %s\n", dest);
421 if((c->conn = newSConn(fd)) == nil){
426 if(!pass_stdin && !pass_nvram){
427 pass = getpassm("secstore password: ");
428 if(strlen(pass) >= sizeof c->pass){
429 fprint(2, "secstore: password too long, skipping secstore login\n");
430 exits("password too long");
432 strcpy(c->pass, pass);
433 memset(pass, 0, strlen(pass));
437 fprint(2, "secstore: null password, skipping secstore login\n");
438 exits("no password");
440 if(PAKclient(c->conn, id, c->pass, &S) >= 0)
442 c->conn->free(c->conn);
444 exits("invalid password on standard input");
446 exits("invalid password in nvram");
447 /* and let user try retyping the password */
449 fprint(2, "Enter an empty password to quit.\n");
451 c->passlen = strlen(c->pass);
452 fprint(2, "%s\n", S);
454 if(readstr(c->conn, s) < 0){
455 c->conn->free(c->conn);
459 if(strcmp(s, "STA") == 0){
464 strncpy(s+3, PINSTA, sizeof s - 3);
466 exits("missing PIN+SecureID on standard input");
469 pass = getpassm("STA PIN+SecureID: ");
470 strncpy(s+3, pass, sizeof s - 4);
471 memset(pass, 0, strlen(pass));
476 fprint(2, "%ld\n", sn);
477 c->conn->write(c->conn, (uchar*)s, sn+3);
478 readstr(c->conn, s); /* TODO: check for error? */
480 if(strcmp(s, "OK") != 0){
481 fprint(2, "%s: %s\n", argv0, s);
482 c->conn->free(c->conn);
490 main(int argc, char **argv)
492 int chpass = 0, pass_stdin = 0, pass_nvram = 0, rc;
493 int ngfile = 0, npfile = 0, nrfile = 0, Gflag[MAXFILES+1];
494 char *serve, *tcpserve, *user;
495 char *gfile[MAXFILES+1], *pfile[MAXFILES+1], *rfile[MAXFILES+1];
500 memset(Gflag, 0, sizeof Gflag);
510 if(ngfile >= MAXFILES)
511 exits("too many gfiles");
512 gfile[ngfile++] = EARGF(usage());
521 if(npfile >= MAXFILES)
522 exits("too many pfiles");
523 pfile[npfile++] = EARGF(usage());
526 if(nrfile >= MAXFILES)
527 exits("too many rfiles");
528 rfile[nrfile++] = EARGF(usage());
531 serve = EARGF(usage());
534 user = EARGF(usage());
547 if(argc!=0 || user==nil)
550 if(chpass && (ngfile || npfile || nrfile)){
551 fprint(2, "secstore: Get, put, and remove invalid with password change.\n");
555 rc = strlen(serve) + sizeof "tcp!!99990";
556 tcpserve = emalloc(rc);
557 if(strchr(serve,'!'))
558 strcpy(tcpserve, serve);
560 snprint(tcpserve, rc, "tcp!%s!5356", serve);
561 c = login(user, tcpserve, pass_stdin, pass_nvram);
564 sysfatal("secstore authentication failed");
566 rc = chpasswd(c, user);
568 rc = cmd(c, gfile, Gflag, pfile, rfile);
570 sysfatal("secstore cmd failed");