pashage

Yet Another Opinionated Re-engineering of the Unix Password Store
git clone https://git.instinctive.eu/pashage.git
Log | Files | Refs | README | LICENSE

commit 4813f7160d5f1f271d7cbfb3e0263c269d599c34
parent 8cfd312b56e4d958f60cd9de133ab28574edc439
Author: Natasha Kerensikova <natgh@instinctive.eu>
Date:   Sun, 22 Sep 2024 11:04:46 +0000

passage-like behavior is also tested in pass_spec.sh
Diffstat:
MARCHITECTURE.md | 6+++---
Mspec/pass_spec.sh | 63++++++++++++++++++++++++++++++++++++++++++++-------------------
Mspec/support/bin/mock-age | 57+++++++++++++++++++++++++++++++++++++++++++--------------
3 files changed, 90 insertions(+), 36 deletions(-)

diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md @@ -39,11 +39,11 @@ The following test sets can be found in `spec/` directory: - `scm_spec.sh` tests SCM functions in isolation, using the real git and filesystem; - TODO tests integration, calling command functions with minimal mocks; -- `pass_spec.sh` tests `pass`-like behavior of the whole script; -- TODO tests `passage`-like behavior of the whole script. +- `pass_spec.sh` tests `pass`-like and `passage`-like behavior of + the whole script. Platform functions are not tested, because the platform adherence make it too difficult to test it automatically. -`age` and `git` are always mocked, to make the tests reproducible and easier +`age` and `gpg` are always mocked, to make the tests reproducible and easier to design, and to make the failures easier to investigate. diff --git a/spec/pass_spec.sh b/spec/pass_spec.sh @@ -15,21 +15,28 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# This test file should pass with either pashage -# or pass (the original password-store). +# This test file should pass with pashage, pass (the original password-store), +# or passage (the pass fork by Filippo Valsorda). -# The original password-store script can be added to the parameters list +# The test suite also checks the internal of the store, thanks to mocked +# cryptography. This should ensure that pashage is indeed a drop-in replacement +# for passage, and that both of them can update interchangeably the same +# password store repository. + +# The other password-store scripts can be added to the parameters list # to run the cases against it. # bash seems to escape the sandbox by resetting PATH on the slightest # provocation, while the tests rely heavily on the mocked cryptography # to check the store behavior. # So it only works when using shellspec with bash and calling the `pass` -# script directly (e.g. on FreeBSD `/usr/local/libexec/password-store/pass` +# or `password-store.sh` script directly +# (e.g. on FreeBSD `/usr/local/libexec/password-store/pass` # instead of `/usr/local/bin/pass`). -Parameters # script/path scriptname encryption -# /usr/bin/pass pass gpg - ./src/run.sh pashage age +Parameters # script/path scriptname encryption +# /usr/bin/pass pass gpg +# /git/passage/src/password-store.sh passage age + ./src/run.sh pashage age End Describe 'Pass-like command' @@ -41,8 +48,8 @@ Describe 'Pass-like command' PREFIX="${SHELLSPEC_WORKDIR}/store" export PASSWORD_STORE_DIR="${PREFIX}" - export PASHAGE_STORE_DIR="${PREFIX}" - export PASHAGE_IDENTITIES_FILE="${SHELLSPEC_WORKDIR}/age-identities" + export PASSAGE_DIR="${PREFIX}" + export PASSAGE_IDENTITIES_FILE="${SHELLSPEC_WORKDIR}/age-identities" git_log() { @git -C "${PREFIX}" log --format='%s' --stat >|"${GITLOG}" @@ -87,7 +94,7 @@ Describe 'Pass-like command' @git init -q -b main "${PREFIX}" @git -C "${PREFIX}" config --local user.name 'Test User' @git -C "${PREFIX}" config --local user.email 'test@example.com' - %putsn 'myself' >"${PASHAGE_IDENTITIES_FILE}" + %putsn 'myself' >"${PASSAGE_IDENTITIES_FILE}" %putsn 'myself' >"${PREFIX}/.gpg-id" %text | setup_secret 'subdir/file' #|Recipient:myself @@ -130,7 +137,7 @@ Describe 'Pass-like command' cleanup() { @rm -rf "${PREFIX}" - @rm -f "${PASHAGE_IDENTITIES_FILE}" + @rm -f "${PASSAGE_IDENTITIES_FILE}" } BeforeEach setup @@ -225,7 +232,7 @@ Describe 'Pass-like command' Mock qrencode . "${SHELLSPEC_SUPPORT_BIN}" - invoke od -v -t x1 | sed 's/ */ /g;s/ *$//' >&2 + invoke od -v -t x1 | sed 's/ */ /g;s/ *$//' End Mock mktemp @@ -289,6 +296,7 @@ Describe 'Pass-like command' Describe 'init' It 're-encrypts the whole store using a new recipient id' Skip if 'pass(age) needs bash' check_skip $2 + Skip if 'passage has no init' [ "$2" = passage ] When run script $1 init 'new-id' The output should include 'Password store' expected_log() { @@ -322,6 +330,7 @@ Describe 'Pass-like command' It 're-encrypts a subdirectory using a new recipient id' Skip if 'pass(age) needs bash' check_skip $2 + Skip if 'passage has no init' [ "$2" = passage ] When run script $1 init -p subdir 'new-id' The output should start with 'Password store' The output should include 'subdir' @@ -352,6 +361,7 @@ Describe 'Pass-like command' It 're-encrypts a subdirectory after replacing recipient ids' Skip if 'pass(age) needs bash' check_skip $2 + Skip if 'passage has no init' [ "$2" = passage ] When run script $1 init -p fluff 'new-id' 'new-master' The output should start with 'Password store' The output should include 'fluff' @@ -386,6 +396,7 @@ Describe 'Pass-like command' It 're-encrypts a subdirectory after removing dedicated recipient ids' Skip if 'pass(age) needs bash' check_skip $2 + Skip if 'passage has no init' [ "$2" = passage ] When run script $1 init -p fluff '' The status should be successful expected_log() { @@ -443,7 +454,11 @@ Describe 'Pass-like command' It 'lists the whole store without argument' Skip if 'pass(age) needs bash' check_skip $2 When run script $1 - The line 1 of output should equal 'Password Store' + if [ $2 = passage ]; then + The line 1 of output should equal 'Passage' + else + The line 1 of output should equal 'Password Store' + fi The line 2 of output should include 'extra' The line 3 of output should include 'subdir' The line 4 of output should include 'file' @@ -522,7 +537,11 @@ Describe 'Pass-like command' #|0000000 31 2d 70 61 73 73 77 6f 72 64 #|0000012 } - The error should equal "$(expected_err "$2")" + if [ $2 = pashage ]; then + The error should equal "$(expected_err 'pashage')" + else + The error should equal "$(expected_err 'pass')" + fi End It 'displays the given line as a QR-code' @@ -534,7 +553,11 @@ Describe 'Pass-like command' #|0000000 55 73 65 72 6e 61 6d 65 3a 20 33 4a 61 6e 65 #|0000017 } - The error should equal "$(expected_err "$2")" + if [ $2 = pashage ]; then + The error should equal "$(expected_err 'pashage')" + else + The error should equal "$(expected_err 'pass')" + fi End It 'pastes into the clipboard' @@ -572,10 +595,10 @@ Describe 'Pass-like command' When run script $1 grep -i Com The lines of output should equal 4 The line 1 of output should include 'fluff' - The line 1 of output should include 'three' + The output should include 'three' The line 2 of output should include 'https://example.' The line 3 of output should include 'fluff' - The line 3 of output should include 'two' + The output should include 'two' The line 4 of output should include 'https://example.' End @@ -921,7 +944,7 @@ Describe 'Pass-like command' When run script $1 generate -qn new The output should not include 'The generated password for' The output should not include "$(@sed -n "2s/$3://p" "${PREFIX}/new.$3")" - The error should start with "$ feh -x --title $2: new -g +200+200 -" + The error should start with '$ feh -x --title pas' expected_log() { %text:expand #|Add generated password for new. #| @@ -1405,7 +1428,9 @@ Describe 'Pass-like command' It 'displays a help text with supported commands' Skip if 'pass(age) needs bash' check_skip $2 When run script $1 help - The output should include ' init ' + if ! [ $2 = passage ]; then + The output should include ' init ' + fi The output should include ' find ' The output should include ' [show] ' The output should include ' grep ' diff --git a/spec/support/bin/mock-age b/spec/support/bin/mock-age @@ -11,34 +11,63 @@ case "$1" in -e) shift MOCK_AGE_OUTPUT="$(@mktemp "$(dirname "$2")/mock-age-encrypt.XXXXXXX")" + DEST='-' while [ $# -gt 0 ]; do case "$1" in - -R|-i) + -R|-i) @sed 's/^/ageRecipient:/' "$2" >>"${MOCK_AGE_OUTPUT}" shift 2 ;; - -r) + -r) printf 'ageRecipient:%s\n' "$2" >>"${MOCK_AGE_OUTPUT}" shift 2 ;; - -o) - [ $# -eq 2 ] || die 'Unexpected age -e [...] %s\n' "$*" - @sed 's/^/age:/' >>"${MOCK_AGE_OUTPUT}" - @mv -f "${MOCK_AGE_OUTPUT}" "$2" - shift 2 ;; - *) + -o) + DEST="$2" + shift 2 + break ;; + *) die 'Unexpected age -e [...] %s\n' "$*" ;; esac done + + if [ $# -gt 1 ]; then + die "Unexpected arguments to age -e [...] $*\n" + elif [ $# -eq 1 ]; then + @sed 's/^/age:/' "$1" >>"${MOCK_AGE_OUTPUT}" + else + @sed 's/^/age:/' >>"${MOCK_AGE_OUTPUT}" + fi + + if [ "${DEST}" = '-' ]; then + @cat "${MOCK_AGE_OUTPUT}" + @rm -f "${MOCK_AGE_OUTPUT}" + else + @mv -f "${MOCK_AGE_OUTPUT}" "${DEST}" + fi ;; -d) - [ "$2" = '-i' ] || die "Unexpected age -d \$2: \"$2\"" - [ "$4" = '--' ] || die "Unexpected age -d \$4: \"$4\"" - @grep -v '^age' "$5" >&2 && die "Bad encrypted file \"$4\"" - if ! @grep -qFx "ageRecipient:$(@cat "$3")" "$5"; then - die "Bad identity \"$3\": $(@cat "$3")" + shift + DEST='-' + if [ "$1" = '-o' ]; then + DEST="$2" + shift 2 + fi + [ "$1" = '-i' ] || die "Unexpected age -d arg: \"$1\"" + MOCK_AGE_ID_SRC="$2" + MOCK_AGE_ID="$(@cat "$2")" + shift 2 + [ "$1" = '--' ] && shift + [ $# -eq 1 ] || die "Too many age -d arguments ($#): $*" + @grep -v '^age' "$1" >&2 && die "Bad encrypted file \"$1\"" + if ! @grep -qFx "ageRecipient:${MOCK_AGE_ID}" "$1"; then + die "Bad identity \"${MOCK_AGE_ID_SRC}\": ${MOCK_AGE_ID}" exit 1 fi - @sed -n 's/^age://p' "$5" + if [ "${DEST}" = '-' ]; then + @sed -n 's/^age://p' "$1" + else + @sed -n 's/^age://p' "$1" >|"${DEST}" + fi ;; *) die "Unexpected age \$1: \"$1\""