From 8769aa6370cd6b4ebd7393c32519a04772f5bd29 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Mon, 3 Sep 2012 04:43:01 +0200 Subject: Make into a real project. --- COPYING | 16 +++ INSTALL | 5 + Makefile | 10 ++ README | 5 +- bash-completion.sh | 28 ---- bash-completion/pass-bash-completion.sh | 28 ++++ man/pass.1 | 135 ++++++++++++++++++ pass | 1 - password-store.sh | 242 -------------------------------- src/password-store.sh | 242 ++++++++++++++++++++++++++++++++ 10 files changed, 440 insertions(+), 272 deletions(-) create mode 100644 COPYING create mode 100644 INSTALL create mode 100644 Makefile delete mode 100644 bash-completion.sh create mode 100644 bash-completion/pass-bash-completion.sh create mode 100644 man/pass.1 delete mode 120000 pass delete mode 100755 password-store.sh create mode 100755 src/password-store.sh diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..b7b8bf0 --- /dev/null +++ b/COPYING @@ -0,0 +1,16 @@ +Password Store is Copyright (C) 2012 Jason A. Donenfeld . + + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..854b6c0 --- /dev/null +++ b/INSTALL @@ -0,0 +1,5 @@ +Simply typing + + make install + +should install pass to the standard locations. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7c78ed3 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +all: + @echo "Password store is a shell script, so there is nothing to do. Try \"make install\" instead." + +install: + @install -v src/password-store.sh /usr/bin/pass + @install -v man/pass.1 /usr/share/man/man1/pass.1 + @install -v bash-completion/pass-bash-completion.sh /usr/share/bash-completion/pass + +uninstall: + @rm -vf /usr/bin/pass /usr/share/man/man1/pass.1 /usr/share/bash-completion/pass diff --git a/README b/README index 7dc8a40..8fb5d91 100644 --- a/README +++ b/README @@ -25,7 +25,7 @@ Usage: pass generate [--no-symbols,-n] [--clip,-c] pass-name pass-length Generate a new password of pass-length with optionally no symbols. Optionally put it on the clipboard and clear board after 45 seconds. - pass remove pass-name + pass rm pass-name Remove existing password. pass push If the password store is a git repository, push the latest changes. @@ -37,6 +37,9 @@ Usage: pass help Show this text. +See the man page for more options -- man 1 pass. + + Examples: - Initialize password store: diff --git a/bash-completion.sh b/bash-completion.sh deleted file mode 100644 index f3d285a..0000000 --- a/bash-completion.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -_pass() -{ - local cur prev prefix suffix gen - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" - prefix="$HOME/.password-store/" - suffix=".gpg" - - if [[ $prev == --* ]]; then - return 0 - fi - - for item in $(compgen -f $prefix$cur); do - if [[ $item == $prefix.* ]]; then - continue - fi - if [[ -d $item ]]; then - item="$item/" - fi - item="${item%$suffix}" - gen="$gen ${item#$prefix}" - done - - COMPREPLY=( $gen ) -} -complete -o filenames -o nospace -F _pass pass diff --git a/bash-completion/pass-bash-completion.sh b/bash-completion/pass-bash-completion.sh new file mode 100644 index 0000000..f3d285a --- /dev/null +++ b/bash-completion/pass-bash-completion.sh @@ -0,0 +1,28 @@ +#!/bin/bash +_pass() +{ + local cur prev prefix suffix gen + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + prefix="$HOME/.password-store/" + suffix=".gpg" + + if [[ $prev == --* ]]; then + return 0 + fi + + for item in $(compgen -f $prefix$cur); do + if [[ $item == $prefix.* ]]; then + continue + fi + if [[ -d $item ]]; then + item="$item/" + fi + item="${item%$suffix}" + gen="$gen ${item#$prefix}" + done + + COMPREPLY=( $gen ) +} +complete -o filenames -o nospace -F _pass pass diff --git a/man/pass.1 b/man/pass.1 new file mode 100644 index 0000000..cfec222 --- /dev/null +++ b/man/pass.1 @@ -0,0 +1,135 @@ +.TH PASS 1 "2012 Sept 2" ZX2C4 "Password Store" + +.SH NAME +pass - stores, retrieves, generates, and synchronizes passwords securely + +.SH SYNOPSIS +.B pass +[ +.I COMMAND +] [ +.I OPTIONS +]... [ +.I ARGS +]... + +.SH DESCRIPTION + +.B pass +is a very simple password store that keeps passwords inside +.BR gpg (1) +encrypted files inside a simple directory tree residing at +.IR $HOME/.password-store . +The +.B pass +utility provides a series of commands for manipulating the password store, +allowing the user to add, remove, edit, synchronize, generate, and manipulate +passwords. + +If no COMMAND is specified, COMMAND defaults to either +.B show +or +.BR ls , +depending on the type of specifier in ARGS. Otherwise COMMAND must be one of +the valid commands listed below. + +Several of the commands below rely on or provide additional functionality if +the password store directory is also a git repository. If the password store +directory is a git repository, all password store modification commands will +cause a corresponding git commit. + +The \fBinit\fP command must be run before other commands in order to initialize +the password store with the correct gpg key id. + +.SH COMMANDS + +.TP +\fBinit\fP \fIgpg-id\fP +Initialize new password storage and use +.I gpg-id +for encryption. This command must be run first before a password store can be +used. +.TP +\fBls\fP \fIsubfolder\fP +List names of passwords inside the tree at +.I subfolder +by using the +.BR tree (1) +program. This command is alternatively named \fBlist\fP. +.TP +\fBshow\fP [ \fI--clip\fP, \fI-c\fP ] \fIpass-name\fP +Decrypt and print a password named \fIpass-name\fP. If \fI--clip\fP or \fI-c\fP +is specified, do not print the password but instead copy it to the clipboard +using +.BR xclip (1) +and then restore the clipboard after 45 seconds. +.TP +\fBinsert\fP [ \fI--multiline\fP, \fI-m\fP ] \fIpass-name\fP +Insert a new password into the password store called \fIpass-name\fP. This will +read the new password from standard in. If \fI--multiline\fP or \fI-m\fP is +specified, lines will be read until EOF or Ctrl+D is reached. Otherwise, only +a single line from standard in is read. +.TP +\fBgenerate\fP [ \fI--no-symbols\fP, \fI-n\fP ] [ \fI--clip\fP, \fI-c\fP ] \fIpass-name pass-length\fP +Generate a new password using +.BR pwgen (1) +of length \fIpass-length\fP and insert into \fIpass-name\fP. If \fI--no-symbols\fP or \fI-n\fP +is specified, do not use any non-alphanumeric characters in the generated password. +If \fI--clip\fP or \fI-c\fP is specified, do not print the password but instead copy +it to the clipboard using +.BR xclip (1) +and then restore the clipboard after 45 seconds. +.TP +\fBrm\fP \fIpass-name\fP +Remove the password named \fIpass-name\fP from the password store. This command is +alternatively named \fBremove\fP. +.TP +\fBpush\fP +If the password store is a git repository, push the latest changes using +.BR git-push (1). +.TP +\fBpull\fP +If the password store is a git repository, pull the latest changes using +.BR git-pull (1). +.TP +\fBgit\fP \fIgit-command-args\fP... +If the password store is a git repository, pass \fIgit-command-args\fP as arguments to +.BR git (1) +using the password store as the git repository. +.TP +\fBhelp\fP +Show usage message. + +.SH FILES + +.TP +\fB~/.password-store\fP +The password storage directory. +.TP +\fB~/.password-store/.gpg-id\fP +Contains the gpg key identification used for encryption and decryption. This should +be set using the \fBinit\fP command. + +.SH SEE ALSO +.BR gpg (1), +.BR pwgen (1), +.BR git (1), +.BR xclip (1). + +.SH AUTHOR +Jason A. Donenfeld + +.SH COPYING +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. diff --git a/pass b/pass deleted file mode 120000 index 0a01348..0000000 --- a/pass +++ /dev/null @@ -1 +0,0 @@ -password-store.sh \ No newline at end of file diff --git a/password-store.sh b/password-store.sh deleted file mode 100755 index 3ec5b37..0000000 --- a/password-store.sh +++ /dev/null @@ -1,242 +0,0 @@ -#!/bin/bash - -umask 077 - -PREFIX="$HOME/.password-store" -ID="$PREFIX/.gpg-id" -GIT="$PREFIX/.git" - -export GIT_DIR="$GIT" -export GIT_WORK_TREE="$PREFIX" - -usage() { - cat <<_EOF -Password Store -by Jason Donenfeld - Jason@zx2c4.com - -Usage: - $program init gpg-id - Initialize new password storage and use gpg-id for encryption. - $program [ls] [subfolder] - List passwords. - $program [show] [--clip,-c] pass-name - Show existing password and optionally put it on the clipboard. - If put on the clipboard, it will be cleared in 45 seconds. - $program insert [--multiline,-m] pass-name - Insert new optionally multiline password. - $program generate [--no-symbols,-n] [--clip,-c] pass-name pass-length - Generate a new password of pass-length with optionally no symbols. - Optionally put it on the clipboard and clear board after 45 seconds. - $program remove pass-name - Remove existing password. - $program push - If the password store is a git repository, push the latest changes. - $program pull - If the password store is a git repository, pull the latest changes. - $program git git-command-args... - If the password store is a git repository, execute a git command - specified by git-command-args. - $program help - Show this text. -_EOF -} -isCommand() { - case "$1" in - init|ls|show|insert|generate|remove|rm|delete|push|pull|git|help) return 0 ;; - *) return 1 ;; - esac -} -clip() { - # This base64 business is a disgusting hack to deal with newline inconsistancies - # in shell. There must be a better way to deal with this, but because I'm a dolt, - # we're going with this for now. - - before="$(xclip -o -selection clipboard | base64)" - echo -n "$1" | xclip -selection clipboard - ( - sleep 45s - now="$(xclip -o -selection clipboard | base64)" - if [[ $now != $(echo -n "$1" | base64) ]]; then - before="$now" - fi - # It might be nice to programatically check to see if klipper exists, - # as well as checking for other common clipboard managers. But for now, - # this works fine. Clipboard managers frequently write their history - # out in plaintext, so we axe it here. - qdbus org.kde.klipper /klipper org.kde.klipper.klipper.clearClipboardHistory >/dev/null 2>&1 - echo "$before" | base64 -d | xclip -selection clipboard - ) & disown - echo "Copied $2 to clipboard. Will clear in 45 seconds." -} -program="$(basename "$0")" -command="$1" -if isCommand "$command"; then - shift -else - command="show" -fi - -case "$command" in - init) - if [[ $# -ne 1 ]]; then - echo "Usage: $program $command gpg-id" - exit 1 - fi - gpg_id="$1" - mkdir -v -p "$PREFIX" - echo "$gpg_id" > "$ID" - echo "Password store initialized for $gpg_id." - exit 0 - ;; - help) - usage - exit 0 - ;; -esac - -if ! [[ -f $ID ]]; then - echo "You must run:" - echo " $0 init your-gpg-id" - echo "before you may use the password store." - echo - usage - exit 1 -else - ID="$(head -n 1 "$ID")" -fi - -case "$command" in - show|ls) - clip=0 - if [[ $1 == "--clip" || $1 == "-c" ]]; then - clip=1 - shift - fi - path="$1" - if [[ -d $PREFIX/$path ]]; then - if [[ $path == "" ]]; then - echo "Password Store" - else - echo $path - fi - tree "$PREFIX/$path" | tail -n +2 | head -n -2 | sed 's/\(.*\)\.gpg$/\1/'; - else - passfile="$PREFIX/$path.gpg" - if ! [[ -f $passfile ]]; then - echo "$path is not in the password store." - exit 1 - fi - if [ $clip -eq 0 ]; then - exec gpg -q -d "$passfile" - else - clip $(gpg -q -d "$passfile") $path - fi - fi - ;; - insert) - ml=0 - if [[ $1 == "--multiline" || $1 == "-m" ]]; then - ml=1 - shift - fi - if [[ $# -ne 1 ]]; then - echo "Usage: $program $command [--multiline,-m] pass-name" - exit 1 - fi - path="$1" - mkdir -p -v "$PREFIX/$(dirname "$path")" - - passfile="$PREFIX/$path.gpg" - if [[ $ml -eq 0 ]]; then - echo -n "Enter password for $path: " - head -n 1 | gpg -e -r "$ID" > "$passfile" - else - echo "Enter contents of $path and press Ctrl+D when finished:" - echo - cat | gpg -e -r "$ID" > "$passfile" - fi - if [[ -d $GIT ]]; then - git add "$passfile" - git commit -m "Added given password for $path to store." - fi - ;; - generate) - clip=0 - symbols="-y" - while true; do - if [[ $1 == "--no-symbols" || $1 == "-n" ]]; then - symbols="" - shift - elif [[ $1 == "--clip" || $1 == "-c" ]]; then - clip=1 - shift - else - break - fi - done - if [[ $# -ne 2 ]]; then - echo "Usage: $program $command [--no-symbols,-n] [--clip,-c] pass-name pass-length" - exit 1 - fi - path="$1" - length="$2" - if ! [[ $length =~ ^[0-9]+$ ]]; then - echo "pass-length \"$length\" must be a number." - exit 1 - fi - mkdir -p -v "$PREFIX/$(dirname "$path")" - pass="$(pwgen -s $symbols $length 1)" - passfile="$PREFIX/$path.gpg" - echo $pass | gpg -e -r "$ID" > "$passfile" - if [[ -d $GIT ]]; then - git add "$passfile" - git commit -m "Added generated password for $path to store." - fi - - if [ $clip -eq 0 ]; then - echo "The generated password to $path is:" - echo "$pass" - else - clip "$pass" "$path" - fi - ;; - delete|rm|remove) - if [[ $# -ne 1 ]]; then - echo "Usage: $program $command pass-name" - exit - fi - path="$1" - passfile="$PREFIX/$path.gpg" - if ! [[ -f $passfile ]]; then - echo "$path is not in the password store." - exit 1 - fi - rm -i -v "$passfile" - if [[ -d $GIT ]] && ! [[ -f $passfile ]]; then - git rm -f "$passfile" - git commit -m "Removed $path from store." - fi - ;; - push|pull) - if [[ -d $GIT ]]; then - exec git $command $@ - else - echo "Error: the password store is not a git repository." - exit 1 - fi - ;; - git) - if [[ -d $GIT ]]; then - exec git $@ - else - echo "Error: the password store is not a git repository." - exit 1 - fi - ;; - *) - usage - exit 1 - ;; -esac -exit 0 diff --git a/src/password-store.sh b/src/password-store.sh new file mode 100755 index 0000000..2c6bd12 --- /dev/null +++ b/src/password-store.sh @@ -0,0 +1,242 @@ +#!/bin/bash + +umask 077 + +PREFIX="$HOME/.password-store" +ID="$PREFIX/.gpg-id" +GIT="$PREFIX/.git" + +export GIT_DIR="$GIT" +export GIT_WORK_TREE="$PREFIX" + +usage() { + cat <<_EOF +Password Store +by Jason Donenfeld + Jason@zx2c4.com + +Usage: + $program init gpg-id + Initialize new password storage and use gpg-id for encryption. + $program [ls] [subfolder] + List passwords. + $program [show] [--clip,-c] pass-name + Show existing password and optionally put it on the clipboard. + If put on the clipboard, it will be cleared in 45 seconds. + $program insert [--multiline,-m] pass-name + Insert new optionally multiline password. + $program generate [--no-symbols,-n] [--clip,-c] pass-name pass-length + Generate a new password of pass-length with optionally no symbols. + Optionally put it on the clipboard and clear board after 45 seconds. + $program rm pass-name + Remove existing password. + $program push + If the password store is a git repository, push the latest changes. + $program pull + If the password store is a git repository, pull the latest changes. + $program git git-command-args... + If the password store is a git repository, execute a git command + specified by git-command-args. + $program help + Show this text. +_EOF +} +isCommand() { + case "$1" in + init|ls|list|show|insert|generate|remove|rm|delete|push|pull|git|help) return 0 ;; + *) return 1 ;; + esac +} +clip() { + # This base64 business is a disgusting hack to deal with newline inconsistancies + # in shell. There must be a better way to deal with this, but because I'm a dolt, + # we're going with this for now. + + before="$(xclip -o -selection clipboard | base64)" + echo -n "$1" | xclip -selection clipboard + ( + sleep 45s + now="$(xclip -o -selection clipboard | base64)" + if [[ $now != $(echo -n "$1" | base64) ]]; then + before="$now" + fi + # It might be nice to programatically check to see if klipper exists, + # as well as checking for other common clipboard managers. But for now, + # this works fine. Clipboard managers frequently write their history + # out in plaintext, so we axe it here. + qdbus org.kde.klipper /klipper org.kde.klipper.klipper.clearClipboardHistory >/dev/null 2>&1 + echo "$before" | base64 -d | xclip -selection clipboard + ) & disown + echo "Copied $2 to clipboard. Will clear in 45 seconds." +} +program="$(basename "$0")" +command="$1" +if isCommand "$command"; then + shift +else + command="show" +fi + +case "$command" in + init) + if [[ $# -ne 1 ]]; then + echo "Usage: $program $command gpg-id" + exit 1 + fi + gpg_id="$1" + mkdir -v -p "$PREFIX" + echo "$gpg_id" > "$ID" + echo "Password store initialized for $gpg_id." + exit 0 + ;; + help) + usage + exit 0 + ;; +esac + +if ! [[ -f $ID ]]; then + echo "You must run:" + echo " $0 init your-gpg-id" + echo "before you may use the password store." + echo + usage + exit 1 +else + ID="$(head -n 1 "$ID")" +fi + +case "$command" in + show|ls|list) + clip=0 + if [[ $1 == "--clip" || $1 == "-c" ]]; then + clip=1 + shift + fi + path="$1" + if [[ -d $PREFIX/$path ]]; then + if [[ $path == "" ]]; then + echo "Password Store" + else + echo $path + fi + tree "$PREFIX/$path" | tail -n +2 | head -n -2 | sed 's/\(.*\)\.gpg$/\1/'; + else + passfile="$PREFIX/$path.gpg" + if ! [[ -f $passfile ]]; then + echo "$path is not in the password store." + exit 1 + fi + if [ $clip -eq 0 ]; then + exec gpg -q -d "$passfile" + else + clip $(gpg -q -d "$passfile") $path + fi + fi + ;; + insert) + ml=0 + if [[ $1 == "--multiline" || $1 == "-m" ]]; then + ml=1 + shift + fi + if [[ $# -ne 1 ]]; then + echo "Usage: $program $command [--multiline,-m] pass-name" + exit 1 + fi + path="$1" + mkdir -p -v "$PREFIX/$(dirname "$path")" + + passfile="$PREFIX/$path.gpg" + if [[ $ml -eq 0 ]]; then + echo -n "Enter password for $path: " + head -n 1 | gpg -e -r "$ID" > "$passfile" + else + echo "Enter contents of $path and press Ctrl+D when finished:" + echo + cat | gpg -e -r "$ID" > "$passfile" + fi + if [[ -d $GIT ]]; then + git add "$passfile" + git commit -m "Added given password for $path to store." + fi + ;; + generate) + clip=0 + symbols="-y" + while true; do + if [[ $1 == "--no-symbols" || $1 == "-n" ]]; then + symbols="" + shift + elif [[ $1 == "--clip" || $1 == "-c" ]]; then + clip=1 + shift + else + break + fi + done + if [[ $# -ne 2 ]]; then + echo "Usage: $program $command [--no-symbols,-n] [--clip,-c] pass-name pass-length" + exit 1 + fi + path="$1" + length="$2" + if ! [[ $length =~ ^[0-9]+$ ]]; then + echo "pass-length \"$length\" must be a number." + exit 1 + fi + mkdir -p -v "$PREFIX/$(dirname "$path")" + pass="$(pwgen -s $symbols $length 1)" + passfile="$PREFIX/$path.gpg" + echo $pass | gpg -e -r "$ID" > "$passfile" + if [[ -d $GIT ]]; then + git add "$passfile" + git commit -m "Added generated password for $path to store." + fi + + if [ $clip -eq 0 ]; then + echo "The generated password to $path is:" + echo "$pass" + else + clip "$pass" "$path" + fi + ;; + delete|rm|remove) + if [[ $# -ne 1 ]]; then + echo "Usage: $program $command pass-name" + exit + fi + path="$1" + passfile="$PREFIX/$path.gpg" + if ! [[ -f $passfile ]]; then + echo "$path is not in the password store." + exit 1 + fi + rm -i -v "$passfile" + if [[ -d $GIT ]] && ! [[ -f $passfile ]]; then + git rm -f "$passfile" + git commit -m "Removed $path from store." + fi + ;; + push|pull) + if [[ -d $GIT ]]; then + exec git $command $@ + else + echo "Error: the password store is not a git repository." + exit 1 + fi + ;; + git) + if [[ -d $GIT ]]; then + exec git $@ + else + echo "Error: the password store is not a git repository." + exit 1 + fi + ;; + *) + usage + exit 1 + ;; +esac +exit 0 -- cgit v1.2.3-59-g8ed1b