#!/usr/bin/env zsh # # Tomb, the Crypto Undertaker # # A commandline tool to easily operate encryption of secret data # # {{{ License # Copyright (C) 2007-2018 Dyne.org Foundation # # Tomb is designed, written and maintained by Denis Roio # # With contributions by Anathema, Boyska, Hellekin O. Wolf and GDrooid # # Gettext internationalization and Spanish translation is contributed by # GDrooid, French translation by Hellekin, Russian translation by fsLeg, # German translation by x3nu. # # Testing, reviews and documentation are contributed by Dreamer, Shining # the Translucent, Mancausoft, Asbesto Molesto, Nignux, Vlax, The Grugq, # Reiven, GDrooid, Alphazo, Brian May, TheJH, fsLeg, JoelMon and the # Linux Action Show! # # Tomb's artwork is contributed by Jordi aka Mon Mort and Logan VanCuren. # # Cryptsetup was developed by Christophe Saout and Clemens Fruhwirth. # This source code is free software; you can redistribute it and/or # modify it under the terms of the GNU Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This source code is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Please refer # to the GNU Public License for more details. # # You should have received a copy of the GNU Public License along with # this source code; if not, write to: Free Software Foundation, Inc., # 675 Mass Ave, Cambridge, MA 02139, USA. # }}} - License # {{{ Global variables typeset VERSION="2.5" typeset DATE="Jan/2018" typeset TOMBEXEC=$0 typeset TMPPREFIX=${TMPPREFIX:-/tmp} # TODO: configure which tmp dir to use from a cli flag # Tomb is using some global variables set by the shell: # TMPPREFIX, UID, GID, PATH, TTY, USERNAME # You can grep 'global variable' to see where they are used. # Keep a reference of the original command line arguments typeset -a OLDARGS for arg in "${(@)argv}"; do OLDARGS+=("$arg"); done # Special command requirements typeset -a DD WIPE PINENTRY DD=(dd) WIPE=(rm -f) PINENTRY=(pinentry) # load zsh regex module zmodload zsh/mapfile zmodload -F zsh/stat b:zstat # make sure variables aren't exported unsetopt allexport # Flag optional commands if available (see _ensure_dependencies()) typeset -i KDF=1 typeset -i STEGHIDE=1 typeset -i RESIZER=1 typeset -i SWISH=1 typeset -i QRENCODE=1 # Default mount options typeset MOUNTOPTS="rw,noatime,nodev" # Makes glob matching case insensitive unsetopt CASE_MATCH typeset -AH OPTS # Command line options (see main()) # Command context (see _whoami()) typeset -H _USER # Running username typeset -Hi _UID # Running user identifier typeset -Hi _GID # Running user group identifier typeset -H _TTY # Connected input terminal # Tomb context (see _plot()) typeset -H TOMBPATH # Full path to the tomb typeset -H TOMBDIR # Directory where the tomb is typeset -H TOMBFILE # File name of the tomb typeset -H TOMBNAME # Name of the tomb # Tomb secrets typeset -H TOMBKEY # Encrypted key contents (see forge_key(), recover_key()) typeset -H TOMBKEYFILE # Key file (ditto) typeset -H TOMBSECRET # Raw deciphered key (see forge_key(), gpg_decrypt()) typeset -H TOMBPASSWORD # Raw tomb passphrase (see gen_key(), ask_key_password()) typeset -H TOMBTMP # Filename of secure temp just created (see _tmp_create()) typeset -aH TOMBTMPFILES # Keep track of temporary files typeset -aH TOMBLOOPDEVS # Keep track of used loop devices # Make sure sbin is in PATH (man zshparam) path+=( /sbin /usr/sbin ) # For gettext export TEXTDOMAIN=tomb # }}} # {{{ Safety functions # Wrap sudo with a more visible message _sudo() { local sudo_eng="[sudo] Enter password for user ::1 user:: to gain superuser privileges" local msg="$(gettext -s "$sudo_eng")" msg=${(S)msg//::1*::/$USER} sudo -p " $msg " ${@} } # Cleanup anything sensitive before exiting. _endgame() { # Prepare some random material to overwrite vars local rr="$RANDOM" while [[ ${#rr} -lt 500 ]]; do rr+="$RANDOM" done # Ensure no information is left in unallocated memory TOMBPATH="$rr"; unset TOMBPATH TOMBDIR="$rr"; unset TOMBDIR TOMBFILE="$rr"; unset TOMBFILE TOMBNAME="$rr"; unset TOMBNAME TOMBKEY="$rr"; unset TOMBKEY TOMBKEYFILE="$rr"; unset TOMBKEYFILE TOMBSECRET="$rr"; unset TOMBSECRET TOMBPASSWORD="$rr"; unset TOMBPASSWORD # Clear temporary files for f in $TOMBTMPFILES; do ${=WIPE} "$f" done unset TOMBTMPFILES # Detach loop devices for l in $TOMBLOOPDEVS; do _sudo losetup -d "$l" done unset TOMBLOOPDEVS } # Trap functions for the _endgame event TRAPINT() { _endgame INT } TRAPEXIT() { _endgame EXIT } TRAPHUP() { _endgame HUP } TRAPQUIT() { _endgame QUIT } TRAPABRT() { _endgame ABORT } TRAPKILL() { _endgame KILL } TRAPPIPE() { _endgame PIPE } TRAPTERM() { _endgame TERM } TRAPSTOP() { _endgame STOP } _cat() { local -a _arr; # read file using mapfile, newline fix _arr=("${(f@)${mapfile[${1}]%$'\n'}}"); print "$_arr" } _is_found() { # returns 0 if binary is found in path [[ "$1" = "" ]] && return 1 command -v "$1" 1>/dev/null 2>/dev/null return $? } # Identify the running user # Set global variables _UID, _GID, _TTY, and _USER, either from the # command line, -U, -G, -T, respectively, or from the environment. # Also update USERNAME and HOME to maintain consistency. _whoami() { # Set username from UID or environment _USER=$SUDO_USER [[ "$_USER" = "" ]] && { _USER=$USERNAME } [[ "$_USER" = "" ]] && { _USER=$(id -u) } [[ "$_USER" = "" ]] && { _failure "Failing to identify the user who is calling us" } # Get GID from option -G or the environment option_is_set -G \ && _GID=$(option_value -G) || _GID=$(id -g $_USER) # Get UID from option -U or the environment option_is_set -U \ && _UID=$(option_value -U) || _UID=$(id -u $_USER) _verbose "Identified caller: ::1 username:: (::2 UID:::::3 GID::)" $_USER $_UID $_GID # Update USERNAME accordingly if possible # [[ $EUID == 0 && $_USER != $USERNAME ]] && { # _verbose "Updating USERNAME from '::1 USERNAME::' to '::2 _USER::')" $USERNAME $_USER # USERNAME=$_USER # } # Force HOME to _USER's HOME if necessary local home=$(awk -F: "/^$_USER:/ { print \$6 }" /etc/passwd 2>/dev/null) [[ $home == $HOME ]] || { _verbose "Updating HOME to match user's: ::1 home:: (was ::2 HOME::)" \ $home $HOME HOME=$home } # Get connecting TTY from option -T or the environment option_is_set -T && _TTY=$(option_value -T) [[ -z $_TTY ]] && _TTY=$TTY } # Define sepulture's plot (setup tomb-related arguments) # Synopsis: _plot /path/to/the.tomb # Set TOMB{PATH,DIR,FILE,NAME} _plot() { # We set global variables typeset -g TOMBPATH TOMBDIR TOMBFILE TOMBNAME TOMBPATH="$1" TOMBDIR=$(dirname $TOMBPATH) TOMBFILE=$(basename $TOMBPATH) # The tomb name is TOMBFILE without an extension and underscores instead of spaces (for mount and cryptsetup) # It can start with dots: ..foo bar baz.tomb -> ..foo_bar_baz TOMBNAME=${${TOMBFILE// /_}%.*} # use the entire filename if the previous transformation returns # an empty string. This handles the corner case of tomb being # hidden files (starting with a dot) and have no extension (only # one dot in string) TOMBNAME=${TOMBNAME:-${TOMBFILE}} [[ "$TOMBNAME" = "" ]] && _failure "Tomb won't work without a TOMBNAME." } # Provide a random filename in shared memory _tmp_create() { [[ -d "$TMPPREFIX" ]] || { # we create the tempdir with the sticky bit on _sudo mkdir -m 1777 "$TMPPREFIX" [[ $? == 0 ]] || _failure "Fatal error creating the temporary directory: ::1 temp dir::" "$TMPPREFIX" } # We're going to add one more $RANDOM for each time someone complains # about this being too weak of a random. tfile="${TMPPREFIX}/$RANDOM$RANDOM$RANDOM$RANDOM" # Temporary file umask 066 [[ $? == 0 ]] || { _failure "Fatal error setting the permission umask for temporary files" } [[ -r "$tfile" ]] && { _failure "Someone is messing up with us trying to hijack temporary files." } touch "$tfile" [[ $? == 0 ]] || { _failure "Fatal error creating a temporary file: ::1 temp file::" "$tfile" } _verbose "Created tempfile: ::1 temp file::" "$tfile" TOMBTMP="$tfile" TOMBTMPFILES+=("$tfile") return 0 } # Check if a *block* device is encrypted # Synopsis: _is_encrypted_block /path/to/block/device # Return 0 if it is an encrypted block device _is_encrypted_block() { local b=$1 # Path to a block device local s="" # lsblk option -s (if available) # Issue #163 # lsblk --inverse appeared in util-linux 2.22 # but --version is not consistent... lsblk --help | grep -Fq -- --inverse [[ $? -eq 0 ]] && s="--inverse" sudo lsblk $s -o type -n $b 2>/dev/null \ | egrep -q '^crypt$' return $? } # Check if swap is activated # Return 0 if NO swap is used, 1 if swap is used. # Return 1 if any of the swaps is not encrypted. # Return 2 if swap(s) is(are) used, but ALL encrypted. # Use _check_swap in functions. It will call this function and # exit if unsafe swap is present. _ensure_safe_swap() { local -i r=1 # Return code: 0 no swap, 1 unsafe swap, 2 encrypted local -a swaps # List of swap partitions local bone is_crypt swaps="$(awk '/^\// { print $1 }' /proc/swaps 2>/dev/null)" [[ -z "$swaps" ]] && return 0 # No swap partition is active _message "An active swap partition is detected..." for s in $=swaps; do if _is_encrypted_block $s; then r=2; else # We're dealing with unencrypted stuff. # Maybe it lives on an encrypted filesystem anyway. # @todo: verify it's actually on an encrypted FS (see #163 and !189) # Well, no: bail out. r=1; break; fi done if [[ $r -eq 2 ]]; then _success "The undertaker found that all swap partitions are encrypted. Good." else _warning "This poses a security risk." _warning "You can deactivate all swap partitions using the command:" _warning " swapoff -a" _warning "[#163] I may not detect plain swaps on an encrypted volume." _warning "But if you want to proceed like this, use the -f (force) flag." fi return $r } # Wrapper to allow encrypted swap and remind the user about possible # data leaks to disk if swap is on, which shouldn't be ignored. It could # be run once in main(), but as swap evolves, it's better to run it # whenever swap may be needed. # Exit if unencrypted swap is active on the system. _check_swap() { if ! option_is_set -f && ! option_is_set --ignore-swap; then _ensure_safe_swap case $? in 0|2) # No, or encrypted swap return 0 ;; *) # Unencrypted swap _failure "Operation aborted." ;; esac fi } # Ask user for a password # Wraps around the pinentry command, from the GnuPG project, as it # provides better security and conveniently use the right toolkit. ask_password() { local description="$1" local title="${2:-Enter tomb password.}" local output local password local gtkrc local theme # Distributions have broken wrappers for pinentry: they do # implement fallback, but they disrupt the output somehow. We are # better off relying on less intermediaries, so we implement our # own fallback mechanisms. Pinentry supported: curses, gtk-2, qt4 # and x11. # make sure LANG is set, default to C LANG=${LANG:-C} _verbose "asking password with tty=$TTY lc-ctype=$LANG" if [[ "$DISPLAY" = "" ]]; then if _is_found "pinentry-curses"; then _verbose "using pinentry-curses" output=`cat <