util  Diff

Differences From Artifact [7a6fbd2214]:

To Artifact [9c1b0583ee]:


    80     80   typedef enum {
    81     81   	tbl_ok = ok, tbl_error = fail
    82     82   } tbl_error_type;
    83     83   typedef unsigned char tbl_word_type;
    84     84   #include "clib/tbl.c"
    85     85   
    86     86   #include "opt.inc"
           87  +
           88  +typedef uint8_t key_priv [db_privkey_len];
           89  +typedef uint8_t key_pub [db_pubkey_len];
           90  +typedef uint8_t db_sz;
           91  +typedef uint8_t byte;
    87     92   
    88     93   #define str_ucase "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    89     94   #define str_lcase "abcdefghijklmnopqrstuvwxyz"
    90     95   #define str_num "0123456789" 
    91     96   const char* reftbl = str_num str_ucase str_lcase;
    92     97   const char* reftbl_lcase = str_num str_lcase;
    93     98   
................................................................................
   224    229   	}
   225    230   
   226    231   	buf[len] = '\n';
   227    232   
   228    233   	return ok;
   229    234   }
   230    235   
   231         -struct entry {
   232         -	pstr account;
   233         -	pstr pw;
   234         -};
          236  +void
          237  +bytedump(byte* bytes, size_t sz) {
          238  +	for (size_t i = 0; i < sz; ++i) {
          239  +		char tpl[] ="\x1b[35m \x1b[m";
          240  +		char* c = tpl + 5;
          241  +		*c = bytes[i];
          242  +		if (*c < ' ' || *c > '~') *c='.', write(2, tpl, sz(tpl));
          243  +		else write(2, c, 1);
          244  +	}
          245  +}
          246  +
          247  +void
          248  +hexdump(byte* bytes, size_t sz) {
          249  +	if(!_g_debug_msgs) return;
          250  +	alert(a_debug, "printing hex dump");
          251  +	byte* st = bytes;
          252  +	write(2, _str("\t\x1b[94m"));
          253  +	for (size_t i = 0; i < sz; ++i) {
          254  +		char hex[5] = "    ";
          255  +		kitoa(16, bytes[i], hex, hex + 2, NULL, true);
          256  +		write(2, hex, 4);
          257  +		if(!((i+1)%8)) {
          258  +			write(2, _str("\x1b[;1m│\x1b[m "));
          259  +			bytedump(st, 8);
          260  +			write(2, "\n\t\x1b[94m", (i == sz - 1 ? 1 : 7));
          261  +			st += 8;
          262  +		} else if (i == sz - 1) {
          263  +			write(2, _str("\x1b[;1m│\x1b[m "));
          264  +			bytedump(st, (bytes + sz) - st);
          265  +			write(2, _str("\n\x1b[m"));
          266  +		}
          267  +	}
          268  +}
          269  +
          270  +struct dbrecord { pstr acct; pstr pw; };
          271  +
          272  +enum bad
          273  +dbtell(int db, db_sz* ciphlen, db_sz* plainlen){
          274  +	if (read(db, ciphlen, 1) != 1)
          275  +		return fail;
          276  +	*plainlen = *ciphlen - crypto_box_SEALBYTES;
          277  +	return ok;
          278  +}
          279  +
          280  +enum bad
          281  +dbnext(int db, struct dbrecord* rec, db_sz acctlen,
          282  +		byte* pub, byte* priv,
          283  +		byte ciphertext[static acctlen],
          284  +		byte plaintext[static acctlen - crypto_box_SEALBYTES]) {
          285  +	db_sz plaintext_sz = acctlen - crypto_box_SEALBYTES;
          286  +
          287  +	if (read(db, ciphertext, acctlen) != acctlen)
          288  +		return bad_db_corrupt;
          289  +	alert(a_debug, "scanned record");
          290  +	hexdump(ciphertext, acctlen);
          291  +
          292  +	if(crypto_box_seal_open(plaintext, ciphertext, acctlen, pub, priv) != 0)
          293  +		return bad_db_corrupt;
          294  +
          295  +	alert(a_debug, "record deciphered");
          296  +	hexdump(plaintext, plaintext_sz);
          297  +
          298  +	db_sz record_name_len = plaintext[0],
          299  +	record_pw_len = plaintext[record_name_len + 1];
          300  +
          301  +	rec -> acct.len = record_name_len;
          302  +	rec -> acct.ptr = plaintext + 1;
          303  +	rec -> pw.len = record_pw_len;
          304  +	rec -> pw.ptr = plaintext + record_name_len + 2;
          305  +
          306  +	return ok;
          307  +}
          308  +
          309  +enum bad
          310  +dbappend(struct dbrecord rec) {
          311  +	return ok;
          312  +}
   235    313   
   236    314   enum term_clear {term_clear_line,
   237    315                    term_clear_screen};
   238    316   
   239    317   void term_clear(int tty, enum term_clear behavior) {
   240    318   	switch(behavior) {
   241    319   		case term_clear_line:   write(tty,"\r\x1b[2K",5); break;
   242    320   		case term_clear_screen: write(tty,"\r\x1b[3J",5); break;
   243    321   	}
   244    322   }
   245    323   void term_bell(int tty) {
   246    324   	write(tty,"\a",1);
   247    325   }
   248         -
          326  +#include<stdio.h>
   249    327   typedef char password[kpw_db_pw_max + 1];
   250    328   bad pwread(bool obscure, char* dest, size_t* out_len, const char* prompt, const size_t plen) {
   251    329   	if (isatty(0)) {
   252    330   		int tty = 1;
   253    331   		if (!isatty(tty)) tty = open("/dev/tty", O_WRONLY);
   254    332   		if (tty == -1) return bad_insane;
   255    333   
................................................................................
   309    387   
   310    388   		if (tty != 1) close(tty);
   311    389   	} else {
   312    390   		alert(a_warn, "reading pw from standard input");
   313    391   		ssize_t ct = read(0, dest, kpw_db_pw_max);
   314    392   		dest[ct] = 0;
   315    393   	}
          394  +	return ok;
   316    395   }
   317    396   
   318    397   int
   319    398   dbopen(int flags) {
   320    399   	const char* dbpath = getenv("kpw_db");
   321    400   	int db;
   322    401   	if (dbpath == NULL) {
................................................................................
   344    423   	} else {
   345    424   		db = open(dbpath, flags, 0600);
   346    425   	}
   347    426   
   348    427   	return db;
   349    428   }
   350    429   
   351         -void bytedump(uint8_t* bytes, size_t sz) {
   352         -	for (size_t i = 0; i < sz; ++i) {
   353         -		char tpl[] ="\x1b[35m \x1b[m";
   354         -		char* c = tpl + 5;
   355         -		*c = bytes[i];
   356         -		if (*c < ' ' || *c > '~') *c='.', write(2, tpl, sz(tpl));
   357         -		else write(2, c, 1);
   358         -	}
   359         -}
   360         -
   361         -void hexdump(uint8_t* bytes, size_t sz) {
   362         -	if(!_g_debug_msgs) return;
   363         -	alert(a_debug, "printing hex dump");
   364         -	uint8_t* st = bytes;
   365         -	for (size_t i = 0; i < sz; ++i) {
   366         -		char hex[5] = "    ";
   367         -		kitoa(16, bytes[i], hex, hex + 2, NULL, true);
   368         -		write(2, hex, 4);
   369         -		if(!((i+1)%8)) {
   370         -			write(2, _str("│ "));
   371         -			bytedump(st, 8);
   372         -			write(2, "\n", 1);
   373         -			st += 8;
   374         -		} else if (i == sz - 1) {
   375         -			write(2, _str("│ "));
   376         -			bytedump(st, (bytes + sz) - st);
   377         -			write(2, "\n", 1);
   378         -		}
   379         -	}
          430  +enum bad
          431  +dbheader_load (int db, byte* salt, byte* salt_enc, byte* pub, byte* priv_enc) {
          432  +	const size_t salt_sz = crypto_pwhash_SALTBYTES,
          433  +	             salt_enc_sz = crypto_box_SEALBYTES + salt_sz,
          434  +				 pub_sz = sizeof(key_pub),
          435  +				 priv_sz = sizeof(key_priv);
          436  +
          437  +	alert(a_debug, "loading public key");
          438  +	ssize_t sr = read(db, pub, pub_sz);
          439  +	if (sr != pub_sz) return bad_db_corrupt;
          440  +	hexdump(pub, pub_sz);
          441  +
          442  +	alert(a_debug, "loading password salt");
          443  +	sr = read(db, salt, salt_sz);
          444  +	if (sr != salt_sz) return bad_db_corrupt;
          445  +	hexdump(salt, salt_sz);
          446  +
          447  +	alert(a_debug, "loading encrypted private key");
          448  +	read(db, priv_enc, priv_sz);
          449  +	hexdump(priv_enc, priv_sz);
          450  +
          451  +	alert(a_debug, "loading verification hash");
          452  +	read(db, salt_enc, salt_enc_sz);
          453  +	hexdump(salt_enc, salt_enc_sz);
          454  +
          455  +	return ok;
   380    456   }
   381    457   
   382    458   enum bad
   383         -dbdecrypt(int db, uint8_t* pubkey, uint8_t* privkey) {
          459  +dbunlock(byte* priv_enc, byte* salt, byte* priv) {
          460  +	const size_t priv_sz = sizeof(key_priv);
          461  +	byte key [db_privkey_len];
          462  +
   384    463   	password dbpw; size_t pwlen;
   385    464   	bad e = pwread(true, dbpw, &pwlen,_str("database key: "));
   386         -
   387         -	uint8_t salt     [crypto_pwhash_SALTBYTES],
   388         -	        key      [db_privkey_len],
   389         -	        priv_enc [db_privkey_len],
   390         -	        priv     [db_privkey_len],
   391         -	        pub      [db_pubkey_len];
   392         -	uint8_t salt_enc [crypto_box_SEALBYTES + sz(salt)],
   393         -	        salt_dec [sz(salt)];
   394         -
   395         -	alert(a_debug, "loading public key");
   396         -	ssize_t sr = read(db, pub, sz(pub));
   397         -	if (sr != sz(pub)) return bad_db_corrupt;
   398         -	hexdump(pub, sz(pub));
   399         -
   400         -	alert(a_debug, "loading password salt");
   401         -	sr = read(db, salt, sz(salt));
   402         -	if (sr != sz(salt)) return bad_db_corrupt;
   403         -	hexdump(salt, sz(salt));
          465  +	if (e != ok) return e;
   404    466   
   405    467   	alert(a_debug, "deriving secret");
   406    468   	if(crypto_pwhash(key, sz(key), dbpw, pwlen, salt,
   407    469   				crypto_pwhash_OPSLIMIT_INTERACTIVE,
   408    470   				crypto_pwhash_MEMLIMIT_INTERACTIVE,
   409    471   				crypto_pwhash_ALG_DEFAULT) != 0) {
   410    472   		return bad_mem;
   411    473   	}
   412    474   	hexdump(key, sz(key));
   413    475   
   414         -	alert(a_debug, "loading encrypted private key");
   415         -	read(db, priv_enc, sz(priv_enc));
   416         -	hexdump(priv_enc, sz(priv_enc));
   417         -
   418         -	alert(a_debug, "decrypting private key");
          476  +	alert(a_debug, "attempting to decrypt private key");
   419    477   	for (size_t i = 0; i < sz(key); ++i) {
   420    478   		priv[i] = priv_enc[i] ^ key[i];
   421    479   	}
   422         -	hexdump(priv, sz(priv));
          480  +	hexdump(priv, sz(key));
   423    481   
   424         -	alert(a_debug, "loading verification hash");
   425         -	read(db, salt_enc, sz(salt_enc));
   426         -	hexdump(salt_enc, sz(salt_enc));
          482  +	return ok;
          483  +}
          484  +
          485  +enum bad
          486  +dbverify(byte* salt, byte* salt_enc, byte* pub, byte* priv) {
          487  +	byte salt_dec [crypto_pwhash_SALTBYTES];
   427    488   
   428    489   	alert(a_debug, "decrypting verification hash");
   429    490   	int r = crypto_box_seal_open(salt_dec, salt_enc,
   430         -			sz(salt_enc), pub, priv);
          491  +			sz(salt_dec) + crypto_box_SEALBYTES, pub, priv);
   431    492   	if (r != 0) return bad_pw;
   432    493   	hexdump(salt_dec, sz(salt_dec));
   433    494   
   434         -	if (memcmp(salt,salt_dec,sz(salt)) != 0) return bad_db_corrupt;
          495  +	if (memcmp(salt,salt_dec,sz(salt_dec)) != 0) return bad_db_corrupt;
          496  +	else return ok;
          497  +}
          498  +
          499  +enum bad
          500  +dbdecrypt(int db, byte* pubkey, byte* privkey) {
          501  +	byte salt     [crypto_pwhash_SALTBYTES],
          502  +	     priv_enc [db_privkey_len],
          503  +	     priv     [db_privkey_len],
          504  +	     pub      [db_pubkey_len];
          505  +	byte salt_enc [crypto_box_SEALBYTES + sz(salt)];
          506  +	bad e;
          507  +
          508  +	if ((e = dbheader_load(db, salt, salt_enc, pub, priv_enc)) != ok)
          509  +		return e;
          510  +	if ((e = dbunlock(priv_enc, salt, priv)) != ok)
          511  +		return e;
          512  +	if ((e = dbverify(salt, salt_enc, pub, priv)) != ok)
          513  +		return e;
   435    514   
   436    515   	/* TODO refactor to avoid unnecessary memcpy */
   437    516   	memcpy(privkey, priv, sz(priv));
   438    517   	memcpy(pubkey, pub, sz(pub));
   439    518   	
   440    519   	return ok;
   441    520   }
   442    521   
   443         -#include<stdio.h>
   444         -void bright(int fd, const char* str, size_t len) {
          522  +void
          523  +bright(int fd, const char* str, size_t len) {
   445    524   	if (_g_term_type[fd] >= ansi_term) write(fd, _str("\x1b[1m"));
   446    525   	write(fd, str, len);
   447    526   	if (_g_term_type[fd] >= ansi_term) write(fd, _str("\x1b[21m"));
   448    527   }
          528  +
          529  +void*
          530  +transcribe(void* dest, void* src, size_t sz) {
          531  +	memcpy(dest, src, sz); return dest + sz;
          532  +}
          533  +
          534  +enum bad
          535  +emit_usage(const char* text) {
          536  +	say("\x1b[1musage:\x1b[m ");
          537  +	write(2, _g_binary_name, strlen(_g_binary_name));
          538  +	if (text == NULL) {
          539  +		write(2, kpw_optstr, sz(kpw_optstr));
          540  +		write(2, kpw_usage,  sz(kpw_usage));
          541  +	} else write(2, text, strlen(text));
          542  +	return bad_usage;
          543  +}
   449    544   
   450    545   int
   451    546   kpw(int argc, const char** argv) {
   452    547   	if (argc == 0) return bad_insane;
   453    548   	_g_binary_name = argv[0];
   454    549   
   455    550   	enum genmode
   456    551   		mode = lower;
   457         -	enum {usage,getpw,addpw,delpw,lspw,genpw,
   458         -	      chpw,keyin,logout,createdb,rekeydb}
          552  +	enum {usage,getpw,addpw,delpw,lspw,genpw,regen,
          553  +	      chpw,keyin,logout,createdb,rekeydb,help}
   459    554   		op = getpw;
   460    555   
   461    556   	const char* params[3];
   462    557   	uint8_t param = 0;
   463    558   
   464    559   	bool print = false,
   465    560   		 clobber = false,
................................................................................
   490    585   			}
   491    586   		} else {
   492    587   			if (param > sz(params)) return bad_syntax;
   493    588   			params[param++] = *arg;
   494    589   		}
   495    590   	}
   496    591   
   497         -	if (op == getpw && param == 0) { 
   498         -		size_t namelen = strlen(argv[0]);
   499         -		say("\x1b[1musage:\x1b[m ");
   500         -		write(2, argv[0], namelen);
   501         -		write(2, kpw_optstr, sz(kpw_optstr));
   502         -		write(2, kpw_usage,  sz(kpw_usage));
   503         -		return bad_usage;
   504         -	}
          592  +	if (op == getpw && param == 0) return emit_usage(NULL);
   505    593   
   506    594   	if (sodium_init() < 0) 
   507    595   		return bad_lib_sodium_init;
   508    596   
   509    597   	switch(op) {
   510    598    
   511         -		case genpw:   /* kpw -g[lmu] <acct> [<len>] */
   512         -		case addpw: { /* kpw -a      <acct> [<pw>] */
          599  +		case genpw:   
          600  +		case addpw: {
          601  +			if (param == 0) return emit_usage(
          602  +					op == addpw ? " -a[p] <account> [<pw>]\n"       :
          603  +					   /* genpw */" -g[lmup] <account> [<pw len>]\n");
          604  +
   513    605   			if (param > 2 || param < 1) return bad_syntax;
   514    606   			const char* acct = params[0],
   515    607   			          * prm = (param == 2 ? params[1] : NULL);
   516    608   
   517    609   			alert(a_debug, "opening database");
   518    610   			int db = dbopen(O_RDWR);
   519    611   			if (db == -1) return bad_db_load;
   520    612   			alert(a_debug, "reading in public key");
   521         -			uint8_t key [db_pubkey_len];
          613  +			byte key [db_pubkey_len];
   522    614   			ssize_t e = read(db, key, sz(key));
   523    615   			if(e < sz(key)) return bad_db_corrupt;
   524    616   
   525    617   			lseek(db, 0, SEEK_END);
   526    618   			bool tty_in = isatty(0),
   527    619   				 tty_out = isatty(1);
   528    620   
................................................................................
   563    655   					write(1, pw, len);
   564    656   					if(tty_out) write(1, "\n", 1);
   565    657   				}
   566    658   				pwlen = len;
   567    659   			}
   568    660   			if (copy_pw) copy(pw, pwlen);
   569    661   			alert(a_debug, "encoding database entry");
   570         -			uint8_t acctlen = strlen(acct);
   571         -			uint8_t plaintext[1 + acctlen +
   572         -			                  1 + pwlen];
          662  +			db_sz acctlen = strlen(acct);
          663  +			byte plaintext[1 + acctlen +
          664  +			               1 + pwlen];
   573    665   			plaintext[0] = acctlen;
   574    666   			strncpy(plaintext + 1, acct, acctlen);
   575    667   			plaintext[1 + acctlen] = pwlen;
   576    668   			strncpy(plaintext + acctlen + 2, pw, pwlen);
   577    669   			hexdump(plaintext, sz(plaintext));
   578    670   
   579    671   			alert(a_debug, "enciphering database entry");
   580    672   
   581         -			uint8_t ciphertext[sz(plaintext) + crypto_box_SEALBYTES];
          673  +			byte ciphertext[sz(plaintext) + crypto_box_SEALBYTES];
   582    674   			crypto_box_seal(ciphertext, plaintext, sz(plaintext), key);
   583    675   			hexdump(ciphertext, sz(ciphertext));
   584    676   
   585    677   			alert(a_debug, "writing ciphertext to db");
   586         -			uint8_t ciphertext_len = sz(ciphertext);
          678  +			db_sz ciphertext_len = sz(ciphertext);
   587    679   			write(db, &ciphertext_len, 1);
   588    680   			write(db, &ciphertext, ciphertext_len);
   589    681   			
   590    682   			close(db);
   591    683   			break;
   592    684   		}
   593    685   
   594    686   		case delpw:{ /* kpw -d <acct> */
          687  +			if (param == 0) return emit_usage(" -d <account>\n");
          688  +			if (param != 1) return bad_syntax;
          689  +			const char* target = params[0];
          690  +			bad e;
          691  +
          692  +			int db = dbopen(O_RDWR);
          693  +			if (db == -1) return bad_db_load;
          694  +
          695  +			const size_t dbsz = lseek(db, 0, SEEK_END);
          696  +			lseek(db, 0, SEEK_SET);
          697  +
          698  +			key_pub  pub;
          699  +			key_priv priv, priv_enc;
          700  +
          701  +			byte salt     [crypto_pwhash_SALTBYTES],
          702  +			     salt_enc [crypto_box_SEALBYTES + sz(salt)];
          703  +
          704  +			if ((e = dbheader_load(db, salt, salt_enc, pub, priv_enc)) != ok)
          705  +				return e;
          706  +			if ((e = dbunlock(priv_enc, salt, priv)) != ok)
          707  +				return e;
          708  +			if ((e = dbverify(salt, salt_enc, pub, priv)) != ok)
          709  +				return e;
          710  +
          711  +			byte newdb [dbsz];
          712  +			byte* cursor = newdb;
          713  +
          714  +			/* header is unchanged, so we copy it back out as is */
          715  +			cursor = transcribe(cursor, pub, sz(pub));
          716  +			cursor = transcribe(cursor, salt, sz(salt));
          717  +			cursor = transcribe(cursor, priv_enc, sz(priv_enc));
          718  +			cursor = transcribe(cursor, salt_enc, sz(salt_enc));
          719  +
          720  +			/* now we iterate through each record, decrypting them
          721  +			 * with the keys we've obtained to compare the name
          722  +			 * against the `target` specified by the user. all
          723  +			 * records that do not match are written back to newdb,
          724  +			 * while records that match are skipped. */
          725  +			db_sz ctlen, ptlen; bool found = false; size_t scanned = 0;
          726  +			while((e = dbtell(db, &ctlen, &ptlen)) == ok) {
          727  +				++ scanned;
          728  +				/* dbtell gives us the length of the buffers we
          729  +				 * need to allocate, allowing us to keep all the
          730  +				 * work on the stack and avoiding any malloc bs */
          731  +				byte ctbuf [ctlen],
          732  +				     ptbuf [ptlen];
          733  +				struct dbrecord rec;
          734  +
          735  +				bad d;
          736  +				if((d = dbnext(db, &rec, ctlen,
          737  +					pub, priv, ctbuf, ptbuf)) != ok) return d;
          738  +
          739  +				if(strncmp(target, rec.acct.ptr, rec.acct.len) == 0) {
          740  +					/* found a matching record; do not copy it */
          741  +					alert(a_debug, "found record to delete");
          742  +					found = true;
          743  +				} else {
          744  +					/* not our target, copy it into the buffer */
          745  +					cursor = transcribe(cursor, &ctlen, sizeof(db_sz));
          746  +					cursor = transcribe(cursor, ctbuf, sz(ctbuf));
          747  +				}
          748  +
          749  +			} if (e != fail) return e;
          750  +			if (scanned == 0) return bad_db_empty;
          751  +			if (!found) return bad_index;
          752  +			/* newdb should now be an image of the database without
          753  +			 * the offending record. we can now copy it back out to
          754  +			 * disk, truncating the file to start from scratch. */
          755  +			alert(a_debug, "writing modified db back out to disk");
          756  +			ftruncate(db,0);
          757  +			lseek(db, 0, SEEK_SET);
          758  +			write(db, newdb, cursor-newdb);
          759  +
          760  +			/* we're done here, time to close up shop */
          761  +			close(db);
          762  +
   595    763   			break;
   596    764   		}
   597    765   
   598    766   		case getpw:  /* kpw <acct> */
   599    767   		case lspw: { /* kpw -t[p] [<prefix>] */
   600         -			alert(a_debug, "opening database for reading");
   601         -		   	int db = dbopen(O_RDONLY);
   602         -		   	if (db == -1) return bad_db_load;
   603         -
   604    768   			const char* target;
   605    769   			if (param == 1) target = params[0];
   606    770   			else if (param == 0) target = NULL;
   607    771   			else return bad_syntax;
   608    772   
   609         -			uint8_t priv [db_privkey_len],
   610         -					pub  [db_pubkey_len];
          773  +			alert(a_debug, "opening database for reading");
          774  +		   	int db = dbopen(O_RDONLY);
          775  +		   	if (db == -1) return bad_db_load;
          776  +
          777  +			key_pub pub;
          778  +			key_priv priv;
   611    779   
   612    780   			/* try to decrypt db */ { 
   613    781   				bad e = dbdecrypt(db,pub,priv);
   614    782   				if (e != ok) return e;
   615    783   				/* TODO allow multiple tries */
   616    784   			}
   617    785   
   618         -			/* cursor should now be positioned
   619         -			 * on first record */
          786  +			/* cursor should be positioned on first
          787  +			 * record if we've made it this far */
   620    788   			alert(a_debug, "beginning to scan records");
   621         -			read_rec: {
   622         -				uint8_t acctlen;
   623         -				if (read(db, &acctlen, 1) != 1)
   624         -					goto done_reading;
   625         -				uint8_t ciphertext[acctlen];
   626         -				if (read(db, &ciphertext, acctlen) != acctlen)
   627         -					return bad_db_corrupt;
   628         -				alert(a_debug, "scanned record");
   629         -				hexdump(ciphertext, sz(ciphertext));
   630         -
   631         -				uint8_t plaintext[sz(ciphertext) - crypto_box_SEALBYTES];
   632         -				if(crypto_box_seal_open(plaintext, ciphertext, sz(ciphertext), pub, priv) != 0)
   633         -					return bad_db_corrupt;
   634         -
   635         -				alert(a_debug, "record deciphered");
   636         -				hexdump(plaintext, sz(plaintext));
   637         -
   638         -				uint8_t record_name_len = plaintext[0],
   639         -				record_pw_len = plaintext[record_name_len + 1];
   640         -
   641         -				pstr record_name = {record_name_len, plaintext + 1},
   642         -					 record_pw = {record_pw_len,
   643         -						 plaintext + record_name_len + 2};
          789  +			struct dbrecord rec;
          790  +			bool found = (op == lspw);
          791  +			enum bad result;
          792  +			db_sz ciphlen, plainlen;
          793  +			size_t scanned = 0;
          794  +			while ((result = dbtell(db, &ciphlen, &plainlen)) == ok) {
          795  +				++ scanned;
          796  +				byte ctbuf[ciphlen], ptbuf[plainlen];
          797  +				if ((result = dbnext(db, &rec, ciphlen,
          798  +								pub, priv,
          799  +								ctbuf, ptbuf)) != ok) 
          800  +					break;
   644    801   
   645    802   				if(op == lspw) {
   646         -					bright(1, record_name.ptr, record_name.len);
          803  +					bright(1, rec.acct.ptr, rec.acct.len);
   647    804   					if (print || !isatty(1)) {
   648    805   						write(1, ": ", 2);
   649         -						write(1, record_pw.ptr, record_pw.len);
          806  +						write(1, rec.pw.ptr, rec.pw.len);
   650    807   					}
   651    808   					write(1, "\n", 1);
   652    809   				} else if (op == getpw) {
   653         -					if (strncmp(record_name.ptr,target,record_name.len) == 0) {
          810  +					if (strncmp(rec.acct.ptr,target,rec.acct.len) == 0) {
   654    811   						if (print || _g_term_type[1] == plain_term) {
   655         -							write(1, record_pw.ptr, record_pw.len);
          812  +							write(1, rec.pw.ptr, rec.pw.len);
   656    813   							if(_g_term_type[1] > plain_term)
   657    814   								write(1, "\n", 1);
   658    815   						}
   659    816   
   660    817   						if (_g_term_type[1] > plain_term) {
   661         -							if (copy_pw) copy(record_pw.ptr, record_pw.len);
          818  +							if (copy_pw) copy(rec.pw.ptr, rec.pw.len);
   662    819   						}
   663         -						goto done_reading;
          820  +						found = true;
          821  +						break;
   664    822   					}
   665    823   				}
   666         -
   667         -				goto read_rec;
   668    824   			}
   669         -			return bad_index;
   670         -
   671         -			done_reading: break;
          825  +			if (scanned == 0) return bad_db_empty;
          826  +			if (result != fail) return result;
          827  +			else if (!found) return bad_index;
          828  +			
          829  +			break;
   672    830   		}
   673    831   
   674    832   		case createdb: { /* kpw -C [<db>] */
   675    833   			alert(a_debug, "creating new database");
   676    834   			if (clobber)
   677    835   				alert(a_warn, "will clobber any existing database");
   678    836   			/* before we open our new file, we need to generate
   679    837   			 * our keypairs.  the private key will be encrypted
   680    838   			 * with a blake2 hash of a user-supplied passphrase 
   681    839   			 * and stored in the database after the unencrypted
   682    840   			 * public key.
   683    841   			 */
   684         -			uint8_t pub [db_pubkey_len],
   685         -			        priv[db_privkey_len];
   686         -			uint8_t priv_enc[sz(priv)];
          842  +			byte pub [db_pubkey_len],
          843  +			     priv[db_privkey_len];
          844  +			byte priv_enc[sz(priv)];
   687    845   
   688    846   			alert(a_debug, "generating keypair");
   689    847   			crypto_box_keypair(pub, priv);
   690    848   
   691    849   			/* kpw   works  very   differently  compared   to
   692    850   			 * most  password  managers. it  uses  public-key
   693    851   			 * encryption so that new  passwords can be saved
................................................................................
   707    865   				e = pwread(!print, dbpw_conf, NULL, _str("- confirm: "));
   708    866   				if (e != ok) return e;
   709    867   
   710    868   				if(strcmp(dbpw,dbpw_conf) != 0)
   711    869   					return bad_pw_match;
   712    870   			}
   713    871   
   714         -			uint8_t salt[crypto_pwhash_SALTBYTES],
   715         -			         key[db_privkey_len];
   716         -			uint8_t salt_enc[crypto_box_SEALBYTES + sz(salt)];
          872  +			byte salt [crypto_pwhash_SALTBYTES],
          873  +			     key[db_privkey_len];
          874  +			byte salt_enc[crypto_box_SEALBYTES + sz(salt)];
   717    875   
   718    876   			alert(a_debug, "generating salt");
   719    877   			if (syscall(SYS_getrandom, salt, sz(salt), 0) == -1)
   720    878   				return bad_entropy;
   721    879   			hexdump(salt, sz(salt));
   722    880   
   723    881   			alert(a_debug, "hashing database keyphrase");