Index: kpw.d/errtab ================================================================== --- kpw.d/errtab +++ kpw.d/errtab @@ -1,7 +1,8 @@ ok completed successfully debug 0 fail unknown error +assert bug detected; please report this mem out of memory num not a number user kpw started as wrong user option unrecognized option passed syntax invalid invocation syntax @@ -8,10 +9,13 @@ entropy could not acquire entropy copy could not copy password pipe could not create pipe insane your environment is not sane index no such entry in database +shm shared memory failure +shm_exist key already in memory notice +no_shm no key in memory notice db_create database could not be created db_load database could not be read db_corrupt database corrupt db_empty no records in database notice cancel user canceled operation notice Index: kpw.d/kpw.c ================================================================== --- kpw.d/kpw.c +++ kpw.d/kpw.c @@ -23,10 +23,11 @@ */ #include #include #include +#include #include #include #include #include #include @@ -45,11 +46,12 @@ #else # define copy(str,len) #endif enum /* constants */ { - null = 0, true = 1, false = 0 + null = 0, true = 1, false = 0, + kpw_shm_key = 0x3CC215A, }; #include "err.inc" enum /* db format constants */ { @@ -95,22 +97,10 @@ #define str_num "0123456789" const char* reftbl = str_num str_ucase str_lcase; const char* reftbl_lcase = str_num str_lcase; const char* _g_binary_name; -#ifdef _CLIPBOARD -char* const* -cbd_cmds[] = { - /* NOTE: these commands must be specified in order of - * most- to least-specific. more than one utility may - * be present on a given system, so we need to make sure - * the right one is called. */ - (char* const[]){"termux-clipboard-set", null}, - (char* const[]){"xsel", "-bi", null}, - /* TODO: allow command to be specified by env var */ - null -}; enum { plain_term, ansi_term, color_term } _g_term_type[3]; enum alert {a_notice, a_warn, a_debug, a_fatal = 1 << 8}; pstr alert_msg[] = { _p("(kpw notice)"), _p("(kpw warn)"), @@ -146,10 +136,24 @@ write(2,msg,strlen(msg)); write(2,"\n",1); if (kind & a_fatal) exit(kind & (~a_fatal)); } + +#ifdef _CLIPBOARD +char* const* +cbd_cmds[] = { + /* NOTE: these commands must be specified in order of + * most- to least-specific. more than one utility may + * be present on a given system, so we need to make sure + * the right one is called. */ + (char* const[]){"termux-clipboard-set", null}, + (char* const[]){"xsel", "-bi", null}, + /* TODO: allow command to be specified by env var */ + null +}; + enum bad copy(const char* str, size_t len) { alert(a_debug, "copying password to clipboard"); if (geteuid() == 0) { @@ -458,28 +462,42 @@ enum bad dbunlock(byte* priv_enc, byte* salt, byte* priv) { const size_t priv_sz = sizeof(key_priv); byte key [db_privkey_len]; - password dbpw; size_t pwlen; - bad e = pwread(true, dbpw, &pwlen,_str("database key: ")); - if (e != ok) return e; - - 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, "attempting to decrypt private key"); - for (size_t i = 0; i < sz(key); ++i) { - priv[i] = priv_enc[i] ^ key[i]; - } - hexdump(priv, sz(key)); + /* is the private key loaded into memory? */ + int shm = shmget(*((key_t*) salt), sizeof(key_priv), 0); + if (shm == -1) { + /* no key in memory - read password from stdin instead */ + password dbpw; size_t pwlen; + bad e = pwread(true, dbpw, &pwlen,_str("database key: ")); + if (e != ok) return e; + + 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, "attempting to decrypt private key"); + for (size_t i = 0; i < sz(key); ++i) { + priv[i] = priv_enc[i] ^ key[i]; + } + hexdump(priv, sz(key)); + } else { + /* found a key in memory; loading it into *priv */ + alert(a_notice, "using saved key"); + key_priv* saved = shmat(shm, 0, 0); + if (saved == (void*)-1) + return bad_shm; + hexdump((byte*)saved, sizeof(key_priv)); + memcpy(priv, saved, sizeof(key_priv)); + shmdt(saved); + } return ok; } enum bad @@ -547,12 +565,13 @@ if (argc == 0) return bad_insane; _g_binary_name = argv[0]; enum genmode mode = lower; - enum {usage,getpw,addpw,delpw,lspw,genpw,regen, - chpw,keyin,logout,createdb,rekeydb,help} + enum {usage,getpw,addpw,delpw,lspw, + genpw,regen,mergedb,chpw,keyin, + logout,createdb,rekeydb,help} op = getpw; const char* params[3]; uint8_t param = 0; @@ -593,16 +612,52 @@ if (sodium_init() < 0) return bad_lib_sodium_init; switch(op) { + case logout: + case keyin: { + if (param != 0) return bad_syntax; + + int db = dbopen(O_RDONLY); + key_pub pub; + key_priv priv, priv_enc; + byte salt [crypto_pwhash_SALTBYTES], + salt_enc [crypto_box_SEALBYTES + sz(salt)]; + + bad e; + if ((e = dbheader_load(db, salt, salt_enc, pub, priv_enc)) != ok) + return e; + + key_t ipck = *((key_t*)salt); + if (op == keyin) { + if ((e = dbunlock(priv_enc, salt, priv)) != ok) + return e; + if ((e = dbverify(salt, salt_enc, pub, priv)) != ok) + return e; + int shm = shmget(ipck, sizeof(key_priv), + IPC_CREAT | IPC_EXCL | 0600); + if (shm == -1) return bad_shm_exist; + + key_priv* saved = shmat(shm, 0, 0); + if (saved == (void*)-1) return bad_shm; + memcpy(saved, priv, sz(priv)); + shmdt(saved); + } else { + int shm = shmget(ipck, sizeof(key_priv), 0); + if (shm == -1) return bad_no_shm; + shmctl(shm, IPC_RMID, NULL); + } + + return ok; + } case genpw: case addpw: { if (param == 0) return emit_usage( op == addpw ? " -a[p] []\n" : - /* genpw */" -g[lmup] []\n"); + /* genpw */" -g[lmusp] []\n"); if (param > 2 || param < 1) return bad_syntax; const char* acct = params[0], * prm = (param == 2 ? params[1] : NULL); @@ -622,25 +677,25 @@ const char* acct_pw; if (op == addpw) { if (prm == NULL) { pstr prompt_l[] = { _p("- new password for "), {0, acct}, _p(": "), }; - char prompt[pstrsum(prompt_l, sz(prompt_l)) + 1]; + char prompt[pstrsum(prompt_l, sz(prompt_l))]; if (tty_in) pstrcoll(prompt_l, sz(prompt_l), prompt); bad e = pwread(!print, pw, &pwlen, - prompt, sz(prompt) - 1); + prompt, sz(prompt)); if (e != ok) return e; if (tty_in && !print) { password pw_conf; e = pwread(true, pw_conf, NULL, _str("- confirm: ")); if (e != ok) return e; if (strcmp(pw,pw_conf) != 0) return bad_pw_match; } acct_pw = pw; - } else acct_pw = prm; + } else acct_pw = prm, pwlen = strlen(prm); } else if (op == genpw) { unsigned long long len; if (prm != NULL) { alert(a_debug, "converting length parameter to integer"); @@ -655,11 +710,13 @@ write(1, pw, len); if(tty_out) write(1, "\n", 1); } pwlen = len; } - if (copy_pw) copy(pw, pwlen); +# ifdef _CLIPBOARD + if (copy_pw) copy(pw, pwlen); +# endif alert(a_debug, "encoding database entry"); db_sz acctlen = strlen(acct); byte plaintext[1 + acctlen + 1 + pwlen]; plaintext[0] = acctlen; @@ -681,15 +738,24 @@ close(db); break; } + case chpw: + case regen: case delpw:{ /* kpw -d */ - if (param == 0) return emit_usage(" -d \n"); - if (param != 1) return bad_syntax; + if (param == 0) return emit_usage + (op==delpw ? " -d \n" : + op==regen ? " -r[lmusp] []" : + /* op==chpw */ " -c []"); + + if (param < 1 || param > (op == delpw ? 1 : 2)) + return bad_syntax; const char* target = params[0]; - bad e; + const char* delta; + if (param == 2) delta=params[1]; + else delta=NULL; int db = dbopen(O_RDWR); if (db == -1) return bad_db_load; const size_t dbsz = lseek(db, 0, SEEK_END); @@ -699,10 +765,11 @@ key_priv priv, priv_enc; byte salt [crypto_pwhash_SALTBYTES], salt_enc [crypto_box_SEALBYTES + sz(salt)]; + bad e; if ((e = dbheader_load(db, salt, salt_enc, pub, priv_enc)) != ok) return e; if ((e = dbunlock(priv_enc, salt, priv)) != ok) return e; if ((e = dbverify(salt, salt_enc, pub, priv)) != ok) @@ -735,15 +802,90 @@ bad d; if((d = dbnext(db, &rec, ctlen, pub, priv, ctbuf, ptbuf)) != ok) return d; if(strncmp(target, rec.acct.ptr, rec.acct.len) == 0) { - /* found a matching record; do not copy it */ - alert(a_debug, "found record to delete"); + /* found a matching record; determine + * what to do to the fucker */ + alert(a_debug, "found target record"); found = true; + if (op == delpw) continue; + + password pwbuf; + const char* newpass; + size_t pwlen; + if (op == regen) { + alert(a_debug, "generating new password"); + /* generating a new password. use the default + * length if the user hasn't supplied one herself, + * or if she has, convert it to an integer. */ + if (delta == NULL) pwlen = default_pw_len; else { + unsigned long long value; + bad k = katoi(10, delta, &value); + if (k != ok) return bad_num; + pwlen = value; + } + bad m = mkpw(mode, pwbuf, pwlen); + if (m != ok) return m; + newpass = pwbuf; + } else if (op == chpw) { + /* the user has requested a password change. take + * it from the command line if available, otherwise + * generate a prompt and read from stdin */ + + if (delta == NULL) { + pstr prompt_l[] = { _p("- new password for "), + {0, target}, _p(": "), }; + char prompt[pstrsum(prompt_l, sz(prompt_l))]; + if (_g_term_type[0] > plain_term) + pstrcoll(prompt_l, sz(prompt_l), prompt); + + bad p = pwread(!print, pwbuf, &pwlen, prompt, sz(prompt)); + if (p != ok) return p; + /* prompt again to make sure the user entered + * her new password correctly */ + if(!print && _g_term_type[0] > plain_term) { + password passconf; + p = pwread(!print, passconf, NULL, _str("confirm: ")); + if (p != ok) return p; + if (strcmp(passconf, pwbuf) != 0) + return bad_pw_match; + } + newpass = pwbuf; + } else newpass = delta, pwlen = strlen(delta); + } else return bad_assert; + + if (op == regen && print) { + write(1, newpass, pwlen); + if (_g_term_type[1] > plain_term) write(1, "\n", 1); + } + +# ifdef _CLIPBOARD + if (copy_pw) copy(newpass, pwlen); +# endif + + /* new pw is pointed to by `newpass`. encrypt it + * and insert it into the new database image */ + byte plaintext [1 + rec.acct.len + + 1 + pwlen ]; + plaintext[0] = rec.acct.len; + memcpy(plaintext + 1, rec.acct.ptr, rec.acct.len); + plaintext[1 + rec.acct.len] = pwlen; + memcpy(plaintext + 2 + rec.acct.len, newpass, pwlen); + + alert(a_debug, "enciphering plaintext of modified record"); + hexdump(plaintext, sz(plaintext)); + + byte ciphertext [sz(plaintext) + crypto_box_SEALBYTES]; + crypto_box_seal(ciphertext, plaintext, sz(plaintext), pub); + db_sz new_ct_len = sz(ciphertext); + + alert(a_debug, "copying ciphertext into database"); + cursor = transcribe(cursor, &new_ct_len, 1); + cursor = transcribe(cursor, ciphertext, sz(ciphertext)); } else { - /* not our target, copy it into the buffer */ + /* not our target, copy it into the buffer as-is */ cursor = transcribe(cursor, &ctlen, sizeof(db_sz)); cursor = transcribe(cursor, ctbuf, sz(ctbuf)); } } if (e != fail) return e; @@ -758,11 +900,11 @@ write(db, newdb, cursor-newdb); /* we're done here, time to close up shop */ close(db); - break; + return ok; } case getpw: /* kpw */ case lspw: { /* kpw -t[p] [] */ const char* target; @@ -812,23 +954,25 @@ write(1, rec.pw.ptr, rec.pw.len); if(_g_term_type[1] > plain_term) write(1, "\n", 1); } - if (_g_term_type[1] > plain_term) { - if (copy_pw) copy(rec.pw.ptr, rec.pw.len); - } +# ifdef _CLIPBOARD + if (_g_term_type[1] > plain_term) { + if (copy_pw) copy(rec.pw.ptr, rec.pw.len); + } +# endif found = true; break; } } } if (scanned == 0) return bad_db_empty; if (result != fail) return result; else if (!found) return bad_index; - break; + return ok; } case createdb: { /* kpw -C [] */ alert(a_debug, "creating new database"); if (clobber) @@ -926,20 +1070,14 @@ write(db, priv_enc, sz(priv_enc)); write(db, salt_enc, sz(salt_enc)); close(db); alert(a_debug, "database created"); - break; + return ok; } } -# ifdef _CLIPBOARD - if (copy_pw) { - /* copy(buf,len); */ - } -# endif - return ok; } int main (int argc, const char** argv) { Index: kpw.d/optab ================================================================== --- kpw.d/optab +++ kpw.d/optab @@ -1,20 +1,21 @@ -C create op = createdb create a new password database -R rekey op = rekeydb change database passphrase -! clobber clobber = true disable safety checks -a add op = addpw add password to the database -g gen op = genpw add account with a random password -d del op = delpw delete password from the database -t list op = lspw list all accounts (with passwords if -p is set) -c chpw op = chpw change password in the database -r regen op = regen generate new password for existing account -l lower mode = lower generate lowercase password -m mix mode = mix generate mix-case password -u upper mode = upper generate uppercase password -s stupid mode = stupid circumvent dumb pw restrictions -k key op = keyin save database key in session memory -o logout op = logout delete db key from session memory -n no-copy copy_pw = false print password instead of copying to clipboard _CLIPBOARD -p print print = true display passwords onscreen -q quiet _g_alert_quiet = true hide non-fatal reports -v verbose _g_debug_msgs = true display debug reports -h help op = help display help text +C create op = createdb create a new password database +R rekey op = rekeydb change database passphrase +M merge op = mergedb merge in another database +! clobber clobber = true disable safety checks +t list op = lspw list all accounts (with passwords if -p is set) +a add op = addpw add password to the database +g gen op = genpw add account with a random password +d del op = delpw delete password from the database +c chg op = chpw change password in the database +r regen op = regen generate new password for existing account +l lower mode = lower generate lowercase password +m mix mode = mix generate mix-case password +u upper mode = upper generate uppercase password +s stupid-mode mode = stupid circumvent dumb pw restrictions +k install-key op = keyin install database key in session memory +o logout op = logout delete db key from session memory +n no-copy copy_pw = false print password instead of copying to clipboard _CLIPBOARD +p print-pw print = true display passwords onscreen +q quiet _g_alert_quiet = true hide non-fatal reports +v verbose _g_debug_msgs = true display debug reports +h help op = help display help text