@@ -235,43 +235,47 @@ enum term_clear {term_clear_line, term_clear_screen}; -void term_clear(enum term_clear behavior) { +void term_clear(int tty, enum term_clear behavior) { switch(behavior) { - case term_clear_line: write(1,"\r\x1b[2K",5); break; - case term_clear_screen: write(1,"\r\x1b[3J",5); break; + case term_clear_line: write(tty,"\r\x1b[2K",5); break; + case term_clear_screen: write(tty,"\r\x1b[3J",5); break; } } -void term_bell() { - write(1,"\a",1); +void term_bell(int tty) { + write(tty,"\a",1); } typedef char password[kpw_db_pw_max + 1]; bad pwread(bool obscure, char* dest, size_t* out_len, const char* prompt, const size_t plen) { if (isatty(0)) { + int tty = 1; + if (!isatty(tty)) tty = open("/dev/tty", O_WRONLY); + if (tty == -1) return bad_insane; + struct termios initial; { /* in order to take PW input, we need to shut * off echo and canonical mode. now we're in * charge of reading each keypress. */ - tcgetattr(1, &initial); + tcgetattr(tty, &initial); struct termios nt = initial; nt.c_lflag &= (~ECHO & ~ICANON); - tcsetattr(1, TCSANOW, &nt); + tcsetattr(tty, TCSANOW, &nt); } *dest = 0; char* p = dest; do { - term_clear(term_clear_line); - if (_g_term_type[1] >= ansi_term) write(1, "\x1b[1m", 4); - write(1, prompt, plen); - if (_g_term_type[1] >= ansi_term) write(1, "\x1b[21m", 5); + term_clear(tty,term_clear_line); + if (_g_term_type[0] >= ansi_term) write(tty, "\x1b[1m", 4); + write(tty, prompt, plen); + if (_g_term_type[0] >= ansi_term) write(tty, "\x1b[21m", 5); if (obscure) for(size_t i = 0; i < p - dest; ++i) - write(1, "*", 1); - else write(1, dest, p-dest); + write(tty, "*", 1); + else write(tty, dest, p-dest); char c; if (read(0, &c, 1) == 1) { switch (c) { @@ -279,38 +283,39 @@ /* accept pw */ if (p > dest) goto end_read_loop; else break; case '\x1b': /* escape */ - term_clear(term_clear_line); + term_clear(tty, term_clear_line); return bad_cancel; case '\b': case '\x7f': if (p > dest) *p--=0; - else term_bell(); + else term_bell(tty); break; default: if (p - dest != 64) *p++=c; - else term_bell(); + else term_bell(tty); } } else { /* either EOF or an error - either way, * we're finished here */ break; } } while(1); - end_read_loop: term_clear(term_clear_line); + end_read_loop: term_clear(tty, term_clear_line); *p = 0; if (out_len!=NULL) *out_len = p - dest; /* return the terminal to normal */ - tcsetattr(1, TCSANOW, &initial); + tcsetattr(tty, TCSANOW, &initial); + + if (tty != 1) close(tty); } else { alert(a_warn, "reading pw from standard input"); ssize_t ct = read(0, dest, kpw_db_pw_max); dest[ct] = 0; } } -#include int dbopen(int flags) { const char* dbpath = getenv("kpw_db"); int db; @@ -336,9 +341,8 @@ db = open(buf, flags, 0600); } } else { - printf("path is %s", dbpath); db = open(dbpath, flags, 0600); } return db; @@ -352,8 +356,9 @@ if (*c < ' ' || *c > '~') *c='.', write(2, tpl, sz(tpl)); else write(2, c, 1); } } + void hexdump(uint8_t* bytes, size_t sz) { if(!_g_debug_msgs) return; alert(a_debug, "printing hex dump"); uint8_t* st = bytes; @@ -372,8 +377,76 @@ write(2, "\n", 1); } } } + +enum bad +dbdecrypt(int db, uint8_t* pubkey, uint8_t* privkey) { + password dbpw; size_t pwlen; + bad e = pwread(true, dbpw, &pwlen,_str("database key: ")); + + uint8_t salt [crypto_pwhash_SALTBYTES], + key [db_privkey_len], + priv_enc [db_privkey_len], + priv [db_privkey_len], + pub [db_pubkey_len]; + uint8_t salt_enc [crypto_box_SEALBYTES + sz(salt)], + salt_dec [sz(salt)]; + + alert(a_debug, "loading public key"); + ssize_t sr = read(db, pub, sz(pub)); + if (sr != sz(pub)) return bad_db_corrupt; + hexdump(pub, sz(pub)); + + alert(a_debug, "loading password salt"); + sr = read(db, salt, sz(salt)); + if (sr != sz(salt)) return bad_db_corrupt; + hexdump(salt, sz(salt)); + + alert(a_debug, "deriving secret"); + if(crypto_pwhash(key, sz(key), dbpw, pwlen, salt, + crypto_pwhash_OPSLIMIT_INTERACTIVE, + crypto_pwhash_MEMLIMIT_INTERACTIVE, + crypto_pwhash_ALG_DEFAULT) != 0) { + return bad_mem; + } + hexdump(key, sz(key)); + + alert(a_debug, "loading encrypted private key"); + read(db, priv_enc, sz(priv_enc)); + hexdump(priv_enc, sz(priv_enc)); + + alert(a_debug, "decrypting private key"); + for (size_t i = 0; i < sz(key); ++i) { + priv[i] = priv_enc[i] ^ key[i]; + } + hexdump(priv, sz(priv)); + + alert(a_debug, "loading verification hash"); + read(db, salt_enc, sz(salt_enc)); + hexdump(salt_enc, sz(salt_enc)); + + alert(a_debug, "decrypting verification hash"); + int r = crypto_box_seal_open(salt_dec, salt_enc, + sz(salt_enc), pub, priv); + if (r != 0) return bad_pw; + hexdump(salt_dec, sz(salt_dec)); + + if (memcmp(salt,salt_dec,sz(salt)) != 0) return bad_db_corrupt; + + /* TODO refactor to avoid unnecessary memcpy */ + memcpy(privkey, priv, sz(priv)); + memcpy(pubkey, pub, sz(pub)); + + return ok; +} + +#include +void bright(int fd, const char* str, size_t len) { + if (_g_term_type[fd] >= ansi_term) write(fd, _str("\x1b[1m")); + write(fd, str, len); + if (_g_term_type[fd] >= ansi_term) write(fd, _str("\x1b[21m")); +} int kpw(int argc, const char** argv) { if (argc == 0) return bad_insane; @@ -433,12 +506,9 @@ if (sodium_init() < 0) return bad_lib_sodium_init; switch(op) { - case getpw:{ /* kpw */ - break; - } - + case genpw: /* kpw -g[lmu] [] */ case addpw: { /* kpw -a [] */ if (param > 2 || param < 1) return bad_syntax; const char* acct = params[0], @@ -524,66 +594,82 @@ case delpw:{ /* kpw -d */ break; } + case getpw: /* kpw */ case lspw: { /* kpw -t[p] [] */ alert(a_debug, "opening database for reading"); - int db = dbopen(O_RDONLY); - if (db == -1) return bad_db_load; - password dbpw; size_t pwlen; - bad e = pwread(!print, dbpw, &pwlen,_str("database key: ")); - - uint8_t salt [crypto_pwhash_SALTBYTES], - key [db_privkey_len], - priv_enc [db_privkey_len], - priv [db_privkey_len], - pub [db_pubkey_len]; - uint8_t salt_enc [crypto_box_SEALBYTES + sz(salt)], - salt_dec [sz(salt)]; - bzero(salt_dec, sz(salt_dec)); - - alert(a_debug, "loading public key"); - ssize_t sr = read(db, pub, sz(pub)); - if (sr != sz(pub)) return bad_db_corrupt; - hexdump(pub, sz(pub)); - - alert(a_debug, "loading password salt"); - sr = read(db, salt, sz(salt)); - if (sr != sz(salt)) return bad_db_corrupt; - hexdump(salt, sz(salt)); - - alert(a_debug, "deriving secret"); - if(crypto_pwhash(key, sz(key), dbpw, pwlen, salt, - crypto_pwhash_OPSLIMIT_INTERACTIVE, - crypto_pwhash_MEMLIMIT_INTERACTIVE, - crypto_pwhash_ALG_DEFAULT) != 0) { - return bad_mem; + int db = dbopen(O_RDONLY); + if (db == -1) return bad_db_load; + + const char* target; + if (param == 1) target = params[0]; + else if (param == 0) target = NULL; + else return bad_syntax; + + uint8_t priv [db_privkey_len], + pub [db_pubkey_len]; + + /* try to decrypt db */ { + bad e = dbdecrypt(db,pub,priv); + if (e != ok) return e; + /* TODO allow multiple tries */ + } + + /* cursor should now be positioned + * on first record */ + alert(a_debug, "beginning to scan records"); + read_rec: { + uint8_t acctlen; + if (read(db, &acctlen, 1) != 1) + goto done_reading; + uint8_t ciphertext[acctlen]; + if (read(db, &ciphertext, acctlen) != acctlen) + return bad_db_corrupt; + alert(a_debug, "scanned record"); + hexdump(ciphertext, sz(ciphertext)); + + uint8_t plaintext[sz(ciphertext) - crypto_box_SEALBYTES]; + if(crypto_box_seal_open(plaintext, ciphertext, sz(ciphertext), pub, priv) != 0) + return bad_db_corrupt; + + alert(a_debug, "record deciphered"); + hexdump(plaintext, sz(plaintext)); + + uint8_t record_name_len = plaintext[0], + record_pw_len = plaintext[record_name_len + 1]; + + pstr record_name = {record_name_len, plaintext + 1}, + record_pw = {record_pw_len, + plaintext + record_name_len + 2}; + + if(op == lspw) { + bright(1, record_name.ptr, record_name.len); + if (print || !isatty(1)) { + write(1, ": ", 2); + write(1, record_pw.ptr, record_pw.len); + } + write(1, "\n", 1); + } else if (op == getpw) { + if (strncmp(record_name.ptr,target,record_name.len) == 0) { + if (print || _g_term_type[1] == plain_term) { + write(1, record_pw.ptr, record_pw.len); + if(_g_term_type[1] > plain_term) + write(1, "\n", 1); + } + + if (_g_term_type[1] > plain_term) { + if (copy_pw) copy(record_pw.ptr, record_pw.len); + } + goto done_reading; + } + } + + goto read_rec; } - hexdump(key, sz(key)); + return bad_index; - alert(a_debug, "loading encrypted private key"); - read(db, priv_enc, sz(priv_enc)); - hexdump(priv_enc, sz(priv_enc)); - - alert(a_debug, "decrypting private key"); - for (size_t i = 0; i < sz(key); ++i) { - priv[i] = priv_enc[i] ^ key[i]; - } - hexdump(priv, sz(priv)); - - alert(a_debug, "loading verification hash"); - read(db, salt_enc, sz(salt_enc)); - hexdump(salt_enc, sz(salt_enc)); - - alert(a_debug, "decrypting verification hash"); - hexdump(pub, sz(pub)); - hexdump(priv, sz(priv)); - printf("sz salt_enc = %zu\n / crypto_box bytes = %zu", sz(salt_enc), crypto_box_SEALBYTES); - int r = crypto_box_seal_open(salt_dec, salt_enc, - sz(salt_enc), pub, priv); - printf("result code: %d\n",r); - hexdump(salt_dec, sz(salt_dec)); - break; + done_reading: break; } case createdb: { /* kpw -C [] */ alert(a_debug, "creating new database"); @@ -650,9 +736,9 @@ alert(a_debug, "private key encrypted"); hexdump(priv_enc, sz(priv_enc)); alert(a_debug, "encrypting salt"); - crypto_box_seal(salt_enc, salt, sz(salt), priv); + crypto_box_seal(salt_enc, salt, sz(salt), pub); hexdump(salt_enc, sz(salt_enc)); /* we have everything we need. now we create the * file, failing if it already exists so as not