From 25849e1810ff01e03f01bd56abfad29f43388c58 Mon Sep 17 00:00:00 2001 From: Brian Davis Date: Sat, 19 Sep 2020 12:05:52 -0400 Subject: [PATCH 01/26] Speed up git-commit-crypt hook --- transcrypt | 83 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/transcrypt b/transcrypt index 46dc77e..ae5b4f6 100755 --- a/transcrypt +++ b/transcrypt @@ -381,34 +381,75 @@ save_helper_hooks() { cat <<-'EOF' >"$pre_commit_hook_installed" #!/usr/bin/env bash # Transcrypt pre-commit hook: fail if secret file in staging lacks the magic prefix "Salted" in B64 + tmp=$(mktemp) IFS=$'\n' - for secret_file in $(git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = ":" }; /crypt$/{ print $1 }'); do + slow_mode_if_failed() { + for secret_file in $(git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = ":" }; /crypt$/{ print $1 }'); do + # Skip symlinks, they contain the linked target file path not plaintext + if [[ -L $secret_file ]]; then + continue + fi + + # Get prefix of raw file in Git's index using the :FILENAME revision syntax + firstbytes=$(git show :$secret_file | head -c 8) + # An empty file does not need to be, and is not, encrypted + if [[ $firstbytes == "" ]]; then + : # Do nothing + # The first bytes of an encrypted file must be "Salted" in Base64 + elif [[ $firstbytes != "U2FsdGVk" ]]; then + printf 'Transcrypt managed file is not encrypted in the Git index: %s\n' $secret_file >&2 + printf '\n' >&2 + printf 'You probably staged this file using a tool that does not apply' >&2 + printf ' .gitattribute filters as required by Transcrypt.\n' >&2 + printf '\n' >&2 + printf 'Fix this by re-staging the file with a compatible tool or with' + printf ' Git on the command line:\n' >&2 + printf '\n' >&2 + printf ' git reset -- %s\n' $secret_file >&2 + printf ' git add %s\n' $secret_file >&2 + printf '\n' >&2 + exit 1 + fi + done + } + + # validate file to see if it failed or not, We don't care about the filename currently for speed, we only care about pass/fail, slow_mode_if_failed is for what failed. + validate_file() { + secret_file=${1} # Skip symlinks, they contain the linked target file path not plaintext if [[ -L $secret_file ]]; then - continue + return fi - # Get prefix of raw file in Git's index using the :FILENAME revision syntax - firstbytes=$(git show :$secret_file | head -c8) - # An empty file does not need to be, and is not, encrypted - if [[ $firstbytes == "" ]]; then - : # Do nothing - # The first bytes of an encrypted file must be "Salted" in Base64 - elif [[ $firstbytes != "U2FsdGVk" ]]; then - printf 'Transcrypt managed file is not encrypted in the Git index: %s\n' $secret_file >&2 - printf '\n' >&2 - printf 'You probably staged this file using a tool that does not apply' >&2 - printf ' .gitattribute filters as required by Transcrypt.\n' >&2 - printf '\n' >&2 - printf 'Fix this by re-staging the file with a compatible tool or with' - printf ' Git on the command line:\n' >&2 - printf '\n' >&2 - printf ' git reset -- %s\n' $secret_file >&2 - printf ' git add %s\n' $secret_file >&2 - printf '\n' >&2 + # The first bytes of an encrypted file are always "Salted" in Base64 + firstbytes=$(git show :${secret_file} | head -c 8) + if [[ $firstbytes != "U2FsdGVk" ]]; then + echo "true" >> ${tmp} + fi + } + + # Transcrypt pre-commit hook: fail if secret file in staging lacks the magic prefix "Salted" in B64 + # if bash version is 4.4 or greater than fork to number of threads otherwise run normally + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]] && [[ "${BASH_VERSINFO[1]}" -ge 4 ]]; then + num_procs=$(nproc) + num_jobs="\j" + for secret_file in $(git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = ":" }; /crypt$/{ print $1 }'); do + while (( ${num_jobs@P} >= num_procs )); do + wait -n + done + validate_file "${secret_file}" & + done + wait + if [[ -s ${tmp} ]]; then + slow_mode_if_failed + rm -f ${tmp} exit 1 fi - done + else + slow_mode_if_failed + fi + + rm -f ${tmp} unset IFS EOF From 3f1b838ffc31c0c66f3780ac682aea70e212415e Mon Sep 17 00:00:00 2001 From: James Murty Date: Tue, 29 Sep 2020 21:30:23 +1000 Subject: [PATCH 02/26] Make pre-commit comply with `shellcheck` and `shfmt -i 2` Minimise the chance of bash bugs by making sure the pre-commit script saved to .git/hooks/pre-commit pass `shellcheck` and `shfmt -i 2` --- transcrypt | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/transcrypt b/transcrypt index ae5b4f6..fff3704 100755 --- a/transcrypt +++ b/transcrypt @@ -391,13 +391,13 @@ save_helper_hooks() { fi # Get prefix of raw file in Git's index using the :FILENAME revision syntax - firstbytes=$(git show :$secret_file | head -c 8) + firstbytes=$(git show :"${secret_file}" | head -c8) # An empty file does not need to be, and is not, encrypted if [[ $firstbytes == "" ]]; then - : # Do nothing + : # Do nothing # The first bytes of an encrypted file must be "Salted" in Base64 elif [[ $firstbytes != "U2FsdGVk" ]]; then - printf 'Transcrypt managed file is not encrypted in the Git index: %s\n' $secret_file >&2 + printf 'Transcrypt managed file is not encrypted in the Git index: %s\n' "$secret_file" >&2 printf '\n' >&2 printf 'You probably staged this file using a tool that does not apply' >&2 printf ' .gitattribute filters as required by Transcrypt.\n' >&2 @@ -405,15 +405,15 @@ save_helper_hooks() { printf 'Fix this by re-staging the file with a compatible tool or with' printf ' Git on the command line:\n' >&2 printf '\n' >&2 - printf ' git reset -- %s\n' $secret_file >&2 - printf ' git add %s\n' $secret_file >&2 + printf ' git reset -- %s\n' "$secret_file" >&2 + printf ' git add %s\n' "$secret_file" >&2 printf '\n' >&2 exit 1 fi done } - # validate file to see if it failed or not, We don't care about the filename currently for speed, we only care about pass/fail, slow_mode_if_failed is for what failed. + # validate file to see if it failed or not, We don't care about the filename currently for speed, we only care about pass/fail, slow_mode_if_failed() is for what failed. validate_file() { secret_file=${1} # Skip symlinks, they contain the linked target file path not plaintext @@ -422,19 +422,18 @@ save_helper_hooks() { fi # Get prefix of raw file in Git's index using the :FILENAME revision syntax # The first bytes of an encrypted file are always "Salted" in Base64 - firstbytes=$(git show :${secret_file} | head -c 8) + firstbytes=$(git show :"${secret_file}" | head -c8) if [[ $firstbytes != "U2FsdGVk" ]]; then - echo "true" >> ${tmp} + echo "true" >>"${tmp}" fi } - # Transcrypt pre-commit hook: fail if secret file in staging lacks the magic prefix "Salted" in B64 # if bash version is 4.4 or greater than fork to number of threads otherwise run normally if [[ "${BASH_VERSINFO[0]}" -ge 4 ]] && [[ "${BASH_VERSINFO[1]}" -ge 4 ]]; then num_procs=$(nproc) num_jobs="\j" for secret_file in $(git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = ":" }; /crypt$/{ print $1 }'); do - while (( ${num_jobs@P} >= num_procs )); do + while ((${num_jobs@P} >= num_procs)); do wait -n done validate_file "${secret_file}" & @@ -442,14 +441,14 @@ save_helper_hooks() { wait if [[ -s ${tmp} ]]; then slow_mode_if_failed - rm -f ${tmp} + rm -f "${tmp}" exit 1 fi else slow_mode_if_failed fi - rm -f ${tmp} + rm -f "${tmp}" unset IFS EOF From dc2f7fd85bf9f69f9cdc0afb46eefa7710852e90 Mon Sep 17 00:00:00 2001 From: James Murty Date: Tue, 29 Sep 2020 21:41:02 +1000 Subject: [PATCH 03/26] Print bash version when running GitHub Action tests --- .github/workflows/run-bats-core-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/run-bats-core-tests.yml b/.github/workflows/run-bats-core-tests.yml index 2438725..c0ae062 100644 --- a/.github/workflows/run-bats-core-tests.yml +++ b/.github/workflows/run-bats-core-tests.yml @@ -34,6 +34,9 @@ jobs: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 + - name: Print bash version + run: bash --version + - name: Print OpenSSL version run: openssl version From 0acc71d7410d49607b4158faad4072e8a1889beb Mon Sep 17 00:00:00 2001 From: Erik Flodin Date: Mon, 28 Dec 2020 15:21:50 +0100 Subject: [PATCH 04/26] Fix error in zsh completion {} should only be used when there are multiple options. Otherwise you get the following error: _arguments:comparguments:325: invalid argument: (--upgrade -c --cipher -d --display -f --flush-credentials -p --password -r --rekey){--upgrade}[upgrade transcrypt] --- contrib/zsh/_transcrypt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/zsh/_transcrypt b/contrib/zsh/_transcrypt index ef2b32e..523df12 100644 --- a/contrib/zsh/_transcrypt +++ b/contrib/zsh/_transcrypt @@ -18,7 +18,7 @@ _transcrypt() { '(-f --flush-credentials -c --cipher -p --password -r --rekey -u --uninstall)'{-f,--flush-credentials}'[flush cached credentials]' \ '(-F --force -d --display -u --uninstall)'{-F,--force}'[ignore repository clean state]' \ '(-u --uninstall -c --cipher -d --display -f --flush-credentials -p --password -r --rekey)'{-u,--uninstall}'[uninstall transcrypt]' \ - '(--upgrade -c --cipher -d --display -f --flush-credentials -p --password -r --rekey)'{--upgrade}'[upgrade transcrypt]' \ + '(--upgrade -c --cipher -d --display -f --flush-credentials -p --password -r --rekey)--upgrade[upgrade transcrypt]' \ '(-i --import-gpg -c --cipher -p --password -d --display -f --flush-credentials -u --uninstall)'{-i,--import-gpg=}'[import config from gpg file]:file:->file' \ && return 0 From 1cbf5e3ea43580734781812e1f033d8afa79ec6f Mon Sep 17 00:00:00 2001 From: James Murty Date: Wed, 3 Feb 2021 23:45:58 +1100 Subject: [PATCH 05/26] Mention zsh completion fix in changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a638858..35753d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ The format is based on [Keep a Changelog][1], and this project adheres to ## [Unreleased] +### Fixed + +- Zsh completion. (#107) + ## [2.1.0] - 2020-09-07 This release includes features to make it easier and safer to use transcrypt, in From af931c328bac3cdedfb368095ffe21efc3dedf2b Mon Sep 17 00:00:00 2001 From: James Murty Date: Sat, 6 Feb 2021 23:33:04 +1100 Subject: [PATCH 06/26] Respect git `core.hooksPath` setting when installing the pre-commit hook #104 (#110) Respect "git config core.hooksPath" setting Co-authored-by: ljm42 --- CHANGELOG.md | 1 + tests/test_init.bats | 17 +++++++++++++++++ transcrypt | 12 ++++++++---- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35753d8..dc45674 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog][1], and this project adheres to ### Fixed +- Respect Git `core.hooksPath` setting when installing the pre-commit hook. (#104) - Zsh completion. (#107) ## [2.1.0] - 2020-09-07 diff --git a/tests/test_init.bats b/tests/test_init.bats index 2dd754c..cc1fb60 100755 --- a/tests/test_init.bats +++ b/tests/test_init.bats @@ -80,3 +80,20 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 [[ "${lines[6]}" = " PASSWORD: abc123" ]] [[ "${lines[8]}" = " transcrypt -c aes-256-cbc -p 'abc123'" ]] } + +@test "init: respects core.hooksPath setting" { + git config core.hooksPath ".git/myhooks" + [[ "$(git config --get core.hooksPath)" = '.git/myhooks' ]] + + init_transcrypt + [[ -d .git/myhooks ]] + [[ -f .git/myhooks/pre-commit ]] + + VERSION=$(../transcrypt -v | awk '{print $2}') + run ../transcrypt --display + [[ "$status" -eq 0 ]] + [[ "${lines[0]}" = "The current repository was configured using transcrypt version $VERSION" ]] + [[ "${lines[5]}" = " CIPHER: aes-256-cbc" ]] + [[ "${lines[6]}" = " PASSWORD: abc123" ]] + [[ "${lines[8]}" = " transcrypt -c aes-256-cbc -p 'abc123'" ]] +} diff --git a/transcrypt b/transcrypt index fff3704..a082915 100755 --- a/transcrypt +++ b/transcrypt @@ -73,6 +73,9 @@ gather_repo_metadata() { readonly RELATIVE_GIT_DIR=$(git rev-parse --git-dir 2>/dev/null || printf '') readonly GIT_DIR=$(realpath "$RELATIVE_GIT_DIR" 2>/dev/null) + # respect core.hooksPath setting, without trailing slash. Fall back to default hooks dir + readonly GIT_HOOKS=$(git config core.hooksPath | sed 's:/*$::' 2>/dev/null || printf "%s/hooks" "${RELATIVE_GIT_DIR}") + # the current git repository's gitattributes file local CORE_ATTRIBUTES CORE_ATTRIBUTES=$(git config --get --local --path core.attributesFile 2>/dev/null || printf '') @@ -377,7 +380,8 @@ save_helper_scripts() { # save helper hooks under the repository's git directory save_helper_hooks() { # Install pre-commit-crypt hook script - pre_commit_hook_installed="${RELATIVE_GIT_DIR}/hooks/pre-commit-crypt" + [[ ! -d "${GIT_HOOKS}" ]] && mkdir -p "${GIT_HOOKS}" + pre_commit_hook_installed="${GIT_HOOKS}/pre-commit-crypt" cat <<-'EOF' >"$pre_commit_hook_installed" #!/usr/bin/env bash # Transcrypt pre-commit hook: fail if secret file in staging lacks the magic prefix "Salted" in B64 @@ -453,7 +457,7 @@ save_helper_hooks() { EOF # Activate hook by copying it to the pre-commit script name, if safe to do so - pre_commit_hook="${RELATIVE_GIT_DIR}/hooks/pre-commit" + pre_commit_hook="${GIT_HOOKS}/pre-commit" if [[ -f "$pre_commit_hook" ]]; then printf 'WARNING:\n' >&2 printf 'Cannot install Git pre-commit hook script because file already exists: %s\n' "$pre_commit_hook" >&2 @@ -639,8 +643,8 @@ uninstall_transcrypt() { [[ ! -d "${GIT_DIR}/crypt" ]] || rmdir "${GIT_DIR}/crypt" # rename helper hooks (don't delete, in case user has custom changes) - pre_commit_hook="${RELATIVE_GIT_DIR}/hooks/pre-commit" - pre_commit_hook_installed="${RELATIVE_GIT_DIR}/hooks/pre-commit-crypt" + pre_commit_hook="${GIT_HOOKS}/pre-commit" + pre_commit_hook_installed="${GIT_HOOKS}/pre-commit-crypt" if [[ -f "$pre_commit_hook" ]]; then hook_md5=$(openssl md5 -hex <"$pre_commit_hook") installed_md5=$(openssl md5 -hex <"$pre_commit_hook_installed") From 6cf07f6a8b58e4e63bdab59505a511c64225fc23 Mon Sep 17 00:00:00 2001 From: James Murty Date: Sat, 27 Feb 2021 21:38:03 +1100 Subject: [PATCH 07/26] Change version to indicate development "pre-release" status --- transcrypt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transcrypt b/transcrypt index a082915..7b47970 100755 --- a/transcrypt +++ b/transcrypt @@ -16,7 +16,7 @@ set -euo pipefail ##### CONSTANTS # the release version of this script -readonly VERSION='2.1.0' +readonly VERSION='2.2.0-pre' # the default cipher to utilize readonly DEFAULT_CIPHER='aes-256-cbc' From 561d158bb79fff95b1c37b195266990c97e6a6c3 Mon Sep 17 00:00:00 2001 From: James Murty Date: Sat, 27 Feb 2021 21:42:36 +1100 Subject: [PATCH 08/26] Install entire transcrypt script into repository When initializing transcrypt, copy the whole script file into the repository and call this one script from the Git handler scripts. This approach gives a number of benefits: - The contents of the clean, smudge, textconv, and merge scripts become part of the main script, and thus check-able and lint-able - The clean, smudge, textconv, and merge scripts become trivial, just different invocations of the main script - Longer-term, logic could be moved out of the individual clean, smudge, textconv, and merge scripts into a common function. --- transcrypt | 195 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 115 insertions(+), 80 deletions(-) diff --git a/transcrypt b/transcrypt index 7b47970..ecd92f4 100755 --- a/transcrypt +++ b/transcrypt @@ -108,6 +108,92 @@ die() { exit "$st" } +# The `decryption -> encryption` process on an unchanged file must be +# deterministic for everything to work transparently. To do that, the same +# salt must be used each time we encrypt the same file. An HMAC has been +# proven to be a PRF, so we generate an HMAC-SHA256 for each decrypted file +# (keyed with a combination of the filename and transcrypt password), and +# then use the last 16 bytes of that HMAC for the file's unique salt. + +git_clean() { + filename=$1 + # ignore empty files + if [[ ! -s $filename ]]; then + return + fi + # cache STDIN to test if it's already encrypted + tempfile=$(mktemp 2>/dev/null || mktemp -t tmp) + trap 'rm -f "$tempfile"' EXIT + tee "$tempfile" &>/dev/null + # the first bytes of an encrypted file are always "Salted" in Base64 + read -rn 8 firstbytes <"$tempfile" + if [[ $firstbytes == "U2FsdGVk" ]]; then + cat "$tempfile" + else + cipher=$(git config --get --local transcrypt.cipher) + password=$(git config --get --local transcrypt.password) + salt=$(openssl dgst -hmac "${filename}:${password}" -sha256 "$filename" | tr -d '\r\n' | tail -c16) + ENC_PASS=$password openssl enc -"$cipher" -md MD5 -pass env:ENC_PASS -e -a -S "$salt" -in "$tempfile" + fi +} + +git_smudge() { + tempfile=$(mktemp 2>/dev/null || mktemp -t tmp) + trap 'rm -f "$tempfile"' EXIT + cipher=$(git config --get --local transcrypt.cipher) + password=$(git config --get --local transcrypt.password) + tee "$tempfile" | ENC_PASS=$password openssl enc -"$cipher" -md MD5 -pass env:ENC_PASS -d -a 2>/dev/null || cat "$tempfile" +} + +git_textconv() { + filename=$1 + # ignore empty files + if [[ ! -s $filename ]]; then + return + fi + cipher=$(git config --get --local transcrypt.cipher) + password=$(git config --get --local transcrypt.password) + ENC_PASS=$password openssl enc -"$cipher" -md MD5 -pass env:ENC_PASS -d -a -in "$filename" 2>/dev/null || cat "$filename" +} + +# shellcheck disable=SC2005,SC2002,SC2181 +git_merge() { + # Look up name of local branch/ref to which changes are being merged + OURS_LABEL=$(git rev-parse --abbrev-ref HEAD) + # Look up name of the incoming "theirs" branch/ref being merged in. + # TODO There must be a better way of doing this than relying on this reflog + # action environment variable, but I don't know what it is + if [[ "$GIT_REFLOG_ACTION" = "merge "* ]]; then + THEIRS_LABEL=$(echo "$GIT_REFLOG_ACTION" | awk '{print $2}') + fi + if [[ ! "$THEIRS_LABEL" ]]; then + THEIRS_LABEL="theirs" + fi + # Decrypt BASE $1, LOCAL $2, and REMOTE $3 versions of file being merged + echo "$(cat "$1" | ./.git/crypt/smudge)" >"$1" + echo "$(cat "$2" | ./.git/crypt/smudge)" >"$2" + echo "$(cat "$3" | ./.git/crypt/smudge)" >"$3" + # Merge the decrypted files to the temp file named by $2 + git merge-file --marker-size="$4" -L "$OURS_LABEL" -L base -L "$THEIRS_LABEL" "$2" "$1" "$3" + # If the merge was not successful (has conflicts) exit with an error code to + # leave the partially-merged file in place for a manual merge. + if [[ "$?" != "0" ]]; then + exit 1 + fi + # If the merge was successful (no conflicts) re-encrypt the merged temp file $2 + # which git will then update in the index in a following "Auto-merging" step. + # We must explicitly encrypt/clean the file, rather than leave Git to do it, + # because we can otherwise trigger safety check failure errors like: + # error: add_cacheinfo failed to refresh for path 'FILE'; merge aborting. + # To re-encrypt we must first copy the merged file to $5 (the name of the + # working-copy file) so the crypt `clean` script can generate the correct hash + # salt based on the file's real name, instead of the $2 temp file name. + cp "$2" "$5" + # Now we use the `clean` script to encrypt the merged file contents back to the + # temp file $2 where Git expects to find the merge result content. + cat "$5" | ./.git/crypt/clean "$5" >"$2" +} + # verify that all requirements have been met run_safety_checks() { # validate that we're in a git repository @@ -276,103 +362,32 @@ stage_rekeyed_files() { save_helper_scripts() { mkdir -p "${GIT_DIR}/crypt" - # The `decryption -> encryption` process on an unchanged file must be - # deterministic for everything to work transparently. To do that, the same - # salt must be used each time we encrypt the same file. An HMAC has been - # proven to be a PRF, so we generate an HMAC-SHA256 for each decrypted file - # (keyed with a combination of the filename and transcrypt password), and - # then use the last 16 bytes of that HMAC for the file's unique salt. + local current_transcrypt + current_transcrypt=$(realpath "$0" 2>/dev/null) + cp "$current_transcrypt" "${GIT_DIR}/crypt/transcrypt" cat <<-'EOF' >"${GIT_DIR}/crypt/clean" #!/usr/bin/env bash - filename=$1 - # ignore empty files - if [[ -s $filename ]]; then - # cache STDIN to test if it's already encrypted - tempfile=$(mktemp 2>/dev/null || mktemp -t tmp) - trap 'rm -f "$tempfile"' EXIT - tee "$tempfile" &>/dev/null - # the first bytes of an encrypted file are always "Salted" in Base64 - read -n 8 firstbytes <"$tempfile" - if [[ $firstbytes == "U2FsdGVk" ]]; then - cat "$tempfile" - else - cipher=$(git config --get --local transcrypt.cipher) - password=$(git config --get --local transcrypt.password) - salt=$(openssl dgst -hmac "${filename}:${password}" -sha256 "$filename" | tr -d '\r\n' | tail -c16) - ENC_PASS=$password openssl enc -$cipher -md MD5 -pass env:ENC_PASS -e -a -S "$salt" -in "$tempfile" - fi - fi + $(dirname "$0")/transcrypt clean "$@" EOF cat <<-'EOF' >"${GIT_DIR}/crypt/smudge" #!/usr/bin/env bash - tempfile=$(mktemp 2>/dev/null || mktemp -t tmp) - trap 'rm -f "$tempfile"' EXIT - cipher=$(git config --get --local transcrypt.cipher) - password=$(git config --get --local transcrypt.password) - tee "$tempfile" | ENC_PASS=$password openssl enc -$cipher -md MD5 -pass env:ENC_PASS -d -a 2>/dev/null || cat "$tempfile" + $(dirname "$0")/transcrypt smudge "$@" EOF cat <<-'EOF' >"${GIT_DIR}/crypt/textconv" #!/usr/bin/env bash - filename=$1 - # ignore empty files - if [[ -s $filename ]]; then - cipher=$(git config --get --local transcrypt.cipher) - password=$(git config --get --local transcrypt.password) - ENC_PASS=$password openssl enc -$cipher -md MD5 -pass env:ENC_PASS -d -a -in "$filename" 2>/dev/null || cat "$filename" - fi + $(dirname "$0")/transcrypt textconv "$@" EOF cat <<-'EOF' >"${GIT_DIR}/crypt/merge" #!/usr/bin/env bash - - # Look up name of local branch/ref to which changes are being merged - OURS_LABEL=$(git rev-parse --abbrev-ref HEAD) - - # Look up name of the incoming "theirs" branch/ref being merged in. - # TODO There must be a better way of doing this than relying on this reflog - # action environment variable, but I don't know what it is - if [[ "$GIT_REFLOG_ACTION" = "merge "* ]]; then - THEIRS_LABEL=$(echo $GIT_REFLOG_ACTION | awk '{print $2}') - fi - if [[ ! "$THEIRS_LABEL" ]]; then - THEIRS_LABEL="theirs" - fi - - # Decrypt BASE $1, LOCAL $2, and REMOTE $3 versions of file being merged - echo "$(cat "$1" | ./.git/crypt/smudge)" > "$1" - echo "$(cat "$2" | ./.git/crypt/smudge)" > "$2" - echo "$(cat "$3" | ./.git/crypt/smudge)" > "$3" - - # Merge the decrypted files to the temp file named by $2 - git merge-file --marker-size=$4 -L "$OURS_LABEL" -L base -L "$THEIRS_LABEL" "$2" "$1" "$3" - - # If the merge was not successful (has conflicts) exit with an error code to - # leave the partially-merged file in place for a manual merge. - if [[ "$?" != "0" ]]; then - exit 1 - fi - - # If the merge was successful (no conflicts) re-encrypt the merged temp file $2 - # which git will then update in the index in a following "Auto-merging" step. - # We must explicitly encrypt/clean the file, rather than leave Git to do it, - # because we can otherwise trigger safety check failure errors like: - # error: add_cacheinfo failed to refresh for path 'FILE'; merge aborting. - - # To re-encrypt we must first copy the merged file to $5 (the name of the - # working-copy file) so the crypt `clean` script can generate the correct hash - # salt based on the file's real name, instead of the $2 temp file name. - cp "$2" "$5" - - # Now we use the `clean` script to encrypt the merged file contents back to the - # temp file $2 where Git expects to find the merge result content. - cat "$5" | ./.git/crypt/clean "$5" > "$2" + $(dirname "$0")/transcrypt merge "$@" EOF # make scripts executable - for script in {clean,smudge,textconv,merge}; do + for script in {transcrypt,clean,smudge,textconv,merge}; do chmod 0755 "${GIT_DIR}/crypt/${script}" done } @@ -637,7 +652,7 @@ uninstall_transcrypt() { fi # remove helper scripts - for script in {clean,smudge,textconv,merge}; do + for script in {transcrypt,clean,smudge,textconv,merge}; do [[ ! -f "${GIT_DIR}/crypt/${script}" ]] || rm "${GIT_DIR}/crypt/${script}" done [[ ! -d "${GIT_DIR}/crypt" ]] || rmdir "${GIT_DIR}/crypt" @@ -982,6 +997,26 @@ ignore_config_status='' # Set for operations where config can exist or not # parse command line options while [[ "${1:-}" != '' ]]; do case $1 in + clean) + shift + git_clean "$@" + exit $? + ;; + smudge) + shift + git_smudge "$@" + exit $? + ;; + textconv) + shift + git_textconv "$@" + exit $? + ;; + merge) + shift + git_merge "$@" + exit $? + ;; -c | --cipher) cipher=$2 shift From dce1ad0dc41d3fb5eb754e5e135761910f275968 Mon Sep 17 00:00:00 2001 From: James Murty Date: Sat, 27 Feb 2021 22:02:40 +1100 Subject: [PATCH 09/26] Let user set a custom path to openssl #108 Add the optional `--set-openssl-path` argument to tell transcrypt to use an explicit path to the openssl binary, instead of whatever version is found on the user's `$PATH`. The OpenSSL path can be changed on init, during an upgrade, or along with any other transcrypt command (even by itself). The openssl path is saved as a new `transcrypt.openssl-path` Git config local setting in the repository, alongside the other transcrypt settings like cipher, password etc. --- CHANGELOG.md | 7 +++++++ README.md | 3 +++ man/transcrypt.1.ronn | 3 +++ tests/test_init.bats | 42 ++++++++++++++++++++++++++++++++++++++++++ transcrypt | 37 ++++++++++++++++++++++++++----------- 5 files changed, 81 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc45674..8b4d880 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,13 @@ The format is based on [Keep a Changelog][1], and this project adheres to ## [Unreleased] +### Added + +- Add `--set-openssl-path` option to configure transcrypt to use a specific + openssl version instead of the default version found in `$PATH`. This will be + most useful to macOS users who might want to use a newer version of OpenSSL. + This option can be used on init, on upgrade, or by itself. + ### Fixed - Respect Git `core.hooksPath` setting when installing the pre-commit hook. (#104) diff --git a/README.md b/README.md index 6f2623b..0983ae8 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,9 @@ directory. the password to derive the key from; defaults to 30 random base64 characters + --set-openssl-path=PATH_TO_OPENSSL + use OpenSSL at this path; defaults to 'openssl' in $PATH + -y, --yes assume yes and accept defaults for non-specified options diff --git a/man/transcrypt.1.ronn b/man/transcrypt.1.ronn index d9c3df0..ef0d072 100644 --- a/man/transcrypt.1.ronn +++ b/man/transcrypt.1.ronn @@ -25,6 +25,9 @@ The transcrypt source code and full documentation may be downloaded from the password to derive the key from; defaults to 30 random base64 characters + * `--set-openssl-path`=: + use OpenSSL at this path; defaults to 'openssl' in $PATH + * `-y`, `--yes`: assume yes and accept defaults for non-specified options diff --git a/tests/test_init.bats b/tests/test_init.bats index cc1fb60..6c7a953 100755 --- a/tests/test_init.bats +++ b/tests/test_init.bats @@ -97,3 +97,45 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 [[ "${lines[6]}" = " PASSWORD: abc123" ]] [[ "${lines[8]}" = " transcrypt -c aes-256-cbc -p 'abc123'" ]] } + +@test "init: transcrypt.openssl-path config setting defaults to 'openssl'" { + init_transcrypt + [[ "$(git config --get transcrypt.openssl-path)" = 'openssl' ]] +} + +@test "init: --set-openssl-path changes transcrypt.openssl-path" { + init_transcrypt + [[ "$(git config --get transcrypt.openssl-path)" = 'openssl' ]] +} + +@test "init: --set-openssl-path is applied during init" { + init_transcrypt + run ../transcrypt --set-openssl-path=/test/path + [[ "$(git config --get transcrypt.openssl-path)" = "/test/path" ]] +} + +@test "init: --set-openssl-path is applied during upgrade" { + init_transcrypt + [[ "$(git config --get transcrypt.openssl-path)" = 'openssl' ]] + + # Set openssl path + FULL_OPENSSL_PATH=$(which openssl) + + "$BATS_TEST_DIRNAME"/../transcrypt --upgrade --yes --set-openssl-path="$FULL_OPENSSL_PATH" + [[ "$(git config --get transcrypt.openssl-path)" = "$FULL_OPENSSL_PATH" ]] + [[ ! "$(git config --get transcrypt.openssl-path)" = 'openssl' ]] +} + +@test "init: transcrypt.openssl-path config setting is retained with --upgrade" { + init_transcrypt + [[ "$(git config --get transcrypt.openssl-path)" = 'openssl' ]] + + # Set openssl path + FULL_OPENSSL_PATH=$(which openssl) + run ../transcrypt --set-openssl-path="$FULL_OPENSSL_PATH"'' + + # Retain transcrypt.openssl-path config setting on upgrade + "$BATS_TEST_DIRNAME"/../transcrypt --upgrade --yes + [[ "$(git config --get transcrypt.openssl-path)" = "$FULL_OPENSSL_PATH" ]] + [[ ! "$(git config --get transcrypt.openssl-path)" = 'openssl' ]] +} diff --git a/transcrypt b/transcrypt index ecd92f4..2ccd088 100755 --- a/transcrypt +++ b/transcrypt @@ -132,8 +132,9 @@ git_clean() { else cipher=$(git config --get --local transcrypt.cipher) password=$(git config --get --local transcrypt.password) - salt=$(openssl dgst -hmac "${filename}:${password}" -sha256 "$filename" | tr -d '\r\n' | tail -c16) - ENC_PASS=$password openssl enc -"$cipher" -md MD5 -pass env:ENC_PASS -e -a -S "$salt" -in "$tempfile" + openssl_path=$(git config --get --local transcrypt.openssl-path) + salt=$("${openssl_path}" dgst -hmac "${filename}:${password}" -sha256 "$filename" | tr -d '\r\n' | tail -c16) + ENC_PASS=$password "$openssl_path" enc "-${cipher}" -md MD5 -pass env:ENC_PASS -e -a -S "$salt" -in "$tempfile" fi } @@ -142,7 +143,8 @@ git_smudge() { trap 'rm -f "$tempfile"' EXIT cipher=$(git config --get --local transcrypt.cipher) password=$(git config --get --local transcrypt.password) - tee "$tempfile" | ENC_PASS=$password openssl enc -"$cipher" -md MD5 -pass env:ENC_PASS -d -a 2>/dev/null || cat "$tempfile" + openssl_path=$(git config --get --local transcrypt.openssl-path) + tee "$tempfile" | ENC_PASS=$password "$openssl_path" enc "-${cipher}" -md MD5 -pass env:ENC_PASS -d -a 2>/dev/null || cat "$tempfile" } git_textconv() { @@ -153,7 +155,8 @@ git_textconv() { fi cipher=$(git config --get --local transcrypt.cipher) password=$(git config --get --local transcrypt.password) - ENC_PASS=$password openssl enc -"$cipher" -md MD5 -pass env:ENC_PASS -d -a -in "$filename" 2>/dev/null || cat "$filename" + openssl_path=$(git config --get --local transcrypt.openssl-path) + ENC_PASS=$password "$openssl_path" enc "-${cipher}" -md MD5 -pass env:ENC_PASS -d -a -in "$filename" 2>/dev/null || cat "$filename" } # shellcheck disable=SC2005,SC2002,SC2181 @@ -209,7 +212,7 @@ run_safety_checks() { fi # check for dependencies - for cmd in {column,grep,mktemp,openssl,sed,tee}; do + for cmd in {column,grep,mktemp,"${openssl_path}",sed,tee}; do command -v "$cmd" >/dev/null || die 'required command "%s" was not found' "$cmd" done @@ -226,12 +229,12 @@ run_safety_checks() { # unset the cipher variable if it is not supported by openssl validate_cipher() { local list_cipher_commands - if openssl list-cipher-commands &>/dev/null; then + if "${openssl_path}" list-cipher-commands &>/dev/null; then # OpenSSL < v1.1.0 - list_cipher_commands='openssl list-cipher-commands' + list_cipher_commands="${openssl_path} list-cipher-commands" else # OpenSSL >= v1.1.0 - list_cipher_commands='openssl list -cipher-commands' + list_cipher_commands="${openssl_path} list -cipher-commands" fi local supported @@ -284,7 +287,7 @@ get_password() { if [[ $answer =~ $YES_REGEX ]] || [[ ! $answer ]]; then local password_length=30 local random_base64 - random_base64=$(openssl rand -base64 $password_length) + random_base64=$(${openssl_path} rand -base64 $password_length) password=$random_base64 else printf 'Password: ' @@ -493,6 +496,7 @@ save_configuration() { git config transcrypt.version "$VERSION" git config transcrypt.cipher "$cipher" git config transcrypt.password "$password" + git config transcrypt.openssl-path "$openssl_path" # write the filter settings if [[ -d $(git rev-parse --git-common-dir) ]]; then @@ -661,8 +665,8 @@ uninstall_transcrypt() { pre_commit_hook="${GIT_HOOKS}/pre-commit" pre_commit_hook_installed="${GIT_HOOKS}/pre-commit-crypt" if [[ -f "$pre_commit_hook" ]]; then - hook_md5=$(openssl md5 -hex <"$pre_commit_hook") - installed_md5=$(openssl md5 -hex <"$pre_commit_hook_installed") + hook_md5=$("${openssl_path}" md5 -hex <"$pre_commit_hook") + installed_md5=$("${openssl_path}" md5 -hex <"$pre_commit_hook_installed") if [[ "$hook_md5" = "$installed_md5" ]]; then rm "$pre_commit_hook" else @@ -736,6 +740,8 @@ upgrade_transcrypt() { # Keep current cipher and password cipher=$(git config --get --local transcrypt.cipher) password=$(git config --get --local transcrypt.password) + # Keep current openssl-path, or set to default if no existing value + openssl_path=$(git config --get --local transcrypt.openssl-path 2>/dev/null || printf '%s' "$openssl_path") # Keep contents of .gitattributes ORIG_GITATTRIBUTES=$(cat "$GIT_ATTRIBUTES") @@ -887,6 +893,9 @@ help() { the password to derive the key from; defaults to 30 random base64 characters + --set-openssl-path=PATH_TO_OPENSSL + use OpenSSL at this path; defaults to 'openssl' in \$PATH + -y, --yes assume yes and accept defaults for non-specified options @@ -988,6 +997,7 @@ rekey='' show_file='' uninstall='' upgrade='' +openssl_path='openssl' # used to bypass certain safety checks requires_existing_config='' @@ -1031,6 +1041,11 @@ while [[ "${1:-}" != '' ]]; do --password=*) password=${1#*=} ;; + --set-openssl-path=*) + openssl_path=${1#*=} + # Immediately apply config setting + git config transcrypt.openssl-path "$openssl_path" + ;; -y | --yes) interactive='' ;; From fc113586f12e2a19024c4d838df90bf3161028a7 Mon Sep 17 00:00:00 2001 From: Adrian Dimitrov Date: Mon, 26 Apr 2021 14:58:02 +0300 Subject: [PATCH 10/26] Use shorthand for grep options for broader compatibility (#121) In busybox there is no line-regexp for grep, using -x as it is used in busybox, macos and gnugrep. Adding also -F to force plain text and non pattern search. --- transcrypt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transcrypt b/transcrypt index 2ccd088..7ea5f13 100755 --- a/transcrypt +++ b/transcrypt @@ -238,7 +238,7 @@ validate_cipher() { fi local supported - supported=$($list_cipher_commands | tr -s ' ' '\n' | grep --line-regexp "$cipher") || true + supported=$($list_cipher_commands | tr -s ' ' '\n' | grep -Fx "$cipher") || true if [[ ! $supported ]]; then if [[ $interactive ]]; then printf '"%s" is not a valid cipher; choose one of the following:\n\n' "$cipher" From 588f1bc4909273cde4ccf3eaf6a1fd94bc8fbe8a Mon Sep 17 00:00:00 2001 From: Aram Dulyan Date: Mon, 26 Apr 2021 22:13:20 +1000 Subject: [PATCH 11/26] Fix incorrect salt when partially staged files are commited (#119) * Fix incorrect salt when partially staged files are commited. Re #118 --- CHANGELOG.md | 1 + transcrypt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b4d880..d793940 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The format is based on [Keep a Changelog][1], and this project adheres to - Respect Git `core.hooksPath` setting when installing the pre-commit hook. (#104) - Zsh completion. (#107) +- Fix salt generation for partial (patch) commits (#118) ## [2.1.0] - 2020-09-07 diff --git a/transcrypt b/transcrypt index 7ea5f13..da6e481 100755 --- a/transcrypt +++ b/transcrypt @@ -133,7 +133,7 @@ git_clean() { cipher=$(git config --get --local transcrypt.cipher) password=$(git config --get --local transcrypt.password) openssl_path=$(git config --get --local transcrypt.openssl-path) - salt=$("${openssl_path}" dgst -hmac "${filename}:${password}" -sha256 "$filename" | tr -d '\r\n' | tail -c16) + salt=$("${openssl_path}" dgst -hmac "${filename}:${password}" -sha256 "$tempfile" | tr -d '\r\n' | tail -c16) ENC_PASS=$password "$openssl_path" enc "-${cipher}" -md MD5 -pass env:ENC_PASS -e -a -S "$salt" -in "$tempfile" fi } From 79f24e0ab63f7d7824ada86c71deebf943310f46 Mon Sep 17 00:00:00 2001 From: James Murty Date: Tue, 27 Apr 2021 00:48:34 +1000 Subject: [PATCH 12/26] Ensure Git index is up-to-date before dirty repo check #37 (#109) Should fix/avoid failures seen in CI systems where the repo seems dirty when it really isn't. --- CHANGELOG.md | 2 ++ transcrypt | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d793940..78d6edf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ The format is based on [Keep a Changelog][1], and this project adheres to ### Fixed +- Ensure Git index is up-to-date before checking for dirty repo, to avoid + failures seen in CI systems where the repo seems dirty when it isn't. (#37) - Respect Git `core.hooksPath` setting when installing the pre-commit hook. (#104) - Zsh completion. (#107) - Fix salt generation for partial (patch) commits (#118) diff --git a/transcrypt b/transcrypt index da6e481..510157e 100755 --- a/transcrypt +++ b/transcrypt @@ -219,6 +219,8 @@ run_safety_checks() { # ensure the repository is clean (if it has a HEAD revision) so we can force # checkout files without the destruction of uncommitted changes if [[ $requires_clean_repo ]] && [[ $HEAD_EXISTS ]] && [[ $IS_BARE == 'false' ]]; then + # ensure index is up-to-date before dirty check + git update-index -q --really-refresh # check if the repo is dirty if ! git diff-index --quiet HEAD --; then die 1 'the repo is dirty; commit or stash your changes before running transcrypt' From a64c88f7760b57e053d087a48489cb102b1edf17 Mon Sep 17 00:00:00 2001 From: James Murty Date: Thu, 29 Apr 2021 00:00:46 +1000 Subject: [PATCH 13/26] Handle rename of primary branch from "master" to "main" References on renaming primary branch in GitHub and Git: - https://github.com/github/renaming#rename-existing - https://docs.github.com/en/github/administering-a-repository/renaming-a-branch#updating-a-local-clone-after-a-branch-name-changes --- .github/workflows/run-bats-core-tests.yml | 8 ++++---- INSTALL.md | 19 ++++++++----------- README.md | 2 +- tests/test_merge.bats | 2 +- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/.github/workflows/run-bats-core-tests.yml b/.github/workflows/run-bats-core-tests.yml index c0ae062..9670e37 100644 --- a/.github/workflows/run-bats-core-tests.yml +++ b/.github/workflows/run-bats-core-tests.yml @@ -1,12 +1,12 @@ name: Tests on: - # Only run tests on push to master branch + # Only run tests on push to main branch push: - branches: [master] - # Run tests for all pull request changes targeting master + branches: [main] + # Run tests for all pull request changes targeting main pull_request: - branches: [master] + branches: [main] jobs: diff --git a/INSTALL.md b/INSTALL.md index 2e7350b..cc2b067 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,16 +1,14 @@ -Install transcrypt -================== +# Install transcrypt The requirements to run transcrypt are minimal: -* Bash -* Git -* OpenSSL +- Bash +- Git +- OpenSSL You also need access to the _transcrypt_ script itself... -Manual Installation -------------------- +## Manual Installation You can add transcrypt directly to your repository, or just put it somewhere in your $PATH: @@ -19,8 +17,7 @@ your $PATH: $ cd transcrypt/ $ sudo ln -s ${PWD}/transcrypt /usr/local/bin/transcrypt -Installation via Packages -------------------------- +## Installation via Packages A number of packages are available for installing transcrypt directly on your system via its native package manager. Some of these packages also include man @@ -29,7 +26,7 @@ page documentation as well as shell auto-completion scripts. ### Arch Linux If you're on Arch Linux, you can build/install transcrypt using the -[provided PKGBUILD](https://github.com/elasticdog/transcrypt/blob/master/contrib/packaging/pacman/PKGBUILD): +[provided PKGBUILD](https://github.com/elasticdog/transcrypt/blob/main/contrib/packaging/pacman/PKGBUILD): $ git clone https://github.com/elasticdog/transcrypt.git $ cd transcrypt/contrib/packaging/pacman/ @@ -50,7 +47,7 @@ If you're on NixOS, you can install transcrypt directly via $ nix-env -iA nixos.gitAndTools.transcrypt > _**Note:** -> The [transcrypt derivation](https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/version-management/git-and-tools/transcrypt/default.nix) +> The [transcrypt derivation](https://github.com/NixOS/nixpkgs/blob/main/pkgs/applications/version-management/git-and-tools/transcrypt/default.nix) > was added in Oct 2015, so it is not available on the 15.09 channel._ ### OS X diff --git a/README.md b/README.md index 0983ae8..cc7263b 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,7 @@ re-configure transcrypt with the new credentials. $ transcrypt --flush-credentials $ git fetch origin - $ git merge origin/master + $ git merge origin/main $ transcrypt -c aes-256-cbc -p 'the-new-password' ### Command Line Options diff --git a/tests/test_merge.bats b/tests/test_merge.bats index b293be6..3e5db52 100755 --- a/tests/test_merge.bats +++ b/tests/test_merge.bats @@ -90,7 +90,7 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" run cat sensitive_file [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "<<<<<<< master" ]] + [[ "${lines[0]}" = "<<<<<<< main" ]] [[ "${lines[1]}" = "a. First step" ]] [[ "${lines[2]}" = "=======" ]] [[ "${lines[3]}" = "1. Step the first" ]] From 588a8d07ddd1032ff70c190e28ae99fd52b4ba51 Mon Sep 17 00:00:00 2001 From: James Murty Date: Sun, 28 Feb 2021 00:09:27 +1100 Subject: [PATCH 14/26] Configure default Git branch name for macOS tests in GitHub --- .github/workflows/run-bats-core-tests.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/run-bats-core-tests.yml b/.github/workflows/run-bats-core-tests.yml index 9670e37..bc3c720 100644 --- a/.github/workflows/run-bats-core-tests.yml +++ b/.github/workflows/run-bats-core-tests.yml @@ -40,6 +40,13 @@ jobs: - name: Print OpenSSL version run: openssl version + - name: Print Git version + run: git version + + # Configure default Git branch name to suppress hint warnings + - name: Configure default Git branch to "main" + run: git config --global init.defaultBranch main + - name: Install and set up bats-core run: | git clone https://github.com/bats-core/bats-core.git /tmp/bats-core-repo From aea3ff83c7aea95b18e18839d5beb6da29b7548c Mon Sep 17 00:00:00 2001 From: Aaron Bull Schaefer Date: Thu, 29 Apr 2021 16:25:56 -0700 Subject: [PATCH 15/26] Remove Ubuntu 16.04 LTS from test matrix (#123) The "Xenial Xerus" version of Ubuntu is EOL (end-of-life) as of 2021-04-30. See: - https://help.ubuntu.com/community/EOL --- .github/workflows/run-bats-core-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-bats-core-tests.yml b/.github/workflows/run-bats-core-tests.yml index bc3c720..4091123 100644 --- a/.github/workflows/run-bats-core-tests.yml +++ b/.github/workflows/run-bats-core-tests.yml @@ -28,7 +28,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-16.04, ubuntu-18.04, ubuntu-20.04, macos-latest] + os: [ubuntu-18.04, ubuntu-20.04, macos-latest] steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it From 331b4afa859df38969aa0689a9a0001e2525f9e8 Mon Sep 17 00:00:00 2001 From: James Murty Date: Sun, 16 Jan 2022 00:07:01 +1100 Subject: [PATCH 16/26] Improve command hint to fix secret files not encrypted in index (#120) (#130) * Disable overly fussy shellcheck rule SC2155 for old code * Update expected test output to match changed command hint Code that's been unchanged for years is suddenly being flagged by rule SC2155; perhaps this rule is stricter in newer versions of `shellcheck` or action https://github.com/luizm/action-sh-checker ? Fixes linting failures due to: SC2155: Declare and assign separately to avoid masking return values. --- CHANGELOG.md | 1 + tests/test_pre_commit.bats | 2 +- transcrypt | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78d6edf..79a9f48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ The format is based on [Keep a Changelog][1], and this project adheres to - Respect Git `core.hooksPath` setting when installing the pre-commit hook. (#104) - Zsh completion. (#107) - Fix salt generation for partial (patch) commits (#118) +- Improve command hint to fix secret files not encrypted in index (#120) ## [2.1.0] - 2020-09-07 diff --git a/tests/test_pre_commit.bats b/tests/test_pre_commit.bats index 4b156fe..b88a250 100755 --- a/tests/test_pre_commit.bats +++ b/tests/test_pre_commit.bats @@ -47,7 +47,7 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" [[ "${output}" = *"Transcrypt managed file is not encrypted in the Git index: sensitive_file"* ]] [[ "${output}" = *"You probably staged this file using a tool that does not apply .gitattribute filters as required by Transcrypt."* ]] [[ "${output}" = *"Fix this by re-staging the file with a compatible tool or with Git on the command line:"* ]] - [[ "${output}" = *" git reset -- sensitive_file"* ]] + [[ "${output}" = *" git rm --cached -- sensitive_file"* ]] [[ "${output}" = *" git add sensitive_file"* ]] } diff --git a/transcrypt b/transcrypt index 510157e..4746286 100755 --- a/transcrypt +++ b/transcrypt @@ -52,6 +52,7 @@ realpath() { } # establish repository metadata and directory handling +# shellcheck disable=SC2155 gather_repo_metadata() { # whether or not transcrypt is already configured readonly CONFIGURED=$(git config --get --local transcrypt.version 2>/dev/null) @@ -429,7 +430,7 @@ save_helper_hooks() { printf 'Fix this by re-staging the file with a compatible tool or with' printf ' Git on the command line:\n' >&2 printf '\n' >&2 - printf ' git reset -- %s\n' "$secret_file" >&2 + printf ' git rm --cached -- %s\n' "$secret_file" >&2 printf ' git add %s\n' "$secret_file" >&2 printf '\n' >&2 exit 1 From 334697a4599a391348148ab439b8c66305d6e68b Mon Sep 17 00:00:00 2001 From: James Murty Date: Sun, 16 Jan 2022 00:25:13 +1100 Subject: [PATCH 17/26] Fix handling of small files and files with null in first 8 bytes (#116) The `read -rn 8` command doesn't seem to cope with small files, and does all kinds of strange things (on macOS at least) including: - not reading anything at all from files with short first lines - reading too much from files with long first lines (more than 8 chars) Switch to us `head -c8` instead, which is safer, plus some fiddly use of `tr` to ignore null bytes early in a way that works for older and newer versions of bash. --- CHANGELOG.md | 1 + tests/test_crypt.bats | 71 +++++++++++++++++++++++++++++++++++++++++++ transcrypt | 3 +- 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79a9f48..0fe088b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ The format is based on [Keep a Changelog][1], and this project adheres to - Zsh completion. (#107) - Fix salt generation for partial (patch) commits (#118) - Improve command hint to fix secret files not encrypted in index (#120) +- Fix handling of files with null in first 8 bytes (#116) ## [2.1.0] - 2020-09-07 diff --git a/tests/test_crypt.bats b/tests/test_crypt.bats index 686f8bd..b821ad0 100755 --- a/tests/test_crypt.bats +++ b/tests/test_crypt.bats @@ -139,6 +139,77 @@ function check_repo_is_clean { rm "$FILENAME" } +@test "crypt: handle very small file" { + FILENAME="small file.txt" + SECRET_CONTENT="sh" + SECRET_CONTENT_ENC="U2FsdGVkX1/NlZCZ0ho/TL+5kTxm/R/X5nfATzQLKfI=" + + encrypt_named_file "$FILENAME" "$SECRET_CONTENT" + [[ "${output}" = *"Encrypt file \"$FILENAME\""* ]] + + # Working copy is decrypted + run cat "$FILENAME" + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT" ] + + # Git internal copy is encrypted + run git show HEAD:"$FILENAME" --no-textconv + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT_ENC" ] + + # transcrypt --show-raw shows encrypted content + run ../transcrypt --show-raw "$FILENAME" + [ "$status" -eq 0 ] + [ "${lines[0]}" = "==> $FILENAME <==" ] + [ "${lines[1]}" = "$SECRET_CONTENT_ENC" ] + + # git ls-crypt lists encrypted file + run git ls-crypt + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$FILENAME" ] + + # transcrypt --list lists encrypted file" + run ../transcrypt --list + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$FILENAME" ] + + rm "$FILENAME" +} + +@test "crypt: handle file with problematic bytes" { + FILENAME="problem bytes file.txt" + SECRET_CONTENT_ENC="U2FsdGVkX1/RVkIfYCsbUMcvQlC2vWZJVQtZSuppd7Q=" + + # Write octal byte 375 and null byte as file contents + printf "\375 \0 shh" > "$FILENAME" + + encrypt_named_file "$FILENAME" + [[ "${output}" = *"Encrypt file \"$FILENAME\""* ]] + + # Git internal copy is encrypted + run git show HEAD:"$FILENAME" --no-textconv + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT_ENC" ] + + # transcrypt --show-raw shows encrypted content + run ../transcrypt --show-raw "$FILENAME" + [ "$status" -eq 0 ] + [ "${lines[0]}" = "==> $FILENAME <==" ] + [ "${lines[1]}" = "$SECRET_CONTENT_ENC" ] + + # git ls-crypt lists encrypted file + run git ls-crypt + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$FILENAME" ] + + # transcrypt --list lists encrypted file" + run ../transcrypt --list + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$FILENAME" ] + + rm "$FILENAME" +} + @test "crypt: transcrypt --upgrade applies new merge driver" { VERSION=$(../transcrypt -v | awk '{print $2}') diff --git a/transcrypt b/transcrypt index 4746286..a6c79a5 100755 --- a/transcrypt +++ b/transcrypt @@ -127,7 +127,8 @@ git_clean() { trap 'rm -f "$tempfile"' EXIT tee "$tempfile" &>/dev/null # the first bytes of an encrypted file are always "Salted" in Base64 - read -rn 8 firstbytes <"$tempfile" + # The `head + LC_ALL=C tr` command handles binary data in old and new Bash (#116) + firstbytes=$(head -c8 "$tempfile" | LC_ALL=C tr -d '\0') if [[ $firstbytes == "U2FsdGVk" ]]; then cat "$tempfile" else From fdf81c53f0ad27651e03a67ea732b164d209e948 Mon Sep 17 00:00:00 2001 From: James Murty Date: Sun, 16 Jan 2022 01:04:27 +1100 Subject: [PATCH 18/26] Consolidate all git operation scripts into a single transcrypt script Instead of installing separate scripts for the git clean, smudge, textconv, and merge operations a single *transcrypt* script that contains all these sub-scripts is installed instead. This reduces the clutter in the repository's _crypt/_ directory and makes it easier to develop and test *transcrypt* itself. Also add support for an optional `transcrypt.crypt-dir` setting for advanced users to override the path of the _.git/crypt/_ directory to permit things like installing transcrypt in a repository on a device without execute permissions. --- CHANGELOG.md | 10 ++ README.md | 2 +- tests/_test_helper.bash | 2 +- tests/test_cleanup.bats | 47 ++++---- tests/test_crypt.bats | 118 +++++++++--------- tests/test_init.bats | 139 ++++++++++++--------- tests/test_merge.bats | 40 +++---- tests/test_not_inited.bats | 32 ++--- tests/test_pre_commit.bats | 44 +++---- transcrypt | 240 +++++++++++++++++-------------------- 10 files changed, 350 insertions(+), 324 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fe088b..bbc9bb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,16 @@ The format is based on [Keep a Changelog][1], and this project adheres to openssl version instead of the default version found in `$PATH`. This will be most useful to macOS users who might want to use a newer version of OpenSSL. This option can be used on init, on upgrade, or by itself. +- Add support for an optional `transcrypt.crypt-dir` setting for advanced users + to override the path of the _.git/crypt/_ directory to permit things like + installing transcrypt in a repository on a device without execute + permissions (#104) + +### Changed + +- No longer need stand-alone scripts for git operations `clean`, `smudge`, + `textconv`, and `merge` in the repository's _crypt/_ directory; the single + consolidated `transcrypt` script is stored there instead. ### Fixed diff --git a/README.md b/README.md index cc7263b..7ce3473 100644 --- a/README.md +++ b/README.md @@ -328,4 +328,4 @@ To run the tests: - [install bats-core](https://github.com/bats-core/bats-core#installation) - run all tests with: `bats tests/` -- run an individual test with e.g: `./tests/test_help.bats` +- run an individual test with e.g: `bats tests/test_crypt.bats` diff --git a/tests/_test_helper.bash b/tests/_test_helper.bash index eeb6ca8..e0f3d37 100644 --- a/tests/_test_helper.bash +++ b/tests/_test_helper.bash @@ -37,7 +37,7 @@ function cleanup_all { } function init_transcrypt { - "$BATS_TEST_DIRNAME"/../transcrypt --cipher=aes-256-cbc --password=abc123 --yes + "$BATS_TEST_DIRNAME"/../transcrypt --cipher=aes-256-cbc --password='abc 123' --yes } function encrypt_named_file { diff --git a/tests/test_cleanup.bats b/tests/test_cleanup.bats index 395a905..fc00077 100755 --- a/tests/test_cleanup.bats +++ b/tests/test_cleanup.bats @@ -3,28 +3,28 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" SECRET_CONTENT="My secret content" -SECRET_CONTENT_ENC="U2FsdGVkX1/kkWK36bn3fbq5DY2d+JXL2YWoN/eoXA1XJZEk9JS7j/856rXK9gPn" +SECRET_CONTENT_ENC="U2FsdGVkX1/6ilR0PmJpAyCF7iG3+k4aBwbgVd48WaQXznsg42nXbQrlWsf/qiCg" @test "cleanup: transcrypt -f flush clears cached plaintext" { encrypt_named_file sensitive_file "$SECRET_CONTENT" # Confirm working copy file is decrypted run cat sensitive_file - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "$SECRET_CONTENT" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT" ] # Show all changes, caches plaintext due to `cachetextconv` setting run git log -p -- sensitive_file - [[ "$status" -eq 0 ]] + [ "$status" -eq 0 ] [[ "${output}" = *"+$SECRET_CONTENT" ]] # Check last line of patch # Look up notes ref to cached plaintext - [[ -f $BATS_TEST_DIRNAME/.git/refs/notes/textconv/crypt ]] + [ -f $BATS_TEST_DIRNAME/.git/refs/notes/textconv/crypt ] cached_plaintext_obj=$(cat "$BATS_TEST_DIRNAME/.git/refs/notes/textconv/crypt") # Confirm plaintext is cached run git show "$cached_plaintext_obj" - [[ "$status" -eq 0 ]] + [ "$status" -eq 0 ] [[ "${output}" = *"+$SECRET_CONTENT" ]] # Check last line of patch # Repack to force all objects into packs (which are trickier to clear) @@ -32,25 +32,25 @@ SECRET_CONTENT_ENC="U2FsdGVkX1/kkWK36bn3fbq5DY2d+JXL2YWoN/eoXA1XJZEk9JS7j/856rXK # Flush credentials run ../transcrypt -f --yes - [[ "$status" -eq 0 ]] + [ "$status" -eq 0 ] # Confirm working copy file is encrypted run cat sensitive_file - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "$SECRET_CONTENT_ENC" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT_ENC" ] # Confirm show all changes shows encrypted content, not plaintext git log -p -- sensitive_file run git log -p -- sensitive_file - [[ "$status" -eq 0 ]] + [ "$status" -eq 0 ] [[ "${output}" = *"+$SECRET_CONTENT_ENC" ]] # Check last line of patch # Confirm plaintext cache ref was cleared - [[ ! -e $BATS_TEST_DIRNAME/.git/refs/notes/textconv/crypt ]] + [ ! -e $BATS_TEST_DIRNAME/.git/refs/notes/textconv/crypt ] # Confirm plaintext obj was truly cleared and is no longer visible run git show "$cached_plaintext_obj" - [[ "$status" -ne 0 ]] + [ "$status" -ne 0 ] } @test "cleanup: transcrypt --uninstall clears cached plaintext" { @@ -58,21 +58,21 @@ SECRET_CONTENT_ENC="U2FsdGVkX1/kkWK36bn3fbq5DY2d+JXL2YWoN/eoXA1XJZEk9JS7j/856rXK # Confirm working copy file is decrypted run cat sensitive_file - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "$SECRET_CONTENT" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT" ] # Show all changes, caches plaintext due to `cachetextconv` setting run git log -p -- sensitive_file - [[ "$status" -eq 0 ]] + [ "$status" -eq 0 ] [[ "${output}" = *"+$SECRET_CONTENT" ]] # Check last line of patch # Look up notes ref to cached plaintext - [[ -f $BATS_TEST_DIRNAME/.git/refs/notes/textconv/crypt ]] + [ -f $BATS_TEST_DIRNAME/.git/refs/notes/textconv/crypt ] cached_plaintext_obj=$(cat "$BATS_TEST_DIRNAME/.git/refs/notes/textconv/crypt") # Confirm plaintext is cached run git show "$cached_plaintext_obj" - [[ "$status" -eq 0 ]] + [ "$status" -eq 0 ] [[ "${output}" = *"+$SECRET_CONTENT" ]] # Check last line of patch # Repack to force all objects into packs (which are trickier to clear) @@ -80,23 +80,22 @@ SECRET_CONTENT_ENC="U2FsdGVkX1/kkWK36bn3fbq5DY2d+JXL2YWoN/eoXA1XJZEk9JS7j/856rXK # Uninstall run ../transcrypt --uninstall --yes - [[ "$status" -eq 0 ]] + [ "$status" -eq 0 ] # Confirm working copy file remains unencrypted (per uninstall contract) run cat sensitive_file - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "$SECRET_CONTENT" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT" ] # Confirm show all changes shows encrypted content, not plaintext - git log -p -- sensitive_file run git log -p -- sensitive_file - [[ "$status" -eq 0 ]] + [ "$status" -eq 0 ] [[ "${output}" = *"+$SECRET_CONTENT_ENC" ]] # Check last line of patch # Confirm plaintext cache ref was cleared - [[ ! -e $BATS_TEST_DIRNAME/.git/refs/notes/textconv/crypt ]] + [ ! -e $BATS_TEST_DIRNAME/.git/refs/notes/textconv/crypt ] # Confirm plaintext obj was truly cleared and is no longer visible run git show "$cached_plaintext_obj" - [[ "$status" -ne 0 ]] + [ "$status" -ne 0 ] } diff --git a/tests/test_crypt.bats b/tests/test_crypt.bats index b821ad0..74209cc 100755 --- a/tests/test_crypt.bats +++ b/tests/test_crypt.bats @@ -3,7 +3,7 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" SECRET_CONTENT="My secret content" -SECRET_CONTENT_ENC="U2FsdGVkX1/kkWK36bn3fbq5DY2d+JXL2YWoN/eoXA1XJZEk9JS7j/856rXK9gPn" +SECRET_CONTENT_ENC="U2FsdGVkX1/6ilR0PmJpAyCF7iG3+k4aBwbgVd48WaQXznsg42nXbQrlWsf/qiCg" function check_repo_is_clean { git diff-index --quiet HEAD -- @@ -12,8 +12,8 @@ function check_repo_is_clean { @test "crypt: git ls-crypt command is available" { # No encrypted file yet, so command should work with no output run git ls-crypt - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "" ] } @test "crypt: encrypt a file" { @@ -23,63 +23,63 @@ function check_repo_is_clean { @test "crypt: encrypted file contents are decrypted in working copy" { encrypt_named_file sensitive_file "$SECRET_CONTENT" run cat sensitive_file - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "$SECRET_CONTENT" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT" ] } @test "crypt: encrypted file contents are encrypted in git (via git show)" { encrypt_named_file sensitive_file "$SECRET_CONTENT" run git show HEAD:sensitive_file --no-textconv - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "$SECRET_CONTENT_ENC" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT_ENC" ] } @test "crypt: encrypted file contents can be decrypted (via git show)" { encrypt_named_file sensitive_file "$SECRET_CONTENT" run git show HEAD:sensitive_file --textconv - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "$SECRET_CONTENT" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT" ] } @test "crypt: transcrypt --show-raw shows encrypted content" { encrypt_named_file sensitive_file "$SECRET_CONTENT" run ../transcrypt --show-raw sensitive_file - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "==> sensitive_file <==" ]] - [[ "${lines[1]}" = "$SECRET_CONTENT_ENC" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "==> sensitive_file <==" ] + [ "${lines[1]}" = "$SECRET_CONTENT_ENC" ] } @test "crypt: git ls-crypt lists encrypted file" { encrypt_named_file sensitive_file "$SECRET_CONTENT" run git ls-crypt - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "sensitive_file" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "sensitive_file" ] } @test "crypt: transcrypt --list lists encrypted file" { encrypt_named_file sensitive_file "$SECRET_CONTENT" run ../transcrypt --list - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "sensitive_file" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "sensitive_file" ] } @test "crypt: transcrypt --uninstall leaves decrypted file and repo dirty" { encrypt_named_file sensitive_file "$SECRET_CONTENT" run ../transcrypt --uninstall --yes - [[ "$status" -eq 0 ]] + [ "$status" -eq 0 ] run cat sensitive_file - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "$SECRET_CONTENT" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT" ] run cat .gitattributes - [[ "${lines[0]}" = "" ]] + [ "${lines[0]}" = "" ] run check_repo_is_clean - [[ "$status" -ne 0 ]] + [ "$status" -ne 0 ] } @test "crypt: git reset after uninstall leaves encrypted file" { @@ -91,9 +91,9 @@ function check_repo_is_clean { check_repo_is_clean run cat sensitive_file - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" != "$SECRET_CONTENT" ]] - [[ "${lines[0]}" = "$SECRET_CONTENT_ENC" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" != "$SECRET_CONTENT" ] + [ "${lines[0]}" = "$SECRET_CONTENT_ENC" ] } @test "crypt: handle challenging file names when 'core.quotePath=true'" { @@ -105,36 +105,37 @@ function check_repo_is_clean { git config --local --add core.quotePath true FILENAME="Mig – røve" # Danish - SECRET_CONTENT_ENC="U2FsdGVkX19Fp9SwTyQ+tz1OgHNIN0OJ+6sMgHIqPMzfdZ6rZ2iVquS293WnjJMx" + SECRET_CONTENT_ENC="U2FsdGVkX18jeEpsv589tzPzs+2KY6Bv6uxAHqAV6WvcSmckLHHVEvq3uItd9oq7" encrypt_named_file "$FILENAME" "$SECRET_CONTENT" [[ "${output}" = *"Encrypt file \"$FILENAME\""* ]] # Working copy is decrypted run cat "$FILENAME" - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "$SECRET_CONTENT" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT" ] # Git internal copy is encrypted run git show HEAD:"$FILENAME" --no-textconv - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "$SECRET_CONTENT_ENC" ]] + echo "${lines}" + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT_ENC" ] # transcrypt --show-raw shows encrypted content run ../transcrypt --show-raw "$FILENAME" - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "==> $FILENAME <==" ]] - [[ "${lines[1]}" = "$SECRET_CONTENT_ENC" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "==> $FILENAME <==" ] + [ "${lines[1]}" = "$SECRET_CONTENT_ENC" ] # git ls-crypt lists encrypted file run git ls-crypt - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "$FILENAME" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$FILENAME" ] # transcrypt --list lists encrypted file" run ../transcrypt --list - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "$FILENAME" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$FILENAME" ] rm "$FILENAME" } @@ -142,7 +143,7 @@ function check_repo_is_clean { @test "crypt: handle very small file" { FILENAME="small file.txt" SECRET_CONTENT="sh" - SECRET_CONTENT_ENC="U2FsdGVkX1/NlZCZ0ho/TL+5kTxm/R/X5nfATzQLKfI=" + SECRET_CONTENT_ENC="U2FsdGVkX1+fWwQTmT7tfxgGSJ+TLQJVV9WWlxtRZ38=" encrypt_named_file "$FILENAME" "$SECRET_CONTENT" [[ "${output}" = *"Encrypt file \"$FILENAME\""* ]] @@ -178,7 +179,7 @@ function check_repo_is_clean { @test "crypt: handle file with problematic bytes" { FILENAME="problem bytes file.txt" - SECRET_CONTENT_ENC="U2FsdGVkX1/RVkIfYCsbUMcvQlC2vWZJVQtZSuppd7Q=" + SECRET_CONTENT_ENC="U2FsdGVkX18oyzDfF0Yjh1oqnz8RvjksOYpv53eaJ7c=" # Write octal byte 375 and null byte as file contents printf "\375 \0 shh" > "$FILENAME" @@ -222,49 +223,50 @@ function check_repo_is_clean { git config --local transcrypt.version "0.0" - rm .git/crypt/merge + git config --local --unset merge.crypt.driver # Check .gitattributes and sensitive_file before re-install run cat .gitattributes - [[ "${lines[0]}" = "sensitive_file filter=crypt diff=crypt" ]] - [[ ! -f .git/crypt/merge ]] + [ "${lines[0]}" = "sensitive_file filter=crypt diff=crypt" ] + # Check merge driver is not installed + [ ! "$(git config --get merge.crypt.driver)" = '"$(git config transcrypt.crypt-dir 2>/dev/null || printf ''%s/crypt'' ""$(git rev-parse --git-dir)"")"/transcrypt merge %O %A %B %L %P' ] run git config --get --local transcrypt.version - [[ "${lines[0]}" = "0.0" ]] + [ "${lines[0]}" = "0.0" ] run git config --get --local transcrypt.cipher - [[ "${lines[0]}" = "aes-256-cbc" ]] + [ "${lines[0]}" = "aes-256-cbc" ] run git config --get --local transcrypt.password - [[ "${lines[0]}" = "abc123" ]] + [ "${lines[0]}" = "abc 123" ] run cat sensitive_file - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "$SECRET_CONTENT" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT" ] # Perform re-install run ../transcrypt --upgrade --yes - [[ "$status" -eq 0 ]] + [ "$status" -eq 0 ] run git config --get --local transcrypt.version - [[ "${lines[0]}" = "$VERSION" ]] + [ "${lines[0]}" = "$VERSION" ] run git config --get --local transcrypt.cipher - [[ "${lines[0]}" = "aes-256-cbc" ]] + [ "${lines[0]}" = "aes-256-cbc" ] run git config --get --local transcrypt.password - [[ "${lines[0]}" = "abc123" ]] + [ "${lines[0]}" = "abc 123" ] # Check sensitive_file is unchanged after re-install run cat sensitive_file - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "$SECRET_CONTENT" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT" ] # Check merge driver is installed - [[ -f .git/crypt/merge ]] + [ "$(git config --get merge.crypt.driver)" = '"$(git config transcrypt.crypt-dir 2>/dev/null || printf ''%s/crypt'' ""$(git rev-parse --git-dir)"")"/transcrypt merge %O %A %B %L %P' ] # Check .gitattributes is updated to include merge driver run cat .gitattributes - [[ "${lines[0]}" = "sensitive_file filter=crypt diff=crypt merge=crypt" ]] + [ "${lines[0]}" = "sensitive_file filter=crypt diff=crypt merge=crypt" ] run check_repo_is_clean - [[ "$status" -ne 0 ]] + [ "$status" -ne 0 ] } @test "crypt: transcrypt --force handles files missing from working copy" { @@ -279,10 +281,10 @@ function check_repo_is_clean { rm sensitive_file # Re-init with --force should check out deleted secret file - ../transcrypt --force --cipher=aes-256-cbc --password=abc123 --yes + ../transcrypt --force --cipher=aes-256-cbc --password='abc 123' --yes # Check sensitive_file is present and decrypted run cat sensitive_file - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "$SECRET_CONTENT" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT" ] } diff --git a/tests/test_init.bats b/tests/test_init.bats index 6c7a953..c0fd7f7 100755 --- a/tests/test_init.bats +++ b/tests/test_init.bats @@ -9,52 +9,47 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 @test "init: works at all" { # Use literal command not function to confirm command works at least once - run ../transcrypt --cipher=aes-256-cbc --password=abc123 --yes - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "The repository has been successfully configured by transcrypt." ]] + run ../transcrypt --cipher=aes-256-cbc --password='abc 123' --yes + [ "$status" -eq 0 ] + [ "${lines[0]}" = "The repository has been successfully configured by transcrypt." ] } @test "init: creates .gitattributes" { init_transcrypt - [[ -f .gitattributes ]] + [ -f .gitattributes ] run cat .gitattributes - [[ "${lines[0]}" = "#pattern filter=crypt diff=crypt merge=crypt" ]] + [ "${lines[0]}" = "#pattern filter=crypt diff=crypt merge=crypt" ] } @test "init: creates scripts in .git/crypt/" { init_transcrypt - [[ -d .git/crypt ]] - [[ -f .git/crypt/clean ]] - [[ -f .git/crypt/smudge ]] - [[ -f .git/crypt/textconv ]] + [ -d .git/crypt ] + [ -f .git/crypt/transcrypt ] } @test "init: applies git config" { init_transcrypt VERSION=$(../transcrypt -v | awk '{print $2}') - [[ "$(git config --get transcrypt.version)" = "$VERSION" ]] - [[ "$(git config --get transcrypt.cipher)" = "aes-256-cbc" ]] - [[ "$(git config --get transcrypt.password)" = "abc123" ]] + [ "$(git config --get transcrypt.version)" = "$VERSION" ] + [ "$(git config --get transcrypt.cipher)" = "aes-256-cbc" ] + [ "$(git config --get transcrypt.password)" = "abc 123" ] + [ "$(git config --get transcrypt.openssl-path)" = "openssl" ] # Use --git-common-dir if available (Git post Nov 2014) otherwise --git-dir # shellcheck disable=SC2016 - if [[ -d $(git rev-parse --git-common-dir) ]]; then - [[ "$(git config --get filter.crypt.clean)" = '"$(git rev-parse --git-common-dir)"/crypt/clean %f' ]] - [[ "$(git config --get filter.crypt.smudge)" = '"$(git rev-parse --git-common-dir)"/crypt/smudge' ]] - [[ "$(git config --get diff.crypt.textconv)" = '"$(git rev-parse --git-common-dir)"/crypt/textconv' ]] - else - [[ "$(git config --get filter.crypt.clean)" = '"$(git rev-parse --git-dir)"/crypt/clean %f' ]] - [[ "$(git config --get filter.crypt.smudge)" = '"$(git rev-parse --git-dir)"/crypt/smudge' ]] - [[ "$(git config --get diff.crypt.textconv)" = '"$(git rev-parse --git-dir)"/crypt/textconv' ]] - fi - - [[ "$(git config --get filter.crypt.required)" = "true" ]] - [[ "$(git config --get diff.crypt.cachetextconv)" = "true" ]] - [[ "$(git config --get diff.crypt.binary)" = "true" ]] - [[ "$(git config --get merge.renormalize)" = "true" ]] - - [[ "$(git config --get alias.ls-crypt)" = "!git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = \":\" }; /crypt$/{ print \$1 }'" ]] + [ "$(git config --get filter.crypt.clean)" = '"$(git config transcrypt.crypt-dir 2>/dev/null || printf ''%s/crypt'' ""$(git rev-parse --git-dir)"")"/transcrypt clean %f' ] + [ "$(git config --get filter.crypt.smudge)" = '"$(git config transcrypt.crypt-dir 2>/dev/null || printf ''%s/crypt'' ""$(git rev-parse --git-dir)"")"/transcrypt smudge' ] + [ "$(git config --get diff.crypt.textconv)" = '"$(git config transcrypt.crypt-dir 2>/dev/null || printf ''%s/crypt'' ""$(git rev-parse --git-dir)"")"/transcrypt textconv' ] + [ "$(git config --get merge.crypt.driver)" = '"$(git config transcrypt.crypt-dir 2>/dev/null || printf ''%s/crypt'' ""$(git rev-parse --git-dir)"")"/transcrypt merge %O %A %B %L %P' ] + + [ "$(git config --get filter.crypt.required)" = "true" ] + [ "$(git config --get diff.crypt.cachetextconv)" = "true" ] + [ "$(git config --get diff.crypt.binary)" = "true" ] + [ "$(git config --get merge.renormalize)" = "true" ] + [ "$(git config --get merge.crypt.name)" = "Merge transcrypt secret files" ] + + [ "$(git config --get alias.ls-crypt)" = "!git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = \":\" }; /crypt$/{ print \$1 }'" ] } @test "init: show details for --display" { @@ -62,11 +57,11 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 VERSION=$(../transcrypt -v | awk '{print $2}') run ../transcrypt --display - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "The current repository was configured using transcrypt version $VERSION" ]] - [[ "${lines[5]}" = " CIPHER: aes-256-cbc" ]] - [[ "${lines[6]}" = " PASSWORD: abc123" ]] - [[ "${lines[8]}" = " transcrypt -c aes-256-cbc -p 'abc123'" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "The current repository was configured using transcrypt version $VERSION" ] + [ "${lines[5]}" = " CIPHER: aes-256-cbc" ] + [ "${lines[6]}" = " PASSWORD: abc 123" ] + [ "${lines[8]}" = " transcrypt -c aes-256-cbc -p 'abc 123'" ] } @test "init: show details for -d" { @@ -74,61 +69,61 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 VERSION=$(../transcrypt -v | awk '{print $2}') run ../transcrypt -d - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "The current repository was configured using transcrypt version $VERSION" ]] - [[ "${lines[5]}" = " CIPHER: aes-256-cbc" ]] - [[ "${lines[6]}" = " PASSWORD: abc123" ]] - [[ "${lines[8]}" = " transcrypt -c aes-256-cbc -p 'abc123'" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "The current repository was configured using transcrypt version $VERSION" ] + [ "${lines[5]}" = " CIPHER: aes-256-cbc" ] + [ "${lines[6]}" = " PASSWORD: abc 123" ] + [ "${lines[8]}" = " transcrypt -c aes-256-cbc -p 'abc 123'" ] } @test "init: respects core.hooksPath setting" { git config core.hooksPath ".git/myhooks" - [[ "$(git config --get core.hooksPath)" = '.git/myhooks' ]] + [ "$(git config --get core.hooksPath)" = '.git/myhooks' ] init_transcrypt - [[ -d .git/myhooks ]] - [[ -f .git/myhooks/pre-commit ]] + [ -d .git/myhooks ] + [ -f .git/myhooks/pre-commit ] VERSION=$(../transcrypt -v | awk '{print $2}') run ../transcrypt --display - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "The current repository was configured using transcrypt version $VERSION" ]] - [[ "${lines[5]}" = " CIPHER: aes-256-cbc" ]] - [[ "${lines[6]}" = " PASSWORD: abc123" ]] - [[ "${lines[8]}" = " transcrypt -c aes-256-cbc -p 'abc123'" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "The current repository was configured using transcrypt version $VERSION" ] + [ "${lines[5]}" = " CIPHER: aes-256-cbc" ] + [ "${lines[6]}" = " PASSWORD: abc 123" ] + [ "${lines[8]}" = " transcrypt -c aes-256-cbc -p 'abc 123'" ] } @test "init: transcrypt.openssl-path config setting defaults to 'openssl'" { init_transcrypt - [[ "$(git config --get transcrypt.openssl-path)" = 'openssl' ]] + [ "$(git config --get transcrypt.openssl-path)" = 'openssl' ] } @test "init: --set-openssl-path changes transcrypt.openssl-path" { init_transcrypt - [[ "$(git config --get transcrypt.openssl-path)" = 'openssl' ]] + [ "$(git config --get transcrypt.openssl-path)" = 'openssl' ] } @test "init: --set-openssl-path is applied during init" { init_transcrypt run ../transcrypt --set-openssl-path=/test/path - [[ "$(git config --get transcrypt.openssl-path)" = "/test/path" ]] + [ "$(git config --get transcrypt.openssl-path)" = "/test/path" ] } @test "init: --set-openssl-path is applied during upgrade" { init_transcrypt - [[ "$(git config --get transcrypt.openssl-path)" = 'openssl' ]] + [ "$(git config --get transcrypt.openssl-path)" = 'openssl' ] # Set openssl path FULL_OPENSSL_PATH=$(which openssl) "$BATS_TEST_DIRNAME"/../transcrypt --upgrade --yes --set-openssl-path="$FULL_OPENSSL_PATH" - [[ "$(git config --get transcrypt.openssl-path)" = "$FULL_OPENSSL_PATH" ]] - [[ ! "$(git config --get transcrypt.openssl-path)" = 'openssl' ]] + [ "$(git config --get transcrypt.openssl-path)" = "$FULL_OPENSSL_PATH" ] + [ ! "$(git config --get transcrypt.openssl-path)" = 'openssl' ] } @test "init: transcrypt.openssl-path config setting is retained with --upgrade" { init_transcrypt - [[ "$(git config --get transcrypt.openssl-path)" = 'openssl' ]] + [ "$(git config --get transcrypt.openssl-path)" = 'openssl' ] # Set openssl path FULL_OPENSSL_PATH=$(which openssl) @@ -136,6 +131,40 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 # Retain transcrypt.openssl-path config setting on upgrade "$BATS_TEST_DIRNAME"/../transcrypt --upgrade --yes - [[ "$(git config --get transcrypt.openssl-path)" = "$FULL_OPENSSL_PATH" ]] - [[ ! "$(git config --get transcrypt.openssl-path)" = 'openssl' ]] + [ "$(git config --get transcrypt.openssl-path)" = "$FULL_OPENSSL_PATH" ] + [ ! "$(git config --get transcrypt.openssl-path)" = 'openssl' ] +} + +@test "init: transcrypt.crypt-dir config setting is applied during init" { + # Set a custom location for the crypt/ directory + git config transcrypt.crypt-dir /tmp/crypt + + init_transcrypt + + # Confirm crypt/ directory is populated in custom location + [ ! -d .git/crypt ] + [ ! -f .git/crypt/transcrypt ] + [ -d /tmp/crypt ] + [ -f /tmp/crypt/transcrypt ] +} + +@test "crypt: transcrypt.crypt-dir config setting produces working scripts" { + # Set a custom location for the crypt/ directory + git config transcrypt.crypt-dir /tmp/crypt + + init_transcrypt + + SECRET_CONTENT="My secret content" + SECRET_CONTENT_ENC="U2FsdGVkX1/6ilR0PmJpAyCF7iG3+k4aBwbgVd48WaQXznsg42nXbQrlWsf/qiCg" + + encrypt_named_file sensitive_file "$SECRET_CONTENT" + + run cat sensitive_file + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT" ] + + run ../transcrypt --show-raw sensitive_file + [ "$status" -eq 0 ] + [ "${lines[0]}" = "==> sensitive_file <==" ] + [ "${lines[1]}" = "$SECRET_CONTENT_ENC" ] } diff --git a/tests/test_merge.bats b/tests/test_merge.bats index 3e5db52..ffe941a 100755 --- a/tests/test_merge.bats +++ b/tests/test_merge.bats @@ -15,9 +15,9 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" git merge branch-2 run cat sensitive_file - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "1. First step" ]] - [[ "${lines[1]}" = "2. Second step" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "1. First step" ] + [ "${lines[1]}" = "2. Second step" ] } @test "merge: branches with encrypted file - line change incoming branch, no conflict" { @@ -32,12 +32,12 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" git checkout - run git merge branch-2 - [[ "$status" -eq 0 ]] + [ "$status" -eq 0 ] run cat sensitive_file - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "1. Step the first" ]] - [[ "${lines[1]}" = "2. Second step" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "1. Step the first" ] + [ "${lines[1]}" = "2. Second step" ] } @test "merge: branches with encrypted file - line changes both branches, no conflict" { @@ -60,13 +60,13 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" git commit -m "Add line 3" run git merge branch-2 - [[ "$status" -eq 0 ]] + [ "$status" -eq 0 ] run cat sensitive_file - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "1. Step the first" ]] - [[ "${lines[1]}" = "2. Second step" ]] - [[ "${lines[2]}" = "3. Third step" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "1. Step the first" ] + [ "${lines[1]}" = "2. Second step" ] + [ "${lines[2]}" = "3. Third step" ] } @test "merge: branches with encrypted file - line changes both branches, with conflicts" { @@ -85,15 +85,15 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" git commit -m "Change line 1 in original branch to set up conflict" run git merge branch-2 - [[ "$status" -ne 0 ]] + [ "$status" -ne 0 ] [[ "${output}" = *"CONFLICT (content): Merge conflict in sensitive_file"* ]] run cat sensitive_file - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "<<<<<<< main" ]] - [[ "${lines[1]}" = "a. First step" ]] - [[ "${lines[2]}" = "=======" ]] - [[ "${lines[3]}" = "1. Step the first" ]] - [[ "${lines[4]}" = "2. Second step" ]] - [[ "${lines[5]}" = ">>>>>>> branch-2" ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = "<<<<<<< main" ] + [ "${lines[1]}" = "a. First step" ] + [ "${lines[2]}" = "=======" ] + [ "${lines[3]}" = "1. Step the first" ] + [ "${lines[4]}" = "2. Second step" ] + [ "${lines[5]}" = ">>>>>>> branch-2" ] } diff --git a/tests/test_not_inited.bats b/tests/test_not_inited.bats index b948f47..a578588 100755 --- a/tests/test_not_inited.bats +++ b/tests/test_not_inited.bats @@ -13,34 +13,34 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 @test "not inited: show help for --help" { run ../transcrypt --help - [[ "${lines[1]}" = " transcrypt -- transparently encrypt files within a git repository" ]] + [ "${lines[1]}" = " transcrypt -- transparently encrypt files within a git repository" ] } @test "not inited: show help for -h" { run ../transcrypt -h - [[ "${lines[1]}" = " transcrypt -- transparently encrypt files within a git repository" ]] + [ "${lines[1]}" = " transcrypt -- transparently encrypt files within a git repository" ] } @test "not inited: show version for --version" { VERSION=$(../transcrypt -v | awk '{print $2}') run ../transcrypt --version - [[ "${lines[0]}" = "transcrypt $VERSION" ]] + [ "${lines[0]}" = "transcrypt $VERSION" ] } @test "not inited: show version for -v" { VERSION=$(../transcrypt -v | awk '{print $2}') run ../transcrypt -v - [[ "${lines[0]}" = "transcrypt $VERSION" ]] + [ "${lines[0]}" = "transcrypt $VERSION" ] } @test "not inited: no files listed for --list" { run ../transcrypt --list - [[ "${lines[0]}" = "" ]] + [ "${lines[0]}" = "" ] } @test "not inited: no files listed for -l" { run ../transcrypt -l - [[ "${lines[0]}" = "" ]] + [ "${lines[0]}" = "" ] } @@ -48,31 +48,31 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 @test "not inited: error on --display" { run ../transcrypt --display - [[ "$status" -ne 0 ]] - [[ "${lines[0]}" = "transcrypt: the current repository is not configured" ]] + [ "$status" -ne 0 ] + [ "${lines[0]}" = "transcrypt: the current repository is not configured" ] } @test "not inited: error on -d" { run ../transcrypt -d - [[ "$status" -ne 0 ]] - [[ "${lines[0]}" = "transcrypt: the current repository is not configured" ]] + [ "$status" -ne 0 ] + [ "${lines[0]}" = "transcrypt: the current repository is not configured" ] } @test "not inited: error on --uninstall" { run ../transcrypt --uninstall - [[ "$status" -ne 0 ]] - [[ "${lines[0]}" = "transcrypt: the current repository is not configured" ]] + [ "$status" -ne 0 ] + [ "${lines[0]}" = "transcrypt: the current repository is not configured" ] } @test "not inited: error on -u" { run ../transcrypt -u - [[ "$status" -ne 0 ]] - [[ "${lines[0]}" = "transcrypt: the current repository is not configured" ]] + [ "$status" -ne 0 ] + [ "${lines[0]}" = "transcrypt: the current repository is not configured" ] } @test "not inited: error on --upgrade" { run ../transcrypt --upgrade - [[ "$status" -ne 0 ]] - [[ "${lines[0]}" = "transcrypt: the current repository is not configured" ]] + [ "$status" -ne 0 ] + [ "${lines[0]}" = "transcrypt: the current repository is not configured" ] } diff --git a/tests/test_pre_commit.bats b/tests/test_pre_commit.bats index b88a250..d85354a 100755 --- a/tests/test_pre_commit.bats +++ b/tests/test_pre_commit.bats @@ -4,14 +4,14 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" @test "pre-commit: pre-commit hook installed on init" { # Confirm pre-commit-crypt file is installed - [[ -f .git/hooks/pre-commit-crypt ]] + [ -f .git/hooks/pre-commit-crypt ] run cat .git/hooks/pre-commit-crypt - [[ "${lines[1]}" = '# Transcrypt pre-commit hook: fail if secret file in staging lacks the magic prefix "Salted" in B64' ]] + [ "${lines[1]}" = '# Transcrypt pre-commit hook: fail if secret file in staging lacks the magic prefix "Salted" in B64' ] # Confirm hook is also installed/activated at pre-commit file name - [[ -f .git/hooks/pre-commit ]] + [ -f .git/hooks/pre-commit ] run cat .git/hooks/pre-commit - [[ "${lines[1]}" = '# Transcrypt pre-commit hook: fail if secret file in staging lacks the magic prefix "Salted" in B64' ]] + [ "${lines[1]}" = '# Transcrypt pre-commit hook: fail if secret file in staging lacks the magic prefix "Salted" in B64' ] } @test "pre-commit: permit commit of encrypted file with encrypted content" { @@ -21,10 +21,10 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" echo " and more secrets" >> sensitive_file git add sensitive_file run git commit -m "Added more" - [[ "$status" -eq 0 ]] + [ "$status" -eq 0 ] run git log --format=oneline - [[ "$status" -eq 0 ]] + [ "$status" -eq 0 ] [[ "${lines[0]}" = *"Added more" ]] [[ "${lines[1]}" = *"Encrypt file \"sensitive_file\"" ]] } @@ -43,7 +43,7 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" # Confirm the pre-commit rejects plain text content in what should be # an encrypted file run git commit -m "Added more" - [[ "$status" -ne 0 ]] + [ "$status" -ne 0 ] [[ "${output}" = *"Transcrypt managed file is not encrypted in the Git index: sensitive_file"* ]] [[ "${output}" = *"You probably staged this file using a tool that does not apply .gitattribute filters as required by Transcrypt."* ]] [[ "${output}" = *"Fix this by re-staging the file with a compatible tool or with Git on the command line:"* ]] @@ -60,7 +60,7 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" git add .gitattributes symlink_to_sensitive_file git commit -m "Commit symlink to encrypted file" - [[ "$status" -eq 0 ]] + [ "$status" -eq 0 ] rm symlink_to_sensitive_file } @@ -72,23 +72,23 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" # Create a pre-existing pre-commit hook touch .git/hooks/pre-commit - run "$BATS_TEST_DIRNAME"/../transcrypt --cipher=aes-256-cbc --password=abc123 --yes - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = "WARNING:" ]] - [[ "${lines[1]}" = "Cannot install Git pre-commit hook script because file already exists: .git/hooks/pre-commit" ]] - [[ "${lines[2]}" = "Please manually install the pre-commit script saved as: .git/hooks/pre-commit-crypt" ]] + run "$BATS_TEST_DIRNAME"/../transcrypt --cipher=aes-256-cbc --password='abc 123' --yes + [ "$status" -eq 0 ] + [ "${lines[0]}" = "WARNING:" ] + [ "${lines[1]}" = "Cannot install Git pre-commit hook script because file already exists: .git/hooks/pre-commit" ] + [ "${lines[2]}" = "Please manually install the pre-commit script saved as: .git/hooks/pre-commit-crypt" ] # Confirm pre-commit-crypt file is installed, but not copied to pre-commit run cat .git/hooks/pre-commit-crypt - [[ "$status" -eq 0 ]] - [[ "${lines[1]}" = '# Transcrypt pre-commit hook: fail if secret file in staging lacks the magic prefix "Salted" in B64' ]] - [[ ! -s .git/hooks/pre-commit ]] # Zero file size] + [ "$status" -eq 0 ] + [ "${lines[1]}" = '# Transcrypt pre-commit hook: fail if secret file in staging lacks the magic prefix "Salted" in B64' ] + [ ! -s .git/hooks/pre-commit ] # Zero file size] } @test "pre-commit: de-activate and remove transcrypt's pre-commit hook" { "$BATS_TEST_DIRNAME"/../transcrypt --uninstall --yes - [[ ! -f .git/hooks/pre-commit ]] - [[ ! -f .git/hooks/pre-commit-crypt ]] + [ ! -f .git/hooks/pre-commit ] + [ ! -f .git/hooks/pre-commit-crypt ] } @test "pre-commit: warn and don't delete customised pre-commit hook on uninstall" { @@ -96,8 +96,8 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" echo "#" >> .git/hooks/pre-commit run "$BATS_TEST_DIRNAME"/../transcrypt --uninstall --yes - [[ "$status" -eq 0 ]] - [[ "${lines[0]}" = 'WARNING: Cannot safely disable Git pre-commit hook .git/hooks/pre-commit please check it yourself' ]] - [[ -f .git/hooks/pre-commit ]] - [[ ! -f .git/hooks/pre-commit-crypt ]] + [ "$status" -eq 0 ] + [ "${lines[0]}" = 'WARNING: Cannot safely disable Git pre-commit hook .git/hooks/pre-commit please check it yourself' ] + [ -f .git/hooks/pre-commit ] + [ ! -f .git/hooks/pre-commit-crypt ] } diff --git a/transcrypt b/transcrypt index a6c79a5..0319f48 100755 --- a/transcrypt +++ b/transcrypt @@ -74,6 +74,9 @@ gather_repo_metadata() { readonly RELATIVE_GIT_DIR=$(git rev-parse --git-dir 2>/dev/null || printf '') readonly GIT_DIR=$(realpath "$RELATIVE_GIT_DIR" 2>/dev/null) + # Respect transcrypt.crypt-dir if present. Default to crypt/ in Git dir + readonly CRYPT_DIR=$(git config transcrypt.crypt-dir 2>/dev/null || printf '%s/crypt' "${RELATIVE_GIT_DIR}") + # respect core.hooksPath setting, without trailing slash. Fall back to default hooks dir readonly GIT_HOOKS=$(git config core.hooksPath | sed 's:/*$::' 2>/dev/null || printf "%s/hooks" "${RELATIVE_GIT_DIR}") @@ -163,6 +166,8 @@ git_textconv() { # shellcheck disable=SC2005,SC2002,SC2181 git_merge() { + # Get path to transcrypt in this script's directory + TRANSCRYPT_PATH="$(dirname "$0")/transcrypt" # Look up name of local branch/ref to which changes are being merged OURS_LABEL=$(git rev-parse --abbrev-ref HEAD) # Look up name of the incoming "theirs" branch/ref being merged in. @@ -175,9 +180,9 @@ git_merge() { THEIRS_LABEL="theirs" fi # Decrypt BASE $1, LOCAL $2, and REMOTE $3 versions of file being merged - echo "$(cat "$1" | ./.git/crypt/smudge)" >"$1" - echo "$(cat "$2" | ./.git/crypt/smudge)" >"$2" - echo "$(cat "$3" | ./.git/crypt/smudge)" >"$3" + echo "$(cat "$1" | "${TRANSCRYPT_PATH}" smudge)" >"$1" + echo "$(cat "$2" | "${TRANSCRYPT_PATH}" smudge)" >"$2" + echo "$(cat "$3" | "${TRANSCRYPT_PATH}" smudge)" >"$3" # Merge the decrypted files to the temp file named by $2 git merge-file --marker-size="$4" -L "$OURS_LABEL" -L base -L "$THEIRS_LABEL" "$2" "$1" "$3" # If the merge was not successful (has conflicts) exit with an error code to @@ -196,7 +201,81 @@ git_merge() { cp "$2" "$5" # Now we use the `clean` script to encrypt the merged file contents back to the # temp file $2 where Git expects to find the merge result content. - cat "$5" | ./.git/crypt/clean "$5" >"$2" + cat "$5" | "${TRANSCRYPT_PATH}" clean "$5" >"$2" +} + +# shellcheck disable=SC2155 +git_pre_commit() { + # Transcrypt pre-commit hook: fail if secret file in staging lacks the magic prefix "Salted" in B64 + tmp=$(mktemp) + IFS=$'\n' + slow_mode_if_failed() { + for secret_file in $(git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = ":" }; /crypt$/{ print $1 }'); do + # Skip symlinks, they contain the linked target file path not plaintext + if [[ -L $secret_file ]]; then + continue + fi + + # Get prefix of raw file in Git's index using the :FILENAME revision syntax + local firstbytes=$(git show :"${secret_file}" | head -c8) + # An empty file does not need to be, and is not, encrypted + if [[ $firstbytes == "" ]]; then + : # Do nothing + # The first bytes of an encrypted file must be "Salted" in Base64 + elif [[ $firstbytes != "U2FsdGVk" ]]; then + printf 'Transcrypt managed file is not encrypted in the Git index: %s\n' "$secret_file" >&2 + printf '\n' >&2 + printf 'You probably staged this file using a tool that does not apply' >&2 + printf ' .gitattribute filters as required by Transcrypt.\n' >&2 + printf '\n' >&2 + printf 'Fix this by re-staging the file with a compatible tool or with' + printf ' Git on the command line:\n' >&2 + printf '\n' >&2 + printf ' git rm --cached -- %s\n' "$secret_file" >&2 + printf ' git add %s\n' "$secret_file" >&2 + printf '\n' >&2 + exit 1 + fi + done + } + + # validate file to see if it failed or not, We don't care about the filename currently for speed, we only care about pass/fail, slow_mode_if_failed() is for what failed. + validate_file() { + secret_file=${1} + # Skip symlinks, they contain the linked target file path not plaintext + if [[ -L $secret_file ]]; then + return + fi + # Get prefix of raw file in Git's index using the :FILENAME revision syntax + # The first bytes of an encrypted file are always "Salted" in Base64 + local firstbytes=$(git show :"${secret_file}" | head -c8) + if [[ $firstbytes != "U2FsdGVk" ]]; then + echo "true" >>"${tmp}" + fi + } + + # if bash version is 4.4 or greater than fork to number of threads otherwise run normally + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]] && [[ "${BASH_VERSINFO[1]}" -ge 4 ]]; then + num_procs=$(nproc) + num_jobs="\j" + for secret_file in $(git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = ":" }; /crypt$/{ print $1 }'); do + while ((${num_jobs@P} >= num_procs)); do + wait -n + done + validate_file "${secret_file}" & + done + wait + if [[ -s ${tmp} ]]; then + slow_mode_if_failed + rm -f "${tmp}" + exit 1 + fi + else + slow_mode_if_failed + fi + + rm -f "${tmp}" + unset IFS } # verify that all requirements have been met @@ -367,35 +446,15 @@ stage_rekeyed_files() { # save helper scripts under the repository's git directory save_helper_scripts() { - mkdir -p "${GIT_DIR}/crypt" + mkdir -p "${CRYPT_DIR}" local current_transcrypt current_transcrypt=$(realpath "$0" 2>/dev/null) - cp "$current_transcrypt" "${GIT_DIR}/crypt/transcrypt" - - cat <<-'EOF' >"${GIT_DIR}/crypt/clean" - #!/usr/bin/env bash - $(dirname "$0")/transcrypt clean "$@" - EOF - - cat <<-'EOF' >"${GIT_DIR}/crypt/smudge" - #!/usr/bin/env bash - $(dirname "$0")/transcrypt smudge "$@" - EOF - - cat <<-'EOF' >"${GIT_DIR}/crypt/textconv" - #!/usr/bin/env bash - $(dirname "$0")/transcrypt textconv "$@" - EOF - - cat <<-'EOF' >"${GIT_DIR}/crypt/merge" - #!/usr/bin/env bash - $(dirname "$0")/transcrypt merge "$@" - EOF + cp "$current_transcrypt" "${CRYPT_DIR}/transcrypt" # make scripts executable - for script in {transcrypt,clean,smudge,textconv,merge}; do - chmod 0755 "${GIT_DIR}/crypt/${script}" + for script in {transcrypt,}; do + chmod 0755 "${CRYPT_DIR}/${script}" done } @@ -407,75 +466,9 @@ save_helper_hooks() { cat <<-'EOF' >"$pre_commit_hook_installed" #!/usr/bin/env bash # Transcrypt pre-commit hook: fail if secret file in staging lacks the magic prefix "Salted" in B64 - tmp=$(mktemp) - IFS=$'\n' - slow_mode_if_failed() { - for secret_file in $(git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = ":" }; /crypt$/{ print $1 }'); do - # Skip symlinks, they contain the linked target file path not plaintext - if [[ -L $secret_file ]]; then - continue - fi - - # Get prefix of raw file in Git's index using the :FILENAME revision syntax - firstbytes=$(git show :"${secret_file}" | head -c8) - # An empty file does not need to be, and is not, encrypted - if [[ $firstbytes == "" ]]; then - : # Do nothing - # The first bytes of an encrypted file must be "Salted" in Base64 - elif [[ $firstbytes != "U2FsdGVk" ]]; then - printf 'Transcrypt managed file is not encrypted in the Git index: %s\n' "$secret_file" >&2 - printf '\n' >&2 - printf 'You probably staged this file using a tool that does not apply' >&2 - printf ' .gitattribute filters as required by Transcrypt.\n' >&2 - printf '\n' >&2 - printf 'Fix this by re-staging the file with a compatible tool or with' - printf ' Git on the command line:\n' >&2 - printf '\n' >&2 - printf ' git rm --cached -- %s\n' "$secret_file" >&2 - printf ' git add %s\n' "$secret_file" >&2 - printf '\n' >&2 - exit 1 - fi - done - } - - # validate file to see if it failed or not, We don't care about the filename currently for speed, we only care about pass/fail, slow_mode_if_failed() is for what failed. - validate_file() { - secret_file=${1} - # Skip symlinks, they contain the linked target file path not plaintext - if [[ -L $secret_file ]]; then - return - fi - # Get prefix of raw file in Git's index using the :FILENAME revision syntax - # The first bytes of an encrypted file are always "Salted" in Base64 - firstbytes=$(git show :"${secret_file}" | head -c8) - if [[ $firstbytes != "U2FsdGVk" ]]; then - echo "true" >>"${tmp}" - fi - } - - # if bash version is 4.4 or greater than fork to number of threads otherwise run normally - if [[ "${BASH_VERSINFO[0]}" -ge 4 ]] && [[ "${BASH_VERSINFO[1]}" -ge 4 ]]; then - num_procs=$(nproc) - num_jobs="\j" - for secret_file in $(git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = ":" }; /crypt$/{ print $1 }'); do - while ((${num_jobs@P} >= num_procs)); do - wait -n - done - validate_file "${secret_file}" & - done - wait - if [[ -s ${tmp} ]]; then - slow_mode_if_failed - rm -f "${tmp}" - exit 1 - fi - else - slow_mode_if_failed - fi - - rm -f "${tmp}" - unset IFS + RELATIVE_GIT_DIR=$(git rev-parse --git-dir 2>/dev/null || printf '') + CRYPT_DIR=$(git config transcrypt.crypt-dir 2>/dev/null || printf '%s/crypt' "${RELATIVE_GIT_DIR}") + "${CRYPT_DIR}/transcrypt" pre_commit EOF # Activate hook by copying it to the pre-commit script name, if safe to do so @@ -502,28 +495,15 @@ save_configuration() { git config transcrypt.password "$password" git config transcrypt.openssl-path "$openssl_path" - # write the filter settings - if [[ -d $(git rev-parse --git-common-dir) ]]; then - # this allows us to support multiple working trees via git-worktree - # ...but the --git-common-dir flag was only added in November 2014 - # shellcheck disable=SC2016 - git config filter.crypt.clean '"$(git rev-parse --git-common-dir)"/crypt/clean %f' - # shellcheck disable=SC2016 - git config filter.crypt.smudge '"$(git rev-parse --git-common-dir)"/crypt/smudge' - # shellcheck disable=SC2016 - git config diff.crypt.textconv '"$(git rev-parse --git-common-dir)"/crypt/textconv' - # shellcheck disable=SC2016 - git config merge.crypt.driver '"$(git rev-parse --git-common-dir)"/crypt/merge %O %A %B %L %P' - else - # shellcheck disable=SC2016 - git config filter.crypt.clean '"$(git rev-parse --git-dir)"/crypt/clean %f' - # shellcheck disable=SC2016 - git config filter.crypt.smudge '"$(git rev-parse --git-dir)"/crypt/smudge' - # shellcheck disable=SC2016 - git config diff.crypt.textconv '"$(git rev-parse --git-dir)"/crypt/textconv' - # shellcheck disable=SC2016 - git config merge.crypt.driver '"$(git rev-parse --git-dir)"/crypt/merge %O %A %B %L %P' - fi + # write the filter settings. Sorry for the horrific quote escaping below... + # shellcheck disable=SC2016 + git config filter.crypt.clean '"$(git config transcrypt.crypt-dir 2>/dev/null || printf ''%s/crypt'' ""$(git rev-parse --git-dir)"")"/transcrypt clean %f' + # shellcheck disable=SC2016 + git config filter.crypt.smudge '"$(git config transcrypt.crypt-dir 2>/dev/null || printf ''%s/crypt'' ""$(git rev-parse --git-dir)"")"/transcrypt smudge' + # shellcheck disable=SC2016 + git config diff.crypt.textconv '"$(git config transcrypt.crypt-dir 2>/dev/null || printf ''%s/crypt'' ""$(git rev-parse --git-dir)"")"/transcrypt textconv' + # shellcheck disable=SC2016 + git config merge.crypt.driver '"$(git config transcrypt.crypt-dir 2>/dev/null || printf ''%s/crypt'' ""$(git rev-parse --git-dir)"")"/transcrypt merge %O %A %B %L %P' git config filter.crypt.required 'true' git config diff.crypt.cachetextconv 'true' git config diff.crypt.binary 'true' @@ -660,10 +640,11 @@ uninstall_transcrypt() { fi # remove helper scripts + # Keep obsolete clean,smudge,textconv,merge refs here to remove them on upgrade for script in {transcrypt,clean,smudge,textconv,merge}; do - [[ ! -f "${GIT_DIR}/crypt/${script}" ]] || rm "${GIT_DIR}/crypt/${script}" + [[ ! -f "${CRYPT_DIR}/${script}" ]] || rm "${CRYPT_DIR}/${script}" done - [[ ! -d "${GIT_DIR}/crypt" ]] || rmdir "${GIT_DIR}/crypt" + [[ ! -d "${CRYPT_DIR}" ]] || rmdir "${CRYPT_DIR}" # rename helper hooks (don't delete, in case user has custom changes) pre_commit_hook="${GIT_HOOKS}/pre-commit" @@ -823,10 +804,10 @@ export_gpg() { current_cipher=$(git config --get --local transcrypt.cipher) local current_password current_password=$(git config --get --local transcrypt.password) - mkdir -p "${GIT_DIR}/crypt" + mkdir -p "${CRYPT_DIR}" local gpg_encrypt_cmd="gpg --batch --recipient $gpg_recipient --trust-model always --yes --armor --quiet --encrypt -" - printf 'password=%s\ncipher=%s\n' "$current_password" "$current_cipher" | $gpg_encrypt_cmd >"${GIT_DIR}/crypt/${gpg_recipient}.asc" + printf 'password=%s\ncipher=%s\n' "$current_password" "$current_cipher" | $gpg_encrypt_cmd >"${CRYPT_DIR}/${gpg_recipient}.asc" printf "The transcrypt configuration has been encrypted and exported to:\n%s/crypt/%s.asc\n" "$GIT_DIR" "$gpg_recipient" } @@ -836,10 +817,10 @@ import_gpg() { command -v gpg >/dev/null || die 'required command "gpg" was not found' local path - if [[ -f "${GIT_DIR}/crypt/${gpg_import_file}" ]]; then - path="${GIT_DIR}/crypt/${gpg_import_file}" - elif [[ -f "${GIT_DIR}/crypt/${gpg_import_file}.asc" ]]; then - path="${GIT_DIR}/crypt/${gpg_import_file}.asc" + if [[ -f "${CRYPT_DIR}/${gpg_import_file}" ]]; then + path="${CRYPT_DIR}/${gpg_import_file}" + elif [[ -f "${CRYPT_DIR}/${gpg_import_file}.asc" ]]; then + path="${CRYPT_DIR}/${gpg_import_file}.asc" elif [[ ! -f $gpg_import_file ]]; then die 1 'the file "%s" does not exist' "$gpg_import_file" else @@ -1031,6 +1012,11 @@ while [[ "${1:-}" != '' ]]; do git_merge "$@" exit $? ;; + pre_commit) + shift + git_pre_commit "$@" + exit $? + ;; -c | --cipher) cipher=$2 shift From c6ec80e4b03f517023c81aadf4968214f548fbf4 Mon Sep 17 00:00:00 2001 From: James Murty Date: Tue, 14 Jun 2022 22:03:54 +1000 Subject: [PATCH 19/26] Fix when using OpenSSL 3 which no longer embeds salt in output (#135) * Ensure encrypted files include required "Salted__" prefix even for OpenSSL 3 * Update tests to ignore OpenSSL warnings recommending pbkdf2 for modern Openssl * Add Ubuntu 22.04 to list of versions tested by GitHub workflow * More easily distinguish encrypt vs decrypt commands by putting `-e` and `-d` flags next to `enc` * Mention OpenSSL 3 fix in changelog * Ensure salt prefix is included in encrypted files *without* resorting to a temporary file --- .github/workflows/run-bats-core-tests.yml | 2 +- CHANGELOG.md | 2 ++ tests/test_crypt.bats | 15 +++++------ tests/test_init.bats | 32 ++++++++++++++--------- tests/test_not_inited.bats | 24 ++++++++++++----- tests/test_pre_commit.bats | 10 +++---- transcrypt | 15 ++++++++--- 7 files changed, 63 insertions(+), 37 deletions(-) diff --git a/.github/workflows/run-bats-core-tests.yml b/.github/workflows/run-bats-core-tests.yml index 4091123..70b9b26 100644 --- a/.github/workflows/run-bats-core-tests.yml +++ b/.github/workflows/run-bats-core-tests.yml @@ -28,7 +28,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-18.04, ubuntu-20.04, macos-latest] + os: [ubuntu-18.04, ubuntu-20.04, ubuntu-22.04, macos-latest] steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it diff --git a/CHANGELOG.md b/CHANGELOG.md index bbc9bb1..839d765 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ The format is based on [Keep a Changelog][1], and this project adheres to ### Fixed +- Remain compatible with OpenSSL versions 3 and above which changes the way + explicit salt values are expressed in ciphertext (#133) - Ensure Git index is up-to-date before checking for dirty repo, to avoid failures seen in CI systems where the repo seems dirty when it isn't. (#37) - Respect Git `core.hooksPath` setting when installing the pre-commit hook. (#104) diff --git a/tests/test_crypt.bats b/tests/test_crypt.bats index 74209cc..26ae85d 100755 --- a/tests/test_crypt.bats +++ b/tests/test_crypt.bats @@ -62,7 +62,7 @@ function check_repo_is_clean { run ../transcrypt --list [ "$status" -eq 0 ] - [ "${lines[0]}" = "sensitive_file" ] + [[ "${output}" = *"sensitive_file" ]] } @test "crypt: transcrypt --uninstall leaves decrypted file and repo dirty" { @@ -117,7 +117,6 @@ function check_repo_is_clean { # Git internal copy is encrypted run git show HEAD:"$FILENAME" --no-textconv - echo "${lines}" [ "$status" -eq 0 ] [ "${lines[0]}" = "$SECRET_CONTENT_ENC" ] @@ -130,12 +129,12 @@ function check_repo_is_clean { # git ls-crypt lists encrypted file run git ls-crypt [ "$status" -eq 0 ] - [ "${lines[0]}" = "$FILENAME" ] + [[ "${output}" = *"$FILENAME" ]] # transcrypt --list lists encrypted file" run ../transcrypt --list [ "$status" -eq 0 ] - [ "${lines[0]}" = "$FILENAME" ] + [[ "${output}" = *"$FILENAME" ]] rm "$FILENAME" } @@ -167,12 +166,12 @@ function check_repo_is_clean { # git ls-crypt lists encrypted file run git ls-crypt [ "$status" -eq 0 ] - [ "${lines[0]}" = "$FILENAME" ] + [[ "${output}" = *"$FILENAME" ]] # transcrypt --list lists encrypted file" run ../transcrypt --list [ "$status" -eq 0 ] - [ "${lines[0]}" = "$FILENAME" ] + [[ "${output}" = *"$FILENAME" ]] rm "$FILENAME" } @@ -201,12 +200,12 @@ function check_repo_is_clean { # git ls-crypt lists encrypted file run git ls-crypt [ "$status" -eq 0 ] - [ "${lines[0]}" = "$FILENAME" ] + [[ "${output}" = *"$FILENAME" ]] # transcrypt --list lists encrypted file" run ../transcrypt --list [ "$status" -eq 0 ] - [ "${lines[0]}" = "$FILENAME" ] + [[ "${output}" = *"$FILENAME" ]] rm "$FILENAME" } diff --git a/tests/test_init.bats b/tests/test_init.bats index c0fd7f7..f92b70e 100755 --- a/tests/test_init.bats +++ b/tests/test_init.bats @@ -11,7 +11,7 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 # Use literal command not function to confirm command works at least once run ../transcrypt --cipher=aes-256-cbc --password='abc 123' --yes [ "$status" -eq 0 ] - [ "${lines[0]}" = "The repository has been successfully configured by transcrypt." ] + [[ "${output}" = *"The repository has been successfully configured by transcrypt."* ]] } @test "init: creates .gitattributes" { @@ -58,10 +58,10 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 run ../transcrypt --display [ "$status" -eq 0 ] - [ "${lines[0]}" = "The current repository was configured using transcrypt version $VERSION" ] - [ "${lines[5]}" = " CIPHER: aes-256-cbc" ] - [ "${lines[6]}" = " PASSWORD: abc 123" ] - [ "${lines[8]}" = " transcrypt -c aes-256-cbc -p 'abc 123'" ] + [[ "${output}" = *"The current repository was configured using transcrypt version $VERSION"* ]] + [[ "${output}" = *" CIPHER: aes-256-cbc"* ]] + [[ "${output}" = *" PASSWORD: abc 123"* ]] + [[ "${output}" = *" transcrypt -c aes-256-cbc -p 'abc 123'"* ]] } @test "init: show details for -d" { @@ -70,10 +70,10 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 run ../transcrypt -d [ "$status" -eq 0 ] - [ "${lines[0]}" = "The current repository was configured using transcrypt version $VERSION" ] - [ "${lines[5]}" = " CIPHER: aes-256-cbc" ] - [ "${lines[6]}" = " PASSWORD: abc 123" ] - [ "${lines[8]}" = " transcrypt -c aes-256-cbc -p 'abc 123'" ] + [[ "${output}" = *"The current repository was configured using transcrypt version $VERSION"* ]] + [[ "${output}" = *" CIPHER: aes-256-cbc"* ]] + [[ "${output}" = *" PASSWORD: abc 123"* ]] + [[ "${output}" = *" transcrypt -c aes-256-cbc -p 'abc 123'"* ]] } @test "init: respects core.hooksPath setting" { @@ -87,10 +87,10 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 VERSION=$(../transcrypt -v | awk '{print $2}') run ../transcrypt --display [ "$status" -eq 0 ] - [ "${lines[0]}" = "The current repository was configured using transcrypt version $VERSION" ] - [ "${lines[5]}" = " CIPHER: aes-256-cbc" ] - [ "${lines[6]}" = " PASSWORD: abc 123" ] - [ "${lines[8]}" = " transcrypt -c aes-256-cbc -p 'abc 123'" ] + [[ "${output}" = *"The current repository was configured using transcrypt version $VERSION"* ]] + [[ "${output}" = *" CIPHER: aes-256-cbc"* ]] + [[ "${output}" = *" PASSWORD: abc 123"* ]] + [[ "${output}" = *" transcrypt -c aes-256-cbc -p 'abc 123'"* ]] } @test "init: transcrypt.openssl-path config setting defaults to 'openssl'" { @@ -136,6 +136,9 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 } @test "init: transcrypt.crypt-dir config setting is applied during init" { + # Clear tmp crypt/ directory, in case junk was left there from prior test runs + rm -fR /tmp/crypt/ + # Set a custom location for the crypt/ directory git config transcrypt.crypt-dir /tmp/crypt @@ -149,6 +152,9 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 } @test "crypt: transcrypt.crypt-dir config setting produces working scripts" { + # Clear tmp crypt/ directory, in case junk was left there from prior test runs + rm -fR /tmp/crypt/ + # Set a custom location for the crypt/ directory git config transcrypt.crypt-dir /tmp/crypt diff --git a/tests/test_not_inited.bats b/tests/test_not_inited.bats index a578588..1f9c9f5 100755 --- a/tests/test_not_inited.bats +++ b/tests/test_not_inited.bats @@ -35,12 +35,22 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 @test "not inited: no files listed for --list" { run ../transcrypt --list - [ "${lines[0]}" = "" ] + if [[ "${output}" = *"WARNING"* ]]; then + [ "${lines[0]}" = "*** WARNING : deprecated key derivation used." ] + [ "${lines[1]}" = "Using -iter or -pbkdf2 would be better." ] + else + [ "${lines[0]}" = "" ] + fi } @test "not inited: no files listed for -l" { run ../transcrypt -l - [ "${lines[0]}" = "" ] + if [[ "${output}" = *"WARNING"* ]]; then + [ "${lines[0]}" = "*** WARNING : deprecated key derivation used." ] + [ "${lines[1]}" = "Using -iter or -pbkdf2 would be better." ] + else + [ "${lines[0]}" = "" ] + fi } @@ -49,30 +59,30 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 @test "not inited: error on --display" { run ../transcrypt --display [ "$status" -ne 0 ] - [ "${lines[0]}" = "transcrypt: the current repository is not configured" ] + [[ "${output}" = *"transcrypt: the current repository is not configured"* ]] } @test "not inited: error on -d" { run ../transcrypt -d [ "$status" -ne 0 ] - [ "${lines[0]}" = "transcrypt: the current repository is not configured" ] + [[ "${output}" = *"transcrypt: the current repository is not configured"* ]] } @test "not inited: error on --uninstall" { run ../transcrypt --uninstall [ "$status" -ne 0 ] - [ "${lines[0]}" = "transcrypt: the current repository is not configured" ] + [[ "${output}" = *"transcrypt: the current repository is not configured"* ]] } @test "not inited: error on -u" { run ../transcrypt -u [ "$status" -ne 0 ] - [ "${lines[0]}" = "transcrypt: the current repository is not configured" ] + [[ "${output}" = *"transcrypt: the current repository is not configured"* ]] } @test "not inited: error on --upgrade" { run ../transcrypt --upgrade [ "$status" -ne 0 ] - [ "${lines[0]}" = "transcrypt: the current repository is not configured" ] + [[ "${output}" = *"transcrypt: the current repository is not configured"* ]] } diff --git a/tests/test_pre_commit.bats b/tests/test_pre_commit.bats index d85354a..9b08795 100755 --- a/tests/test_pre_commit.bats +++ b/tests/test_pre_commit.bats @@ -74,14 +74,14 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" run "$BATS_TEST_DIRNAME"/../transcrypt --cipher=aes-256-cbc --password='abc 123' --yes [ "$status" -eq 0 ] - [ "${lines[0]}" = "WARNING:" ] - [ "${lines[1]}" = "Cannot install Git pre-commit hook script because file already exists: .git/hooks/pre-commit" ] - [ "${lines[2]}" = "Please manually install the pre-commit script saved as: .git/hooks/pre-commit-crypt" ] + [[ "${output}" = *"WARNING:"* ]] + [[ "${output}" = *"Cannot install Git pre-commit hook script because file already exists: .git/hooks/pre-commit"* ]] + [[ "${output}" = *"Please manually install the pre-commit script saved as: .git/hooks/pre-commit-crypt"* ]] # Confirm pre-commit-crypt file is installed, but not copied to pre-commit run cat .git/hooks/pre-commit-crypt [ "$status" -eq 0 ] - [ "${lines[1]}" = '# Transcrypt pre-commit hook: fail if secret file in staging lacks the magic prefix "Salted" in B64' ] + [[ "${output}" = *'# Transcrypt pre-commit hook: fail if secret file in staging lacks the magic prefix "Salted" in B64'* ]] [ ! -s .git/hooks/pre-commit ] # Zero file size] } @@ -97,7 +97,7 @@ load "$BATS_TEST_DIRNAME/_test_helper.bash" run "$BATS_TEST_DIRNAME"/../transcrypt --uninstall --yes [ "$status" -eq 0 ] - [ "${lines[0]}" = 'WARNING: Cannot safely disable Git pre-commit hook .git/hooks/pre-commit please check it yourself' ] + [[ "${output}" = *'WARNING: Cannot safely disable Git pre-commit hook .git/hooks/pre-commit please check it yourself'* ]] [ -f .git/hooks/pre-commit ] [ ! -f .git/hooks/pre-commit-crypt ] } diff --git a/transcrypt b/transcrypt index 0319f48..175e4b8 100755 --- a/transcrypt +++ b/transcrypt @@ -139,7 +139,16 @@ git_clean() { password=$(git config --get --local transcrypt.password) openssl_path=$(git config --get --local transcrypt.openssl-path) salt=$("${openssl_path}" dgst -hmac "${filename}:${password}" -sha256 "$tempfile" | tr -d '\r\n' | tail -c16) - ENC_PASS=$password "$openssl_path" enc "-${cipher}" -md MD5 -pass env:ENC_PASS -e -a -S "$salt" -in "$tempfile" + # Encrypt the file to base64, ensuring it always includes the prefix 'Salted__' with the salt. #133 + ( + # Always prepend encrypted ciphertext with "Salted__" prefix and binary salt value + echo -n "Salted__" && echo -n "$salt" | xxd -r -p && + # Encrypt file to binary ciphertext + ENC_PASS=$password "$openssl_path" enc -e "-${cipher}" -md MD5 -pass env:ENC_PASS -S "$salt" -in "$tempfile" | + # Strip "Salted__" prefix and salt value if also added by OpenSSL (version < 3) + LC_ALL=C sed -e "s/^\(Salted__.\{8\}\)\(.*\)/\2/" + ) | + base64 fi } @@ -149,7 +158,7 @@ git_smudge() { cipher=$(git config --get --local transcrypt.cipher) password=$(git config --get --local transcrypt.password) openssl_path=$(git config --get --local transcrypt.openssl-path) - tee "$tempfile" | ENC_PASS=$password "$openssl_path" enc "-${cipher}" -md MD5 -pass env:ENC_PASS -d -a 2>/dev/null || cat "$tempfile" + tee "$tempfile" | ENC_PASS=$password "$openssl_path" enc -d "-${cipher}" -md MD5 -pass env:ENC_PASS -a 2>/dev/null || cat "$tempfile" } git_textconv() { @@ -161,7 +170,7 @@ git_textconv() { cipher=$(git config --get --local transcrypt.cipher) password=$(git config --get --local transcrypt.password) openssl_path=$(git config --get --local transcrypt.openssl-path) - ENC_PASS=$password "$openssl_path" enc "-${cipher}" -md MD5 -pass env:ENC_PASS -d -a -in "$filename" 2>/dev/null || cat "$filename" + ENC_PASS=$password "$openssl_path" enc -d "-${cipher}" -md MD5 -pass env:ENC_PASS -a -in "$filename" 2>/dev/null || cat "$filename" } # shellcheck disable=SC2005,SC2002,SC2181 From 029ba932c190ce117b391bb3ac74f2c6382f7d7e Mon Sep 17 00:00:00 2001 From: James Murty Date: Tue, 14 Jun 2022 22:16:42 +1000 Subject: [PATCH 20/26] Prepare for 2.2.0 release - Bump version number to 2.2.0 - Add bash and zsh completions for `--set-openssl-path` argument - Update change log to match release version and tags --- CHANGELOG.md | 5 +++-- contrib/bash/transcrypt | 2 +- contrib/packaging/pacman/PKGBUILD | 2 +- contrib/zsh/_transcrypt | 1 + transcrypt | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 839d765..6d6d416 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog][1], and this project adheres to [1]: https://keepachangelog.com/en/1.0.0/ [2]: https://semver.org/spec/v2.0.0.html -## [Unreleased] +## [2.2.0] - 2022-06-14 ### Added @@ -237,7 +237,8 @@ Since the v0.9.7 release, these are the notable improvements made to transcrypt: ## [0.9.4] - 2014-03-03 -[unreleased]: https://github.com/elasticdog/transcrypt/compare/v2.1.0...HEAD +[unreleased]: https://github.com/elasticdog/transcrypt/compare/v2.2.0...HEAD +[2.2.0]: https://github.com/elasticdog/transcrypt/compare/v2.1.0...v2.2.0 [2.1.0]: https://github.com/elasticdog/transcrypt/compare/v2.0.0...v2.1.0 [2.0.0]: https://github.com/elasticdog/transcrypt/compare/v1.1.0...v2.0.0 [1.1.0]: https://github.com/elasticdog/transcrypt/compare/v1.0.3...v1.1.0 diff --git a/contrib/bash/transcrypt b/contrib/bash/transcrypt index 07dafa0..a1096e4 100644 --- a/contrib/bash/transcrypt +++ b/contrib/bash/transcrypt @@ -22,7 +22,7 @@ _transcrypt() { cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" opts="-c -p -y -d -r -f -F -u -l -s -e -i -v -h \ - --cipher --password --yes --display --rekey --flush-credentials --force --uninstall --upgrade --list --show-raw --export-gpg --import-gpg --version --help" + --cipher --password --set-openssl-path --yes --display --rekey --flush-credentials --force --uninstall --upgrade --list --show-raw --export-gpg --import-gpg --version --help" case "${prev}" in -c | --cipher) diff --git a/contrib/packaging/pacman/PKGBUILD b/contrib/packaging/pacman/PKGBUILD index beb4759..9556e1f 100644 --- a/contrib/packaging/pacman/PKGBUILD +++ b/contrib/packaging/pacman/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Aaron Bull Schaefer pkgname=transcrypt -pkgver=2.1.0 +pkgver=2.2.0 pkgrel=1 pkgdesc='A script to configure transparent encryption of files within a Git repository' arch=('any') diff --git a/contrib/zsh/_transcrypt b/contrib/zsh/_transcrypt index 523df12..7bed4a7 100644 --- a/contrib/zsh/_transcrypt +++ b/contrib/zsh/_transcrypt @@ -18,6 +18,7 @@ _transcrypt() { '(-f --flush-credentials -c --cipher -p --password -r --rekey -u --uninstall)'{-f,--flush-credentials}'[flush cached credentials]' \ '(-F --force -d --display -u --uninstall)'{-F,--force}'[ignore repository clean state]' \ '(-u --uninstall -c --cipher -d --display -f --flush-credentials -p --password -r --rekey)'{-u,--uninstall}'[uninstall transcrypt]' \ + '(--set-openssl-path -c --cipher -d --display -f --flush-credentials -p --password -r --rekey)'{--set-openssl-path}'[use OpenSSL at this path]' \ '(--upgrade -c --cipher -d --display -f --flush-credentials -p --password -r --rekey)--upgrade[upgrade transcrypt]' \ '(-i --import-gpg -c --cipher -p --password -d --display -f --flush-credentials -u --uninstall)'{-i,--import-gpg=}'[import config from gpg file]:file:->file' \ && return 0 diff --git a/transcrypt b/transcrypt index 175e4b8..9340997 100755 --- a/transcrypt +++ b/transcrypt @@ -16,7 +16,7 @@ set -euo pipefail ##### CONSTANTS # the release version of this script -readonly VERSION='2.2.0-pre' +readonly VERSION='2.2.0' # the default cipher to utilize readonly DEFAULT_CIPHER='aes-256-cbc' From a258dc442b3e7d0a766fb289492a794d909b6378 Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 27 Jun 2022 22:21:13 +1000 Subject: [PATCH 21/26] Document `xxd` requirement, and make optional with OpenSSL < 3 (#138) --- CHANGELOG.md | 2 +- INSTALL.md | 7 +++++++ README.md | 2 ++ transcrypt | 30 ++++++++++++++++++++---------- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d6d416..de040d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ The format is based on [Keep a Changelog][1], and this project adheres to ### Fixed - Remain compatible with OpenSSL versions 3 and above which changes the way - explicit salt values are expressed in ciphertext (#133) + explicit salt values are expressed in ciphertext, requires `xxd` command (#133) - Ensure Git index is up-to-date before checking for dirty repo, to avoid failures seen in CI systems where the repo seems dirty when it isn't. (#37) - Respect Git `core.hooksPath` setting when installing the pre-commit hook. (#104) diff --git a/INSTALL.md b/INSTALL.md index cc2b067..baf5c5b 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -5,6 +5,13 @@ The requirements to run transcrypt are minimal: - Bash - Git - OpenSSL +- `column` command (on Ubuntu/Debian install `bsdmainutils`) +- `xxd` command if using OpenSSL version 3 + (on Ubuntu/Debian is included with `vim`) + +...and optionally: + +- GnuPG - for secure configuration import/export You also need access to the _transcrypt_ script itself... diff --git a/README.md b/README.md index 7ce3473..a8e0b9a 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ The requirements to run transcrypt are minimal: - Git - OpenSSL - `column` command (on Ubuntu/Debian install `bsdmainutils`) +- `xxd` command if using OpenSSL version 3 + (on Ubuntu/Debian is included with `vim`) ...and optionally: diff --git a/transcrypt b/transcrypt index 9340997..c83679f 100755 --- a/transcrypt +++ b/transcrypt @@ -139,16 +139,20 @@ git_clean() { password=$(git config --get --local transcrypt.password) openssl_path=$(git config --get --local transcrypt.openssl-path) salt=$("${openssl_path}" dgst -hmac "${filename}:${password}" -sha256 "$tempfile" | tr -d '\r\n' | tail -c16) - # Encrypt the file to base64, ensuring it always includes the prefix 'Salted__' with the salt. #133 - ( - # Always prepend encrypted ciphertext with "Salted__" prefix and binary salt value - echo -n "Salted__" && echo -n "$salt" | xxd -r -p && - # Encrypt file to binary ciphertext - ENC_PASS=$password "$openssl_path" enc -e "-${cipher}" -md MD5 -pass env:ENC_PASS -S "$salt" -in "$tempfile" | - # Strip "Salted__" prefix and salt value if also added by OpenSSL (version < 3) - LC_ALL=C sed -e "s/^\(Salted__.\{8\}\)\(.*\)/\2/" - ) | - base64 + + openssl_major_version=$($openssl_path version | cut -d' ' -f2 | cut -d'.' -f1) + if [ "$openssl_major_version" -ge "3" ]; then + # Encrypt the file to base64, ensuring it includes the prefix 'Salted__' with the salt. #133 + ( + echo -n "Salted__" && echo -n "$salt" | xxd -r -p && + # Encrypt file to binary ciphertext + ENC_PASS=$password "$openssl_path" enc -e "-${cipher}" -md MD5 -pass env:ENC_PASS -S "$salt" -in "$tempfile" + ) | + base64 + else + # Encrypt file to base64 ciphertext + ENC_PASS=$password "$openssl_path" enc -e -a "-${cipher}" -md MD5 -pass env:ENC_PASS -S "$salt" -in "$tempfile" + fi fi } @@ -305,6 +309,12 @@ run_safety_checks() { for cmd in {column,grep,mktemp,"${openssl_path}",sed,tee}; do command -v "$cmd" >/dev/null || die 'required command "%s" was not found' "$cmd" done + # check for extra `xxd` dependency when running against OpenSSL version 3+ + openssl_major_version=$($openssl_path version | cut -d' ' -f2 | cut -d'.' -f1) + if [ "$openssl_major_version" -ge "3" ]; then + cmd="xxd" + command -v "$cmd" >/dev/null || die 'required command "%s" was not found' "$cmd" + fi # ensure the repository is clean (if it has a HEAD revision) so we can force # checkout files without the destruction of uncommitted changes From 3041bc7ed70d04a38bd7f6535fe62523a68178c4 Mon Sep 17 00:00:00 2001 From: Adrian Dimitrov Date: Mon, 27 Jun 2022 15:40:49 +0300 Subject: [PATCH 22/26] Use core attributesFile from worktree (#137) When using attribute file overrides (for example if override in .dotfiles or by other includes), get the core.attributesFile from the current worktree if available. --- transcrypt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transcrypt b/transcrypt index c83679f..ed60d37 100755 --- a/transcrypt +++ b/transcrypt @@ -82,7 +82,7 @@ gather_repo_metadata() { # the current git repository's gitattributes file local CORE_ATTRIBUTES - CORE_ATTRIBUTES=$(git config --get --local --path core.attributesFile 2>/dev/null || printf '') + CORE_ATTRIBUTES=$(git config --get --local --path core.attributesFile 2>/dev/null || git config --get --path core.attributesFile 2>/dev/null || printf '') if [[ $CORE_ATTRIBUTES ]]; then readonly GIT_ATTRIBUTES=$CORE_ATTRIBUTES elif [[ $IS_BARE == 'true' ]] || [[ $IS_VCSH == 'true' ]]; then From 00a2cbb660f9e47e553159d3cb9461f226100c8a Mon Sep 17 00:00:00 2001 From: James Murty Date: Fri, 1 Jul 2022 21:28:03 +1000 Subject: [PATCH 23/26] Use OpenSSL for B64 encoding not `base64` which differs between Linux and Mac #140 --- transcrypt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transcrypt b/transcrypt index ed60d37..b392e2d 100755 --- a/transcrypt +++ b/transcrypt @@ -148,7 +148,7 @@ git_clean() { # Encrypt file to binary ciphertext ENC_PASS=$password "$openssl_path" enc -e "-${cipher}" -md MD5 -pass env:ENC_PASS -S "$salt" -in "$tempfile" ) | - base64 + openssl base64 else # Encrypt file to base64 ciphertext ENC_PASS=$password "$openssl_path" enc -e -a "-${cipher}" -md MD5 -pass env:ENC_PASS -S "$salt" -in "$tempfile" From 243245a8b095b1226c47f94ffd1ad938ea229e5a Mon Sep 17 00:00:00 2001 From: ljm42 Date: Sat, 9 Jul 2022 04:36:08 -0700 Subject: [PATCH 24/26] Ensure tests use "main" as default branch name #143 --- tests/_test_helper.bash | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/_test_helper.bash b/tests/_test_helper.bash index e0f3d37..fd41353 100644 --- a/tests/_test_helper.bash +++ b/tests/_test_helper.bash @@ -8,6 +8,7 @@ function init_git_repo { else # Initialise test git repo at the same path as the test files git init "$BATS_TEST_DIRNAME" + git checkout -b main # Tests will fail if name and email aren't set git config user.name "John Doe" git config user.email johndoe@example.com From ac99a937ccd7e04663f4bb0cdd9536d059ffd2db Mon Sep 17 00:00:00 2001 From: James Murty Date: Sat, 9 Jul 2022 21:38:35 +1000 Subject: [PATCH 25/26] Fix date of 2.2.0 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de040d1..a240578 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog][1], and this project adheres to [1]: https://keepachangelog.com/en/1.0.0/ [2]: https://semver.org/spec/v2.0.0.html -## [2.2.0] - 2022-06-14 +## [2.2.0] - 2022-07-09 ### Added From 93f9d4c67f7e06266669f84b0e161f5ed9dc4735 Mon Sep 17 00:00:00 2001 From: ljm42 Date: Sun, 10 Jul 2022 04:47:05 -0700 Subject: [PATCH 26/26] Centralise load and save of password into functions #141 Read and write password with new `load_password()` and `save_password()` functions. This makes it easier for others to override password handling with minimal changes. --- transcrypt | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/transcrypt b/transcrypt index b392e2d..072b1c2 100755 --- a/transcrypt +++ b/transcrypt @@ -23,6 +23,21 @@ readonly DEFAULT_CIPHER='aes-256-cbc' ##### FUNCTIONS +# load encryption password +# by default is stored in git config, modify this function to move elsewhere +load_password() { + local password + password=$(git config --get --local transcrypt.password) + echo "$password" +} + +# save encryption password +# by default is stored in git config, modify this function to move elsewhere +save_password() { + local password=$1 + git config transcrypt.password "$password" +} + # print a canonicalized absolute pathname realpath() { local path=$1 @@ -136,7 +151,7 @@ git_clean() { cat "$tempfile" else cipher=$(git config --get --local transcrypt.cipher) - password=$(git config --get --local transcrypt.password) + password=$(load_password) openssl_path=$(git config --get --local transcrypt.openssl-path) salt=$("${openssl_path}" dgst -hmac "${filename}:${password}" -sha256 "$tempfile" | tr -d '\r\n' | tail -c16) @@ -160,7 +175,7 @@ git_smudge() { tempfile=$(mktemp 2>/dev/null || mktemp -t tmp) trap 'rm -f "$tempfile"' EXIT cipher=$(git config --get --local transcrypt.cipher) - password=$(git config --get --local transcrypt.password) + password=$(load_password) openssl_path=$(git config --get --local transcrypt.openssl-path) tee "$tempfile" | ENC_PASS=$password "$openssl_path" enc -d "-${cipher}" -md MD5 -pass env:ENC_PASS -a 2>/dev/null || cat "$tempfile" } @@ -172,7 +187,7 @@ git_textconv() { return fi cipher=$(git config --get --local transcrypt.cipher) - password=$(git config --get --local transcrypt.password) + password=$(load_password) openssl_path=$(git config --get --local transcrypt.openssl-path) ENC_PASS=$password "$openssl_path" enc -d "-${cipher}" -md MD5 -pass env:ENC_PASS -a -in "$filename" 2>/dev/null || cat "$filename" } @@ -511,7 +526,7 @@ save_configuration() { # write the encryption info git config transcrypt.version "$VERSION" git config transcrypt.cipher "$cipher" - git config transcrypt.password "$password" + save_password "$password" git config transcrypt.openssl-path "$openssl_path" # write the filter settings. Sorry for the horrific quote escaping below... @@ -538,7 +553,7 @@ display_configuration() { local current_cipher current_cipher=$(git config --get --local transcrypt.cipher) local current_password - current_password=$(git config --get --local transcrypt.password) + current_password=$(load_password) local escaped_password=${current_password//\'/\'\\\'\'} printf 'The current repository was configured using transcrypt version %s\n' "$CONFIGURED" @@ -743,7 +758,7 @@ upgrade_transcrypt() { # Keep current cipher and password cipher=$(git config --get --local transcrypt.cipher) - password=$(git config --get --local transcrypt.password) + password=$(load_password) # Keep current openssl-path, or set to default if no existing value openssl_path=$(git config --get --local transcrypt.openssl-path 2>/dev/null || printf '%s' "$openssl_path") @@ -822,7 +837,7 @@ export_gpg() { local current_cipher current_cipher=$(git config --get --local transcrypt.cipher) local current_password - current_password=$(git config --get --local transcrypt.password) + current_password=$(load_password) mkdir -p "${CRYPT_DIR}" local gpg_encrypt_cmd="gpg --batch --recipient $gpg_recipient --trust-model always --yes --armor --quiet --encrypt -"