Skip to content

Commit a77ca76

Browse files
committed
Internalize transcrypt crypt functions
At the moment, the crypt functions are initialized and permanently set to whatever the version of transcrypt the user is using contains. This means updates to these scripts are never propagated out to existing users. This commit moves the crypt functions into transcrypt itself, which should allow for more transparent updates to the scripts to be propagated out to users.
1 parent 12f2c9d commit a77ca76

File tree

1 file changed

+64
-71
lines changed

1 file changed

+64
-71
lines changed

transcrypt

Lines changed: 64 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -268,92 +268,68 @@ stage_rekeyed_files() {
268268
fi
269269
}
270270

271-
# save helper scripts under the repository's git directory
272-
save_helper_scripts() {
273-
mkdir -p "${GIT_DIR}/crypt"
274-
275-
# The `decryption -> encryption` process on an unchanged file must be
276-
# deterministic for everything to work transparently. To do that, the same
277-
# salt must be used each time we encrypt the same file. An HMAC has been
278-
# proven to be a PRF, so we generate an HMAC-SHA256 for each decrypted file
279-
# (keyed with a combination of the filename and transcrypt password), and
280-
# then use the last 16 bytes of that HMAC for the file's unique salt.
281-
282-
cat <<-'EOF' >"${GIT_DIR}/crypt/clean"
283-
#!/usr/bin/env bash
284-
filename=$1
285-
# ignore empty files
286-
if [[ -s $filename ]]; then
287-
# cache STDIN to test if it's already encrypted
288-
tempfile=$(mktemp 2>/dev/null || mktemp -t tmp)
289-
trap 'rm -f "$tempfile"' EXIT
290-
tee "$tempfile" &>/dev/null
291-
# the first bytes of an encrypted file are always "Salted" in Base64
292-
read -n 8 firstbytes <"$tempfile"
293-
if [[ $firstbytes == "U2FsdGVk" ]]; then
294-
cat "$tempfile"
295-
else
296-
cipher=$(git config --get --local transcrypt.cipher)
297-
password=$(git config --get --local transcrypt.password)
298-
salt=$(openssl dgst -hmac "${filename}:${password}" -sha256 "$filename" | tr -d '\r\n' | tail -c 16)
299-
ENC_PASS=$password openssl enc -$cipher -md MD5 -pass env:ENC_PASS -e -a -S "$salt" -in "$tempfile"
300-
fi
301-
fi
302-
EOF
303-
304-
cat <<-'EOF' >"${GIT_DIR}/crypt/smudge"
305-
#!/usr/bin/env bash
271+
# The `decryption -> encryption` process on an unchanged file must be
272+
# deterministic for everything to work transparently. To do that, the same
273+
# salt must be used each time we encrypt the same file. An HMAC has been
274+
# proven to be a PRF, so we generate an HMAC-SHA256 for each decrypted file
275+
# (keyed with a combination of the filename and transcrypt password), and
276+
# then use the last 16 bytes of that HMAC for the file's unique salt.
277+
crypt_clean() {
278+
filename=$1
279+
# ignore empty files
280+
if [[ -s $filename ]]; then
281+
# cache STDIN to test if it's already encrypted
306282
tempfile=$(mktemp 2>/dev/null || mktemp -t tmp)
307283
trap 'rm -f "$tempfile"' EXIT
308-
cipher=$(git config --get --local transcrypt.cipher)
309-
password=$(git config --get --local transcrypt.password)
310-
tee "$tempfile" | ENC_PASS=$password openssl enc -$cipher -md MD5 -pass env:ENC_PASS -d -a 2>/dev/null || cat "$tempfile"
311-
EOF
312-
313-
cat <<-'EOF' >"${GIT_DIR}/crypt/textconv"
314-
#!/usr/bin/env bash
315-
filename=$1
316-
# ignore empty files
317-
if [[ -s $filename ]]; then
318-
cipher=$(git config --get --local transcrypt.cipher)
319-
password=$(git config --get --local transcrypt.password)
320-
ENC_PASS=$password openssl enc -$cipher -md MD5 -pass env:ENC_PASS -d -a -in "$filename" 2>/dev/null || cat "$filename"
284+
tee "$tempfile" &>/dev/null
285+
# the first bytes of an encrypted file are always "Salted" in Base64
286+
read -r -n 8 firstbytes <"$tempfile"
287+
if [[ $firstbytes == "U2FsdGVk" ]]; then
288+
cat "$tempfile"
289+
else
290+
cipher=$(git config --get --local transcrypt.cipher)
291+
password=$(git config --get --local transcrypt.password)
292+
salt=$(openssl dgst -hmac "${filename}:${password}" -sha256 "$filename" | tr -d '\r\n' | tail -c 16)
293+
ENC_PASS=$password openssl enc "-${cipher}" -md MD5 -pass env:ENC_PASS -e -a -S "$salt" -in "$tempfile"
321294
fi
322-
EOF
295+
fi
296+
}
323297

324-
# make scripts executable
325-
for script in {clean,smudge,textconv}; do
326-
chmod 0755 "${GIT_DIR}/crypt/${script}"
327-
done
298+
crypt_smudge() {
299+
tempfile=$(mktemp 2>/dev/null || mktemp -t tmp)
300+
trap 'rm -f "$tempfile"' EXIT
301+
cipher=$(git config --get --local transcrypt.cipher)
302+
password=$(git config --get --local transcrypt.password)
303+
tee "$tempfile" | ENC_PASS=$password openssl enc "-${cipher}" -md MD5 -pass env:ENC_PASS -d -a 2>/dev/null || cat "$tempfile"
304+
}
305+
306+
crypt_textconv() {
307+
filename=$1
308+
# ignore empty files
309+
if [[ -s $filename ]]; then
310+
cipher=$(git config --get --local transcrypt.cipher)
311+
password=$(git config --get --local transcrypt.password)
312+
ENC_PASS=$password openssl enc "-${cipher}" -md MD5 -pass env:ENC_PASS -d -a -in "$filename" 2>/dev/null || cat "$filename"
313+
fi
328314
}
329315

330316
# write the configuration to the repository's git config
331317
save_configuration() {
332-
save_helper_scripts
318+
# This directory is used by transcrypt as a working directory.
319+
mkdir -p "${GIT_DIR}/crypt"
333320

334321
# write the encryption info
335322
git config transcrypt.version "$VERSION"
336323
git config transcrypt.cipher "$cipher"
337324
git config transcrypt.password "$password"
338325

339326
# write the filter settings
340-
if [[ -d $(git rev-parse --git-common-dir) ]]; then
341-
# this allows us to support multiple working trees via git-worktree
342-
# ...but the --git-common-dir flag was only added in November 2014
343-
# shellcheck disable=SC2016
344-
git config filter.crypt.clean '"$(git rev-parse --git-common-dir)"/crypt/clean %f'
345-
# shellcheck disable=SC2016
346-
git config filter.crypt.smudge '"$(git rev-parse --git-common-dir)"/crypt/smudge'
347-
# shellcheck disable=SC2016
348-
git config diff.crypt.textconv '"$(git rev-parse --git-common-dir)"/crypt/textconv'
349-
else
350-
# shellcheck disable=SC2016
351-
git config filter.crypt.clean '"$(git rev-parse --git-dir)"/crypt/clean %f'
352-
# shellcheck disable=SC2016
353-
git config filter.crypt.smudge '"$(git rev-parse --git-dir)"/crypt/smudge'
354-
# shellcheck disable=SC2016
355-
git config diff.crypt.textconv '"$(git rev-parse --git-dir)"/crypt/textconv'
356-
fi
327+
# shellcheck disable=SC2016
328+
git config filter.crypt.clean "$0 --crypt-clean %f"
329+
# shellcheck disable=SC2016
330+
git config filter.crypt.smudge "$0 --crypt-smudge"
331+
# shellcheck disable=SC2016
332+
git config diff.crypt.textconv "$0 --crypt-textconv"
357333
git config filter.crypt.required 'true'
358334
git config diff.crypt.cachetextconv 'true'
359335
git config diff.crypt.binary 'true'
@@ -466,6 +442,8 @@ uninstall_transcrypt() {
466442
clean_gitconfig
467443

468444
# remove helper scripts
445+
# This is obsolete, but we should keep it to clean up these
446+
# scripts from old versions of transcrypt.
469447
for script in {clean,smudge,textconv}; do
470448
[[ ! -f "${GIT_DIR}/crypt/${script}" ]] || rm "${GIT_DIR}/crypt/${script}"
471449
done
@@ -800,6 +778,21 @@ while [[ "${1:-}" != '' ]]; do
800778
--import-gpg=*)
801779
gpg_import_file=${1#*=}
802780
;;
781+
--crypt-clean)
782+
shift
783+
crypt_clean "$1"
784+
exit 0
785+
;;
786+
--crypt-smudge)
787+
shift
788+
crypt_smudge
789+
exit 0
790+
;;
791+
--crypt-textconv)
792+
shift
793+
crypt_textconv "$1"
794+
exit 0
795+
;;
803796
-v | --version)
804797
printf 'transcrypt %s\n' "$VERSION"
805798
exit 0

0 commit comments

Comments
 (0)