From 4314d40696381d6e53f7923edde7ad132f49a9be Mon Sep 17 00:00:00 2001 From: "Manuel Amador (Rudd-O)" Date: Sun, 7 May 2017 14:27:08 +0000 Subject: [PATCH] Initial commit. --- Makefile | 31 +++++++ README.md | 63 +++++++++++++ bin/qubes-pass-client | 52 +++++++++++ bin/qvm-pass | 124 ++++++++++++++++++++++++++ etc/qubes-rpc/policy/ruddo.PassManage | 6 ++ etc/qubes-rpc/policy/ruddo.PassRead | 6 ++ etc/qubes-rpc/ruddo.PassManage | 80 +++++++++++++++++ etc/qubes-rpc/ruddo.PassRead | 29 ++++++ qubes-pass.spec | 78 ++++++++++++++++ 9 files changed, 469 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100755 bin/qubes-pass-client create mode 100755 bin/qvm-pass create mode 100644 etc/qubes-rpc/policy/ruddo.PassManage create mode 100644 etc/qubes-rpc/policy/ruddo.PassRead create mode 100644 etc/qubes-rpc/ruddo.PassManage create mode 100644 etc/qubes-rpc/ruddo.PassRead create mode 100644 qubes-pass.spec diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7649f24 --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +BINDIR=/usr/bin +SYSCONFDIR=/etc +DESTDIR= +PROGNAME=qubes-pass + +clean: + find -name '*~' -print0 | xargs -0 rm -fv + rm -fv *.tar.gz *.rpm + +dist: clean + excludefrom= ; test -f .gitignore && excludefrom=--excludefrom=.gitignore ; DIR=$(PROGNAME)-`awk '/^Version:/ {print $$2}' $(PROGNAME).spec` && FILENAME=$$DIR.tar.gz && tar cvzf "$$FILENAME" --exclude="$$FILENAME" --exclude=.git --exclude=.gitignore $$excludefrom --transform="s|^|$$DIR/|" --show-transformed * + +rpm: dist + T=`mktemp -d` && rpmbuild --define "_topdir $$T" -ta $(PROGNAME)-`awk '/^Version:/ {print $$2}' $(PROGNAME).spec`.tar.gz || { rm -rf "$$T"; exit 1; } && mv "$$T"/RPMS/*/* "$$T"/SRPMS/* . || { rm -rf "$$T"; exit 1; } && rm -rf "$$T" + +srpm: dist + T=`mktemp -d` && rpmbuild --define "_topdir $$T" -ts $(PROGNAME)-`awk '/^Version:/ {print $$2}' $(PROGNAME).spec`.tar.gz || { rm -rf "$$T"; exit 1; } && mv "$$T"/SRPMS/* . || { rm -rf "$$T"; exit 1; } && rm -rf "$$T" + +install-client: + install -Dm 755 bin/qvm-pass -t $(DESTDIR)/$(BINDIR)/ + install -Dm 755 bin/qubes-pass-client -t $(DESTDIR)/$(BINDIR)/ + +install-service: + install -Dm 644 etc/qubes-rpc/ruddo.PassRead -t $(DESTDIR)/$(SYSCONFDIR)/qubes-rpc/ + install -Dm 644 etc/qubes-rpc/ruddo.PassManage -t $(DESTDIR)/$(SYSCONFDIR)/qubes-rpc/ + +install-dom0: + install -Dm 664 etc/qubes-rpc/policy/ruddo.PassRead -t $(DESTDIR)/$(SYSCONFDIR)/qubes-rpc/policy/ + getent group qubes && chgrp qubes $(DESTDIR)/$(SYSCONFDIR)/qubes-rpc/policy/ || true + install -Dm 664 etc/qubes-rpc/policy/ruddo.PassManage -t $(DESTDIR)/$(SYSCONFDIR)/qubes-rpc/policy/ + getent group qubes && chgrp qubes $(DESTDIR)/$(SYSCONFDIR)/qubes-rpc/policy/ || true diff --git a/README.md b/README.md new file mode 100644 index 0000000..7eca08e --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# Inter-VM Pass password manager for Qubes OS + +This is a very simple bridge between Qubes OS VMs. With it, you can +store and retrieve passwords between VMs without having to grant +any of the VMs any special policy privileges other than access to the +Qubes services implemented here. + +## Using the software + +These instructions assume you have installed the software. See the +*Installing the software* heading below for more information. + +Run `qvm-pass -?` on a terminal to get usage information. + +## Installing the software + +There are three components for this software: + +* The client `qvm-pass-client` you install in the VMs (or their templates) + where you want to manage your passwords. +* The service `qvm-pass-service` you install in the VMs (or their templates) + where you want to store your passwords. +* The policy `qvm-pass-dom0` is the dom0 side of things, necessary to + enable the services and control access from the clients to the service. + +First, build the software, After cloning this repository on a suitable VM, +run the command: + +``` +make rpm +``` + +This will generate three installable packages on the local directory: + +* `qvm-pass-client-.noarch.rpm` +* `qvm-pass-service-.noarch.rpm` +* `qvm-pass-dom0-.noarch.rpm` + +Copy the `qvm-pass-client-.noarch.rpm` file to the template VM +or standalone VM where you plan to manage passwords. Install the RPM with +`dnf install `. At this point, this VM, as well as +any VMs using this as a template, have gained the ability to list +and store passwords stored in other VMs. + +Now copy the `qvm-pass-service-.noarch.rpm` file to the template +VM or standalone VM where you plan to store passwords. Install the RPM with +`dnf install `. At this point, this VM, as well as +any VMs using this as a template, have gained the ability to securely store +passwords in `/home/user/.password-store`. + +Now copy the `qvm-pass-policy-dom0-.noarch.rpm` file to +your dom0. At this point, the default policy (`ask`) is active on +your Qubes OS system, and you can begin using the client. + +Those clever among you will have discovered that there is a `Makefile` +included, and that you can use the `Makefile` to install the software on +other non-RPM templates. I welcome pull requests to add support for +other distro packages and Qubes OS templates. + +## Troubleshooting and debugging + +As always, you can file new issues on the repo of this project for help +with fixing bugs that the programs may have. Pull requests also welcome. diff --git a/bin/qubes-pass-client b/bin/qubes-pass-client new file mode 100755 index 0000000..3a533e4 --- /dev/null +++ b/bin/qubes-pass-client @@ -0,0 +1,52 @@ +#!/bin/bash +set -e + +if [ -s /rw/config/pass-split-domain -a -z "$QUBES_PASS_DOMAIN" ] ; then + export QUBES_PASS_DOMAIN=$( cat /rw/config/pass-split-domain ) +fi + +if [ -z "$QUBES_PASS_DOMAIN" ] ; then + title="Qubes pass error" + msg="The QUBES_PASS_DOMAIN variable is not defined. Either create /rw/config/pass-split-domain with the VM containing your pass setup, set the environment variable yourself, or pass -d on the command line." + echo "$title: $msg" >&2 + zenity --error --text "$msg" --title "$title" & + exit 124 +fi + +if [ "$1" == "list" ] ; then + + cmd=$(echo "$1" | base64) + echo "$cmd" | /usr/lib/qubes/qrexec-client-vm "$QUBES_PASS_DOMAIN" ruddo.PassRead + +elif [ "$1" == "get" ] ; then + + cmd=$(echo "$1" | base64) + key=$(echo "$2" | base64) + echo "$cmd +$key" | /usr/lib/qubes/qrexec-client-vm "$QUBES_PASS_DOMAIN" ruddo.PassRead + +elif [ "$1" == "get-or-generate" ] ; then + cmd=$(echo "$1" | base64) + key=$(echo "$2" | base64) + autogen=$(echo 1 | base64) + echo "$cmd +$key +$autogen" | /usr/lib/qubes/qrexec-client-vm "$QUBES_PASS_DOMAIN" ruddo.PassManage + +elif [ "$1" == "insert" ] ; then + + cmd=$(echo "$1" | base64) + key=$(echo "$2" | base64) + multiline=$(echo "$3" | base64) + contents=$(echo "$4" | base64) + echo "$cmd +$key +$multiline +$contents" | /usr/lib/qubes/qrexec-client-vm "$QUBES_PASS_DOMAIN" ruddo.PassManage + +elif [ "$1" == "init" ] ; then + + cmd=$(echo "$1" | base64) + echo "$cmd" | /usr/lib/qubes/qrexec-client-vm "$QUBES_PASS_DOMAIN" ruddo.PassManage + +fi diff --git a/bin/qvm-pass b/bin/qvm-pass new file mode 100755 index 0000000..00ee028 --- /dev/null +++ b/bin/qvm-pass @@ -0,0 +1,124 @@ +#!/bin/bash + +TEMP=`getopt -o ?dmfe: -- "$@"` +force=0 +multiline=0 +echo=0 +eval set -- "$TEMP" +set -e + +usage() { + echo "qvm-pass usage:" + echo "" + echo " qvm-pass [-d ] [arguments...]" + echo "" + echo "subcommands:" + echo "" + echo " list" + echo " Retrieves the list of keys from the pass store." + echo " No subcommand accomplishes the same results" + echo " get " + echo " Retrieves a key from the pass store." + echo " If your key is not named after a subcommand, you can also" + echo " get its contents by passing it as the first argument of" + echo " this command, omitting the get subcommand." + echo " get-or-generate " + echo " Retrieves a key from the pass store; creates the key" + echo " with 32 characters length if it does not exist yet," + echo " and returns the generated key on standard output." + echo " insert [--echo,-e | --multiline,-m] [--force,-f] " + echo " Creates a key in the pass store." + exit 0 +} + +while true ; do + case "$1" in + -d) + case "$2" in + "") shift 2 ;; + *) export QUBES_PASS_DOMAIN="$2" ; shift 2 ;; + esac ;; + -m) + multiline=1 ; shift ;; + -f) + force=1 ; shift ;; + -e) + echo=1 ; shift ;; + "-?") + usage ;; + --) shift ; break ;; + *) echo "error processing options; run with -? for more information" ; exit 64 ;; + esac +done + +case "$1" in + get|get-or-generate) + if [ "$force$multiline$echo" != "000" ] ; then + echo "the $1 subcommand does not accept that option; run with -? for more information" >&2 ; exit 64 + fi + if [ -z "$2" ] ; then + echo "the $1 subcommand requires a key; run with -? for more information" >&2 ; exit 64 + fi + if [ -n "$3" ] ; then + echo "the $1 subcommand only accepts one argument; run with -? for more information" >&2 ; exit 64 + fi + exec qubes-pass-client "$1" "$2" + ;; + init) + if [ "$force$multiline$echo" != "000" ] ; then + echo "the $1 subcommand does not accept that option; run with -? for more information" >&2 ; exit 64 + fi + if [ -n "$2" ] ; then + echo "the $1 subcommand does not accept any arguments; run with -? for more information" >&2 ; exit 64 + fi + exec qubes-pass-client "$1" + ;; + insert) + shift + + if [ "$force" != "1" ] ; then + ret=0 ; errs=$(qubes-pass-client get "$1" >/dev/null 2>&1) || ret=$? + if [ "$ret" == "0" ] ; then + read -p "An entry already exists for $1. Overwrite it? [y/N] " response + if [ "$response" != "y" ] ; then exit 0 ; fi + elif [ "$ret" == "8" ] ; then + true + else + echo "$errs" >&2 + exit $ret + fi + fi + + contents= + if [ "$multiline" == "1" ] ; then + echo "Enter contents of $1 and press Ctrl+D when finished:" + echo "" + contents=$(cat) + elif [ "$echo" == "1" ] ; then + read -p "Enter password for b: " contents >&2 + else + read -s -p "Enter password for b: " contents >&2 + echo + read -s -p "Retype password for b: " retypedcontents >&2 + echo + if [ "$retypedcontents" != "$contents" ] ; then + echo "Error: the entered passwords do not match." + exit 1 + fi + fi + + exec qubes-pass-client insert "$1" "$multiline" "$contents" + ;; + list) + if [ "$force$multiline$echo" != "000" ] ; then + echo "the $1 subcommand does not accept that option; run with -? for more information" >&2 ; exit 64 + fi + exec qubes-pass-client list + ;; + *) + if [ "$force$multiline$echo" != "000" ] ; then + echo "the get subcommand does not accept that option; run with -? for more information" >&2 ; exit 64 + fi + exec qubes-pass-client get "$1" + ;; +esac diff --git a/etc/qubes-rpc/policy/ruddo.PassManage b/etc/qubes-rpc/policy/ruddo.PassManage new file mode 100644 index 0000000..e6957f5 --- /dev/null +++ b/etc/qubes-rpc/policy/ruddo.PassManage @@ -0,0 +1,6 @@ +## Note that policy parsing stops at the first match, +## so anything below the last line will have no effect. + +## Please use a single # to start your custom comments. + +$anyvm $anyvm ask diff --git a/etc/qubes-rpc/policy/ruddo.PassRead b/etc/qubes-rpc/policy/ruddo.PassRead new file mode 100644 index 0000000..e6957f5 --- /dev/null +++ b/etc/qubes-rpc/policy/ruddo.PassRead @@ -0,0 +1,6 @@ +## Note that policy parsing stops at the first match, +## so anything below the last line will have no effect. + +## Please use a single # to start your custom comments. + +$anyvm $anyvm ask diff --git a/etc/qubes-rpc/ruddo.PassManage b/etc/qubes-rpc/ruddo.PassManage new file mode 100644 index 0000000..62a961e --- /dev/null +++ b/etc/qubes-rpc/ruddo.PassManage @@ -0,0 +1,80 @@ +#!/bin/bash + +set -e + +read -n 4096 cmd +cmd=$(echo "$cmd" | base64 -d) + +if [ "$cmd" == "init" ] ; then + + if test -f "$HOME"/.password-store/.gpg-id ; then + key=$(cat "$HOME"/.password-store/.gpg-id) + echo "Not creating password store already exists and uses GPG key $key." >&2 + exit 8 + fi + + tmp=$(mktemp) + trap 'rm -f "$tmp"' EXIT + cat > "$tmp" <&1) || ret=$? + if [ "$ret" != "0" ] ; then + echo "$out" >&2 + exit "$ret" + fi + + key=$(echo "$out" | awk '/gpg: key .* marked as ultimately trusted/ { print $3 }') + pass init "$key" + echo "Do not forget to back up your password store regularly." >&2 + 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 + + read -n 4096 entry + read -n 4096 autogen + entry=$(echo "$entry" | base64 -d) + autogen=$(echo "$autogen" | base64 -d) + + if [ "$autogen" == "1" ] ; then + ret=0 ; out=$(pass -- "$entry") || ret=$? + if [ "$ret" == "1" ] && echo "$out" | grep -q "not in the password store" ; then + logger -t ruddo.PassManage "creating password entry $entry" + ret=0 ; out=$(pass generate -- "$entry" 32) || ret=$? + if [ "$ret" == "1" ] ; then + echo "Password generation failed: $out" + exit "$ret" + fi + elif [ "$ret" != "0" ] ; then + echo "$out" >&2 + exit "$ret" + fi + logger -t ruddo.PassManage "requested password entry $entry" + exec pass -- "$entry" + else + exit 23 + fi + +elif [ "$cmd" == "insert" ] ; then + + read -n 4096 entry + read -n 4096 multiline + read -n 1048576 contents + entry=$(echo "$entry" | base64 -d) + multiline=$(echo "$multiline" | base64 -d) + contents=$(echo "$contents" | base64 -d) + + logger -t ruddo.PassManage "creating password entry $entry" + + if [ "$multiline" == "1" ] ; then + echo "$contents" | pass insert --multiline --force -- "$entry" + else + echo "$contents" | pass insert -e --force -- "$entry" + fi + +fi diff --git a/etc/qubes-rpc/ruddo.PassRead b/etc/qubes-rpc/ruddo.PassRead new file mode 100644 index 0000000..ba260b4 --- /dev/null +++ b/etc/qubes-rpc/ruddo.PassRead @@ -0,0 +1,29 @@ +#!/bin/bash + +set -e + +read -n 4096 cmd +cmd=$(echo "$cmd" | base64 -d) + +if [ "$cmd" == "list" ] ; then + + logger -t ruddo.PassRead "requested password list". + exec pass + +elif [ "$cmd" == "get" ] ; then + + read -n 4096 entry + entry=$(echo "$entry" | base64 -d) + logger -t ruddo.PassRead "requested password entry $entry" + + tmp=$(mktemp) + trap 'rm -f "$tmp"' EXIT + ret=0 ; pass -- "$entry" 2> "$tmp" || ret=$? + if grep -qF "$entry is not in the password store." "$tmp" ; then + cat "$tmp" >&2 + exit 8 + fi + cat "$tmp" >&2 + exit $? + +fi diff --git a/qubes-pass.spec b/qubes-pass.spec new file mode 100644 index 0000000..850d912 --- /dev/null +++ b/qubes-pass.spec @@ -0,0 +1,78 @@ +%define debug_package %{nil} + +%define mybuildnumber %{?build_number}%{?!build_number:1} + +Name: qubes-pass +Version: 0.0.1 +Release: %{mybuildnumber}%{?dist} +Summary: Inter-VM pass password management for Qubes OS AppVMs and StandaloneVMs +BuildArch: noarch + +License: GPLv3+ +URL: https://github.com/Rudd-O/%{name} +Source0: https://github.com/Rudd-O/%{name}/archive/{%version}.tar.gz#/%{name}-%{version}.tar.gz + +BuildRequires: make + +%package service +Summary: Service package for Qubes OS dom0s that services %{name} + +Requires: pass + +%package dom0 +Summary: Policy package for Qubes OS dom0s that arbitrates %{name} + +Requires: qubes-core-dom0-linux + +%description +This package lets you setup a safe password management VM and then +manage the password store remotely from other VMs. You are meant to +install this package on the VM or template where you want to access +and manage your password store. + +%description service +This package lets you serve a safe password store to other VMs. +You are meant to install this package on the VM or template where +you want to keep your password store safe. + +%description dom0 +This package contains the Qubes OS execution policy for the %{name} package. +You are meant to install this package on the dom0 of the machine where you +have VMs that have the %{name} package installed. + +%prep +%setup -q + +%build +# variables must be kept in sync with install +make DESTDIR=$RPM_BUILD_ROOT BINDIR=%{_bindir} SYSCONFDIR=%{_sysconfdir} + +%install +rm -rf $RPM_BUILD_ROOT +# variables must be kept in sync with build +for target in install-client install-service install-dom0; do + make $target DESTDIR=$RPM_BUILD_ROOT BINDIR=%{_bindir} SYSCONFDIR=%{_sysconfdir} +done + +%check +if grep -r '@.*@' $RPM_BUILD_ROOT ; then + echo "Check failed: files with AT identifiers appeared" >&2 + exit 1 +fi + +%files +%attr(0755, root, root) %{_bindir}/qvm-pass +%attr(0755, root, root) %{_bindir}/qubes-pass-client +%doc README.md + +%files service +%attr(0644, root, root) %{_sysconfdir}/qubes-rpc/ruddo.PassRead +%attr(0644, root, root) %{_sysconfdir}/qubes-rpc/ruddo.PassManage + +%files dom0 +%config(noreplace) %attr(0644, root, qubes) %{_sysconfdir}/qubes-rpc/policy/ruddo.PassRead +%config(noreplace) %attr(0644, root, qubes) %{_sysconfdir}/qubes-rpc/policy/ruddo.PassManage + +%changelog +* Mon Oct 24 2016 Manuel Amador (Rudd-O) +- Initial release.