diff --git a/bin/qvm-pass b/bin/qvm-pass index 22ece3d..c4675f2 100755 --- a/bin/qvm-pass +++ b/bin/qvm-pass @@ -23,9 +23,15 @@ usage = "\n".join([ " Retrieves the list of keys from the pass store.", " ", " Retrieves a key from the pass store.", - " get-or-generate [-n] ", + " generate [-n] [-f] [pass-length]", " Retrieves a key from the pass store; creates the key", - " with 32 characters length if it does not exist yet,", + " with 25 characters length if it does not exist yet,", + " and returns the generated key on standard output.", + " The -n option excludes symbols from being used", + " during password generation.", + " get-or-generate [-n] [pass-length]", + " Retrieves a key from the pass store; creates the key", + " with 25 characters length if it does not exist yet,", " and returns the generated key on standard output.", " The -n option excludes symbols from being used", " during password generation.", @@ -93,18 +99,25 @@ else: p.add_argument("key", help="name of the key to be removed", type=str) p = _newcmd("get-or-generate", - "retrieves a key from the store, generating one if it does not exist") + "retrieves a key from the password store, generating one if it does not exist") p.add_argument("key", help="name of the key to be retrieved / generated", type=str) + p = _newcmd("generate", + "generates a key in the password store") + p.add_argument("key", help="name of the key to be generated", type=str) + p = _newcmd("insert", "inserts a new key into the pass store") p.add_argument("key", help="name of the key to be inserted", type=str) - for p in ["get-or-generate"]: + for p in ["get-or-generate", "generate"]: + _parsers[p].add_argument("pass_length", type=int, nargs='?', + help="number of characters in generated password", + default=25) _parsers[p].add_argument("-n", "--no-symbols", action="store_true", help="no symbols in generated password", default=False) - for p in ["mv", "cp", "rm", "insert"]: + for p in ["mv", "cp", "rm", "insert", "generate"]: _parsers[p].add_argument("-f", "--force", action="store_true", help="force overwriting / removing passwords instead of prompting", default=False) @@ -146,6 +159,11 @@ PASS_MANAGE = "ruddo.PassManage" def send_args(rpc, *args, **kwargs): cmd = ['/usr/lib/qubes/qrexec-client-vm', opts.dest_vm, rpc] # print(cmd, file=sys.stderr) + return_stdout = kwargs.get("return_stdout", False) + if "return_stdout" in kwargs: + del kwargs["return_stdout"] + kwargs['stdout'] = subprocess.PIPE + p = subprocess.Popen(cmd, stdin=subprocess.PIPE, **kwargs) for arg in args: # print(arg, file=sys.stderr) @@ -154,8 +172,13 @@ def send_args(rpc, *args, **kwargs): else: arg = base64.b64encode(arg) + b"\n" p.stdin.write(arg) + if return_stdout: + out, unused_err = p.communicate('') p.stdin.close() - return p.wait() + if return_stdout: + return p.wait(), out + else: + return p.wait() def pass_read(*args, **kwargs): @@ -191,17 +214,59 @@ elif opts.subcommand == "rm": sys.exit(1) sys.exit(pass_manage(opts.subcommand, opts.key)) elif opts.subcommand == "get-or-generate": - sys.exit(pass_manage(opts.subcommand, opts.key, str(int(opts.no_symbols)))) + with open(os.devnull, "w") as null: + ret, stdout = pass_read("get", opts.key, return_stdout=True, stderr=null) + if ret == 8: + # Not there. + with open(os.devnull, "w") as null: + ret = pass_manage("generate", opts.key, str(int(opts.no_symbols)), str(int(opts.pass_length)), stdout=null) + if ret != 0: + sys.exit(ret) + sys.exit(pass_read("get", opts.key)) + elif ret == 0: + # There. + sys.stdout.buffer.write(stdout) + sys.exit(ret) + else: + # Woops. + sys.exit(ret) +elif opts.subcommand == "generate": + doit = lambda: sys.exit(pass_manage(opts.subcommand, opts.key, str(int(opts.no_symbols)), str(int(opts.pass_length)))) + with open(os.devnull, "w") as null: + ret = pass_read("get", opts.key, stdout=null, stderr=null) + if ret == 8: + # Not there. + doit() + elif ret == 0: + # There: + if not opts.force and sys.stdin.isatty(): + sys.stderr.write("An entry already exists for %s. Overwrite it? [y/N] " % (opts.key,)) + ans = sys.stdin.readline().strip() + if ans and ans[0] in "yY": + doit() + else: + sys.exit(1) + else: + doit() + else: + sys.exit(ret) elif opts.subcommand == "insert": if not opts.force and sys.stdin.isatty(): with open(os.devnull, "w") as null: - if pass_read("get", opts.key, stdout=null, stderr=null) == 0: + ret = pass_read("get", opts.key, stdout=null, stderr=null) + if ret == 0: + # There. Confirm. sys.stderr.write("An entry already exists for %s. Overwrite it? [y/N] " % (opts.key,)) ans = sys.stdin.readline().strip() if ans and ans[0] in "yY": pass else: sys.exit(1) + elif ret == 8: + # Not there. Fall through. + pass + else: + sys.exit(ret) if opts.multiline: print("Enter contents of %s and press Ctrl+D when finished:\n" % (opts.key, ), file=sys.stderr) contents = sys.stdin.buffer.read() diff --git a/etc/qubes-rpc/ruddo.PassManage b/etc/qubes-rpc/ruddo.PassManage index 538db6b..f54dc78 100644 --- a/etc/qubes-rpc/ruddo.PassManage +++ b/etc/qubes-rpc/ruddo.PassManage @@ -25,32 +25,26 @@ if [ "$cmd" == "init" ] ; then echo "To back up your password store, back up the entire $HOSTNAME VM using Qubes backup." >&2 echo "Key files to backup: $HOME/.password-store and $HOME/.gnupg2" >&2 -elif [ "$cmd" == "get-or-generate" ] ; then +elif [ "$cmd" == "generate" ] ; then read -n 4096 entry read -n 4096 nosymbols + read -n 4096 numchars entry=$(echo "$entry" | base64 -d) nosymbols=$(echo "$nosymbols" | base64 -d) + numchars=$(echo "$numchars" | base64 -d) - ret=0 ; out=$(pass -- "$entry" 2>&1) || ret=$? - if [ "$ret" == "1" ] && echo "$out" | grep -q "not in the password store" ; then - logger -t ruddo.PassManage "creating password entry $entry" - ret=0 - if [ "$nosymbols" == "1" ] ; then - out=$(pass generate -n -- "$entry" 32) || ret=$? - else - out=$(pass generate -- "$entry" 32) || ret=$? - fi - if [ "$ret" == "1" ] ; then - echo "Password generation failed: $out" - exit "$ret" - fi - elif [ "$ret" != "0" ] ; then - echo "$out" >&2 - exit "$ret" + logger -t ruddo.PassManage "creating password entry $entry" + ret=0 + if [ "$nosymbols" == "1" ] ; then + pass generate -n -f -- "$entry" "$numchars" || ret=$? + else + pass generate -f -- "$entry" "$numchars" || ret=$? + fi + if [ "$ret" != "0" ] ; then + logger -t ruddo.PassManage "Password generation failed: $out" >&2 + exit "$ret" fi - logger -t ruddo.PassManage "requested password entry $entry" - exec pass -- "$entry" elif [ "$cmd" == "insert" ] ; then diff --git a/qubes-pass.spec b/qubes-pass.spec index 6e20478..4d688c2 100644 --- a/qubes-pass.spec +++ b/qubes-pass.spec @@ -3,7 +3,7 @@ %define mybuildnumber %{?build_number}%{?!build_number:1} Name: qubes-pass -Version: 0.0.15 +Version: 0.0.16 Release: %{mybuildnumber}%{?dist} Summary: Inter-VM pass password management for Qubes OS AppVMs and StandaloneVMs BuildArch: noarch