[pass] Export command discussion and PATCH

Fredrik Wallgren fredrik.wallgren at gmail.com
Tue Oct 20 10:34:00 CEST 2015


Hello!

I'm thinking about migrating my passwords to `pass`, but I found that it was 
missing a function I "need", so I implemented it.

This is ment to be a discussion about the supplied patch, since I'm not sure 
it is in mergeable condition. 

* The description in `--help` and `man pass` output should be looked at so it 
  gets the same feel as other output.
* Only exports three fields, name,password,extra where extra is all multiline 
  data except the first line. Is more data needed?
* The tests are pretty basic.

My motivation behind the `export` command is that I would like to have a plain 
text backup (that is stored separately, or even in analog format somewhere 
safe). It might be that I'm paranoid (or not enough), but I'm more afraid that 
I would mess up with the GPG-key/password than the possibility of my plain 
text export falling in the wrong hands, since I'm not that proficient with GPG 
"yet".

Description of the `export` command
The command outputs all password in plain text to STDOUT or file if supplied.
The command warns about that it might be a bad idea to export plain text 
passwords.
The command warns before overwriting existing file if file output is 
requested.
The output is CSV data with three fields name,password,extra and the data is 
quoted to support multiline data and names/passwords/extra data with quotes in 
them.
The output contains CSV headers default, but has flag to skip them.
The command contains flags to ignore warnings about existing files and plain 
text output.

What do you think about this addition? 
Is it something you think should be in the application?
Have I made any obvious mistakes? (I'm not that used to shell programming.)
Do you have extra functionality you would like to see implemented? 

This is my first email patch, so please tell me if I made some mistake with 
it.


Adds functionality for exporting passwords as plain text CSV data.
---
 man/pass.1            | 10 ++++++++++
 src/password-store.sh | 45 +++++++++++++++++++++++++++++++++++++++++++++
 tests/t0510-export.sh | 21 +++++++++++++++++++++
 3 files changed, 76 insertions(+)
 create mode 100755 tests/t0510-export.sh

diff --git a/man/pass.1 b/man/pass.1
index e1fe605..439fb5b 100644
--- a/man/pass.1
+++ b/man/pass.1
@@ -111,6 +111,13 @@ ensure that temporary files are created in \fI/dev/shm\fP in order to avoid writ
 difficult-to-erase disk sectors. If \fI/dev/shm\fP is not accessible, fallback to
 the ordinary \fITMPDIR\fP location, and print a warning.
 .TP
+\fBexport\fP [\fI--no-headers\fP, \fI-n\fP ] [ \fI--verbose\fP, \fI-v\fP ] [\fI--ignore\fP, \fI-i\fP ] [ \fI--force\fP, \fI-f\fP ] [ \fItarget-file\fP ]
+Exports all passwords in plain text as CSV data. Writes to \fISTDOUT\fP if \fItarget-file\fP is not supplied.
+Prompt before outputting plain text passwords, unless \fI--ignore\fP or \fI-i\fP is specified.
+Prompt before overwriting an existing \fItarget-file\fP, unless \fI--force\fP or \fI-f\fP is specified.
+Outputs name of secrets if \fI--verbose\fP or \fI-v\fP is specified.
+Outputs headers in CSV data, unless \fI--no-headers\fP or \fI-n\fP is specified.
+.TP
 \fBgenerate\fP [ \fI--no-symbols\fP, \fI-n\fP ] [ \fI--clip\fP, \fI-c\fP ] [ \fI--in-place\fP, \fI-i\fP | \fI--force\fP, \fI-f\fP ] \fIpass-name pass-length\fP
 Generate a new password using
 .BR pwgen (1)
@@ -278,6 +285,9 @@ Remove password from store
 rm: remove regular file \[u2018]/home/zx2c4/.password-store/Business/cheese-whiz-factory.gpg\[u2019]? y 
 .br
 removed \[u2018]/home/zx2c4/.password-store/Business/cheese-whiz-factory.gpg\[u2019]
+.TP
+Export passwords to CSV file
+.B zx2c4 at laptop ~ $ pass export pass-export.csv
 
 .SH EXTENDED GIT EXAMPLE
 Here, we initialize new password store, create a git repository, and then manipulate and sync passwords. Make note of the arguments to the first call of \fBpass git push\fP; consult
diff --git a/src/password-store.sh b/src/password-store.sh
index d535a74..e5cab19 100755
--- a/src/password-store.sh
+++ b/src/password-store.sh
@@ -234,6 +234,14 @@ cmd_usage() {
 	        overwriting existing password unless forced.
 	    $PROGRAM edit pass-name
 	        Insert a new password or edit an existing password using ${EDITOR:-vi}.
+	    $PROGRAM export [--no-headers,-n] [--verbose,-v] [--ignore,-i] [--force,-f] [target-file]
+	        Exports all passwords in plain text as CSV data to target-file or
+	        to STDOUT if no target-file is supplied.
+	        Outputs a warning prompt that passwords will be written to file
+	        unencrypted, unless the --ignore flag is present.
+	        Use the --force flag to force overwrite of target-file.
+	        The --verbose flag outputs the name of secrets as they are exported.
+	        Headers are generated for CSV data, unless the --no-headers flag is present.
 	    $PROGRAM generate [--no-symbols,-n] [--clip,-c] [--in-place,-i | --force,-f] 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 $CLIP_TIME seconds.
@@ -428,6 +436,42 @@ cmd_edit() {
 	git_add_file "$passfile" "$action password for $path using ${EDITOR:-vi}."
 }
 
+cmd_export() {
+	local opts no_headers=0 verbose=0 ignore=0 force=0
+	opts="$($GETOPT -o ifvn -l no-headers,verbose,ignore,force -n "$PROGRAM" -- "$@")"
+	local err=$?
+	eval set -- "$opts"
+	while true; do case $1 in
+		-f|--force) force=1; shift ;;
+		-i|--ignore) ignore=1; shift ;;
+		-v|--verbose) verbose=1; shift ;;
+		-n|--no-headers) no_headers=1; shift ;;
+		--) shift; break ;;
+	esac done
+
+	local outfile="$1"
+	[[ $1 && $force -eq 0 && -e $outfile ]] && yesno "File $outfile does already exist. Overwrite it?"
+	[[ $1 ]] && exec 4>$1 || exec 4>&1
+
+	[[ $no_headers -eq 0 ]] && echo "name,password,extra" >&4
+
+	[[ $ignore -eq 0 ]] && yesno "Exporting data will write unencrypted data to disk. Continue?"
+
+	shopt -s nullglob globstar
+	for file in "$PREFIX"/**/*.gpg; do
+		local filen="${file#$PREFIX/}"
+		local name=${filen%.*}
+		local contents="$($GPG -d "${GPG_OPTS[@]}" "$file")"
+		local pass=$(echo "$contents" | head -n 1)
+		local extra=$(echo "$contents" | tail -n +2)
+		name=${name//\"/\"\"}
+		pass=${pass//\"/\"\"}
+		extra=${extra//\"/\"\"}
+		[[ $verbose -ne 0 ]] && echo $name
+		printf "%s,%s,%s\n" "\"$name\"" "\"$pass\"" "\"$extra\"" >&4
+	done
+}
+
 cmd_generate() {
 	local opts clip=0 force=0 symbols="-y" inplace=0
 	opts="$($GETOPT -o ncif -l no-symbols,clip,in-place,force -n "$PROGRAM" -- "$@")"
@@ -585,6 +629,7 @@ case "$1" in
 	grep) shift;			cmd_grep "$@" ;;
 	insert|add) shift;		cmd_insert "$@" ;;
 	edit) shift;			cmd_edit "$@" ;;
+	export) shift;			cmd_export "$@" ;;
 	generate) shift;		cmd_generate "$@" ;;
 	delete|rm|remove) shift;	cmd_delete "$@" ;;
 	rename|mv) shift;		cmd_copy_move "move" "$@" ;;
diff --git a/tests/t0510-export.sh b/tests/t0510-export.sh
new file mode 100755
index 0000000..aae309f
--- /dev/null
+++ b/tests/t0510-export.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+test_description='Test export'
+cd "$(dirname "$0")"
+. ./setup.sh
+
+test_expect_success 'Test "export" command' '
+	"$PASS" init $KEY1 &&
+	"$PASS" export -i
+'
+
+test_expect_success 'Test "export" output' '
+	[[ $("$PASS" export -i) == name,password,extra ]]
+'
+
+test_expect_success 'Test "export" output with passwords' '
+	"$PASS" insert -e "cred1"<<<"BLAH!!" &&
+	"$PASS" export -i | grep cred1
+'
+
+test_done

-- 
Fredrik Wallgren

-------------- next part --------------
>From 19120c495b1881404b9bd27d7a9019786b377b17 Mon Sep 17 00:00:00 2001
From: Fredrik Wallgren <fredrik.wallgren at gmail.com>
Date: Tue, 20 Oct 2015 09:38:37 +0200
Subject: [PATCH] Add export command

Adds functionality for exporting passwords as plain text CSV data.
---
 man/pass.1            | 10 ++++++++++
 src/password-store.sh | 45 +++++++++++++++++++++++++++++++++++++++++++++
 tests/t0510-export.sh | 21 +++++++++++++++++++++
 3 files changed, 76 insertions(+)
 create mode 100755 tests/t0510-export.sh

diff --git a/man/pass.1 b/man/pass.1
index e1fe605..439fb5b 100644
--- a/man/pass.1
+++ b/man/pass.1
@@ -111,6 +111,13 @@ ensure that temporary files are created in \fI/dev/shm\fP in order to avoid writ
 difficult-to-erase disk sectors. If \fI/dev/shm\fP is not accessible, fallback to
 the ordinary \fITMPDIR\fP location, and print a warning.
 .TP
+\fBexport\fP [\fI--no-headers\fP, \fI-n\fP ] [ \fI--verbose\fP, \fI-v\fP ] [\fI--ignore\fP, \fI-i\fP ] [ \fI--force\fP, \fI-f\fP ] [ \fItarget-file\fP ]
+Exports all passwords in plain text as CSV data. Writes to \fISTDOUT\fP if \fItarget-file\fP is not supplied.
+Prompt before outputting plain text passwords, unless \fI--ignore\fP or \fI-i\fP is specified.
+Prompt before overwriting an existing \fItarget-file\fP, unless \fI--force\fP or \fI-f\fP is specified.
+Outputs name of secrets if \fI--verbose\fP or \fI-v\fP is specified.
+Outputs headers in CSV data, unless \fI--no-headers\fP or \fI-n\fP is specified.
+.TP
 \fBgenerate\fP [ \fI--no-symbols\fP, \fI-n\fP ] [ \fI--clip\fP, \fI-c\fP ] [ \fI--in-place\fP, \fI-i\fP | \fI--force\fP, \fI-f\fP ] \fIpass-name pass-length\fP
 Generate a new password using
 .BR pwgen (1)
@@ -278,6 +285,9 @@ Remove password from store
 rm: remove regular file \[u2018]/home/zx2c4/.password-store/Business/cheese-whiz-factory.gpg\[u2019]? y 
 .br
 removed \[u2018]/home/zx2c4/.password-store/Business/cheese-whiz-factory.gpg\[u2019]
+.TP
+Export passwords to CSV file
+.B zx2c4 at laptop ~ $ pass export pass-export.csv
 
 .SH EXTENDED GIT EXAMPLE
 Here, we initialize new password store, create a git repository, and then manipulate and sync passwords. Make note of the arguments to the first call of \fBpass git push\fP; consult
diff --git a/src/password-store.sh b/src/password-store.sh
index d535a74..e5cab19 100755
--- a/src/password-store.sh
+++ b/src/password-store.sh
@@ -234,6 +234,14 @@ cmd_usage() {
 	        overwriting existing password unless forced.
 	    $PROGRAM edit pass-name
 	        Insert a new password or edit an existing password using ${EDITOR:-vi}.
+	    $PROGRAM export [--no-headers,-n] [--verbose,-v] [--ignore,-i] [--force,-f] [target-file]
+	        Exports all passwords in plain text as CSV data to target-file or
+	        to STDOUT if no target-file is supplied.
+	        Outputs a warning prompt that passwords will be written to file
+	        unencrypted, unless the --ignore flag is present.
+	        Use the --force flag to force overwrite of target-file.
+	        The --verbose flag outputs the name of secrets as they are exported.
+	        Headers are generated for CSV data, unless the --no-headers flag is present.
 	    $PROGRAM generate [--no-symbols,-n] [--clip,-c] [--in-place,-i | --force,-f] 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 $CLIP_TIME seconds.
@@ -428,6 +436,42 @@ cmd_edit() {
 	git_add_file "$passfile" "$action password for $path using ${EDITOR:-vi}."
 }
 
+cmd_export() {
+	local opts no_headers=0 verbose=0 ignore=0 force=0
+	opts="$($GETOPT -o ifvn -l no-headers,verbose,ignore,force -n "$PROGRAM" -- "$@")"
+	local err=$?
+	eval set -- "$opts"
+	while true; do case $1 in
+		-f|--force) force=1; shift ;;
+		-i|--ignore) ignore=1; shift ;;
+		-v|--verbose) verbose=1; shift ;;
+		-n|--no-headers) no_headers=1; shift ;;
+		--) shift; break ;;
+	esac done
+
+	local outfile="$1"
+	[[ $1 && $force -eq 0 && -e $outfile ]] && yesno "File $outfile does already exist. Overwrite it?"
+	[[ $1 ]] && exec 4>$1 || exec 4>&1
+
+	[[ $no_headers -eq 0 ]] && echo "name,password,extra" >&4
+
+	[[ $ignore -eq 0 ]] && yesno "Exporting data will write unencrypted data to disk. Continue?"
+
+	shopt -s nullglob globstar
+	for file in "$PREFIX"/**/*.gpg; do
+		local filen="${file#$PREFIX/}"
+		local name=${filen%.*}
+		local contents="$($GPG -d "${GPG_OPTS[@]}" "$file")"
+		local pass=$(echo "$contents" | head -n 1)
+		local extra=$(echo "$contents" | tail -n +2)
+		name=${name//\"/\"\"}
+		pass=${pass//\"/\"\"}
+		extra=${extra//\"/\"\"}
+		[[ $verbose -ne 0 ]] && echo $name
+		printf "%s,%s,%s\n" "\"$name\"" "\"$pass\"" "\"$extra\"" >&4
+	done
+}
+
 cmd_generate() {
 	local opts clip=0 force=0 symbols="-y" inplace=0
 	opts="$($GETOPT -o ncif -l no-symbols,clip,in-place,force -n "$PROGRAM" -- "$@")"
@@ -585,6 +629,7 @@ case "$1" in
 	grep) shift;			cmd_grep "$@" ;;
 	insert|add) shift;		cmd_insert "$@" ;;
 	edit) shift;			cmd_edit "$@" ;;
+	export) shift;			cmd_export "$@" ;;
 	generate) shift;		cmd_generate "$@" ;;
 	delete|rm|remove) shift;	cmd_delete "$@" ;;
 	rename|mv) shift;		cmd_copy_move "move" "$@" ;;
diff --git a/tests/t0510-export.sh b/tests/t0510-export.sh
new file mode 100755
index 0000000..aae309f
--- /dev/null
+++ b/tests/t0510-export.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+test_description='Test export'
+cd "$(dirname "$0")"
+. ./setup.sh
+
+test_expect_success 'Test "export" command' '
+	"$PASS" init $KEY1 &&
+	"$PASS" export -i
+'
+
+test_expect_success 'Test "export" output' '
+	[[ $("$PASS" export -i) == name,password,extra ]]
+'
+
+test_expect_success 'Test "export" output with passwords' '
+	"$PASS" insert -e "cred1"<<<"BLAH!!" &&
+	"$PASS" export -i | grep cred1
+'
+
+test_done
-- 
2.6.1



More information about the Password-Store mailing list