tomb (102333B)
1 #!/usr/bin/env zsh 2 # 3 # Tomb, the Crypto Undertaker 4 # 5 # A commandline tool to easily operate encryption of secret data 6 # 7 8 # {{{ License 9 10 # Copyright (C) 2007-2017 Dyne.org Foundation 11 # 12 # Tomb is designed, written and maintained by Denis Roio <jaromil@dyne.org> 13 # 14 # With contributions by Anathema, Boyska, Hellekin O. Wolf and GDrooid 15 # 16 # Gettext internationalization and Spanish translation is contributed by 17 # GDrooid, French translation by Hellekin, Russian translation by fsLeg, 18 # German translation by x3nu. 19 # 20 # Testing, reviews and documentation are contributed by Dreamer, Shining 21 # the Translucent, Mancausoft, Asbesto Molesto, Nignux, Vlax, The Grugq, 22 # Reiven, GDrooid, Alphazo, Brian May, TheJH, fsLeg, JoelMon and the 23 # Linux Action Show! 24 # 25 # Tomb's artwork is contributed by Jordi aka Mon Mort and Logan VanCuren. 26 # 27 # Cryptsetup was developed by Christophe Saout and Clemens Fruhwirth. 28 29 # This source code is free software; you can redistribute it and/or 30 # modify it under the terms of the GNU Public License as published by 31 # the Free Software Foundation; either version 3 of the License, or 32 # (at your option) any later version. 33 # 34 # This source code is distributed in the hope that it will be useful, 35 # but WITHOUT ANY WARRANTY; without even the implied warranty of 36 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Please refer 37 # to the GNU Public License for more details. 38 # 39 # You should have received a copy of the GNU Public License along with 40 # this source code; if not, write to: Free Software Foundation, Inc., 41 # 675 Mass Ave, Cambridge, MA 02139, USA. 42 43 # }}} - License 44 45 # {{{ Global variables 46 47 typeset VERSION="2.4" 48 typeset DATE="Apr/2017" 49 typeset TOMBEXEC=$0 50 typeset TMPPREFIX=${TMPPREFIX:-/tmp} 51 # TODO: configure which tmp dir to use from a cli flag 52 53 # Tomb is using some global variables set by the shell: 54 # TMPPREFIX, UID, GID, PATH, TTY, USERNAME 55 # You can grep 'global variable' to see where they are used. 56 57 # Keep a reference of the original command line arguments 58 typeset -a OLDARGS 59 for arg in "${(@)argv}"; do OLDARGS+=("$arg"); done 60 61 # Special command requirements 62 typeset -a DD WIPE PINENTRY 63 DD=(dd) 64 WIPE=(rm -f) 65 PINENTRY=(pinentry) 66 67 # load zsh regex module 68 zmodload zsh/regex 69 zmodload zsh/mapfile 70 zmodload -F zsh/stat b:zstat 71 72 # make sure variables aren't exported 73 unsetopt allexport 74 75 # Flag optional commands if available (see _ensure_dependencies()) 76 typeset -i KDF=1 77 typeset -i STEGHIDE=1 78 typeset -i RESIZER=1 79 typeset -i SWISH=1 80 typeset -i QRENCODE=1 81 82 # Default mount options 83 typeset MOUNTOPTS="rw,noatime,nodev" 84 85 # Makes glob matching case insensitive 86 unsetopt CASE_MATCH 87 88 typeset -AH OPTS # Command line options (see main()) 89 90 # Command context (see _whoami()) 91 typeset -H _USER # Running username 92 typeset -Hi _UID # Running user identifier 93 typeset -Hi _GID # Running user group identifier 94 typeset -H _TTY # Connected input terminal 95 96 # Tomb context (see _plot()) 97 typeset -H TOMBPATH # Full path to the tomb 98 typeset -H TOMBDIR # Directory where the tomb is 99 typeset -H TOMBFILE # File name of the tomb 100 typeset -H TOMBNAME # Name of the tomb 101 102 # Tomb secrets 103 typeset -H TOMBKEY # Encrypted key contents (see forge_key(), recover_key()) 104 typeset -H TOMBKEYFILE # Key file (ditto) 105 typeset -H TOMBSECRET # Raw deciphered key (see forge_key(), gpg_decrypt()) 106 typeset -H TOMBPASSWORD # Raw tomb passphrase (see gen_key(), ask_key_password()) 107 typeset -H TOMBTMP # Filename of secure temp just created (see _tmp_create()) 108 109 typeset -aH TOMBTMPFILES # Keep track of temporary files 110 typeset -aH TOMBLOOPDEVS # Keep track of used loop devices 111 112 # Make sure sbin is in PATH (man zshparam) 113 path+=( /sbin /usr/sbin ) 114 115 # For gettext 116 export TEXTDOMAIN=tomb 117 118 # }}} 119 120 # {{{ Safety functions 121 122 # Wrap sudo with a more visible message 123 _sudo() { 124 local sudo_eng="[sudo] Enter password for user ::1 user:: to gain superuser privileges" 125 local msg="$(gettext -s "$sudo_eng")" 126 msg=${(S)msg//::1*::/$USER} 127 sudo -p " 128 $msg 129 130 " ${@} 131 } 132 133 # Cleanup anything sensitive before exiting. 134 _endgame() { 135 136 # Prepare some random material to overwrite vars 137 local rr="$RANDOM" 138 while [[ ${#rr} -lt 500 ]]; do 139 rr+="$RANDOM" 140 done 141 142 # Ensure no information is left in unallocated memory 143 TOMBPATH="$rr"; unset TOMBPATH 144 TOMBDIR="$rr"; unset TOMBDIR 145 TOMBFILE="$rr"; unset TOMBFILE 146 TOMBNAME="$rr"; unset TOMBNAME 147 TOMBKEY="$rr"; unset TOMBKEY 148 TOMBKEYFILE="$rr"; unset TOMBKEYFILE 149 TOMBSECRET="$rr"; unset TOMBSECRET 150 TOMBPASSWORD="$rr"; unset TOMBPASSWORD 151 152 # Clear temporary files 153 for f in $TOMBTMPFILES; do 154 ${=WIPE} "$f" 155 done 156 unset TOMBTMPFILES 157 158 # Detach loop devices 159 for l in $TOMBLOOPDEVS; do 160 _sudo losetup -d "$l" 161 done 162 unset TOMBLOOPDEVS 163 164 } 165 166 # Trap functions for the _endgame event 167 TRAPINT() { _endgame INT } 168 TRAPEXIT() { _endgame EXIT } 169 TRAPHUP() { _endgame HUP } 170 TRAPQUIT() { _endgame QUIT } 171 TRAPABRT() { _endgame ABORT } 172 TRAPKILL() { _endgame KILL } 173 TRAPPIPE() { _endgame PIPE } 174 TRAPTERM() { _endgame TERM } 175 TRAPSTOP() { _endgame STOP } 176 177 _cat() { local -a _arr; 178 # read file using mapfile, newline fix 179 _arr=("${(f@)${mapfile[${1}]%$'\n'}}"); print "$_arr" 180 } 181 182 _is_found() { 183 # returns 0 if binary is found in path 184 [[ "$1" = "" ]] && return 1 185 command -v "$1" 1>/dev/null 2>/dev/null 186 return $? 187 } 188 189 # Identify the running user 190 # Set global variables _UID, _GID, _TTY, and _USER, either from the 191 # command line, -U, -G, -T, respectively, or from the environment. 192 # Also update USERNAME and HOME to maintain consistency. 193 _whoami() { 194 195 # Set username from UID or environment 196 _USER=$SUDO_USER 197 [[ "$_USER" = "" ]] && { _USER=$USERNAME } 198 [[ "$_USER" = "" ]] && { _USER=$(id -u) } 199 [[ "$_USER" = "" ]] && { 200 _failure "Failing to identify the user who is calling us" } 201 202 # Get GID from option -G or the environment 203 option_is_set -G \ 204 && _GID=$(option_value -G) || _GID=$(id -g $_USER) 205 206 # Get UID from option -U or the environment 207 option_is_set -U \ 208 && _UID=$(option_value -U) || _UID=$(id -u $_USER) 209 210 _verbose "Identified caller: ::1 username:: (::2 UID:::::3 GID::)" $_USER $_UID $_GID 211 212 # Update USERNAME accordingly if possible 213 # [[ $EUID == 0 && $_USER != $USERNAME ]] && { 214 # _verbose "Updating USERNAME from '::1 USERNAME::' to '::2 _USER::')" $USERNAME $_USER 215 # USERNAME=$_USER 216 # } 217 218 # Force HOME to _USER's HOME if necessary 219 local home=$(awk -F: "/^$_USER:/ { print \$6 }" /etc/passwd 2>/dev/null) 220 [[ $home == $HOME ]] || { 221 _verbose "Updating HOME to match user's: ::1 home:: (was ::2 HOME::)" \ 222 $home $HOME 223 HOME=$home } 224 225 # Get connecting TTY from option -T or the environment 226 option_is_set -T && _TTY=$(option_value -T) 227 [[ -z $_TTY ]] && _TTY=$TTY 228 229 } 230 231 # Define sepulture's plot (setup tomb-related arguments) 232 # Synopsis: _plot /path/to/the.tomb 233 # Set TOMB{PATH,DIR,FILE,NAME} 234 _plot() { 235 236 # We set global variables 237 typeset -g TOMBPATH TOMBDIR TOMBFILE TOMBNAME 238 239 TOMBPATH="$1" 240 241 TOMBDIR=$(dirname $TOMBPATH) 242 243 TOMBFILE=$(basename $TOMBPATH) 244 245 # The tomb name is TOMBFILE without an extension and underscores instead of spaces (for mount and cryptsetup) 246 # It can start with dots: ..foo bar baz.tomb -> ..foo_bar_baz 247 TOMBNAME=${${TOMBFILE// /_}%.*} 248 [[ -z $TOMBNAME ]] && { 249 _failure "Tomb won't work without a TOMBNAME." } 250 251 } 252 253 # Provide a random filename in shared memory 254 _tmp_create() { 255 [[ -d "$TMPPREFIX" ]] || { 256 # we create the tempdir with the sticky bit on 257 _sudo mkdir -m 1777 "$TMPPREFIX" 258 [[ $? == 0 ]] || _failure "Fatal error creating the temporary directory: ::1 temp dir::" "$TMPPREFIX" 259 } 260 261 # We're going to add one more $RANDOM for each time someone complains 262 # about this being too weak of a random. 263 tfile="${TMPPREFIX}/$RANDOM$RANDOM$RANDOM$RANDOM" # Temporary file 264 umask 066 265 [[ $? == 0 ]] || { 266 _failure "Fatal error setting the permission umask for temporary files" } 267 268 [[ -r "$tfile" ]] && { 269 _failure "Someone is messing up with us trying to hijack temporary files." } 270 271 touch "$tfile" 272 [[ $? == 0 ]] || { 273 _failure "Fatal error creating a temporary file: ::1 temp file::" "$tfile" } 274 275 _verbose "Created tempfile: ::1 temp file::" "$tfile" 276 TOMBTMP="$tfile" 277 TOMBTMPFILES+=("$tfile") 278 279 return 0 280 } 281 282 # Check if a *block* device is encrypted 283 # Synopsis: _is_encrypted_block /path/to/block/device 284 # Return 0 if it is an encrypted block device 285 _is_encrypted_block() { 286 local b=$1 # Path to a block device 287 local s="" # lsblk option -s (if available) 288 289 # Issue #163 290 # lsblk --inverse appeared in util-linux 2.22 291 # but --version is not consistent... 292 lsblk --help | grep -Fq -- --inverse 293 [[ $? -eq 0 ]] && s="--inverse" 294 295 sudo lsblk $s -o type -n $b 2>/dev/null \ 296 | egrep -q '^crypt$' 297 298 return $? 299 } 300 301 # Check if swap is activated 302 # Return 0 if NO swap is used, 1 if swap is used. 303 # Return 1 if any of the swaps is not encrypted. 304 # Return 2 if swap(s) is(are) used, but ALL encrypted. 305 # Use _check_swap in functions. It will call this function and 306 # exit if unsafe swap is present. 307 _ensure_safe_swap() { 308 309 local -i r=1 # Return code: 0 no swap, 1 unsafe swap, 2 encrypted 310 local -a swaps # List of swap partitions 311 local bone is_crypt 312 313 swaps="$(awk '/^\// { print $1 }' /proc/swaps 2>/dev/null)" 314 [[ -z "$swaps" ]] && return 0 # No swap partition is active 315 316 _message "An active swap partition is detected..." 317 for s in $=swaps; do 318 if _is_encrypted_block $s; then 319 r=2; 320 else 321 # We're dealing with unencrypted stuff. 322 # Maybe it lives on an encrypted filesystem anyway. 323 # @todo: verify it's actually on an encrypted FS (see #163 and !189) 324 # Well, no: bail out. 325 r=1; break; 326 fi 327 done 328 329 if [[ $r -eq 2 ]]; then 330 _success "The undertaker found that all swap partitions are encrypted. Good." 331 else 332 _warning "This poses a security risk." 333 _warning "You can deactivate all swap partitions using the command:" 334 _warning " swapoff -a" 335 _warning "[#163] I may not detect plain swaps on an encrypted volume." 336 _warning "But if you want to proceed like this, use the -f (force) flag." 337 fi 338 return $r 339 340 } 341 342 # Wrapper to allow encrypted swap and remind the user about possible 343 # data leaks to disk if swap is on, which shouldn't be ignored. It could 344 # be run once in main(), but as swap evolves, it's better to run it 345 # whenever swap may be needed. 346 # Exit if unencrypted swap is active on the system. 347 _check_swap() { 348 if ! option_is_set -f && ! option_is_set --ignore-swap; then 349 _ensure_safe_swap 350 case $? in 351 0|2) # No, or encrypted swap 352 return 0 353 ;; 354 *) # Unencrypted swap 355 _failure "Operation aborted." 356 ;; 357 esac 358 fi 359 } 360 361 # Ask user for a password 362 # Wraps around the pinentry command, from the GnuPG project, as it 363 # provides better security and conveniently use the right toolkit. 364 ask_password() { 365 366 local description="$1" 367 local title="${2:-Enter tomb password.}" 368 local output 369 local password 370 local gtkrc 371 local theme 372 373 # Distributions have broken wrappers for pinentry: they do 374 # implement fallback, but they disrupt the output somehow. We are 375 # better off relying on less intermediaries, so we implement our 376 # own fallback mechanisms. Pinentry supported: curses, gtk-2, qt4 377 # and x11. 378 379 # make sure LANG is set, default to C 380 LANG=${LANG:-C} 381 382 _verbose "asking password with tty=$TTY lc-ctype=$LANG" 383 384 if [[ "$DISPLAY" = "" ]]; then 385 386 if _is_found "pinentry-curses"; then 387 _verbose "using pinentry-curses" 388 output=`cat <<EOF | pinentry-curses 389 OPTION ttyname=$TTY 390 OPTION lc-ctype=$LANG 391 SETTITLE $title 392 SETDESC $description 393 SETPROMPT Password: 394 GETPIN 395 EOF` 396 else 397 _failure "Cannot find pinentry-curses and no DISPLAY detected." 398 fi 399 400 else # a DISPLAY is found to be active 401 402 # customized gtk2 dialog with a skull (if extras are installed) 403 if _is_found "pinentry-gtk-2"; then 404 _verbose "using pinentry-gtk2" 405 406 gtkrc="" 407 theme=/share/themes/tomb/gtk-2.0-key/gtkrc 408 for i in /usr/local /usr; do 409 [[ -r $i/$theme ]] && { 410 gtkrc="$i/$theme" 411 break 412 } 413 done 414 [[ "$gtkrc" = "" ]] || { 415 gtkrc_old="$GTK2_RC_FILES" 416 export GTK2_RC_FILES="$gtkrc" 417 } 418 output=`cat <<EOF | pinentry-gtk-2 419 OPTION ttyname=$TTY 420 OPTION lc-ctype=$LANG 421 SETTITLE $title 422 SETDESC $description 423 SETPROMPT Password: 424 GETPIN 425 EOF` 426 [[ "$gtkrc" = "" ]] || export GTK2_RC_FILES="$gtkrc_old" 427 428 # TODO QT4 customization of dialog 429 elif _is_found "pinentry-qt4"; then 430 _verbose "using pinentry-qt4" 431 432 output=`cat <<EOF | pinentry-qt4 433 OPTION ttyname=$TTY 434 OPTION lc-ctype=$LANG 435 SETTITLE $title 436 SETDESC $description 437 SETPROMPT Password: 438 GETPIN 439 EOF` 440 441 # TODO X11 customization of dialog 442 elif _is_found "pinentry-x11"; then 443 _verbose "using pinentry-x11" 444 445 output=`cat <<EOF | pinentry-x11 446 OPTION ttyname=$TTY 447 OPTION lc-ctype=$LANG 448 SETTITLE $title 449 SETDESC $description 450 SETPROMPT Password: 451 GETPIN 452 EOF` 453 454 else 455 456 if _is_found "pinentry-curses"; then 457 _verbose "using pinentry-curses" 458 459 _warning "Detected DISPLAY, but only pinentry-curses is found." 460 output=`cat <<EOF | pinentry-curses 461 OPTION ttyname=$TTY 462 OPTION lc-ctype=$LANG 463 SETTITLE $title 464 SETDESC $description 465 SETPROMPT Password: 466 GETPIN 467 EOF` 468 else 469 _failure "Cannot find any pinentry: impossible to ask for password." 470 fi 471 472 fi 473 474 fi # end of DISPLAY block 475 476 # parse the pinentry output 477 for i in ${(f)output}; do 478 [[ "$i" =~ "^ERR.*" ]] && { 479 _warning "Pinentry error: ::1 error::" ${i[(w)3]} 480 print "canceled" 481 return 1 482 } 483 484 # here the password is found 485 [[ "$i" =~ "^D .*" ]] && password="${i##D }"; 486 done 487 488 [[ "$password" = "" ]] && { 489 _warning "Empty password" 490 print "empty" 491 return 1 492 } 493 494 print "$password" 495 return 0 496 } 497 498 499 500 # Check if a filename is a valid tomb 501 is_valid_tomb() { 502 _verbose "is_valid_tomb ::1 tomb file::" $1 503 504 # First argument must be the path to a tomb 505 [[ -z "$1" ]] && { 506 _failure "Tomb file is missing from arguments." } 507 508 _fail=0 509 # Tomb file must be a readable, writable, non-empty regular file. 510 # If passed the "ro" mount option, the writable check is skipped. 511 [[ ! -w "$1" ]] && [[ $(option_value -o) != *"ro"* ]] && { 512 _warning "Tomb file is not writable: ::1 tomb file::" $1 513 _fail=1 514 } 515 _verbose "tomb file is readable" 516 517 [[ ! -f "$1" ]] && { 518 _warning "Tomb file is not a regular file: ::1 tomb file::" $1 519 _fail=1 520 } 521 _verbose "tomb file is a regular file" 522 523 [[ ! -s "$1" ]] && { 524 _warning "Tomb file is empty (zero length): ::1 tomb file::" $1 525 _fail=1 526 } 527 _verbose "tomb file is not empty" 528 529 # no more checking on the uid 530 # _uid="`zstat +uid $1`" 531 # [[ "$_uid" = "$UID" ]] || { 532 # _user="`zstat -s +uid $1`" 533 # _warning "Tomb file is owned by another user: ::1 tomb owner::" $_user 534 # } 535 # _verbose "tomb is not owned by another user" 536 537 [[ $_fail = 1 ]] && { 538 _failure "Tomb command failed: ::1 command name::" $subcommand 539 } 540 541 # TODO: split the rest of that function out. 542 # We already have a valid tomb, now we're checking 543 # whether we can alter it. 544 545 # Tomb file may be a LUKS FS (or we are creating it) 546 [[ "`file $1`" =~ "luks encrypted file" ]] || { 547 _warning "File is not yet a tomb: ::1 tomb file::" $1 } 548 549 _plot $1 # Set TOMB{PATH,DIR,FILE,NAME} 550 551 # Tomb already mounted (or we cannot alter it) 552 [[ "`mount -l | 553 awk -vtomb="[$TOMBNAME]" ' 554 /^\/dev\/mapper\/tomb/ { if($7==tomb) print $1 }'`" = "" ]] || { 555 _failure "Tomb is currently in use: ::1 tomb name::" $TOMBNAME 556 } 557 _verbose "tomb file is not currently in use" 558 559 _message "Valid tomb file found: ::1 tomb path::" $TOMBPATH 560 561 return 0 562 } 563 564 # $1 is the tomb file to be lomounted 565 lo_mount() { 566 tpath="$1" 567 568 # check if we have support for loop mounting 569 _nstloop=`_sudo losetup -f` 570 [[ $? = 0 ]] || { 571 _warning "Loop mount of volumes is not possible on this machine, this error" 572 _warning "often occurs on VPS and kernels that don't provide the loop module." 573 _warning "It is impossible to use Tomb on this machine under these conditions." 574 _failure "Operation aborted." 575 } 576 577 _sudo losetup -f "$tpath" # allocates the next loopback for our file 578 579 TOMBLOOPDEVS+=("$_nstloop") # add to array of lodevs used 580 581 return 0 582 } 583 584 # print out latest loopback mounted 585 lo_new() { print - "${TOMBLOOPDEVS[${#TOMBLOOPDEVS}]}" } 586 587 # $1 is the path to the lodev to be preserved after quit 588 lo_preserve() { 589 _verbose "lo_preserve on ::1 path::" $1 590 # remove the lodev from the tomb_lodevs array 591 TOMBLOOPDEVS=("${(@)TOMBLOOPDEVS:#$1}") 592 } 593 594 # eventually used for debugging 595 dump_secrets() { 596 print "TOMBPATH: $TOMBPATH" 597 print "TOMBNAME: $TOMBNAME" 598 599 print "TOMBKEY len: ${#TOMBKEY}" 600 print "TOMBKEYFILE: $TOMBKEYFILE" 601 print "TOMBSECRET len: ${#TOMBSECRET}" 602 print "TOMBPASSWORD: $TOMBPASSWORD" 603 604 print "TOMBTMPFILES: ${(@)TOMBTMPFILES}" 605 print "TOMBLOOPDEVS: ${(@)TOMBLOOPDEVS}" 606 } 607 608 # }}} 609 610 # {{{ Commandline interaction 611 612 usage() { 613 _print "Syntax: tomb [options] command [arguments]" 614 _print "\000" 615 _print "Commands:" 616 _print "\000" 617 _print " // Creation:" 618 _print " dig create a new empty TOMB file of size -s in MiB" 619 _print " forge create a new KEY file and set its password" 620 _print " lock installs a lock on a TOMB to use it with KEY" 621 _print "\000" 622 _print " // Operations on tombs:" 623 _print " open open an existing TOMB (-k KEY file or - for stdin)" 624 _print " index update the search indexes of tombs" 625 _print " search looks for filenames matching text patterns" 626 _print " list list of open TOMBs and information on them" 627 _print " close close a specific TOMB (or 'all')" 628 _print " slam slam a TOMB killing all programs using it" 629 [[ $RESIZER == 1 ]] && { 630 _print " resize resize a TOMB to a new size -s (can only grow)" 631 } 632 _print "\000" 633 _print " // Operations on keys:" 634 _print " passwd change the password of a KEY (needs old pass)" 635 _print " setkey change the KEY locking a TOMB (needs old key and pass)" 636 _print "\000" 637 [[ $QRENCODE == 1 ]] && { 638 _print " // Backup on paper:" 639 _print " engrave makes a QR code of a KEY to be saved on paper" 640 } 641 _print "\000" 642 [[ $STEGHIDE == 1 ]] && { 643 _print " // Steganography:" 644 _print " bury hide a KEY inside a JPEG image (for use with -k)" 645 _print " exhume extract a KEY from a JPEG image (prints to stdout)" 646 } 647 _print "\000" 648 _print "Options:" 649 _print "\000" 650 _print " -s size of the tomb file when creating/resizing one (in MiB)" 651 _print " -k path to the key to be used ('-k -' to read from stdin)" 652 _print " -n don't process the hooks found in tomb" 653 _print " -o options passed to commands: open, lock, forge (see man)" 654 _print " -f force operation (i.e. even if swap is active)" 655 _print " -g use a GnuPG key to encrypt a tomb key" 656 _print " -r provide GnuPG recipients (separated by coma)" 657 _print " -R provide GnuPG hidden recipients (separated by coma)" 658 [[ $KDF == 1 ]] && { 659 _print " --kdf forge keys armored against dictionary attacks" 660 } 661 662 _print "\000" 663 _print " -h print this help" 664 _print " -v print version, license and list of available ciphers" 665 _print " -q run quietly without printing informations" 666 _print " -D print debugging information at runtime" 667 _print "\000" 668 _print "For more information on Tomb read the manual: man tomb" 669 _print "Please report bugs on <http://github.com/dyne/tomb/issues>." 670 } 671 672 673 # Check whether a commandline option is set. 674 # 675 # Synopsis: option_is_set -flag [out] 676 # 677 # First argument is the commandline flag (e.g., "-s"). 678 # If the second argument is present and set to 'out', print out the 679 # result: either 'set' or 'unset' (useful for if conditions). 680 # 681 # Return 0 if is set, 1 otherwise 682 option_is_set() { 683 local -i r # the return code (0 = set, 1 = unset) 684 685 [[ -n ${(k)OPTS[$1]} ]]; 686 r=$? 687 688 [[ $2 == "out" ]] && { 689 [[ $r == 0 ]] && { print 'set' } || { print 'unset' } 690 } 691 692 return $r; 693 } 694 695 # Print the option value matching the given flag 696 # Unique argument is the commandline flag (e.g., "-s"). 697 option_value() { 698 print -n - "${OPTS[$1]}" 699 } 700 701 # Messaging function with pretty coloring 702 function _msg() { 703 local msg="$2" 704 command -v gettext 1>/dev/null 2>/dev/null && msg="$(gettext -s "$2")" 705 for i in $(seq 3 ${#}); 706 do 707 msg=${(S)msg//::$(($i - 2))*::/$*[$i]} 708 done 709 710 local command="print -P" 711 local progname="$fg[magenta]${TOMBEXEC##*/}$reset_color" 712 local message="$fg_bold[normal]$fg_no_bold[normal]$msg$reset_color" 713 local -i returncode 714 715 case "$1" in 716 inline) 717 command+=" -n"; pchars=" > "; pcolor="yellow" 718 ;; 719 message) 720 pchars=" . "; pcolor="white"; message="$fg_no_bold[$pcolor]$msg$reset_color" 721 ;; 722 verbose) 723 pchars="[D]"; pcolor="blue" 724 ;; 725 success) 726 pchars="(*)"; pcolor="green"; message="$fg_no_bold[$pcolor]$msg$reset_color" 727 ;; 728 warning) 729 pchars="[W]"; pcolor="yellow"; message="$fg_no_bold[$pcolor]$msg$reset_color" 730 ;; 731 failure) 732 pchars="[E]"; pcolor="red"; message="$fg_no_bold[$pcolor]$msg$reset_color" 733 returncode=1 734 ;; 735 print) 736 progname="" 737 ;; 738 *) 739 pchars="[F]"; pcolor="red" 740 message="Developer oops! Usage: _msg MESSAGE_TYPE \"MESSAGE_CONTENT\"" 741 returncode=127 742 ;; 743 esac 744 ${=command} "${progname} $fg_bold[$pcolor]$pchars$reset_color ${message}$color[reset_color]" >&2 745 return $returncode 746 } 747 748 function _message() { 749 local notice="message" 750 [[ "$1" = "-n" ]] && shift && notice="inline" 751 option_is_set -q || _msg "$notice" $@ 752 return 0 753 } 754 755 function _verbose() { 756 option_is_set -D && _msg verbose $@ 757 return 0 758 } 759 760 function _success() { 761 option_is_set -q || _msg success $@ 762 return 0 763 } 764 765 function _warning() { 766 option_is_set -q || _msg warning $@ 767 return 1 768 } 769 770 function _failure() { 771 typeset -i exitcode=${exitv:-1} 772 option_is_set -q || _msg failure $@ 773 # be sure we forget the secrets we were told 774 exit $exitcode 775 } 776 777 function _print() { 778 option_is_set -q || _msg print $@ 779 return 0 780 } 781 782 _list_optional_tools() { 783 typeset -a _deps 784 _deps=(gettext dcfldd wipe steghide) 785 _deps+=(resize2fs tomb-kdb-pbkdf2 qrencode swish-e unoconv lsof) 786 for d in $_deps; do 787 _print "`which $d`" 788 done 789 return 0 790 } 791 792 793 # Check program dependencies 794 # 795 # Tomb depends on system utilities that must be present, and other 796 # functionality that can be provided by various programs according to 797 # what's available on the system. If some required commands are 798 # missing, bail out. 799 _ensure_dependencies() { 800 801 # Check for required programs 802 for req in cryptsetup pinentry sudo gpg mkfs.ext4 e2fsck; do 803 command -v $req 1>/dev/null 2>/dev/null || { 804 _failure "Missing required dependency ::1 command::. Please install it." $req; } 805 done 806 807 # Ensure system binaries are available in the PATH 808 path+=(/sbin /usr/sbin) # zsh magic 809 810 # Which dd command to use 811 command -v dcfldd 1>/dev/null 2>/dev/null && DD=(dcfldd statusinterval=1) 812 813 # Which wipe command to use 814 command -v wipe 1>/dev/null 2>/dev/null && WIPE=(wipe -f -s) 815 816 # Check for lsof for slamming tombs 817 command -v lsof 1>/dev/null 2>/dev/null || LSOF=0 818 # Check for steghide 819 command -v steghide 1>/dev/null 2>/dev/null || STEGHIDE=0 820 # Check for resize 821 command -v resize2fs 1>/dev/null 2>/dev/null || RESIZER=0 822 # Check for KDF auxiliary tools 823 command -v tomb-kdb-pbkdf2 1>/dev/null 2>/dev/null || KDF=0 824 # Check for Swish-E file content indexer 825 command -v swish-e 1>/dev/null 2>/dev/null || SWISH=0 826 # Check for QREncode for paper backups of keys 827 command -v qrencode 1>/dev/null 2>/dev/null || QRENCODE=0 828 } 829 830 # }}} - Commandline interaction 831 832 # {{{ Key operations 833 834 # $@ is the list of all the recipient used to encrypt a tomb key 835 is_valid_recipients() { 836 typeset -a recipients 837 recipients=($@) 838 839 _verbose "is_valid_recipients" 840 841 # All the keys ID must be valid (the public keys must be present in the database) 842 for gpg_id in ${recipients[@]}; do 843 gpg --list-keys "$gpg_id" &> /dev/null 844 [[ $? != 0 ]] && { 845 _warning "Not a valid GPG key ID: ::1 gpgid:: " $gpg_id 846 return 1 847 } 848 done 849 850 # At least one private key must be present 851 for gpg_id in ${recipients[@]}; do 852 gpg --list-secret-keys "$gpg_id" &> /dev/null 853 [[ $? = 0 ]] && { 854 return 0 855 } 856 done 857 858 return 1 859 } 860 861 # $@ is the list of all the recipient used to encrypt a tomb key 862 # Print the recipient arg to be used in gpg. 863 _recipients_arg() { 864 local arg="$1"; shift 865 typeset -a recipients 866 recipients=($@) 867 868 for gpg_id in ${recipients[@]}; do 869 print -R -n "$arg $gpg_id " 870 done 871 return 0 872 } 873 874 # $1 is a GPG key recipient 875 # Print the fingerprint of the GPG key 876 _fingerprint() { 877 local recipient="$1" 878 gpg --with-colons --fingerprint "$recipient" | grep fpr | head -1 | cut -d ':' -f 10 | sed 's/.\{4\}/& /g' 879 } 880 881 882 # $1 is the encrypted key contents we are checking 883 is_valid_key() { 884 local key="$1" # Unique argument is an encrypted key to test 885 886 _verbose "is_valid_key" 887 888 [[ -z $key ]] && key=$TOMBKEY 889 [[ "$key" = "cleartext" ]] && { 890 { option_is_set --unsafe } || { 891 _warning "cleartext key from stdin selected: this is unsafe." 892 exitv=127 _failure "please use --unsafe if you really want to do this." 893 } 894 _warning "received key in cleartext from stdin (unsafe mode)" 895 return 0 } 896 897 [[ -z $key ]] && { 898 _warning "is_valid_key() called without an argument." 899 return 1 900 } 901 902 # If the key file is an image don't check file header 903 [[ -r $TOMBKEYFILE ]] \ 904 && [[ $(file $TOMBKEYFILE) =~ "JP.G" ]] \ 905 && { 906 _message "Key is an image, it might be valid." 907 return 0 } 908 909 [[ $key =~ "BEGIN PGP" ]] && { 910 _message "Key is valid." 911 return 0 } 912 913 return 1 914 } 915 916 # $1 is a string containing an encrypted key 917 recover_key() { 918 local key="${1}" # Unique argument is an encrypted key 919 920 _warning "Attempting key recovery." 921 922 _head="${key[(f)1]}" # take the first line 923 924 TOMBKEY="" # Reset global variable 925 926 [[ $_head =~ "^_KDF_" ]] && TOMBKEY+="$_head\n" 927 928 TOMBKEY+="-----BEGIN PGP MESSAGE-----\n" 929 TOMBKEY+="$key\n" 930 TOMBKEY+="-----END PGP MESSAGE-----\n" 931 932 return 0 933 } 934 935 # Retrieve the tomb key from the file specified from the command line, 936 # or from stdin if -k - was selected. Run validity checks on the 937 # file. On success, return 0 and print out the full path of the key. 938 # Set global variables TOMBKEY and TOMBKEYFILE. 939 _load_key() { 940 local keyfile="$1" # Unique argument is an optional keyfile 941 942 [[ -z $keyfile ]] && keyfile=$(option_value -k) 943 [[ -z $keyfile ]] && { 944 _failure "This operation requires a key file to be specified using the -k option." } 945 946 if [[ $keyfile == "-" ]]; then 947 _verbose "load_key reading from stdin." 948 _message "Waiting for the key to be piped from stdin... " 949 TOMBKEYFILE=stdin 950 TOMBKEY=$(cat) 951 elif [[ $keyfile == "cleartext" ]]; then 952 _verbose "load_key reading SECRET from stdin" 953 _message "Waiting for the key to be piped from stdin... " 954 TOMBKEYFILE=cleartext 955 TOMBKEY=cleartext 956 TOMBSECRET=$(cat) 957 else 958 _verbose "load_key argument: ::1 key file::" $keyfile 959 [[ -r $keyfile ]] || _failure "Key not found, specify one using -k." 960 TOMBKEYFILE=$keyfile 961 TOMBKEY="${mapfile[$TOMBKEYFILE]}" 962 fi 963 964 _verbose "load_key: ::1 key::" $TOMBKEYFILE 965 966 [[ "$TOMBKEY" = "" ]] && { 967 # something went wrong, there is no key to load 968 # this occurs especially when piping from stdin and aborted 969 _failure "Key not found, specify one using -k." 970 } 971 972 is_valid_key $TOMBKEY || { 973 _warning "The key seems invalid or its format is not known by this version of Tomb." 974 recover_key $TOMBKEY 975 } 976 977 # Declared TOMBKEYFILE (path) 978 # Declared TOMBKEY (contents) 979 980 return 0 981 } 982 983 # takes two args just like get_lukskey 984 # prints out the decrypted content 985 # contains tweaks for different gpg versions 986 # support both symmetric and asymmetric encryption 987 gpg_decrypt() { 988 # fix for gpg 1.4.11 where the --status-* options don't work ;^/ 989 local gpgver=$(gpg --version --no-permission-warning | awk '/^gpg/ {print $3}') 990 local gpgpass="$1\n$TOMBKEY" 991 local tmpres ret 992 typeset -a gpgopt 993 gpgpopt=(--batch --no-tty --passphrase-fd 0 --no-options) 994 995 { option_is_set -g } && { 996 gpgpass="$TOMBKEY" 997 gpgpopt=(--yes) 998 999 # GPG option '--try-secret-key' exist since GPG 2.1 1000 { option_is_set -R } && [[ $gpgver =~ "2.1." ]] && { 1001 typeset -a recipients 1002 recipients=(${(s:,:)$(option_value -R)}) 1003 { is_valid_recipients $recipients } || { 1004 _failure "You set an invalid GPG ID." 1005 } 1006 gpgpopt+=(`_recipients_arg "--try-secret-key" $recipients`) 1007 } 1008 } 1009 1010 [[ $gpgver == "1.4.11" ]] && { 1011 _verbose "GnuPG is version 1.4.11 - adopting status fix." 1012 TOMBSECRET=`print - "$gpgpass" | \ 1013 gpg --decrypt ${gpgpopt[@]}` 1014 ret=$? 1015 unset gpgpass 1016 return $ret 1017 } 1018 1019 _tmp_create 1020 tmpres=$TOMBTMP 1021 TOMBSECRET=`print - "$gpgpass" | \ 1022 gpg --decrypt ${gpgpopt[@]} \ 1023 --status-fd 2 --no-mdc-warning --no-permission-warning \ 1024 --no-secmem-warning 2> $tmpres` 1025 unset gpgpass 1026 ret=1 1027 for i in ${(f)"$(cat $tmpres)"}; do 1028 _verbose "$i" 1029 [[ "$i" =~ "DECRYPTION_OKAY" ]] && ret=0; 1030 done 1031 return $ret 1032 1033 } 1034 1035 1036 # Gets a key file and a password, prints out the decoded contents to 1037 # be used directly by Luks as a cryptographic key 1038 get_lukskey() { 1039 # $1 is the password 1040 _verbose "get_lukskey" 1041 1042 _password="$1" 1043 1044 1045 firstline="${TOMBKEY[(f)1]}" 1046 1047 # key is KDF encoded 1048 if [[ $firstline =~ '^_KDF_' ]]; then 1049 kdf_hash="${firstline[(ws:_:)2]}" 1050 _verbose "KDF: ::1 kdf::" "$kdf_hash" 1051 case "$kdf_hash" in 1052 "pbkdf2sha1") 1053 kdf_salt="${firstline[(ws:_:)3]}" 1054 kdf_ic="${firstline[(ws:_:)4]}" 1055 kdf_len="${firstline[(ws:_:)5]}" 1056 _message "Unlocking KDF key protection (::1 kdf::)" $kdf_hash 1057 _verbose "KDF salt: $kdf_salt" 1058 _verbose "KDF ic: $kdf_ic" 1059 _verbose "KDF len: $kdf_len" 1060 _password=$(tomb-kdb-pbkdf2 $kdf_salt $kdf_ic $kdf_len 2>/dev/null <<<$_password) 1061 ;; 1062 *) 1063 _failure "No suitable program for KDF ::1 program::." $pbkdf_hash 1064 unset _password 1065 return 1 1066 ;; 1067 esac 1068 1069 # key needs to be exhumed from an image 1070 elif [[ -r $TOMBKEYFILE && $(file $TOMBKEYFILE) =~ "JP.G" ]]; then 1071 if option_is_set -g; then 1072 # When using a GPG key, the tomb key is buried using a steganography password 1073 if option_is_set --tomb-pwd; then 1074 _password="`option_value --tomb-pwd`" 1075 _verbose "tomb-pwd = ::1 tomb pass::" $_password 1076 else 1077 _password=$(ask_password "Insert password to exhume key from $imagefile") 1078 [[ $? != 0 ]] && { 1079 _warning "User aborted password dialog." 1080 return 1 1081 } 1082 fi 1083 exhume_key $TOMBKEYFILE "$_password" 1084 unset _password 1085 else 1086 exhume_key $TOMBKEYFILE "$_password" 1087 fi 1088 fi 1089 1090 gpg_decrypt "$_password" # Save decrypted contents into $TOMBSECRET 1091 1092 ret="$?" 1093 1094 _verbose "get_lukskey returns ::1::" $ret 1095 return $ret 1096 } 1097 1098 # This function asks the user for the password to use the key it tests 1099 # it against the return code of gpg on success returns 0 and saves 1100 # the password in the global variable $TOMBPASSWORD 1101 ask_key_password() { 1102 [[ -z "$TOMBKEYFILE" ]] && { 1103 _failure "Internal error: ask_key_password() called before _load_key()." } 1104 1105 [[ "$TOMBKEYFILE" = "cleartext" ]] && { 1106 _verbose "no password needed, using secret bytes from stdin" 1107 return 0 } 1108 1109 if option_is_set -g; then 1110 _verbose "no password needed, using GPG key" 1111 get_lukskey 1112 return $? 1113 fi 1114 1115 _message "A password is required to use key ::1 key::" $TOMBKEYFILE 1116 passok=0 1117 tombpass="" 1118 if [[ "$1" = "" ]]; then 1119 1120 for c in 1 2 3; do 1121 if [[ $c == 1 ]]; then 1122 tombpass=$(ask_password "Insert password to: $TOMBKEYFILE") 1123 else 1124 tombpass=$(ask_password "Insert password to: $TOMBKEYFILE (attempt $c)") 1125 fi 1126 [[ $? = 0 ]] || { 1127 _warning "User aborted password dialog." 1128 return 1 1129 } 1130 1131 get_lukskey "$tombpass" 1132 1133 [[ $? = 0 ]] && { 1134 passok=1; _message "Password OK." 1135 break; 1136 } 1137 done 1138 1139 else 1140 # if a second argument is present then the password is already known 1141 tombpass="$1" 1142 _verbose "ask_key_password with tombpass: ::1 tomb pass::" $tombpass 1143 1144 get_lukskey "$tombpass" 1145 1146 [[ $? = 0 ]] && { 1147 passok=1; _message "Password OK." 1148 } 1149 1150 fi 1151 [[ $passok == 1 ]] || return 1 1152 1153 TOMBPASSWORD=$tombpass 1154 return 0 1155 } 1156 1157 # call cryptsetup with arguments using the currently known secret 1158 # echo flags eliminate newline and disable escape (BSD_ECHO) 1159 _cryptsetup() { 1160 print -R -n - "$TOMBSECRET" | _sudo cryptsetup --key-file - ${@} 1161 return $? 1162 } 1163 1164 # change tomb key password 1165 change_passwd() { 1166 local tmpnewkey lukskey c tombpass tombpasstmp 1167 1168 _check_swap # Ensure swap is secure, if any 1169 _load_key # Try loading key from option -k and set TOMBKEYFILE 1170 1171 { option_is_set -g } && { 1172 _message "Commanded to change GnuPG key for tomb key ::1 key::" $TOMBKEYFILE 1173 } || { 1174 _message "Commanded to change password for tomb key ::1 key::" $TOMBKEYFILE 1175 } 1176 1177 _tmp_create 1178 tmpnewkey=$TOMBTMP 1179 1180 if option_is_set --tomb-old-pwd; then 1181 local tomboldpwd="`option_value --tomb-old-pwd`" 1182 _verbose "tomb-old-pwd = ::1 old pass::" $tomboldpwd 1183 ask_key_password "$tomboldpwd" 1184 else 1185 ask_key_password 1186 fi 1187 [[ $? == 0 ]] || _failure "No valid password supplied." 1188 1189 { option_is_set -g } && { 1190 _success "Changing GnuPG key for ::1 key file::" $TOMBKEYFILE 1191 } || { 1192 _success "Changing password for ::1 key file::" $TOMBKEYFILE 1193 } 1194 1195 # Here $TOMBSECRET contains the key material in clear 1196 1197 { option_is_set --tomb-pwd } && { 1198 local tombpwd="`option_value --tomb-pwd`" 1199 _verbose "tomb-pwd = ::1 new pass::" $tombpwd 1200 gen_key "$tombpwd" >> "$tmpnewkey" 1201 } || { 1202 gen_key >> "$tmpnewkey" 1203 } 1204 1205 { is_valid_key "${mapfile[$tmpnewkey]}" } || { 1206 _failure "Error: the newly generated keyfile does not seem valid." } 1207 1208 # Copy the new key as the original keyfile name 1209 cp -f "${tmpnewkey}" $TOMBKEYFILE 1210 { option_is_set -g } && { 1211 _success "Your GnuPG key was successfully changed" 1212 } || { 1213 _success "Your passphrase was successfully updated." 1214 } 1215 1216 return 0 1217 } 1218 1219 1220 # takes care to encrypt a key 1221 # honored options: --kdf --tomb-pwd -o -g -r 1222 gen_key() { 1223 # $1 the password to use; if not set ask user 1224 # -o is the --cipher-algo to use (string taken by GnuPG) 1225 local algopt="`option_value -o`" 1226 local algo="${algopt:-AES256}" 1227 local gpgpass opt 1228 local recipients_opt 1229 typeset -a gpgopt 1230 # here user is prompted for key password 1231 tombpass="" 1232 tombpasstmp="" 1233 1234 { option_is_set -g } && { 1235 gpgopt=(--encrypt) 1236 1237 { option_is_set -r || option_is_set -R } && { 1238 typeset -a recipients 1239 { option_is_set -r } && { 1240 recipients=(${(s:,:)$(option_value -r)}) 1241 recipients_opt="--recipient" 1242 } || { 1243 recipients=(${(s:,:)$(option_value -R)}) 1244 recipients_opt="--hidden-recipient" 1245 } 1246 1247 { is_valid_recipients $recipients } || { 1248 _failure "You set an invalid GPG ID." 1249 } 1250 1251 _warning "You are going to encrypt a tomb key with ::1 nrecipients:: recipient(s)." ${#recipients} 1252 _warning "It is your responsibility to check these fingerprints." 1253 _warning "The fingerprints are:" 1254 for gpg_id in ${recipients[@]}; do 1255 _warning " `_fingerprint "$gpg_id"`" 1256 done 1257 1258 gpgopt+=(`_recipients_arg "$recipients_opt" $recipients`) 1259 } || { 1260 _message "No recipient specified, using default GPG key." 1261 gpgopt+=("--default-recipient-self") 1262 } 1263 1264 # Set gpg inputs and options 1265 gpgpass="$TOMBSECRET" 1266 opt='' 1267 } || { 1268 if [ "$1" = "" ]; then 1269 while true; do 1270 # 3 tries to write two times a matching password 1271 tombpass=`ask_password "Type the new password to secure your key"` 1272 if [[ $? != 0 ]]; then 1273 _failure "User aborted." 1274 fi 1275 if [ -z $tombpass ]; then 1276 _failure "You set empty password, which is not possible." 1277 fi 1278 tombpasstmp=$tombpass 1279 tombpass=`ask_password "Type the new password to secure your key (again)"` 1280 if [[ $? != 0 ]]; then 1281 _failure "User aborted." 1282 fi 1283 if [ "$tombpasstmp" = "$tombpass" ]; then 1284 break; 1285 fi 1286 unset tombpasstmp 1287 unset tombpass 1288 done 1289 else 1290 tombpass="$1" 1291 _verbose "gen_key takes tombpass from CLI argument: ::1 tomb pass::" $tombpass 1292 fi 1293 1294 header="" 1295 [[ $KDF == 1 ]] && { 1296 { option_is_set --kdf } && { 1297 # KDF is a new key strenghtening technique against brute forcing 1298 # see: https://github.com/dyne/Tomb/issues/82 1299 itertime="`option_value --kdf`" 1300 # removing support of floating points because they can't be type checked well 1301 if [[ "$itertime" != <-> ]]; then 1302 unset tombpass 1303 unset tombpasstmp 1304 _warning "Wrong argument for --kdf: must be an integer number (iteration seconds)." 1305 _failure "Depending on the speed of machines using this tomb, use 1 to 10, or more" 1306 return 1 1307 fi 1308 # --kdf takes one parameter: iter time (on present machine) in seconds 1309 local -i microseconds 1310 microseconds=$(( itertime * 1000000 )) 1311 _success "Using KDF, iteration time: ::1 microseconds::" $microseconds 1312 _message "generating salt" 1313 pbkdf2_salt=`tomb-kdb-pbkdf2-gensalt` 1314 _message "calculating iterations" 1315 pbkdf2_iter=`tomb-kdb-pbkdf2-getiter $microseconds` 1316 _message "encoding the password" 1317 # We use a length of 64bytes = 512bits (more than needed!?) 1318 tombpass=`tomb-kdb-pbkdf2 $pbkdf2_salt $pbkdf2_iter 64 <<<"${tombpass}"` 1319 1320 header="_KDF_pbkdf2sha1_${pbkdf2_salt}_${pbkdf2_iter}_64\n" 1321 } 1322 } 1323 print $header 1324 1325 # Set gpg inputs and options 1326 gpgpass="${tombpass}\n$TOMBSECRET" 1327 gpgopt=(--passphrase-fd 0 --symmetric --no-options) 1328 opt='-n' 1329 } 1330 1331 _tmp_create 1332 local tmpres=$TOMBTMP 1333 print $opt - "$gpgpass" \ 1334 | gpg --openpgp --force-mdc --cipher-algo ${algo} \ 1335 --batch --no-tty ${gpgopt[@]} \ 1336 --status-fd 2 -o - --armor 2> $tmpres 1337 unset gpgpass 1338 # check result of gpg operation 1339 for i in ${(f)"$(cat $tmpres)"}; do 1340 _verbose "$i" 1341 done 1342 1343 # print -n "${tombpass}" \ 1344 # | gpg --openpgp --force-mdc --cipher-algo ${algo} \ 1345 # --batch --no-options --no-tty --passphrase-fd 0 --status-fd 2 \ 1346 # -o - -c -a ${lukskey} 1347 1348 TOMBPASSWORD="$tombpass" # Set global variable 1349 unset tombpass 1350 unset tombpasstmp 1351 } 1352 1353 # prints an array of ciphers available in gnupg (to encrypt keys) 1354 list_gnupg_ciphers() { 1355 # prints an error if GnuPG is not found 1356 which gpg 2>/dev/null || _failure "gpg (GnuPG) is not found, Tomb cannot function without it." 1357 1358 ciphers=(`gpg --version | awk ' 1359 BEGIN { ciphers=0 } 1360 /^Cipher:/ { gsub(/,/,""); sub(/^Cipher:/,""); print; ciphers=1; next } 1361 /^Hash:/ { ciphers=0 } 1362 { if(ciphers==0) { next } else { gsub(/,/,""); print; } } 1363 '`) 1364 print " ${ciphers}" 1365 return 1 1366 } 1367 1368 # Steganographic function to bury a key inside an image. 1369 # Requires steghide(1) to be installed 1370 bury_key() { 1371 1372 _load_key # Try loading key from option -k and set TOMBKEY 1373 1374 imagefile=$PARAM 1375 1376 [[ "`file $imagefile`" =~ "JPEG" ]] || { 1377 _warning "Encode failed: ::1 image file:: is not a jpeg image." $imagefile 1378 return 1 1379 } 1380 1381 _success "Encoding key ::1 tomb key:: inside image ::2 image file::" $TOMBKEY $imagefile 1382 { option_is_set -g } && { 1383 _message "Using GnuPG Key ID" 1384 } || { 1385 _message "Please confirm the key password for the encoding" 1386 } 1387 1388 # We ask the password and test if it is the same encoding the 1389 # base key, to insure that the same password is used for the 1390 # encryption and the steganography. This is a standard enforced 1391 # by Tomb, but it isn't strictly necessary (and having different 1392 # password would enhance security). Nevertheless here we prefer 1393 # usability. 1394 # However, steganography cannot be done with GPG key. Therefore, 1395 # if using a GPG key, we test if the user can decrypt the tomb 1396 # with its key and we ask for a steganography password. 1397 1398 { option_is_set --tomb-pwd } && { ! option_is_set -g } && { 1399 local tombpwd="`option_value --tomb-pwd`" 1400 _verbose "tomb-pwd = ::1 tomb pass::" $tombpwd 1401 ask_key_password "$tombpwd" 1402 } || { 1403 ask_key_password 1404 } 1405 [[ $? != 0 ]] && { 1406 _warning "Wrong password/GnuPG ID supplied." 1407 _failure "You shall not bury a key whose password is unknown to you." } 1408 1409 if option_is_set -g && option_is_set --tomb-pwd; then 1410 TOMBPASSWORD="`option_value --tomb-pwd`" 1411 _verbose "tomb-pwd = ::1 tomb pass::" $TOMBPASSWORD 1412 elif option_is_set -g; then 1413 tombpass="" 1414 tombpasstmp="" 1415 while true; do 1416 # 3 tries to write two times a matching password 1417 tombpass=`ask_password "Type a password to bury your key"` 1418 if [[ $? != 0 ]]; then 1419 _failure "User aborted." 1420 fi 1421 if [ -z $tombpass ]; then 1422 _failure "You set empty password, which is not possible." 1423 fi 1424 tombpasstmp=$tombpass 1425 tombpass=`ask_password "Type a password to bury your key (again)"` 1426 if [[ $? != 0 ]]; then 1427 _failure "User aborted." 1428 fi 1429 if [ "$tombpasstmp" = "$tombpass" ]; then 1430 break; 1431 fi 1432 unset tombpasstmp 1433 unset tombpass 1434 done 1435 TOMBPASSWORD="$tombpass" 1436 fi 1437 1438 # We omit armor strings since having them as constants can give 1439 # ground to effective attacks on steganography 1440 print - "$TOMBKEY" | awk ' 1441 /^-----/ {next} 1442 /^Version/ {next} 1443 {print $0}' \ 1444 | steghide embed --embedfile - --coverfile ${imagefile} \ 1445 -p $TOMBPASSWORD -z 9 -e serpent cbc 1446 if [ $? != 0 ]; then 1447 _warning "Encoding error: steghide reports problems." 1448 res=1 1449 else 1450 _success "Tomb key encoded succesfully into image ::1 image file::" $imagefile 1451 res=0 1452 fi 1453 1454 return $res 1455 } 1456 1457 # mandatory 1st arg: the image file where key is supposed to be 1458 # optional 2nd arg: the password to use (same as key, internal use) 1459 # optional 3rd arg: the key where to save the result (- for stdout) 1460 exhume_key() { 1461 [[ "$1" = "" ]] && { 1462 _failure "Exhume failed, no image specified" } 1463 1464 local imagefile="$1" # The image file where to look for the key 1465 local tombpass="$2" # (Optional) the password to use (internal use) 1466 local destkey="$3" # (Optional) the key file where to save the 1467 # result (- for stdout) 1468 local r=1 # Return code (default: fail) 1469 1470 # Ensure the image file is a readable JPEG 1471 [[ ! -r $imagefile ]] && { 1472 _failure "Exhume failed, image file not found: ::1 image file::" "${imagefile:-none}" } 1473 [[ ! $(file "$imagefile") =~ "JP.G" ]] && { 1474 _failure "Exhume failed: ::1 image file:: is not a jpeg image." $imagefile } 1475 1476 # When a password is passed as argument then always print out 1477 # the exhumed key on stdout without further checks (internal use) 1478 [[ -n "$tombpass" ]] && { 1479 TOMBKEY=$(steghide extract -sf $imagefile -p $tombpass -xf -) 1480 [[ $? != 0 ]] && { 1481 _failure "Wrong password or no steganographic key found" } 1482 1483 recover_key $TOMBKEY 1484 1485 return 0 1486 } 1487 1488 # Ensure we have a valid destination for the key 1489 [[ -z $destkey ]] && { option_is_set -k } && destkey=$(option_value -k) 1490 [[ -z $destkey ]] && { 1491 destkey="-" # No key was specified: fallback to stdout 1492 _message "printing exhumed key on stdout" } 1493 1494 # Bail out if destination exists, unless -f (force) was passed 1495 [[ $destkey != "-" && -s $destkey ]] && { 1496 _warning "File exists: ::1 tomb key::" $destkey 1497 { option_is_set -f } && { 1498 _warning "Use of --force selected: overwriting." 1499 rm -f $destkey 1500 } || { 1501 _warning "Make explicit use of --force to overwrite." 1502 _failure "Refusing to overwrite file. Operation aborted." } 1503 } 1504 1505 _message "Trying to exhume a key out of image ::1 image file::" $imagefile 1506 { option_is_set --tomb-pwd } && { 1507 tombpass=$(option_value --tomb-pwd) 1508 _verbose "tomb-pwd = ::1 tomb pass::" $tombpass 1509 } || { 1510 [[ -n $TOMBPASSWORD ]] && tombpass=$TOMBPASSWORD 1511 } || { 1512 tombpass=$(ask_password "Insert password to exhume key from $imagefile") 1513 [[ $? != 0 ]] && { 1514 _warning "User aborted password dialog." 1515 return 1 1516 } 1517 } 1518 1519 # Extract the key from the image 1520 steghide extract -sf $imagefile -p ${tombpass} -xf $destkey 1521 r=$? 1522 1523 # Report to the user 1524 [[ "$destkey" = "-" ]] && destkey="stdout" 1525 [[ $r == 0 ]] && { 1526 _success "Key succesfully exhumed to ::1 key::." $destkey 1527 } || { 1528 _warning "Nothing found in ::1 image file::" $imagefile 1529 } 1530 1531 return $r 1532 } 1533 1534 # Produces a printable image of the key contents so a backup on paper 1535 # can be made and hidden in books etc. 1536 engrave_key() { 1537 1538 _load_key # Try loading key from option -k and set TOMBKEYFILE 1539 1540 local keyname=$(basename $TOMBKEYFILE) 1541 local pngname="$keyname.qr.png" 1542 1543 _success "Rendering a printable QRCode for key: ::1 tomb key file::" $TOMBKEYFILE 1544 # we omit armor strings to save space 1545 awk '/^-----/ {next}; /^Version/ {next}; {print $0}' $TOMBKEYFILE \ 1546 | qrencode --size 4 --level H --casesensitive -o $pngname 1547 [[ $? != 0 ]] && { 1548 _failure "QREncode reported an error." } 1549 1550 _success "Operation successful:" 1551 # TODO: only if verbose and/or not silent 1552 ls -lh $pngname 1553 file $pngname 1554 } 1555 1556 # }}} - Key handling 1557 1558 # {{{ Create 1559 1560 # Since version 1.5.3, tomb creation is a three-step process that replaces create_tomb(): 1561 # 1562 # * dig a .tomb (the large file) using /dev/urandom (takes some minutes at least) 1563 # 1564 # * forge a .key (the small file) using /dev/random (good entropy needed) 1565 # 1566 # * lock the .tomb file with the key, binding the key to the tomb (requires dm_crypt format) 1567 1568 # Step one - Dig a tomb 1569 # 1570 # Synopsis: dig_tomb /path/to/tomb -s sizemebibytes 1571 # 1572 # It will create an empty file to be formatted as a loopback 1573 # filesystem. Initially the file is filled with random data taken 1574 # from /dev/urandom to improve overall tomb's security and prevent 1575 # some attacks aiming at detecting how much data is in the tomb, or 1576 # which blocks in the filesystem contain that data. 1577 1578 dig_tomb() { 1579 local tombpath="$1" # Path to tomb 1580 # Require the specification of the size of the tomb (-s) in MiB 1581 local -i tombsize=$(option_value -s) 1582 1583 _message "Commanded to dig tomb ::1 tomb path::" $tombpath 1584 1585 [[ -n "$tombpath" ]] || _failure "Missing path to tomb" 1586 [[ -n "$tombsize" ]] || _failure "Size argument missing, use -s" 1587 [[ $tombsize == <-> ]] || _failure "Size must be an integer (mebibytes)" 1588 [[ $tombsize -ge 10 ]] || _failure "Tombs can't be smaller than 10 mebibytes" 1589 1590 _plot $tombpath # Set TOMB{PATH,DIR,FILE,NAME} 1591 1592 [[ -e $TOMBPATH ]] && { 1593 _warning "A tomb exists already. I'm not digging here:" 1594 ls -lh $TOMBPATH 1595 return 1 1596 } 1597 1598 _success "Creating a new tomb in ::1 tomb path::" $TOMBPATH 1599 1600 _message "Generating ::1 tomb file:: of ::2 size::MiB" $TOMBFILE $tombsize 1601 1602 # Ensure that file permissions are safe even if interrupted 1603 touch $TOMBPATH 1604 [[ $? = 0 ]] || { 1605 _warning "Error creating the tomb ::1 tomb path::" $TOMBPATH 1606 _failure "Operation aborted." 1607 } 1608 chmod 0600 $TOMBPATH 1609 1610 _verbose "Data dump using ::1:: from /dev/urandom" ${DD[1]} 1611 ${=DD} if=/dev/urandom bs=1048576 count=$tombsize of=$TOMBPATH 1612 1613 [[ $? == 0 && -e $TOMBPATH ]] && { 1614 ls -lh $TOMBPATH 1615 } || { 1616 _warning "Error creating the tomb ::1 tomb path::" $TOMBPATH 1617 _failure "Operation aborted." 1618 } 1619 1620 _success "Done digging ::1 tomb name::" $TOMBNAME 1621 _message "Your tomb is not yet ready, you need to forge a key and lock it:" 1622 _message "tomb forge ::1 tomb path::.key" $TOMBPATH 1623 _message "tomb lock ::1 tomb path:: -k ::1 tomb path::.key" $TOMBPATH 1624 1625 return 0 1626 } 1627 1628 # Step two -- Create a detached key to lock a tomb with 1629 # 1630 # Synopsis: forge_key [destkey|-k destkey] [-o cipher] [-r|-R gpgid] 1631 # 1632 # Arguments: 1633 # -k path to destination keyfile 1634 # -o Use an alternate algorithm 1635 # -r GPG recipients to be used 1636 # 1637 forge_key() { 1638 # can be specified both as simple argument or using -k 1639 local destkey="$1" 1640 { option_is_set -k } && { destkey=$(option_value -k) } 1641 1642 local algo="AES256" # Default encryption algorithm 1643 1644 [[ -z "$destkey" ]] && { 1645 _failure "A filename needs to be specified using -k to forge a new key." } 1646 1647 # _message "Commanded to forge key ::1 key::" $destkey 1648 1649 _check_swap # Ensure the available memory is safe to use 1650 1651 # Ensure GnuPG won't exit with an error before first run 1652 [[ -r $HOME/.gnupg/pubring.gpg ]] || { 1653 mkdir -m 0700 $HOME/.gnupg 1654 touch $HOME/.gnupg/pubring.gpg } 1655 1656 # Do not overwrite any files accidentally 1657 [[ -r "$destkey" ]] && { 1658 ls -lh $destkey 1659 _failure "Forging this key would overwrite an existing file. Operation aborted." } 1660 1661 touch $destkey 1662 [[ $? == 0 ]] || { 1663 _warning "Cannot generate encryption key." 1664 _failure "Operation aborted." } 1665 chmod 0600 $destkey 1666 1667 # Update algorithm if it was passed on the command line with -o 1668 { option_is_set -o } && algopt="$(option_value -o)" 1669 [[ -n "$algopt" ]] && algo=$algopt 1670 1671 _message "Commanded to forge key ::1 key:: with cipher algorithm ::2 algorithm::" \ 1672 $destkey $algo 1673 1674 [[ $KDF == 1 ]] && { ! option_is_set -g } && { 1675 _message "Using KDF to protect the key password (`option_value --kdf` rounds)" 1676 } 1677 1678 TOMBKEYFILE="$destkey" # Set global variable 1679 1680 _warning "This operation takes time. Keep using this computer on other tasks." 1681 _warning "Once done you will be asked to choose a password for your tomb." 1682 _warning "To make it faster you can move the mouse around." 1683 _warning "If you are on a server, you can use an Entropy Generation Daemon." 1684 1685 # Use /dev/random as the entropy source, unless --use-urandom is specified 1686 local random_source=/dev/random 1687 { option_is_set --use-urandom } && random_source=/dev/urandom 1688 1689 _verbose "Data dump using ::1:: from ::2 source::" ${DD[1]} $random_source 1690 TOMBSECRET=$(${=DD} bs=1 count=512 if=$random_source) 1691 [[ $? == 0 ]] || { 1692 _warning "Cannot generate encryption key." 1693 _failure "Operation aborted." } 1694 1695 # Here the global variable TOMBSECRET contains the naked secret 1696 1697 { option_is_set -g } && { 1698 _success "Using GnuPG key(s) to encrypt your key: ::1 tomb key::" $TOMBKEYFILE 1699 } || { 1700 _success "Choose the password of your key: ::1 tomb key::" $TOMBKEYFILE 1701 } 1702 _message "(You can also change it later using 'tomb passwd'.)" 1703 # _user_file $TOMBKEYFILE 1704 1705 tombname="$TOMBKEYFILE" # XXX ??? 1706 # the gen_key() function takes care of the new key's encryption 1707 { option_is_set --tomb-pwd } && { 1708 local tombpwd="`option_value --tomb-pwd`" 1709 _verbose "tomb-pwd = ::1 new pass::" $tombpwd 1710 gen_key "$tombpwd" >> $TOMBKEYFILE 1711 } || { 1712 gen_key >> $TOMBKEYFILE 1713 } 1714 1715 # load the key contents (set global variable) 1716 TOMBKEY="${mapfile[$TOMBKEYFILE]}" 1717 1718 # this does a check on the file header 1719 is_valid_key $TOMBKEY || { 1720 _warning "The key does not seem to be valid." 1721 _warning "Dumping contents to screen:" 1722 print "${mapfile[$TOMBKEY]}" 1723 _warning "--" 1724 _sudo umount ${keytmp} 1725 rm -r $keytmp 1726 _failure "Operation aborted." 1727 } 1728 1729 _message "Done forging ::1 key file::" $TOMBKEYFILE 1730 _success "Your key is ready:" 1731 ls -lh $TOMBKEYFILE 1732 } 1733 1734 # Step three -- Lock tomb 1735 # 1736 # Synopsis: tomb_lock file.tomb file.tomb.key [-o cipher] [-r gpgid] 1737 # 1738 # Lock the given tomb with the given key file, in fact formatting the 1739 # loopback volume as a LUKS device. 1740 # Default cipher 'aes-xts-plain64:sha256'can be overridden with -o 1741 lock_tomb_with_key() { 1742 # old default was aes-cbc-essiv:sha256 1743 # Override with -o 1744 # for more alternatives refer to cryptsetup(8) 1745 local cipher="aes-xts-plain64:sha256" 1746 1747 local tombpath="$1" # First argument is the path to the tomb 1748 1749 [[ -n $tombpath ]] || { 1750 _warning "No tomb specified for locking." 1751 _warning "Usage: tomb lock file.tomb file.tomb.key" 1752 return 1 1753 } 1754 1755 _plot $tombpath 1756 1757 _message "Commanded to lock tomb ::1 tomb file::" $TOMBFILE 1758 1759 [[ -f $TOMBPATH ]] || { 1760 _failure "There is no tomb here. You have to dig it first." } 1761 1762 _verbose "Tomb found: ::1 tomb path::" $TOMBPATH 1763 1764 lo_mount $TOMBPATH 1765 nstloop=`lo_new` 1766 1767 _verbose "Loop mounted on ::1 mount point::" $nstloop 1768 1769 _message "Checking if the tomb is empty (we never step on somebody else's bones)." 1770 _sudo cryptsetup isLuks ${nstloop} 1771 if [ $? = 0 ]; then 1772 # is it a LUKS encrypted nest? then bail out and avoid reformatting it 1773 _warning "The tomb was already locked with another key." 1774 _failure "Operation aborted. I cannot lock an already locked tomb. Go dig a new one." 1775 else 1776 _message "Fine, this tomb seems empty." 1777 fi 1778 1779 _load_key # Try loading key from option -k and set TOMBKEYFILE 1780 1781 # the encryption cipher for a tomb can be set when locking using -c 1782 { option_is_set -o } && algopt="$(option_value -o)" 1783 [[ -n "$algopt" ]] && cipher=$algopt 1784 _message "Locking using cipher: ::1 cipher::" $cipher 1785 1786 # get the pass from the user and check it 1787 if option_is_set --tomb-pwd; then 1788 tomb_pwd="`option_value --tomb-pwd`" 1789 _verbose "tomb-pwd = ::1 tomb pass::" $tomb_pwd 1790 ask_key_password "$tomb_pwd" 1791 else 1792 ask_key_password 1793 fi 1794 [[ $? == 0 ]] || _failure "No valid password supplied." 1795 1796 _success "Locking ::1 tomb file:: with ::2 tomb key file::" $TOMBFILE $TOMBKEYFILE 1797 1798 _message "Formatting Luks mapped device." 1799 _cryptsetup --batch-mode \ 1800 --cipher ${cipher} --key-size 512 --key-slot 0 \ 1801 luksFormat ${nstloop} 1802 [[ $? == 0 ]] || { 1803 _warning "cryptsetup luksFormat returned an error." 1804 _failure "Operation aborted." } 1805 1806 _cryptsetup --cipher ${cipher} luksOpen ${nstloop} tomb.tmp 1807 [[ $? == 0 ]] || { 1808 _warning "cryptsetup luksOpen returned an error." 1809 _failure "Operation aborted." } 1810 1811 _message "Formatting your Tomb with Ext3/Ext4 filesystem." 1812 _sudo mkfs.ext4 -q -F -j -L $TOMBNAME /dev/mapper/tomb.tmp 1813 1814 [[ $? == 0 ]] || { 1815 _warning "Tomb format returned an error." 1816 _warning "Your tomb ::1 tomb file:: may be corrupted." $TOMBFILE } 1817 1818 # Sync 1819 _sudo cryptsetup luksClose tomb.tmp 1820 1821 _message "Done locking ::1 tomb name:: using Luks dm-crypt ::2 cipher::" $TOMBNAME $cipher 1822 _success "Your tomb is ready in ::1 tomb path:: and secured with key ::2 tomb key::" \ 1823 $TOMBPATH $TOMBKEYFILE 1824 1825 } 1826 1827 # This function changes the key that locks a tomb 1828 change_tomb_key() { 1829 local tombkey="$1" # Path to the tomb's key file 1830 local tombpath="$2" # Path to the tomb 1831 1832 _message "Commanded to reset key for tomb ::1 tomb path::" $tombpath 1833 1834 [[ -z "$tombpath" ]] && { 1835 _warning "Command 'setkey' needs two arguments: the old key file and the tomb." 1836 _warning "I.e: tomb -k new.tomb.key old.tomb.key secret.tomb" 1837 _failure "Execution aborted." 1838 } 1839 1840 _check_swap 1841 1842 # this also calls _plot() 1843 is_valid_tomb $tombpath 1844 1845 lo_mount $TOMBPATH 1846 nstloop=`lo_new` 1847 _sudo cryptsetup isLuks ${nstloop} 1848 # is it a LUKS encrypted nest? we check one more time 1849 [[ $? == 0 ]] || { 1850 _failure "Not a valid LUKS encrypted volume: ::1 volume::" $TOMBPATH } 1851 1852 _load_key $tombkey # Try loading given key and set TOMBKEY and 1853 # TOMBKEYFILE 1854 local oldkey=$TOMBKEY 1855 local oldkeyfile=$TOMBKEYFILE 1856 1857 # we have everything, prepare to mount 1858 _success "Changing lock on tomb ::1 tomb name::" $TOMBNAME 1859 _message "Old key: ::1 old key::" $oldkeyfile 1860 1861 # render the mapper 1862 mapdate=`date +%s` 1863 # save date of mount in minutes since 1970 1864 mapper="tomb.$TOMBNAME.$mapdate.$(basename $nstloop)" 1865 1866 # load the old key 1867 if option_is_set --tomb-old-pwd; then 1868 tomb_old_pwd="`option_value --tomb-old-pwd`" 1869 _verbose "tomb-old-pwd = ::1 old pass::" $tomb_old_pwd 1870 ask_key_password "$tomb_old_pwd" 1871 else 1872 ask_key_password 1873 fi 1874 [[ $? == 0 ]] || { 1875 _failure "No valid password supplied for the old key." } 1876 old_secret=$TOMBSECRET 1877 1878 # luksOpen the tomb (not really mounting, just on the loopback) 1879 print -R -n - "$old_secret" | _sudo cryptsetup --key-file - \ 1880 luksOpen ${nstloop} ${mapper} 1881 [[ $? == 0 ]] || _failure "Unexpected error in luksOpen." 1882 1883 _load_key # Try loading new key from option -k and set TOMBKEYFILE 1884 1885 _message "New key: ::1 key file::" $TOMBKEYFILE 1886 1887 if option_is_set --tomb-pwd; then 1888 tomb_new_pwd="`option_value --tomb-pwd`" 1889 _verbose "tomb-pwd = ::1 tomb pass::" $tomb_new_pwd 1890 ask_key_password "$tomb_new_pwd" 1891 else 1892 ask_key_password 1893 fi 1894 [[ $? == 0 ]] || { 1895 _failure "No valid password supplied for the new key." } 1896 1897 _tmp_create 1898 tmpnewkey=$TOMBTMP 1899 print -R -n - "$TOMBSECRET" >> $tmpnewkey 1900 1901 print -R -n - "$old_secret" | _sudo cryptsetup --key-file - \ 1902 luksChangeKey "$nstloop" "$tmpnewkey" 1903 1904 [[ $? == 0 ]] || _failure "Unexpected error in luksChangeKey." 1905 1906 _sudo cryptsetup luksClose "${mapper}" || _failure "Unexpected error in luksClose." 1907 1908 _success "Succesfully changed key for tomb: ::1 tomb file::" $TOMBFILE 1909 _message "The new key is: ::1 new key::" $TOMBKEYFILE 1910 1911 return 0 1912 } 1913 1914 # }}} - Creation 1915 1916 # {{{ Open 1917 1918 # $1 = tombfile $2(optional) = mountpoint 1919 mount_tomb() { 1920 local tombpath="$1" # First argument is the path to the tomb 1921 [[ -n "$tombpath" ]] || _failure "No tomb name specified for opening." 1922 1923 _message "Commanded to open tomb ::1 tomb name::" $tombpath 1924 1925 _check_swap 1926 1927 # this also calls _plot() 1928 is_valid_tomb $tombpath 1929 1930 _load_key # Try loading new key from option -k and set TOMBKEYFILE 1931 1932 tombmount="$2" 1933 [[ "$tombmount" = "" ]] && { 1934 tombmount=/media/$TOMBNAME 1935 [[ -d /media ]] || { # no /media found, adopting /run/media/$USER (udisks2 compat) 1936 tombmount=/run/media/$_USER/$TOMBNAME 1937 } 1938 _message "Mountpoint not specified, using default: ::1 mount point::" $tombmount 1939 } 1940 1941 _success "Opening ::1 tomb file:: on ::2 mount point::" $TOMBNAME $tombmount 1942 1943 lo_mount $TOMBPATH 1944 nstloop=`lo_new` 1945 1946 _sudo cryptsetup isLuks ${nstloop} || { 1947 # is it a LUKS encrypted nest? see cryptsetup(1) 1948 _failure "::1 tomb file:: is not a valid Luks encrypted storage file." $TOMBFILE } 1949 1950 _message "This tomb is a valid LUKS encrypted device." 1951 1952 luksdump="`_sudo cryptsetup luksDump ${nstloop}`" 1953 tombdump=(`print $luksdump | awk ' 1954 /^Cipher name/ {print $3} 1955 /^Cipher mode/ {print $3} 1956 /^Hash spec/ {print $3}'`) 1957 _message "Cipher is \"::1 cipher::\" mode \"::2 mode::\" hash \"::3 hash::\"" $tombdump[1] $tombdump[2] $tombdump[3] 1958 1959 slotwarn=`print $luksdump | awk ' 1960 BEGIN { zero=0 } 1961 /^Key slot 0/ { zero=1 } 1962 /^Key slot.*ENABLED/ { if(zero==1) print "WARN" }'` 1963 [[ "$slotwarn" == "WARN" ]] && { 1964 _warning "Multiple key slots are enabled on this tomb. Beware: there can be a backdoor." } 1965 1966 # save date of mount in minutes since 1970 1967 mapdate=`date +%s` 1968 1969 mapper="tomb.$TOMBNAME.$mapdate.$(basename $nstloop)" 1970 1971 _verbose "dev mapper device: ::1 mapper::" $mapper 1972 _verbose "Tomb key: ::1 key file::" $TOMBKEYFILE 1973 1974 # take the name only, strip extensions 1975 _verbose "Tomb name: ::1 tomb name:: (to be engraved)" $TOMBNAME 1976 1977 { option_is_set --tomb-pwd } && { ! option_is_set -g } && { 1978 tomb_pwd="`option_value --tomb-pwd`" 1979 _verbose "tomb-pwd = ::1 tomb pass::" $tomb_pwd 1980 ask_key_password "$tomb_pwd" 1981 } || { 1982 ask_key_password 1983 } 1984 [[ $? == 0 ]] || _failure "No valid password supplied." 1985 1986 _cryptsetup luksOpen ${nstloop} ${mapper} 1987 [[ $? = 0 ]] || { 1988 _failure "Failure mounting the encrypted file." } 1989 1990 # preserve the loopdev after exit 1991 lo_preserve "$nstloop" 1992 1993 # array: [ cipher, keysize, loopdevice ] 1994 tombstat=(`_sudo cryptsetup status ${mapper} | awk ' 1995 /cipher:/ {print $2} 1996 /keysize:/ {print $2} 1997 /device:/ {print $2}'`) 1998 _success "Success unlocking tomb ::1 tomb name::" $TOMBNAME 1999 _verbose "Key size is ::1 size:: for cipher ::2 cipher::" $tombstat[2] $tombstat[1] 2000 2001 _message "Checking filesystem via ::1::" $tombstat[3] 2002 _sudo fsck -p -C0 /dev/mapper/${mapper} 2003 _verbose "Tomb engraved as ::1 tomb name::" $TOMBNAME 2004 _sudo tune2fs -L $TOMBNAME /dev/mapper/${mapper} > /dev/null 2005 2006 # we need root from here on 2007 _sudo mkdir -p $tombmount 2008 2009 # Default mount options are overridden with the -o switch 2010 { option_is_set -o } && { 2011 local oldmountopts=$MOUNTOPTS 2012 MOUNTOPTS="$(option_value -o)" } 2013 2014 # TODO: safety check MOUNTOPTS 2015 # safe_mount_options && \ 2016 _sudo mount -o $MOUNTOPTS /dev/mapper/${mapper} ${tombmount} 2017 # Clean up if the mount failed 2018 [[ $? == 0 ]] || { 2019 _warning "Error mounting ::1 mapper:: on ::2 tombmount::" $mapper $tombmount 2020 [[ $oldmountopts != $MOUNTOPTS ]] && \ 2021 _warning "Are mount options '::1 mount options::' valid?" $MOUNTOPTS 2022 # TODO: move cleanup to _endgame() 2023 [[ -d $tombmount ]] && _sudo rmdir $tombmount 2024 [[ -e /dev/mapper/$mapper ]] && _sudo cryptsetup luksClose $mapper 2025 # The loop is taken care of in _endgame() 2026 _failure "Cannot mount ::1 tomb name::" $TOMBNAME 2027 } 2028 2029 # we do not change ownership anymore when mounting tombs 2030 # _sudo chown $UID:$GID ${tombmount} 2031 # _sudo chmod 0711 ${tombmount} 2032 2033 _success "Success opening ::1 tomb file:: on ::2 mount point::" $TOMBFILE $tombmount 2034 2035 local tombtty tombhost tombuid tombuser 2036 2037 # print out when it was opened the last time, by whom and where 2038 [[ -r ${tombmount}/.last ]] && { 2039 tombsince=$(_cat ${tombmount}/.last) 2040 tombsince=$(date --date=@$tombsince +%c) 2041 tombtty=$(_cat ${tombmount}/.tty) 2042 tombhost=$(_cat ${tombmount}/.host) 2043 tomblast=$(_cat ${tombmount}/.last) 2044 tombuid=$(_cat ${tombmount}/.uid | tr -d ' ') 2045 2046 tombuser=$(getent passwd $tombuid) 2047 tombuser=${tombuser[(ws@:@)1]} 2048 2049 _message "Last visit by ::1 user::(::2 tomb build::) from ::3 tty:: on ::4 host::" $tombuser $tombuid $tombtty $tombhost 2050 _message "on date ::1 date::" $tombsince 2051 } 2052 # write down the UID and TTY that opened the tomb 2053 rm -f ${tombmount}/.uid 2054 print $_UID > ${tombmount}/.uid 2055 rm -f ${tombmount}/.tty 2056 print $_TTY > ${tombmount}/.tty 2057 # also the hostname 2058 rm -f ${tombmount}/.host 2059 hostname > ${tombmount}/.host 2060 # and the "last time opened" information 2061 # in minutes since 1970, this is printed at next open 2062 rm -f ${tombmount}/.last 2063 date +%s > ${tombmount}/.last 2064 # human readable: date --date=@"`cat .last`" +%c 2065 2066 2067 # process bind-hooks (mount -o bind of directories) 2068 # and exec-hooks (execute on open) 2069 option_is_set -n || { 2070 exec_safe_bind_hooks ${tombmount} 2071 exec_safe_func_hooks open ${tombmount} 2072 } 2073 2074 return 0 2075 } 2076 2077 ## HOOKS EXECUTION 2078 # 2079 # Execution of code inside a tomb may present a security risk, e.g., 2080 # if the tomb is shared or compromised, an attacker could embed 2081 # malicious code. When in doubt, open the tomb with the -n switch in 2082 # order to skip this feature and verify the files mount-hooks and 2083 # bind-hooks inside the tomb yourself before letting them run. 2084 2085 # Mount files and directories from the tomb to the current user's HOME. 2086 # 2087 # Synopsis: exec_safe_bind_hooks /path/to/mounted/tomb 2088 # 2089 # This can be a security risk if you share tombs with untrusted people. 2090 # In that case, use the -n switch to turn off this feature. 2091 exec_safe_bind_hooks() { 2092 local mnt="$1" # First argument is the mount point of the tomb 2093 2094 # Default mount options are overridden with the -o switch 2095 [[ -n ${(k)OPTS[-o]} ]] && MOUNTOPTS=${OPTS[-o]} 2096 2097 # No HOME set? Note: this should never happen again. 2098 [[ -z $HOME ]] && { 2099 _warning "How pitiful! A tomb, and no HOME." 2100 return 1 } 2101 2102 [[ -z $mnt || ! -d $mnt ]] && { 2103 _warning "Cannot exec bind hooks without a mounted tomb." 2104 return 1 } 2105 2106 [[ -r "$mnt/bind-hooks" ]] || { 2107 _verbose "bind-hooks not found in ::1 mount point::" $mnt 2108 return 0 } 2109 2110 typeset -Al maps # Maps of files and directories to mount 2111 typeset -al mounted # Track already mounted files and directories 2112 2113 # better parsing for bind hooks checks for two separated words on 2114 # each line, using zsh word separator array subscript 2115 _bindhooks="${mapfile[${mnt}/bind-hooks]}" 2116 for h in ${(f)_bindhooks}; do 2117 s="${h[(w)1]}" 2118 d="${h[(w)2]}" 2119 [[ "$s" = "" ]] && { _warning "bind-hooks file is broken"; return 1 } 2120 [[ "$d" = "" ]] && { _warning "bind-hooks file is broken"; return 1 } 2121 maps+=($s $d) 2122 _verbose "bind-hook found: $s -> $d" 2123 done 2124 unset _bindhooks 2125 2126 for dir in ${(k)maps}; do 2127 [[ "${dir[1]}" == "/" || "${dir[1,2]}" == ".." ]] && { 2128 _warning "bind-hooks map format: local/to/tomb local/to/\$HOME" 2129 continue } 2130 2131 [[ "${${maps[$dir]}[1]}" == "/" || "${${maps[$dir]}[1,2]}" == ".." ]] && { 2132 _warning "bind-hooks map format: local/to/tomb local/to/\$HOME. Rolling back" 2133 for dir in ${mounted}; do _sudo umount $dir; done 2134 return 0 } 2135 2136 if [[ ! -r "$HOME/${maps[$dir]}" ]]; then 2137 _warning "bind-hook target not existent, skipping ::1 home::/::2 subdir::" $HOME ${maps[$dir]} 2138 elif [[ ! -r "$mnt/$dir" ]]; then 2139 _warning "bind-hook source not found in tomb, skipping ::1 mount point::/::2 subdir::" $mnt $dir 2140 else 2141 _sudo mount -o bind,$MOUNTOPTS $mnt/$dir $HOME/${maps[$dir]} \ 2142 && mounted+=("$HOME/${maps[$dir]}") 2143 fi 2144 done 2145 } 2146 2147 # Execute automated actions configured in the tomb. 2148 # 2149 # Synopsis: exec_safe_func_hooks /path/to/mounted/tomb 2150 # 2151 # If an executable file named 'exec-hooks' is found inside the tomb, 2152 # run it as a user. This might need a dialog for security on what is 2153 # being run, however we expect you know well what is inside your tomb. 2154 # If you're mounting an untrusted tomb, be safe and use the -n switch 2155 # to verify what it would run if you let it. This feature opens the 2156 # possibility to make encrypted executables. 2157 exec_safe_func_hooks() { 2158 # Only run if post-hooks has the executable bit set 2159 [[ -x $mnt/exec-hooks ]] && { 2160 _success "Exec hook: ::1 exec hook:: ::2 action:: ::3 argument::" \ 2161 "${mnt}/exec-hooks" "$1" "$2" 2162 $mnt/exec-hooks "$1" "$2" 2163 return $? 2164 } 2165 return 0 2166 } 2167 2168 # }}} - Tomb open 2169 2170 # {{{ List 2171 2172 # list all tombs mounted in a readable format 2173 # $1 is optional, to specify a tomb 2174 list_tombs() { 2175 2176 local tombname tombmount tombfs tombfsopts tombloop 2177 local ts tombtot tombused tombavail tombpercent tombp tombsince 2178 local tombtty tombhost tombuid tombuser 2179 # list all open tombs 2180 mounted_tombs=(`list_tomb_mounts $1`) 2181 [[ ${#mounted_tombs} == 0 ]] && { 2182 _failure "I can't see any ::1 status:: tomb, may they all rest in peace." ${1:-open} } 2183 2184 for t in ${mounted_tombs}; do 2185 mapper=`basename ${t[(ws:;:)1]}` 2186 tombname=${t[(ws:;:)5]} 2187 tombmount=${t[(ws:;:)2]} 2188 tombfs=${t[(ws:;:)3]} 2189 tombfsopts=${t[(ws:;:)4]} 2190 tombloop=${mapper[(ws:.:)4]} 2191 2192 # calculate tomb size 2193 ts=`df -hP /dev/mapper/$mapper | 2194 awk "/mapper/"' { print $2 ";" $3 ";" $4 ";" $5 }'` 2195 tombtot=${ts[(ws:;:)1]} 2196 tombused=${ts[(ws:;:)2]} 2197 tombavail=${ts[(ws:;:)3]} 2198 tombpercent=${ts[(ws:;:)4]} 2199 tombp=${tombpercent%%%} 2200 2201 # obsolete way to get the last open date from /dev/mapper 2202 # which doesn't work when tomb filename contain dots 2203 # tombsince=`date --date=@${mapper[(ws:.:)3]} +%c` 2204 2205 # find out who opens it from where 2206 [[ -r ${tombmount}/.tty ]] && { 2207 tombsince=$(_cat ${tombmount}/.last) 2208 tombsince=$(date --date=@$tombsince +%c) 2209 tombtty=$(_cat ${tombmount}/.tty) 2210 tombhost=$(_cat ${tombmount}/.host) 2211 tombuid=$(_cat ${tombmount}/.uid | tr -d ' ') 2212 2213 tombuser=$(getent passwd $tombuid) 2214 tombuser=${tombuser[(ws@:@)1]} 2215 } 2216 2217 { option_is_set --get-mountpoint } && { print $tombmount; continue } 2218 2219 _message "::1 tombname:: open on ::2 tombmount:: using ::3 tombfsopts::" \ 2220 $tombname $tombmount $tombfsopts 2221 2222 _verbose "::1 tombname:: /dev/::2 tombloop:: device mounted (detach with losetup -d)" $tombname $tombloop 2223 2224 _message "::1 tombname:: open since ::2 tombsince::" $tombname $tombsince 2225 2226 [[ -z "$tombtty" ]] || { 2227 _message "::1 tombname:: open by ::2 tombuser:: from ::3 tombtty:: on ::4 tombhost::" \ 2228 $tombname $tombuser $tombtty $tombhost 2229 } 2230 2231 _message "::1 tombname:: size ::2 tombtot:: of which ::3 tombused:: (::5 tombpercent::%) is used: ::4 tombavail:: free " \ 2232 $tombname $tombtot $tombused $tombavail $tombpercent 2233 2234 [[ ${tombp} -ge 90 ]] && { 2235 _warning "::1 tombname:: warning: your tomb is almost full!" $tombname 2236 } 2237 2238 # Now check hooks 2239 mounted_hooks=(`list_tomb_binds $tombname $tombmount`) 2240 for h in ${mounted_hooks}; do 2241 _message "::1 tombname:: hooks ::2 hookname:: on ::3 hookdest::" \ 2242 $tombname "`basename ${h[(ws:;:)1]}`" ${h[(ws:;:)2]} 2243 done 2244 done 2245 } 2246 2247 2248 # Print out an array of mounted tombs (internal use) 2249 # Format is semi-colon separated list of attributes 2250 # if 1st arg is supplied, then list only that tomb 2251 # 2252 # String positions in the semicolon separated array: 2253 # 2254 # 1. full mapper path 2255 # 2256 # 2. mountpoint 2257 # 2258 # 3. filesystem type 2259 # 2260 # 4. mount options 2261 # 2262 # 5. tomb name 2263 list_tomb_mounts() { 2264 [[ -z "$1" ]] && { 2265 # list all open tombs 2266 mount -l \ 2267 | awk ' 2268 BEGIN { main="" } 2269 /^\/dev\/mapper\/tomb/ { 2270 if(main==$1) next; 2271 print $1 ";" $3 ";" $5 ";" $6 ";" $7 2272 main=$1 2273 } 2274 ' 2275 } || { 2276 # list a specific tomb 2277 mount -l \ 2278 | awk -vtomb="[$1]" ' 2279 BEGIN { main="" } 2280 /^\/dev\/mapper\/tomb/ { 2281 if($7!=tomb) next; 2282 if(main==$1) next; 2283 print $1 ";" $3 ";" $5 ";" $6 ";" $7 2284 main=$1 2285 } 2286 ' 2287 } 2288 } 2289 2290 # list_tomb_binds 2291 # print out an array of mounted bind hooks (internal use) 2292 # format is semi-colon separated list of attributes 2293 # needs two arguments: name of tomb whose hooks belong 2294 # mount tomb 2295 list_tomb_binds() { 2296 [[ -z "$2" ]] && { 2297 _failure "Internal error: list_tomb_binds called without argument." } 2298 2299 # OK well, prepare for some insanity: parsing the mount table on GNU/Linux 2300 # is like combing a Wookie while he is riding a speedbike down a valley. 2301 2302 typeset -A tombs 2303 typeset -a binds 2304 for t in "${(f)$(mount -l | grep '/dev/mapper/tomb.*]$')}"; do 2305 len="${(w)#t}" 2306 [[ "${t[(w)$len]}" = "$1" ]] || continue 2307 tombs+=( ${t[(w)1]} ${t[(w)$len]} ) 2308 2309 done 2310 2311 for m in ${(k)tombs}; do 2312 for p in "${(f)$(cat /proc/mounts):s/\\040(deleted)/}"; do 2313 # Debian's kernel appends a '\040(deleted)' to the mountpoint in /proc/mounts 2314 # so if we parse the string as-is then this will break the parsing. How nice of them! 2315 # Some bugs related to this are more than 10yrs old. Such Debian! Much stable! Very parsing! 2316 # Bug #711183 umount parser for /proc/mounts broken on stale nfs mount (gets renamed to "/mnt/point (deleted)") 2317 # Bug #711184 mount should not stat mountpoints on mount 2318 # Bug #711187 linux-image-3.2.0-4-amd64: kernel should not rename mountpoint if nfs server is dead/unreachable 2319 [[ "${p[(w)1]}" = "$m" ]] && { 2320 [[ "${(q)p[(w)2]}" != "${(q)2}" ]] && { 2321 # Our output format: 2322 # mapper;mountpoint;fs;flags;name 2323 binds+=("$m;${(q)p[(w)2]};${p[(w)3]};${p[(w)4]};${tombs[$m]}") } 2324 } 2325 done 2326 done 2327 2328 # print the results out line by line 2329 for b in $binds; do print - "$b"; done 2330 } 2331 2332 # }}} - Tomb list 2333 2334 # {{{ Index and search 2335 2336 # index files in all tombs for search 2337 # $1 is optional, to specify a tomb 2338 index_tombs() { 2339 { command -v updatedb 1>/dev/null 2>/dev/null } || { 2340 _failure "Cannot index tombs on this system: updatedb (mlocate) not installed." } 2341 2342 updatedbver=`updatedb --version | grep '^updatedb'` 2343 [[ "$updatedbver" =~ "GNU findutils" ]] && { 2344 _warning "Cannot use GNU findutils for index/search commands." } 2345 [[ "$updatedbver" =~ "mlocate" ]] || { 2346 _failure "Index command needs 'mlocate' to be installed." } 2347 2348 _verbose "$updatedbver" 2349 2350 mounted_tombs=(`list_tomb_mounts $1`) 2351 [[ ${#mounted_tombs} == 0 ]] && { 2352 # Considering one tomb 2353 [[ -n "$1" ]] && { 2354 _failure "There seems to be no open tomb engraved as [::1::]" $1 } 2355 # Or more 2356 _failure "I can't see any open tomb, may they all rest in peace." } 2357 2358 _success "Creating and updating search indexes." 2359 2360 # start the LibreOffice document converter if installed 2361 { command -v unoconv 1>/dev/null 2>/dev/null } && { 2362 unoconv -l 2>/dev/null & 2363 _verbose "unoconv listener launched." 2364 sleep 1 } 2365 2366 for t in ${mounted_tombs}; do 2367 mapper=`basename ${t[(ws:;:)1]}` 2368 tombname=${t[(ws:;:)5]} 2369 tombmount=${t[(ws:;:)2]} 2370 [[ -r ${tombmount}/.noindex ]] && { 2371 _message "Skipping ::1 tomb name:: (.noindex found)." $tombname 2372 continue } 2373 _message "Indexing ::1 tomb name:: filenames..." $tombname 2374 updatedb -l 0 -o ${tombmount}/.updatedb -U ${tombmount} 2375 2376 # here we use swish to index file contents 2377 [[ $SWISH == 1 ]] && { 2378 _message "Indexing ::1 tomb name:: contents..." $tombname 2379 rm -f ${tombmount}/.swishrc 2380 _message "Generating a new swish-e configuration file: ::1 swish conf::" ${tombmount}/.swishrc 2381 cat <<EOF > ${tombmount}/.swishrc 2382 # index directives 2383 DefaultContents TXT* 2384 IndexDir $tombmount 2385 IndexFile $tombmount/.swish 2386 # exclude images 2387 FileRules filename regex /\.jp.?g/i 2388 FileRules filename regex /\.png/i 2389 FileRules filename regex /\.gif/i 2390 FileRules filename regex /\.tiff/i 2391 FileRules filename regex /\.svg/i 2392 FileRules filename regex /\.xcf/i 2393 FileRules filename regex /\.eps/i 2394 FileRules filename regex /\.ttf/i 2395 # exclude audio 2396 FileRules filename regex /\.mp3/i 2397 FileRules filename regex /\.ogg/i 2398 FileRules filename regex /\.wav/i 2399 FileRules filename regex /\.mod/i 2400 FileRules filename regex /\.xm/i 2401 # exclude video 2402 FileRules filename regex /\.mp4/i 2403 FileRules filename regex /\.avi/i 2404 FileRules filename regex /\.ogv/i 2405 FileRules filename regex /\.ogm/i 2406 FileRules filename regex /\.mkv/i 2407 FileRules filename regex /\.mov/i 2408 FileRules filename regex /\.flv/i 2409 FileRules filename regex /\.webm/i 2410 # exclude system 2411 FileRules filename is ok 2412 FileRules filename is lock 2413 FileRules filename is control 2414 FileRules filename is status 2415 FileRules filename is proc 2416 FileRules filename is sys 2417 FileRules filename is supervise 2418 FileRules filename regex /\.asc$/i 2419 FileRules filename regex /\.gpg$/i 2420 # pdf and postscript 2421 FileFilter .pdf pdftotext "'%p' -" 2422 FileFilter .ps ps2txt "'%p' -" 2423 # compressed files 2424 FileFilterMatch lesspipe "%p" /\.tgz$/i 2425 FileFilterMatch lesspipe "%p" /\.zip$/i 2426 FileFilterMatch lesspipe "%p" /\.gz$/i 2427 FileFilterMatch lesspipe "%p" /\.bz2$/i 2428 FileFilterMatch lesspipe "%p" /\.Z$/ 2429 # spreadsheets 2430 FileFilterMatch unoconv "-d spreadsheet -f csv --stdout %P" /\.xls.*/i 2431 FileFilterMatch unoconv "-d spreadsheet -f csv --stdout %P" /\.xlt.*/i 2432 FileFilter .ods unoconv "-d spreadsheet -f csv --stdout %P" 2433 FileFilter .ots unoconv "-d spreadsheet -f csv --stdout %P" 2434 FileFilter .dbf unoconv "-d spreadsheet -f csv --stdout %P" 2435 FileFilter .dif unoconv "-d spreadsheet -f csv --stdout %P" 2436 FileFilter .uos unoconv "-d spreadsheet -f csv --stdout %P" 2437 FileFilter .sxc unoconv "-d spreadsheet -f csv --stdout %P" 2438 # word documents 2439 FileFilterMatch unoconv "-d document -f txt --stdout %P" /\.doc.*/i 2440 FileFilterMatch unoconv "-d document -f txt --stdout %P" /\.odt.*/i 2441 FileFilterMatch unoconv "-d document -f txt --stdout %P" /\.rtf.*/i 2442 FileFilterMatch unoconv "-d document -f txt --stdout %P" /\.tex$/i 2443 # native html support 2444 IndexContents HTML* .htm .html .shtml 2445 IndexContents XML* .xml 2446 EOF 2447 2448 swish-e -c ${tombmount}/.swishrc -S fs -v3 2449 } 2450 _message "Search index updated." 2451 done 2452 } 2453 2454 search_tombs() { 2455 { command -v locate 1>/dev/null 2>/dev/null } || { 2456 _failure "Cannot index tombs on this system: updatedb (mlocate) not installed." } 2457 2458 updatedbver=`updatedb --version | grep '^updatedb'` 2459 [[ "$updatedbver" =~ "GNU findutils" ]] && { 2460 _warning "Cannot use GNU findutils for index/search commands." } 2461 [[ "$updatedbver" =~ "mlocate" ]] || { 2462 _failure "Index command needs 'mlocate' to be installed." } 2463 2464 _verbose "$updatedbver" 2465 2466 # list all open tombs 2467 mounted_tombs=(`list_tomb_mounts`) 2468 [[ ${#mounted_tombs} == 0 ]] && { 2469 _failure "I can't see any open tomb, may they all rest in peace." } 2470 2471 _success "Searching for: ::1::" ${(f)@} 2472 for t in ${mounted_tombs}; do 2473 _verbose "Checking for index: ::1::" ${t} 2474 mapper=`basename ${t[(ws:;:)1]}` 2475 tombname=${t[(ws:;:)5]} 2476 tombmount=${t[(ws:;:)2]} 2477 [[ -r ${tombmount}/.updatedb ]] && { 2478 # Use mlocate to search hits on filenames 2479 _message "Searching filenames in tomb ::1 tomb name::" $tombname 2480 locate -d ${tombmount}/.updatedb -e -i "${(f)@}" 2481 _message "Matches found: ::1 matches::" \ 2482 $(locate -d ${tombmount}/.updatedb -e -i -c ${(f)@}) 2483 2484 # Use swish-e to search over contents 2485 [[ $SWISH == 1 && -r $tombmount/.swish ]] && { 2486 _message "Searching contents in tomb ::1 tomb name::" $tombname 2487 swish-e -w ${@} -f $tombmount/.swish -H0 } 2488 } || { 2489 _warning "Skipping tomb ::1 tomb name::: not indexed." $tombname 2490 _warning "Run 'tomb index' to create indexes." } 2491 done 2492 _message "Search completed." 2493 } 2494 2495 # }}} - Index and search 2496 2497 # {{{ Resize 2498 2499 # resize tomb file size 2500 resize_tomb() { 2501 local tombpath="$1" # First argument is the path to the tomb 2502 2503 _message "Commanded to resize tomb ::1 tomb name:: to ::2 size:: mebibytes." $1 $OPTS[-s] 2504 2505 [[ -z "$tombpath" ]] && _failure "No tomb name specified for resizing." 2506 [[ ! -r $tombpath ]] && _failure "Cannot find ::1::" $tombpath 2507 2508 newtombsize="`option_value -s`" 2509 [[ -z "$newtombsize" ]] && { 2510 _failure "Aborting operations: new size was not specified, use -s" } 2511 2512 # this also calls _plot() 2513 is_valid_tomb $tombpath 2514 2515 _load_key # Try loading new key from option -k and set TOMBKEYFILE 2516 2517 local oldtombsize=$(( `stat -c %s "$TOMBPATH" 2>/dev/null` / 1048576 )) 2518 local mounted_tomb=`mount -l | 2519 awk -vtomb="[$TOMBNAME]" '/^\/dev\/mapper\/tomb/ { if($7==tomb) print $1 }'` 2520 2521 # Tomb must not be open 2522 [[ -z "$mounted_tomb" ]] || { 2523 _failure "Please close the tomb ::1 tomb name:: before trying to resize it." $TOMBNAME } 2524 # New tomb size must be specified 2525 [[ -n "$newtombsize" ]] || { 2526 _failure "You must specify the new size of ::1 tomb name::" $TOMBNAME } 2527 # New tomb size must be an integer 2528 [[ $newtombsize == <-> ]] || _failure "Size is not an integer." 2529 2530 # Tombs can only grow in size 2531 if [[ "$newtombsize" -gt "$oldtombsize" ]]; then 2532 2533 delta="$(( $newtombsize - $oldtombsize ))" 2534 2535 _message "Generating ::1 tomb file:: of ::2 size::MiB" $TOMBFILE $newtombsize 2536 2537 _verbose "Data dump using ::1:: from /dev/urandom" ${DD[1]} 2538 ${=DD} if=/dev/urandom bs=1048576 count=${delta} >> $TOMBPATH 2539 [[ $? == 0 ]] || { 2540 _failure "Error creating the extra resize ::1 size::, operation aborted." \ 2541 $tmp_resize } 2542 2543 # If same size this allows to re-launch resize if pinentry expires 2544 # so that it will continue resizing without appending more space. 2545 # Resizing the partition to the file size cannot harm data anyway. 2546 elif [[ "$newtombsize" = "$oldtombsize" ]]; then 2547 _message "Tomb seems resized already, operating filesystem stretch" 2548 else 2549 _failure "The new size must be greater then old tomb size." 2550 fi 2551 2552 { option_is_set --tomb-pwd } && { 2553 tomb_pwd="`option_value --tomb-pwd`" 2554 _verbose "tomb-pwd = ::1 tomb pass::" $tomb_pwd 2555 ask_key_password "$tomb_pwd" 2556 } || { 2557 ask_key_password 2558 } 2559 [[ $? == 0 ]] || _failure "No valid password supplied." 2560 2561 lo_mount "$TOMBPATH" 2562 nstloop=`lo_new` 2563 2564 mapdate=`date +%s` 2565 mapper="tomb.$TOMBNAME.$mapdate.$(basename $nstloop)" 2566 2567 _message "opening tomb" 2568 _cryptsetup luksOpen ${nstloop} ${mapper} || { 2569 _failure "Failure mounting the encrypted file." } 2570 2571 _sudo cryptsetup resize "${mapper}" || { 2572 _failure "cryptsetup failed to resize ::1 mapper::" $mapper } 2573 2574 _sudo e2fsck -p -f /dev/mapper/${mapper} || { 2575 _failure "e2fsck failed to check ::1 mapper::" $mapper } 2576 2577 _sudo resize2fs /dev/mapper/${mapper} || { 2578 _failure "resize2fs failed to resize ::1 mapper::" $mapper } 2579 2580 # close and free the loop device 2581 _sudo cryptsetup luksClose "${mapper}" 2582 2583 return 0 2584 } 2585 2586 # }}} 2587 2588 # {{{ Close 2589 2590 umount_tomb() { 2591 local tombs how_many_tombs 2592 local pathmap mapper tombname tombmount loopdev 2593 local ans pidk pname 2594 2595 if [ "$1" = "all" ]; then 2596 mounted_tombs=(`list_tomb_mounts`) 2597 else 2598 mounted_tombs=(`list_tomb_mounts $1`) 2599 fi 2600 2601 [[ ${#mounted_tombs} == 0 ]] && { 2602 _failure "There is no open tomb to be closed." } 2603 2604 [[ ${#mounted_tombs} -gt 1 && -z "$1" ]] && { 2605 _warning "Too many tombs mounted, please specify one (see tomb list)" 2606 _warning "or issue the command 'tomb close all' to close them all." 2607 _failure "Operation aborted." } 2608 2609 for t in ${mounted_tombs}; do 2610 mapper=`basename ${t[(ws:;:)1]}` 2611 2612 # strip square parens from tombname 2613 tombname=${t[(ws:;:)5]} 2614 tombmount=${t[(ws:;:)2]} 2615 tombfs=${t[(ws:;:)3]} 2616 tombfsopts=${t[(ws:;:)4]} 2617 tombloop=${mapper[(ws:.:)4]} 2618 2619 _verbose "Name: ::1 tomb name::" $tombname 2620 _verbose "Mount: ::1 mount point::" $tombmount 2621 _verbose "Loop: ::1 mount loop::" $tombloop 2622 _verbose "Mapper: ::1 mapper::" $mapper 2623 2624 [[ -e "$mapper" ]] && { 2625 _warning "Tomb not found: ::1 tomb file::" $1 2626 _warning "Please specify an existing tomb." 2627 return 0 } 2628 2629 option_is_set -n || { 2630 exec_safe_func_hooks \ 2631 close "$tombmount" "$tombname" "$tombloop" "$mapper" 2632 exec_hook_res=$? 2633 [[ $exec_hook_res = 0 ]] || { 2634 _warning "close exec-hook returns a non-zero error code: ::1 error::" $exec_hook_res 2635 _failure "Operation aborted" 2636 } 2637 } 2638 2639 [[ -n $SLAM ]] && { 2640 _success "Slamming tomb ::1 tomb name:: mounted on ::2 mount point::" \ 2641 $tombname $tombmount 2642 _message "Kill all processes busy inside the tomb." 2643 { slam_tomb "$tombmount" } || { 2644 _failure "Cannot slam the tomb ::1 tomb name::" $tombname } 2645 } || { 2646 _message "Closing tomb ::1 tomb name:: mounted on ::2 mount point::" \ 2647 $tombname $tombmount } 2648 2649 # check if there are binded dirs and close them 2650 bind_tombs=(`list_tomb_binds $tombname $tombmount`) 2651 for b in ${bind_tombs}; do 2652 bind_mapper="${b[(ws:;:)1]}" 2653 bind_mount="${b[(ws:;:)2]}" 2654 _message "Closing tomb bind hook: ::1 hook::" $bind_mount 2655 _sudo umount "`print - ${bind_mount}`" || { 2656 [[ -n $SLAM ]] && { 2657 _success "Slamming tomb: killing all processes using this hook." 2658 slam_tomb "`print - ${bind_mount}`" || _failure "Cannot slam the bind hook ::1 hook::" $bind_mount 2659 umount "`print - ${bind_mount}`" || _failure "Cannot slam the bind hook ::1 hook::" $bind_mount 2660 } || { 2661 _failure "Tomb bind hook ::1 hook:: is busy, cannot close tomb." $bind_mount 2662 } 2663 } 2664 done 2665 2666 # check if the tomb is actually still mounted. Background: 2667 # When mounted on a binded directory in appears twice in 'list_tomb_binds' 2668 # and will get umounted automatically through the above function 2669 # causing an error and a remaining (decrypted!) loop device 2670 # posing a security risk. 2671 # See https://github.com/dyne/Tomb/issues/273 2672 2673 # checking for tombs 2674 mount | grep -w "$tombmount" >/dev/null 2675 mount_status=$? 2676 # return value of 0 for grep means it found at least one entry 2677 # return value of 1 means nothing was found, implying, the tomb 2678 # mount was already umounted. 2679 if [ $mount_status = 0 ]; then 2680 # Tomb was not umounted through the above command 2681 # Will do so now 2682 _verbose "Performing umount of ::1 mount point::" $tombmount 2683 _sudo umount ${tombmount} 2684 [[ $? = 0 ]] || { _failure "Tomb is busy, cannot umount!" } 2685 else 2686 # Tomb was already umounted, will not do it again 2687 _warning "Tomb was already umounted, possibly through a binded directory" 2688 fi 2689 2690 # If we used a default mountpoint and is now empty, delete it 2691 tombname_regex=${tombname//\[/} 2692 tombname_regex=${tombname_regex//\]/} 2693 2694 [[ "$tombmount" -regex-match "[/run]?/media[/$_USER]?/$tombname_regex" ]] && { 2695 _sudo rmdir $tombmount } 2696 2697 _sudo cryptsetup luksClose $mapper 2698 [[ $? == 0 ]] || { 2699 _failure "Error occurred in cryptsetup luksClose ::1 mapper::" $mapper } 2700 2701 # Normally the loopback device is detached when unused 2702 [[ -e "/dev/$tombloop" ]] && { 2703 _sudo losetup -d "/dev/$tombloop" 2704 [[ $? = 0 ]] || _verbose "/dev/$tombloop was already closed." 2705 } 2706 2707 _success "Tomb ::1 tomb name:: closed: your bones will rest in peace." $tombname 2708 2709 done # loop across mounted tombs 2710 2711 return 0 2712 } 2713 2714 # Kill all processes using the tomb 2715 slam_tomb() { 2716 # $1 = tomb mount point 2717 if [[ -z `lsof -t +D "$1" 2>/dev/null` ]]; then 2718 return 0 2719 fi 2720 #Note: shells are NOT killed by INT or TERM, but they are killed by HUP 2721 for s in TERM HUP KILL; do 2722 _verbose "Sending ::1:: to processes inside the tomb:" $s 2723 if option_is_set -D; then 2724 ps -fp `lsof -t +D "$1" 2>/dev/null`| 2725 while read line; do 2726 _verbose $line 2727 done 2728 fi 2729 kill -$s `lsof -t +D "$1"` 2730 if [[ -z `lsof -t +D "$1" 2>/dev/null` ]]; then 2731 return 0 2732 fi 2733 if ! option_is_set -f; then 2734 sleep 3 2735 fi 2736 done 2737 return 1 2738 } 2739 2740 # }}} - Tomb close 2741 2742 # {{{ Main routine 2743 2744 main() { 2745 2746 _ensure_dependencies # Check dependencies are present or bail out 2747 2748 local -A subcommands_opts 2749 ### Options configuration 2750 # 2751 # Hi, dear developer! Are you trying to add a new subcommand, or 2752 # to add some options? Well, keep in mind that option names are 2753 # global: they cannot bear a different meaning or behaviour across 2754 # subcommands. The only exception is "-o" which means: "options 2755 # passed to the local subcommand", and thus can bear a different 2756 # meaning for different subcommands. 2757 # 2758 # For example, "-s" means "size" and accepts one argument. If you 2759 # are tempted to add an alternate option "-s" (e.g., to mean 2760 # "silent", and that doesn't accept any argument) DON'T DO IT! 2761 # 2762 # There are two reasons for that: 2763 # I. Usability; users expect that "-s" is "size" 2764 # II. Option parsing WILL EXPLODE if you do this kind of bad 2765 # things (it will complain: "option defined more than once") 2766 # 2767 # If you want to use the same option in multiple commands then you 2768 # can only use the non-abbreviated long-option version like: 2769 # -force and NOT -f 2770 # 2771 main_opts=(q -quiet=q D -debug=D h -help=h v -version=v f -force=f -tmp: U: G: T: -no-color -unsafe g -gpgkey=g) 2772 subcommands_opts[__default]="" 2773 # -o in open and mount is used to pass alternate mount options 2774 subcommands_opts[open]="n -nohook=n k: -kdf: o: -ignore-swap -tomb-pwd: r: R: " 2775 subcommands_opts[mount]=${subcommands_opts[open]} 2776 2777 subcommands_opts[create]="" # deprecated, will issue warning 2778 2779 # -o in forge and lock is used to pass an alternate cipher. 2780 subcommands_opts[forge]="-ignore-swap k: -kdf: o: -tomb-pwd: -use-urandom r: R: " 2781 subcommands_opts[dig]="-ignore-swap s: -size=s " 2782 subcommands_opts[lock]="-ignore-swap k: -kdf: o: -tomb-pwd: r: R: " 2783 subcommands_opts[setkey]="k: -ignore-swap -kdf: -tomb-old-pwd: -tomb-pwd: r: R: " 2784 subcommands_opts[engrave]="k: " 2785 2786 subcommands_opts[passwd]="k: -ignore-swap -kdf: -tomb-old-pwd: -tomb-pwd: r: R: " 2787 subcommands_opts[close]="" 2788 subcommands_opts[help]="" 2789 subcommands_opts[slam]="" 2790 subcommands_opts[list]="-get-mountpoint " 2791 2792 subcommands_opts[index]="" 2793 subcommands_opts[search]="" 2794 2795 subcommands_opts[help]="" 2796 subcommands_opts[bury]="k: -tomb-pwd: r: R: " 2797 subcommands_opts[exhume]="k: -tomb-pwd: r: R: " 2798 # subcommands_opts[decompose]="" 2799 # subcommands_opts[recompose]="" 2800 # subcommands_opts[install]="" 2801 subcommands_opts[askpass]="" 2802 subcommands_opts[source]="" 2803 subcommands_opts[resize]="-ignore-swap s: -size=s k: -tomb-pwd: r: R: " 2804 subcommands_opts[check]="-ignore-swap " 2805 # subcommands_opts[translate]="" 2806 2807 ### Detect subcommand 2808 local -aU every_opts #every_opts behave like a set; that is, an array with unique elements 2809 for optspec in $subcommands_opts$main_opts; do 2810 for opt in ${=optspec}; do 2811 every_opts+=${opt} 2812 done 2813 done 2814 local -a oldstar 2815 oldstar=("${(@)argv}") 2816 #### detect early: useful for --option-parsing 2817 zparseopts -M -D -Adiscardme ${every_opts} 2818 if [[ -n ${(k)discardme[--option-parsing]} ]]; then 2819 print $1 2820 if [[ -n "$1" ]]; then 2821 return 1 2822 fi 2823 return 0 2824 fi 2825 unset discardme 2826 if ! zparseopts -M -E -D -Adiscardme ${every_opts}; then 2827 _failure "Error parsing." 2828 return 127 2829 fi 2830 unset discardme 2831 subcommand=$1 2832 if [[ -z $subcommand ]]; then 2833 subcommand="__default" 2834 fi 2835 2836 if [[ -z ${(k)subcommands_opts[$subcommand]} ]]; then 2837 _warning "There's no such command \"::1 subcommand::\"." $subcommand 2838 exitv=127 _failure "Please try -h for help." 2839 fi 2840 argv=("${(@)oldstar}") 2841 unset oldstar 2842 2843 ### Parsing global + command-specific options 2844 # zsh magic: ${=string} will split to multiple arguments when spaces occur 2845 set -A cmd_opts ${main_opts} ${=subcommands_opts[$subcommand]} 2846 # if there is no option, we don't need parsing 2847 if [[ -n $cmd_opts ]]; then 2848 zparseopts -M -E -D -AOPTS ${cmd_opts} 2849 if [[ $? != 0 ]]; then 2850 _warning "Some error occurred during option processing." 2851 exitv=127 _failure "See \"tomb help\" for more info." 2852 fi 2853 fi 2854 #build PARAM (array of arguments) and check if there are unrecognized options 2855 ok=0 2856 PARAM=() 2857 for arg in $*; do 2858 if [[ $arg == '--' || $arg == '-' ]]; then 2859 ok=1 2860 continue #it shouldn't be appended to PARAM 2861 elif [[ $arg[1] == '-' ]]; then 2862 if [[ $ok == 0 ]]; then 2863 exitv=127 _failure "Unrecognized option ::1 arg:: for subcommand ::2 subcommand::" $arg $subcommand 2864 fi 2865 fi 2866 PARAM+=$arg 2867 done 2868 # First parameter actually is the subcommand: delete it and shift 2869 [[ $subcommand != '__default' ]] && { PARAM[1]=(); shift } 2870 2871 ### End parsing command-specific options 2872 2873 # Use colors unless told not to 2874 { ! option_is_set --no-color } && { autoload -Uz colors && colors } 2875 # Some options are only available during insecure mode 2876 { ! option_is_set --unsafe } && { 2877 for opt in --tomb-pwd --use-urandom --tomb-old-pwd; do 2878 { option_is_set $opt } && { 2879 exitv=127 _failure "You specified option ::1 option::, which is DANGEROUS and should only be used for testing\nIf you really want so, add --unsafe" $opt } 2880 done 2881 } 2882 # read -t or --tmp flags to set a custom temporary directory 2883 option_is_set --tmp && TMPPREFIX=$(option_value --tmp) 2884 2885 2886 # When we run as root, we remember the original uid:gid to set 2887 # permissions for the calling user and drop privileges 2888 _whoami # Reset _UID, _GID, _TTY 2889 2890 [[ "$PARAM" == "" ]] && { 2891 _verbose "Tomb command: ::1 subcommand::" $subcommand 2892 } || { 2893 _verbose "Tomb command: ::1 subcommand:: ::2 param::" $subcommand $PARAM 2894 } 2895 2896 [[ -z $_UID ]] || { 2897 _verbose "Caller: uid[::1 uid::], gid[::2 gid::], tty[::3 tty::]." \ 2898 $_UID $_GID $_TTY 2899 } 2900 2901 _verbose "Temporary directory: $TMPPREFIX" 2902 2903 # Process subcommand 2904 case "$subcommand" in 2905 2906 # USAGE 2907 help) 2908 usage 2909 ;; 2910 2911 # DEPRECATION notice (leave here as 'create' is still present in old docs) 2912 create) 2913 _warning "The create command is deprecated, please use dig, forge and lock instead." 2914 _warning "For more informations see Tomb's manual page (man tomb)." 2915 _failure "Operation aborted." 2916 ;; 2917 2918 # CREATE Step 1: dig -s NN file.tomb 2919 dig) 2920 dig_tomb $PARAM 2921 ;; 2922 2923 # CREATE Step 2: forge file.tomb.key 2924 forge) 2925 forge_key $PARAM 2926 ;; 2927 2928 # CREATE Step 2: lock -k file.tomb.key file.tomb 2929 lock) 2930 lock_tomb_with_key $PARAM 2931 ;; 2932 2933 # Open the tomb 2934 mount|open) 2935 mount_tomb $PARAM 2936 ;; 2937 2938 # Close the tomb 2939 # `slam` is used to force closing. 2940 umount|close|slam) 2941 [[ "$subcommand" == "slam" ]] && { 2942 SLAM=1 2943 [[ $LSOF == 0 ]] && { 2944 unset SLAM 2945 _warning "lsof not installed: cannot slam tombs." 2946 _warning "Trying a regular close." }} 2947 umount_tomb $PARAM[1] 2948 ;; 2949 2950 # Grow tomb's size 2951 resize) 2952 [[ $RESIZER == 0 ]] && { 2953 _failure "Resize2fs not installed: cannot resize tombs." } 2954 resize_tomb $PARAM[1] 2955 ;; 2956 2957 ## Contents manipulation 2958 2959 # Index tomb contents 2960 index) 2961 index_tombs $PARAM[1] 2962 ;; 2963 2964 # List tombs 2965 list) 2966 list_tombs $PARAM[1] 2967 ;; 2968 2969 # Search tomb contents 2970 search) 2971 search_tombs $PARAM 2972 ;; 2973 2974 ## Locking operations 2975 2976 # Export key to QR Code 2977 engrave) 2978 [[ $QRENCODE == 0 ]] && { 2979 _failure "QREncode not installed: cannot engrave keys on paper." } 2980 engrave_key $PARAM 2981 ;; 2982 2983 # Change password on existing key 2984 passwd) 2985 change_passwd $PARAM[1] 2986 ;; 2987 2988 # Change tomb key 2989 setkey) 2990 change_tomb_key $PARAM 2991 ;; 2992 2993 # STEGANOGRAPHY: hide key inside an image 2994 bury) 2995 [[ $STEGHIDE == 0 ]] && { 2996 _failure "Steghide not installed: cannot bury keys into images." } 2997 bury_key $PARAM[1] 2998 ;; 2999 3000 # STEGANOGRAPHY: read key hidden in an image 3001 exhume) 3002 [[ $STEGHIDE == 0 ]] && { 3003 _failure "Steghide not installed: cannot exhume keys from images." } 3004 exhume_key $PARAM[1] 3005 ;; 3006 3007 ## Internal commands useful to developers 3008 3009 # Make tomb functions available to the calling shell or script 3010 'source') return 0 ;; 3011 3012 # Ask user for a password interactively 3013 askpass) ask_password $PARAM[1] $PARAM[2] ;; 3014 3015 # Default operation: presentation, or version information with -v 3016 __default) 3017 _print "Tomb ::1 version:: - a strong and gentle undertaker for your secrets" $VERSION 3018 _print "\000" 3019 _print " Copyright (C) 2007-2017 Dyne.org Foundation, License GNU GPL v3+" 3020 _print " This is free software: you are free to change and redistribute it" 3021 _print " For the latest sourcecode go to <http://dyne.org/software/tomb>" 3022 _print "\000" 3023 option_is_set -v && { 3024 local langwas=$LANG 3025 LANG=en 3026 _print " This source code is distributed in the hope that it will be useful," 3027 _print " but WITHOUT ANY WARRANTY; without even the implied warranty of" 3028 _print " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." 3029 LANG=$langwas 3030 _print " When in need please refer to <http://dyne.org/support>." 3031 _print "\000" 3032 _print "System utils:" 3033 _print "\000" 3034 cat <<EOF 3035 `sudo -V | head -n1` 3036 `cryptsetup --version` 3037 `pinentry --version` 3038 `gpg --version | head -n1` - key forging algorithms (GnuPG symmetric ciphers): 3039 `list_gnupg_ciphers` 3040 EOF 3041 _print "\000" 3042 _print "Optional utils:" 3043 _print "\000" 3044 _list_optional_tools version 3045 return 0 3046 } 3047 usage 3048 ;; 3049 3050 # Reject unknown command and suggest help 3051 *) 3052 _warning "Command \"::1 subcommand::\" not recognized." $subcommand 3053 _message "Try -h for help." 3054 return 1 3055 ;; 3056 esac 3057 return $? 3058 } 3059 3060 # }}} 3061 3062 # {{{ Run 3063 3064 main "$@" || exit $? # Prevent `source tomb source` from exiting 3065 3066 # }}} 3067 3068 # -*- tab-width: 4; indent-tabs-mode:nil; -*- 3069 # vim: set shiftwidth=4 expandtab: