From 4376e91b94cf06ac1132e87337937811932b05b5 Mon Sep 17 00:00:00 2001 From: Kevin MacMartin Date: Wed, 11 Nov 2020 00:56:37 -0500 Subject: [PATCH] Rewrite the script using best practices, way more error checks, nice colour output and an updated encryption algorithm for new images --- README.md | 28 ++--- cryptobox | 314 ++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 262 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 7292791..fffb796 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,29 @@ -# Cryptobox # +# Cryptobox -A wrapper script for **cryptsetup** that makes it easy to create, mount and unmount encrypted image files using LUKS. +A script that makes it easy to create, mount and unmount encrypted images in Linux using LUKS ## Requirements ## -* **cryptsetup**: Userspace setup tool for transparent encryption of block devices using. -* **util-linux**: Miscellaneous system utilities for Linux, required for **losetup** and **mkfs**. +* **cryptsetup** +* **util-linux**: + * **losetup** + * **mkfs** +* **coreutils**: + * **dd** ## Usage ## -* `cryptobox`: displays the list of commands -* `cryptobox c filename.img filesystem size-in-mb`: creates an image file with a given filesystem and size in megabytes. - * eg: `cryptobox c myimg.img ext4 128` -* `cryptobox m filename.img /mount/point`: mounts a given image file on a given directory. - * eg: `cryptobox m myimg.img /mnt/cryptoimg` -* `cryptobox u /mount/point`: unmounts an image file from a given directory. - * eg: `cryptobox u /mnt/cryptoimg` +* **create**: `c|-c|--create [filename] [filesystem] [size-in-mb]` +* **mount**: `m|-m|--mount [filename] [mountpoint]` +* **umount**: `u|-u|--unmount [mountpoint]` +* **help**: `h|-h|--help` ## Credits ## -Written by Kevin MacMartin: [GitHub Projects](https://github.com/prurigro?tab=repositories) | [Arch Linux AUR Packages](https://aur.archlinux.org/packages/?SeB=m&K=prurigro) +Written by Kevin MacMartin + +* [GitHub Projects](https://github.com/prurigro?tab=repositories) +* [Arch Linux AUR Packages](https://aur.archlinux.org/packages/?SeB=m&K=prurigro) ## License ## diff --git a/cryptobox b/cryptobox index a8bf2d2..1588d8d 100755 --- a/cryptobox +++ b/cryptobox @@ -1,87 +1,265 @@ #!/usr/bin/env bash -############################################################## -# # -# CryptoBox # -# # -# A script that wraps cryptsetup, mkfs and losetup to make # -# it easy to create, mount and unmount encrypted image # -# files using LUKS. # -# # -############################################################## +# +# CryptoBox +# +# A script that makes it easy to create, mount +# and unmount encrypted images in Linux using LUKS +# +# Written by Kevin MacMartin +# +# Licensed under the GPLv3 +# -NAME=`echo "$0" | grep -o -e "[^\/]*$"` +[[ -t 1 ]] && { + c_d=$'\e[1;30m' # DARK GREY + c_r=$'\e[1;31m' # RED + c_g=$'\e[1;32m' # GREEN + c_y=$'\e[1;33m' # YELLOW + c_b=$'\e[1;34m' # BLUE + c_m=$'\e[1;35m' # VIOLET + c_t=$'\e[1;36m' # TEAL + c_w=$'\e[1;37m' # WHITE + c_u=$'\e[1;4;37m' # UNDERLINE WHITE + c_h=$'\e[1;41m' # HIGHLIGHT RED + c_c=$'\e[0m' # DISABLES COLOUR +} -function errorquit { - echo -e "Error: $1" - [[ "$1" = "syntax" ]] && (echo; usage) +# The name of the script +scriptname="${0/*\/}" + +# Display uage information +function usage { + printf '%s\n\n' "${c_w}Create, mount and unmount encrypted images${c_c}" + printf '%s\n' "${c_w}Usage:${c_c}" + printf ' %s\n' "${c_y}create${c_w}: ${c_m}c${c_w}|${c_m}-c${c_w}|${c_m}--create${c_c} ${c_w}$scriptname${c_c} ${c_m}c${c_c} [${c_b}filename${c_c}] [${c_g}filesystem${c_c}] [${c_t}size-in-mb${c_c}]" + printf ' %s\n' "${c_y}mount${c_w}: ${c_m}m${c_w}|${c_m}-m${c_w}|${c_m}--mount${c_c} ${c_w}$scriptname${c_c} ${c_m}m${c_c} [${c_b}filename${c_c}] [${c_r}mountpoint${c_c}]" + printf ' %s\n' "${c_y}umount${c_w}: ${c_m}u${c_w}|${c_m}-u${c_w}|${c_m}--unmount${c_c} ${c_w}$scriptname${c_c} ${c_m}u${c_c} [${c_r}mountpoint${c_c}]" + printf ' %s\n' "${c_y}help${c_w}: ${c_m}h${c_w}|${c_m}-h${c_w}|${c_m}--help${c_c} ${c_w}$scriptname${c_c} ${c_m}h${c_c}" + exit "$1" +} + +# Exit with a nicely formatted error +function error_exit { + printf '%s\n' "${c_r}Error${c_c}: ${c_w}$1${c_c}" >&2 exit 1 } -function usage { - echo -e "${NAME}: create and mount encrypted images\n" - echo "Usage: $NAME option arguments" - echo -e "\tc (create) -> $NAME c filename.img filesystem size-in-mb" - echo -e "\tm (mount) -> $NAME m filename.img /mount/point" - echo -e "\tu (umount) -> $NAME u /mount/point" +function luks_open { + loopdev="$1" + container="$2" + + # Decrypt the image so we can use it + while (( 1 )); do + if cryptsetup luksOpen "$loopdev" "$container"; then + break + else + printf '%s' "${c_w}Failed to decrypt ${c_m}$container${c_w}, press return to try again or ctrl+c to exit$c_c" + read -r + fi + done } +function luks_close { + loopdev="$1" + container="$2" + + # Close the encrypted device + cryptsetup luksClose "$container" || error_exit "Unable to close the decrypted device for ${c_m}$container" + sleep 1 + + # Close the loop device + losetup -d "$loopdev" || error_exit "Unable to close the loop device ${c_m}$loopdev" +} + +function create_image { + # Exit with an error if exactly 3 arguments haven't been given + [[ -n "$3" && -z "$4" ]] || error_exit 'Incorrect number of arguments for the create command' + + # Store the provided arguments as named variables so they're easier to keep track of + filename="$1" + filesystem="$2" + size="$3" + + # Exit with an error if the image file already exists + [[ ! -e "$filename" ]] || error_exit "$filename already exists" + + # Exit with an error if the filesystem isn't available + mkfs_binaries=("$(type -P mkfs)".*) + grep -q mkfs."$filesystem" <<< "${mkfs_binaries[*]}" || error_exit "The filesystem ${c_m}$filesystem${c_w} is not available" + + # Exit with an error if size isn't a number + [[ "$size" =~ ^[0-9]*$ ]] || error_exit 'The argument for the size is not a number' + + # Exit with an error if size is less than 18 + (( size >= 18 )) || error_exit 'The size of the image must be 18 megabytes or larger' + + # Retrieve the first unused loop device name + loopdev=$(losetup -f) + + # Retrieve an appropriate name for the container + container="${loopdev/*\/}" + + # Exit with an error if a container already exists with this name + [[ ! -e "/dev/mapper/$container" ]] || error_exit "A container already exists at ${c_m}/dev/mapper/$container" + + # Create the image file at the requested size and filled with random data + dd bs=1M count="$size" if=/dev/urandom of="$filename" || error_exit "Unable to create ${c_m}$filename" + + # Setup the loop device + losetup "$loopdev" "$filename" || error_exit "Unable to connect ${c_m}$filename${c_w} to the loop device ${c_m}$loopdev" + + # Initialize encryption on the image + while (( 1 )); do + if cryptsetup -c aes-xts-plain64 -y -s 512 luksFormat "$loopdev"; then + break + else + printf '%s' "${c_w}Failed to encrypt the image, press return to try again or ctrl+c to exit$c_c" + read -r + fi + done + + # Decrypt the image so we can use it + luks_open "$loopdev" "$container" + + # Initialize the target filesystem on the device + mkfs -t "$filesystem" "/dev/mapper/${container}" || error_exit "Unable to create the ${c_m}$filesystem${c_w} filesystem" + sync + + # Close the decrypted image + luks_close "$loopdev" "$container" +} + +function mount_image { + # Exit with an error if exactly 3 arguments haven't been given + [[ -n "$2" && -z "$3" ]] || error_exit 'Incorrect number of arguments for the create command' + + # Store the provided arguments as named variables so they're easier to keep track of + filename="$1" + mountpoint="$2" + + # Exit with an error if either the image file or mount point do not exist + [[ ! -f "$filename" ]] && error_exit "${c_m}$filename${c_w} does not exist" + [[ -e "$mountpoint" ]] || error_exit "${c_m}$mountpoint${c_w} does not exist" + [[ -d "$mountpoint" ]] || error_exit "${c_m}$mountpoint${c_w} is not a directory" + + # Retrieve the first unused loop device name + loopdev=$(losetup -f) + + # Retrieve an appropriate name for the container + container="${loopdev/*\/}" + + # Exit with an error if a container already exists with this name + [[ ! -e "/dev/mapper/$container" ]] || error_exit "A container already exists at ${c_m}/dev/mapper/$container" + + # Setup the loop device + losetup "$loopdev" "$filename" || error_exit "Unable to connect ${c_m}$filename${c_w} to the loop device ${c_m}$loopdev${c_w}" + + # Decrypt the image so we can use it + luks_open "$loopdev" "$container" + + # Mount the mapped decrypted image + mount "/dev/mapper/$container" "$mountpoint" || error_exit "Unable to mount ${c_m}/dev/mapper/${container}${c_w} on ${c_m}$mountpoint" +} + +function unmount_image { + # Exit with an error if exactly 3 arguments haven't been given + [[ -n "$1" && -z "$2" ]] || error_exit 'Incorrect number of arguments for the create command' + + # Store the the absolute path of the mount point + mountpoint="$(readlink -f "$1")" + + # Exit with an error if the mount point does not exist or isn't a directory + [[ -e "$mountpoint" ]] || error_exit "${c_m}$mountpoint${c_w} does not exist" + [[ -d "$mountpoint" ]] || error_exit "${c_m}$mountpoint${c_w} is not a directory" + + # Check the list of mounts for the mount point + mount=$(grep "$mountpoint" /proc/mounts) + + # Exit with an error if the mount point isn't in the list of mounts + [[ -n "$mount" ]] || error_exit "${c_m}$mountpoint${c_w} is not mounted" + + # Retrieve the name of the container + container_path="${mount/ *}" + + # Retrieve the loop device associated with the mount + loopdev="${container_path/*\//\/dev\/}" + + # Exit with an error if the container_path doesn't exist + [[ -e "$container_path" ]] || error_exit "The mount does not appear to be a decrypted container" + + # Unmount the mount point + umount "$mountpoint" || error_exit "Unable to unmount ${c_m}$mountpoint" + + # Close the decrypted image + luks_close "$loopdev" "${container_path/*\/}" +} + +# Exit with an error on ctrl-c +trap 'error_exit "$scriptname has been killed"' SIGINT SIGQUIT + # Check for root -[[ "$UID" -ne 0 ]] && errorquit "run with root permission\n" +(( UID == 0 )) || error_exit 'Must be run with root permissions' -# Check dependencies -[[ `type -P dd` ]] || errorquit "Error: The 'dd' program is missing" -[[ `type -P losetup` ]] || errorquit "Error: The 'losetup' program is missing" -[[ `type -P cryptsetup` ]] || errorquit "Error: The 'cryptsetup' program is missing" -[[ `type -P mkfs` ]] || errorquit "Error: The 'mkfs' program is missing" +# Dependencies +deps=('dd' 'losetup' 'cryptsetup' 'mkfs') -# Load modules if they aren't present -[[ `lsmod | grep loop` ]] || echo "loading 'loop' module"; modprobe loop || errorquit "Error: failed to load 'loop' module" -[[ `lsmod | grep dm_mod` ]] || echo "loading 'dm_mod' module"; modprobe dm_mod || errorquit "Error: failed to load 'dm_mod' module" +# Check for missing dependencies +declare -a missing_deps=() -if [ -z "$1" ]; then - usage; exit 1 -elif [ ! "$1" = "c" -a ! "$1" = "m" -a ! "$1" = "u" ]; then - errorquit "syntax" -fi +for dep in "${deps[@]}"; do + type -P "$dep" >/dev/null \ + || missing_deps=( "${missing_deps[@]}" "$dep" ) +done -case "$1" in - c) - if [ -z "$2" -o -z "$3" -o -z "$4" ]; then errorquit "syntax"; fi - [[ -f "$2" ]] && errorquit "$2 already exists" - LOOPDEV=`losetup -f` - CONTAINER=`echo "$2" | sed s/"[^\/]*\/"//g | sed s/"\.".*$//g` - dd bs=1M count="$4" if=/dev/urandom of="$2" || errorquit "couldn't create create image file" - losetup "$LOOPDEV" "$2" || errorquit "couldn't setup loop device (${LOOPDEV})" - cryptsetup -c aes-xts-plain -y -s 512 luksFormat "$LOOPDEV" || errorquit "couldn't encrypt image file" - cryptsetup luksOpen "$LOOPDEV" "$CONTAINER" || errorquit "couldn't decrypt $CONTAINER" - mkfs -t "$3" "/dev/mapper/${CONTAINER}" || errorquit "mkfs failed for filesystem type: $3" - cryptsetup luksClose "$CONTAINER" || errorquit "couldn't close encryption for $CONTAINER" - sleep 1 - losetup -d "$LOOPDEV" || errorquit "couldn't close loop device (${LOOPDEV})" +[[ -n "${missing_deps[*]}" ]] && { + error_exit "${c_w}missing dependencies ($( + for (( x=0; x < ${#missing_deps[@]}; x++ )); do + printf '%s' "$c_m${missing_deps[$x]}$c_c" + (( (( x + 1 )) < ${#missing_deps[@]} )) && printf '%s' ', ' + done + )$c_w)" +} + +# Load the required modules if not already available +modules=('loop' 'dm_mod') + +for module in "${modules[@]}"; do + modinfo "$module" >/dev/null 2>&1 || { + printf '%s ' "${c_w}Loading module: ${c_m}$module${c_w}..." + + if modprobe "$module" >/dev/null 2>&1; then + printf '%s\n' "${c_g}done!${c_c}" + else + printf '%s\n' "${c_r}failed!${c_c}" + error_exit "Unable to load module ${c_m}$module" + fi + } +done + +# Store the option and then drop it from the list of arguments +option="$1" +shift + +# Run the appropriate function based on the provided option +case "$option" in + c|-c|--create) + create_image "$@" ;; - m) - if [ -z "$2" -o -z "$3" ]; then errorquit "syntax"; fi - [[ ! -f "$2" ]] && errorquit "$2 does not exist" - [[ -d "$3" ]] || errorquit "$3 does not exist" - LOOPDEV=$(losetup -f) - CONTAINER=$(echo "$LOOPDEV" | sed s/"[^\/]*\/"//g | sed s/"\.".*$//g) - losetup "$LOOPDEV" "$2" || errorquit "couldn't setup loop device (${LOOPDEV})" - cryptsetup luksOpen "$LOOPDEV" "$CONTAINER" || errorquit "couldn't decrypt $CONTAINER" - mount "/dev/mapper/${CONTAINER}" "$3" || errorquit "couldn't mount /dev/mapper/${CONTAINER} on $3" + + m|-m|--mount) + mount_image "$@" ;; - u) - if [ -z "$2" ]; then errorquit "syntax"; fi - MOUNT=`mount | grep $(echo "$2" | sed s/"\/"$//)` - [[ -z "$MOUNT" ]] && errorquit "$2 is not mounted" - LOOPDEV=`echo "$MOUNT" | sed s/\ .*//g | sed s/"\/mapper"//` - CONTAINER=`echo "$LOOPDEV" | sed s/"[^\/]*\/"//g | sed s/"\.".*$//g` - umount "$2" || errorquit "Couldn't unmount $2" - cryptsetup luksClose "$CONTAINER" || errorquit "couldn't close encryption for $CONTAINER" - sleep 1 - losetup -d "$LOOPDEV" || errorquit "couldn't close loop device (${LOOPDEV})" + + u|-u|--unmount) + unmount_image "$@" ;; - generic) - usage + + h|-h|--help) + usage 0 + ;; + + *) + usage 1 ;; esac