#!/usr/bin/perl # # ks [option] {keyname} [[{from}] {to}] # ks -c encfs {keyname} [{from}] [[{to}]] # ks -c cryfs {keyname} {from} [{to}] # ks -x # ks -xe # # Key Storage (ks) is a key management system for various Encryption methods. # User passwords are used to decrypt a key file that not only stores the master # key for some type of cryptographic system, but many other details, such as the # location of the data, and the command and other needed to access the data, # Even executable scripts, configuration files, or just a file of data that is # stored in the encrypted key file itself. # # Multiple 'key store' directories can be used, and is typically stored in # a "ks" sub-directory of some mounted USB device, or the users home. The # given 'key name' is hashed to generated the filename of the key file in the # key store. # # The encrypted 'key file' typically contains information such as... # * High Entropy Master Password (to decrypt actual data) # * The command to run to decrypt, mount, or display the data. # * Location of the Data Store (optional, and changeable) # * Where to mount the decrypted data (optional, and changeable) # * Any extra configuration or data needed (such as a EncFS Config file) # This data could itself be the encrypted information being stored. # # The command in the 'key file' is run with the location of the data source, # and mount point (if appropriate) as part of the commands arguments. The # master password is then feed to command via standard input, and optionally # a named-pipe is used to feed any extra data (such as a EncFS configuration # file). The pipe filename is passed in the environment variables 'DATA' and # 'ENCFS_CONFIG' to the command or command arguments. # # As you may have guessed, this setup is designed to mount larger encrypted # data stores, with "encfs" or "cryfs" commands in mind, but should be usable # by just about any other encryption method, or for other nefarious purpose you # may have in mind. # # Options... # Normal usage # -e Edit the 'key file' (requires extra setup for editor) # -u Umount (or specify a directory name containing a '/') # Password Reading # -x Use X Windows to collect arguments and passwords (no tty) # -t Force used of stdin to get password # Administration # -c type Create a encryption (types: Data, EncFS, CryFS) # -p Change the users password for this key file # -r Rename the keyfile to a new 'name', with same password # -m Change stored 'from' and 'to' mount paths in key file # -D Delete this key file (DANGER DANGER DANGER) # Low Level Administration and Debugging # -l Look at raw command/from/to settings found in 'key file' # -f Output the real filename of 'key file' in any 'key store' # -kf Output the full path to the 'key file' that will be used # -ks Use this specific 'key store' directory (instead of default) # -kre Re-encrypt keyfiles, perhaps to new encryption file format # -d Debugging trace of programs activities # -v Verbose, Trace and show external commands used. # Help # -help Print the man options # -doc Print the full manual (long) # ### # # Example Usage... Create a new EncFS data store # # Create a key store... # Plug in, and mount, a USB stick # mkdir /path_to_usb_mount/ks # # Create a key file for a new EncFS of CryFS data... # mkdir /encrypted/data/store/directory # ks -c EncFS name /encrypted/data/store/directory # # Now we can mount the empty data store for the first time. # You can check that it actually mounted using "df". # ks name # df # # Create or Copy at least one file into mounted decrypted data store. # vi name/README.txt # # You can now unmount the encrypted filesystem. # ks name/ # # You can either unmount using the -u option, or add a '/' on end of key. # The latter lets you unmount using the -x (X windows only) option, where you # are asked for the name for mount (or in this case unmount). # # From this point you can mount and unmount that EncFS data store using... # ks -x # # Using -x option from command will also will avoid the 'key file' names from # being recorded in the 'shell history log', and you can add that command to # a hotkey or pop-up menu (recommended). # #### # # Example Usage... # Create a small text file in the key file itself # # Create and then edit the encrypted key file... # ks -c Data my_secrets # ks -e my_secrets # # This requires your editor, as defined by the environment variables, EDITOR, # VISUAL or XEDITOR (is "ks -x" is used), to understand how to edit encrypted # files, saved with an appropriate 'file suffix' for the keyfile encryption # command you are using. # # Details on how you can set up "vim" to do is in the file # https://antofthy.gitlab.io/software/#encrypt.vim # # The top four lines of the unencryoted key file containes... # COMMAND=$PAGER $DATA # PASSWORD=Encrypted Data File. No Master Password needed. # FROM= # TO= # __DATA__ # # You can add any secret data you want under '__DATA__' to store it as # information that you want to encrypt. You can look at and edit that # information using 'ks -e'. If you add "ks -xe" to a window manager menu or # hotkey it will let you edit a keyfile in a popup window running "vi". # # You can see the data if run "ks my_secrets" without any special options.. # ks my_secrets # # After typing the password, the command simply output the __DATA__ section, in # your 'pager' command. # # If you set up a "$XPAGER" environment variable (to something like "xless") # then $PAGER will be set to $XPAGER and the command "ks -x" will display # contents of data section in a temporary GUI popup window instead. # #### # # Software Requirements (minimal)... # # The external program "shred" is essential to deleting and removing any # temporary files that may have been created, though rarely happens. # # Also an external program to encrypt/decrypt keyfiles, which is define at the # start of the code below. # # "keepout" https://antofthy.gitlab.io/software/#keepout # "encrypt" https://antofthy.gitlab.io/software/#encrypt # "openssl enc" # "gpg -c" # # Preferably "vim" should be set up to understand how to edit such encypted # files, directly and securely in memory (see below). # # For actual encrypted filesystems you will should also install either # "EncFS" and/or "CryFS" programs. # # Other than that all other external programs are optional... # # # Software Requirements (recommended)... # # I designed and use this program with many other helper programs to make this # 'work' a lot more easily and seamlessly in my day-to-day work. And while not # necessary these other programs are recommended. # # You can specify alternative password reader using environment variables # "$TTY_ASKPASS" and "$X_ASKPASS" for terminal and X-window GUI password # reading. # # By specifying a "$vim_suffix" below, and setting up "vim", you can then # directly edit the keyfiles, using "ks -e"... # https://antofthy.gitlab.io/software/#encrypt.vim # # The "ks -x" option allows you to avoid the use of a command line shell and # its history logging. You can then arrange to run this program from window # manager 'hotkeys' or 'popup menus'. This requires the use of linux commands # such as "zenity", "Xdialog", and "notify-send" They are a recommended # addition. # # You may also like to set the "$PAGER" and "$XPAGER" environment variables to # point to appropriate 'paging' programs for terminal and X-window file # display, respectfully. This allows secure handling of data output (as stored # in a key file) so as to display it as needed. Such data is useful for storing # a small amounts of data such as passwords (for web sites or accounts), or # bank account details, without requiring a file system mount used for # encrypted data directories. # # And finally I recommend you arrange to automatically unmount encrypted file # systems (passwords should not be needed to do this) when you logout, as a # precaution against accidentally leaving them logged in when you are not # around. For example add this to your logout scripts... # # mount | while read dev on fs type; do # case "$dev" in # encfs) umount $fs ;; # cryfs@*) umount $fs ;; # esac # done # # You may want to link it to your terminal locking setup, or to expand on the # above to also unmount LUKS encrypted disks, CIFS mounts, and MTPFS android # phone mounts. Perhaps even unmount USB sticks, DVD's and so on while you are # at it. # #### # # Internal Design notes... # # This program uses principles found in a Paper by Clemens Fruhwirth, "TKS1, # An anti-forensic, two level, and iterated key setup scheme." # # http://clemens.endorphin.org/TKS1-draft.pdf # # The main features being that the users generally low entropy pass-phrase # only decrypts the high entropy (random) master key (and any other data # needed for decryption purposes) using the iterative PBKDF2 (RFC 2898) to # make brute force attacks slow and thus unfeasible. It also provides the # ability for users to change their passwords without needing to re-encrypt # the data store. Only a re-encryption of the key file. This even allows # you to set up multiple pass-phrases to decrypt the same data store, by # duplicating and encrypting key files using different names and password # pairs. This is particularly useful for multi-user access to encrypted data. # # This scheme is similar to but expanded upon the method implemented in the # Linux "LUKS" dm-crypt Disk Encryption scheme (see linux "cryptsetup' # command), but without the same pass-phrase being applied to multiple 'key # slots', and not limited to a single specific encrypted data store. # # This scheme also allows you to completely separate the 'key files' from the # actual encrypted data. This means the encrypted 'keys' needed to unlock # data can be saved to a completely different device such as a small USB key, # allowing you to add physical security to the encrypted data. The 'key' also # only needs to be available when the access to the encrypted data is being # setup, and can be removed once the data is unlocked. If power is removed # the data should automatically be 'locked' providing instant security. # # As the command needed is also stored with the master key, it means that the # 'key file' could be used for just about any encryption scheme, from disk # partitions (dm_crypt), filesystem directory (EncFS), or a just a file of # information (configuration details). # # That is all the information needed to decrypt a 'data store' regardless of # the actual encryption method used, can be saved in the encrypted 'key file' # separate to the data. This file also can store all the supposedly 'public' # information, such as salts, iteration counts, configuration info, etc. # This overcomes a known critical flaw with EncFS filesystems. # # The 'command' being executed does not have to actually decrypt any 'data # store', but could just simply output the master password, or the 'extra # data' that is also stored in the 'key file', for other purposes. For example # I often use a 'key file' DATA section to hold 'private information' such as # internet passwords, and bank account details (it is an encrypted file!) It # can then be access using the 'key name' rather than a file location. # # Variables that are defined for the command include... # # $PAGER A data display command suitable for the environment. # For PAGER environment variable, or "less" for a tty. # Set to the XPAGER environment variable for X windows ("ks -x"). # Or fall back to "less" in a "xterm" ("ks -x"). # # $DATA The file name of a named pipe to read the __DATA__ section from # # Other uses of "ks" keyfiles.... # # * Just output the Master Password (no decryption command), # so you can pipe that master password into something else. # COMMAND=cat - # # That is the key file is only used to hold a master password so you can # feed it into something else. For example you can use the above to get # "ks" to initialise the Gnome KeyRing Daemon... # ks -x gnome_key | /usr/libexec/pam-keyring-tool -u -s --keyring=default # # * You can also have the keyfile initialise the Gnome Key Ring directly, # so you don't have to remember the above command. Not having to remember # the complex command is part of the beauty of the "ks" system. # # Edit the encrypted 'ks' file and set the command to... # COMMAND=/usr/libexec/pam-keyring-tool -u -s --keyring=default # # The command to initialise the Gname keyring is then... # ks -x gnome_key # # * Output the DATA section of the encrypted 'key file' (master password # in the key file is not used) rather than the password field. # COMMAND=cat $DATA # # The "$DATA" if the filename of a temporary named-pipe to which 'ks' will # send the contents of the DATA section of the 'ks file'. For EncFS mounts # this is used to pass it the EncFS configuration data. # # * Or show the data in a 'pager' program (like 'less' or 'xless'). # COMMAND=$PAGER $DATA # # This is a good way to have some the encrypted data pop-up on screen when # requested on your X window display, but otherwise kept encrypted. # # You can create a encryoted file of this type (for editing) using # ks -c data # # * Mount a EncFS encrypted directory. The '%s' passes the 'from' and 'to' # arguments respectively to the command. # COMMAND=encfs --stdinpass --anykey "%s" "%s" # # This is the more normal setup and what is automatically set when you use # the -c option (as described above). The -c option will also generates # a completely random 'master password' but also create and save # a configuration file for EncFS, which is then stored in the DATA area of # the 'key file'. All that is then necessary is to specify the source # directory and optionally the destination directory # # The EncFS command requires a FROM path, which is generally set within the # 'key file'. The TO (mount) path is optional and may be an absolute path, # or relative to the current directory. If not provided the 'key name' is # used for the current directory. The TO sub-directory is created, and the # FROM path is encfs mounted at that point. # # If the input 'key name' ends in a '/' or a '-u' option is given, the # EncFS file system attached to the specified directory will be unmounted # and the empty directory removed. # # * Or run the data section as a encrypted shell script # COMMAND=/bin/sh $DATA # # This allows you to create a very complex sequence of secret operations, # that could do anything! # # For example a script to re-organise some of the encrypted data, making # that data un-decryptable, perhaps in a reversible way to provide a extra # hidden locking mechanism. # # Or a script that mounts a encrypted filesystem from a secure site and # either decrypts it itself, or set things up so you can then decrypt it # through another 'ks' key file. # # Basically any command can be run for a given 'name' and 'password' pair, # including larger scripts, that can perform any action, while keeping the # complex command used an encrypted secret. # # A command can be designed to use any encrypted data method, or anything else # for that matter. It can even be used to remove or shred, to make an # encrypted 'data store' useless if a wrong password, or key store was used. # It could even run a large and complex script stored within the encrypted key # file, to keep such a script secret. Only those that know the 'name' and # 'password' pair can execute, look-at or modify the command or contents of the # keyfile. # # Basically the key store scheme is specifically designed to make # differentiating between chaff, fake keys, fake data stores, and the real # encrypted very very difficult. Especially in situations where rubber hose # attacks are a possibility. # # Chaff Keys and Data Stores containing fake/random data that is encrypted # with a forgotten random pad should always be added. Better still the files # such data should be 'touched' on occasion, by some means to keep dates # randomized. # # See Also # "TKS1, An anti-forensic, two level, and iterated key setup scheme." # http://clemens.endorphin.org/TKS1-draft.pdf # "Dual Password Encryption with EncFS" # https://web.archive.org/web/20160305023515/ # http://magazine.redhat.com:80/2007/06/13/ # dual-password-encryption-with-encfs # # Anthony Thyssen 18 November 2009 A.Thyssen@griffith.edu.au # ###### # use strict; #use warnings; use FindBin; my $PROGNAME = $FindBin::Script; use File::Spec; use File::Temp qw(tempfile tempdir); use IPC::Open2; # calling encryption command use Fcntl; use Digest::SHA qw(sha1_base64); # filename hashing umask 077; # # Encryption commands # # This command is assumed to understand "-pass fd:N" method of passing # a password securely via a file descriptor handle. # # If some other syntax is required (EG: gpg's "--passphrase-fd N" then the code # below may need to be modified to suit. # # I do not recommend direct use of "openssl enc" as it has become VERY # dependant on the options used to encrypt, to also be used for decrypt # and the default options has changed, and will likely change. # # The "keepout" wrapper solves this by including the options used in the # saved file. # my $vim_suffix = ".kpt"; # suffix for vim editing my @encrypt_cmd = qw( keepout ); # metadata saving wrapped around openssl enc #my $vim_suffix = ".enc"; # suffix for vim editing #my @encrypt_cmd = qw( encrypt ); # old personal encryption command #my $vim_suffix = ".ossl" #my @encrypt_cmd = qw( openssl enc -aes-256-cbc -md md5 -nosalt ); #my $vim_suffix = ".aes" #my @encrypt_cmd = qw( openssl enc -aes-256-cbc -md md5 ); # more modern openssl encryption, but options need to be remembered! #my @encrypt_cmd = # qw( openssl enc -aes-256-cbc -md sha256 -pbkdf2 -iter 500000 ); # How to decrypt a 'Salted__' keyfile if seen (backward compatibility) my @decrypt_salted = qw( openssl enc -aes-256-cbc -md md5 -d ); #my @decrypt_salted = ( @encrypt_cmd, '-d' ); # if using newer openssl # ---------------------------------------------------------------------------- # Error Handling my $sd = 0; # Use STDIN to read password my $xw = 0; # Use X windows my $rt = 0; # Retry on failure my @retry_args = @ARGV; # Repeat with the same original arguements # Script Usage, Help, Doc... Standard for all my perl programs sub Usage { print STDERR @_, "\n" if @_; @ARGV = ( "$FindBin::Bin/$PROGNAME" ); while( <> ) { next if 1 .. 2; last if /^###/; s/^#+$//; s/^# //; last if /^$/; print STDERR $. == 3 ? "Usage: $_" : " $_"; } print STDERR "For full manual use --help\n"; exit 10; } sub Help { @ARGV = ( "$FindBin::Bin/$PROGNAME" ); while( <> ) { next if $. == 1; last if /^###/; s/^#+$//; s/^# //; print STDERR; } exit 10; } sub Doc { @ARGV = ( "$FindBin::Bin/$PROGNAME" ); while( <> ) { next if $. == 1; last if /^######/; s/^#+$//; s/^# //; print STDERR; } exit 10; } # Report Failure (under X ask if they want to 'Try Again') sub Fail { if ( $xw ) { # use Xdialog, as it provides better control than zenity system("Xdialog --title 'KS Failure' --under-mouse ". "--ok-label 'Try Again' --yesno '@_' 0x0" ); #system("zenity --title 'KS Failure' ". # "--question --text='@_'" ); #system("xmessage -name KS -title 'KS failure' -center ". # "-buttons 'Cancel:0,Try Again:10' -default 'Try Again' ". # "-xrm '*message.borderWidth: 0' ". # "-xrm '*message.scrollVertical: Never' ". # "@_"); my $result=$?; # If user selected the default 'try again' restart program exec("$0", @retry_args) if $result == 0; print STDERR "$$PROGNAME: Exec retry failure!"; exit 10; # Precaution should never be needed } die ( "$PROGNAME: @_\n" ); # Just die with this error! } # Notify user with temporary, preferably non-intrusive message sub Notify { if ( $sd ) { print "@_", "\n"; } elsif ( $xw ) { #system("notify-send", "@_"); # auto-backgrounds (from libnotify package) system("notify_message", "@_"); # alternative message notifier script } else { print STDERR "@_", "\n"; } } # Some debugging reporting tools... my $verbose = 0; my $debug = 0; sub Command { print STDERR "KS Command: @_\n" if $verbose; } sub Debug { print STDERR "KS Debug: @_\n" if $debug; } # ---------------------------- # Argument handling my ( $um, $fn, $kc, $kl, $ke, $kr, $kd, $km, $kp, $kf ) = map 0, 0..20; my ( $ks, $kre ) = map undef, 0..10; OPTION: # Multi-switch option handling while( @ARGV && $ARGV[0] =~ s/^-(?=.)// ) { $_ = shift; { m/^$/ && do { next }; # Next option m/^-$/ && do { last }; # End of options '--' m/^\?/ && do { Usage }; # Usage Help '-?' m/^-?help$/ && Help; # Print the options m/^-?(doc|man|manual)$/ && Doc; # Print full documentation s/^x// && do { $xw++; redo }; # force password input via X windows s/^t// && do { $sd++; redo }; # force password input via stdin s/^u// && do { $um++; next }; # unmount this encryption s/^p// && do { $kp++; next }; # change password of keyfile s/^r// && do { $kr++; next }; # rename the keyfile name s/^D// && do { $kd++; next }; # delete the keyfile (DANGER) s/^f// && do { $fn++; next }; # report filename of keyfile s/^l// && do { $kl++; next }; # list keyfile settings s/^e// && do { $ke++; next }; # edit keyfile and data part s/^m// && do { $km++; next }; # change mount from/to paths s/^d// && do { $debug++; redo }; # debug, trace programs actions s/^v// && do { $verbose++; redo }; # verbose and debugging # rerely used options (debugging) s/^c// && do { $kc = $_ || shift; next }; # create specific encryption s/^ks// && do { $ks = $_ || shift; next }; # set keystore to use s/^kf// && do { $kf++; next }; # report full keyfile path s/^kre// && do { $kre++; next }; # read keyfile, and re-encrypt Usage( "$PROGNAME: Unknown Option \"-$_\"" ); } continue { next OPTION }; last OPTION; } $debug++ if $verbose; # turn on debug tracing Debug("Option Parsing Finished"); # No more arguments and X windows flag has been set - Request Key Name # This can be used both for mounting and unmounting. if ( @ARGV == 0 && $xw ) { # read the key name (including spaces in name) #Set_Focus('KS', 1); my $command = #"zenity --title=KS --entry --text='Key Name:'"; #"Xdialog --title KS --stdout --inputbox 'Key Name:' 0x0"; "x_ask_question KS 'Key Name'"; Command($command); $ARGV[0] = `$command`; chomp($ARGV[0]); exit 1 if $?; } # Option Sanity Checks Usage( "$PROGNAME: Options used are mutually exclusive" ) if $um+$fn+$kf+$kc+$kl+$ke+$kp+$kr+$kd+$km > 1; Usage( "$PROGNAME: Missing keyname, too few arguments" ) if @ARGV < 1; Usage( "$PROGNAME: Missing alternative mount point argument" ) if @ARGV < 2 && $km; Usage( "$PROGNAME: Missing second keyname to rename to" ) if @ARGV < 2 && $kr; Usage( "$PROGNAME: Too Many Arguments" ) if @ARGV > 1 && $um+$fn+$kf+$kl+$ke+$kp+$kd # one arg only || @ARGV > 2 && $kr # two ars only || @ARGV > 3; # max 3 args # Record the other arguments as appropriate my ($nm, $mf, $mt); $nm = shift; # key file unencoded name $mf = shift if @ARGV && ( @ARGV > 1 || $kc ); # mount from $mt = shift if @ARGV; # mount/rename to Usage( "$PROGNAME: FROM argument must be an absolute path" ) if $mf && $mf !~ /^\//; #Debug("\$mf = $mf"); #Debug("\$mt = $mt"); # Temporary directory, used for EncFS config creation only my $tmpdir = File::Spec->tmpdir(); # ---------------------------- # Other Subroutines # get original TTY settings - to restore chomp( my $stty_reset = qx(stty -F /dev/tty -g 2>/dev/null) ); sub prompt_read { print @_ if length $stty_reset; # TTY? my $i = ; return $i; } sub passwd_read { my $p; if ( $sd ) { # direct read from stdin -- prompt if needed, no other TTY handling $p = prompt_read(@_); } elsif ( $xw ) { # Use a GUI Password Helper to read password #Set_Focus('KS', 0); if ( defined $ENV{X_ASKPASS} ) { $p = `$ENV{X_ASKPASS} "@_"` } else { $p = #`zenity --title=KS --entry --text="@_" --hide-text` #`Xdialog --title KS --stdout --password --inputbox "@_" 0x0` `/usr/libexec/openssh/x11-ssh-askpass -title KS "@_"` #`x_ask_password KS "@_"` ; } } elsif ( length $stty_reset ) { # Use a TTY Password Helper to read password # I actually don't like the 'curses' based password helpers, # like "dialog" or "whiptail", as they take over the whole TTY # They also leave the decrypted file on screen when used with "vim" if ( defined $ENV{TTY_ASKPASS} ) { $p = `$ENV{TTY_ASKPASS} '@_'` } else { $p = #`dialog --stdout --insecure --passwordbox "@_" 0 0` #`whiptail --title "@_" --passwordbox _ 5 50 &1 >/dev/tty` `systemd-ask-password "@_" ; if ( length $stty_reset ) { system('stty', $stty_reset); print "\n"; } } chomp( $p ); # remove the final newline Fail("Zero length password not allowed!") unless length $p; return $p; } sub generate_random_key { chomp( my $k=`openssl rand -base64 45` ); # base64 length of 60 bytes return $k; } sub fname { # convert keyname to a EncFS 'look-a-like' filename my $fn = substr(sha1_base64(shift),2,24); $fn =~ tr%+/%,-%; return $fn; } sub umount { my $command = "fusermount -u '$_[0]' 2>/dev/null"; Command($command); system($command) && return; my $command = "umount '$_[0]' 2>/dev/null"; Command($command); system($command) && return; } # Call an openssl-like program to encrypt or decrypt data. # Essentually it needs to understand "-pass fd:N" password input option. sub crypto_filter { my ($data, $passwd, @crypt) = @_; my ($r_pwd,$w_pwd); pipe $r_pwd, $w_pwd; # disable close-on-exec for $r_pwd my $flags = fcntl($r_pwd, F_GETFD, 0) or die "fcntl F_GETFD: $!"; fcntl($r_pwd, F_SETFD, $flags & ~FD_CLOEXEC) or die "fcntl F_SETFD: $!"; # call encryption command, adding password input method my $rfn = fileno($r_pwd); my $pid = open2(my $data_out, my $data_in, @crypt, '-pass', "fd:$rfn" ); # send password print $w_pwd "$passwd"; close $w_pwd; # send data to encrypt print $data_in "$data"; close $data_in; # read the encrypted data my $result = do { local $/; <$data_out> }; # finish up waitpid $pid, 0; close $r_pwd; return $result; } sub encrypt_keyfile { my ( $keyfile, $data, $passwd ) = @_; Debug("Encrypting Keyfile Data"); # Only encrypt using the encryption program specified. my $encrypt = crypto_filter($data, $passwd, @encrypt_cmd ); Debug("Write Encrypted Keyfile"); open(KEYFILE, ">$keyfile") || Fail("Unable to read keyfile: $!\n"); print KEYFILE $encrypt; close KEYFILE; } sub decrypt_keyfile { my ( $keyfile, $passwd ) = @_; Debug("Read Keyfile for Decryption"); open(KEYFILE, $keyfile) || Fail("Unable to read keyfile: $!\n"); my $data = do { local $/; }; close KEYFILE; # Check the filemagic if ( "Salted__" eq substr($data,0,8) ) { # Old OpenSSL (assuming pre-v1.1.0) Debug("Decryption OpenSSL 'Salted' keyfile"); return crypto_filter($data, $passwd, @decrypt_salted); } elsif ( "KeepOut\n" eq substr($data,0,8) ) { # KeepOut wrapper around newer "openssl" Debug("Decryption 'KeepOut' OpenSSL wrapper keyfile"); return crypto_filter($data, $passwd, qw( keepout -d ) ); } elsif ( "PBKDF2__" eq substr($data,0,8) ) { # Old "encrypt" script (depreciated) Debug("Decryption 'Encrypt' keyfile"); $kre=1; return crypto_filter($data, $passwd, qw( encrypt -d ) ); } else { Fail("Keyfile saved using an unknown encryption"); } } # ---------------------------- # Handle House Keeping Options # if ( $nm =~ /\// ) { # key name is a directory path - try to unmount it Debug("Unmount given directory"); Fail("Directory not found, can not unmount") unless -d $nm; umount($nm); rmdir($nm) || Fail("Failed to unmount!"); exit 0; } # Locate the appropriate key store directory # This may be on a USB stick or other mounted file system # Do not look in NFS or tmpfs file systems. my $f = fname($nm); # report the filename of keyfile if ( $fn ) { Debug("Report keyfile filename"); print "$f\n"; exit(0); } Debug("Collect mounted file systems to search for key store"); Command("df -x nfs -x tmpfs --output=target | tail -n+2"); chomp( my @fs = `df -x nfs -x tmpfs --output=target 2>/dev/null | tail -n+2` ); # FUTURE COMMAND: findmnt --invert --pseud -nr -o TARGET Debug("Search file systems for a key store with the keyfile"); unless ( defined $ks ) { #for ( map { "$_/ks" } map { glob } qw( /media/* ~ ~/misc ) ) for ( map { "$_/ks" } ( @fs, map { glob } qw ( ~ ~/misc ) )) { #print "FS Search: $_\n"; $ks=$_,last if length && -f "$_/$f"; } # default to use if a key store is not found $ks ||= glob("~/misc/ks"); } Debug("Key Store Directory: $ks"); Debug("Key File = $f"); # Report the full path to the keyfile if ( $kf ) { print "$ks/$f\n"; exit(0); } # --------------------------------------------------------------------- # Specific Functions # # Create a specific filesystem encryption # $kf=""; # Finished with -kf option # Re-use for fetfile contents if ( lc($kc) eq 'data' ) { Debug("Creating a basic empty keyfile that you can store data in"); Fail("Keyname already present in key store!") if -f "$ks/$f"; Fail("The 'from' and 'to' directory are NOT required") if $mf; # Create the keyfile contents $kf = join("\n", "COMMAND=\$PAGER \$DATA", "PASSWORD=Encrypted Data File. No Master Password needed.", "FROM=No from dir needed", "TO=No mount point needed", "__DATA__", "Data to be displayed to the user. Replace this with your data." ); # Fall through to encrypt and save keyfile contents } if ( lc($kc) =~ 'encfs' ) { Debug("Creating a keyfile for a EncFS encryption"); Fail("Keyname already present in key store!") if -f "$ks/$f"; # This does not actually need the from/to arguments, # but if provided, it will store them in the keyfile for later mounting. # EncFS 1.6 has a --standard option for easier FS creation, and avoiding # the need to pipe input during creation. But you still need directories # to create the config file, which we store in the KS keyfile. my $k = generate_random_key; # created but will not be used! my $d1 = tempdir( "ks-XXXXXX", DIR => $tmpdir, CLEANUP => 1 ); my $d2 = tempdir( "ks-XXXXXX", DIR => $tmpdir, CLEANUP => 1 ); Command("encfs --stdinpass '$d1' '$d2' >/dev/null"); open(E, '|-') or exec "encfs --stdinpass '$d1' '$d2' >/dev/null"; print E "\n"; # request default parameters print E "$k\n"; # now give it the password we will never use! close E; umount($d2); rmdir($d2); # Read the generated config file, to include in keyfile Fail("EncFS Configuration build failure") unless -f "$d1/.encfs6.xml"; local $/ = undef; open(C, "$d1/.encfs6.xml"); my $cf = ; close C; system( 'shred', '-uz', "$d1/.encfs6.xml" ); unlink("$d1/.encfs6.xml"); rmdir($d1); Fail("EncFS Configuration failure") unless length $cf > 1024; # This speeds up actual encfs mounts (as we do the password check). # But it also completely destroys encfs password check within this config. # However we don't use that password anyway, only the other config settings, # so it does not matter in any case. No 'cracker' should see this, anyway. $cf =~ s/\d+1 $tmpdir, CLEANUP => 1 ); $ENV{CRYFS_FRONTEND}="noninteractive"; Command("cryfs '$d1' '$d2' >/dev/null"); open(E, '|-') or exec "cryfs '$d1' '$d2' >/dev/null"; print E "$k\n"; # now give it the password (for its own encryption) close E; umount($d2); rmdir($d2); # Read the generated config file, to include in keyfile Fail("CryFS Configuration build failure") unless -f "$d1/cryfs.config"; local $/ = undef; open(C, "$d1/cryfs.config"); my $cf = ; close C; system( 'shred', '-uz', "$d1/cryfs.config" ); unlink("$d1/cryfs.config"); Fail("CryFS Configuration failure") unless length $cf > 1024; # Create the keyfile contents $kf = join("\n", "COMMAND=cryfs --config \"\$DATA\" \"%s\" \"%s\" >/dev/null", "PASSWORD=". $k, "FROM=".($mf||''), "TO=".($mt||''), "__DATA__", $cf ); # Fall through to encrypt and save keyfile contents } if ( "$kf" ) { # encrypt and save the keyfile contents my $p; while ( 1 ) { $p = passwd_read "Password :"; my $p2 = passwd_read "Pwd Again: "; last if $p eq $p2; print STDERR "Password Mismatch -- Try Again!\n"; } encrypt_keyfile("$ks/$f", $kf, $p); exit(0); } if ( $kc ) { Fail("Unknown Creation Type") if -f "$ks/$f"; } # We probably should read the password NOW, and then junk it, regardless of if # keyfile exists or not, so as to give no indication if that keyfile exists # or not. # Fail( "Error key file not found!!!!" ) unless ( defined $ks && -f "$ks/$f" ); if ( $kd ) { Debug("Deleting keyfile"); Command("shred --verbose --force \"$ks/$f\""); system('shred', '--verbose', '--force', "$ks/$f"); unlink("$ks/$f") or Fail("Error key file delete failure: $!"); exit(0); } if ( $kr ) { Debug("Rename keyfile"); my $f2 = fname($mt); # work out new filename link("$ks/$f", "$ks/$f2") and unlink("$ks/$f") or Fail("Key store key file rename failed."); exit(0); } if ( $ke ) { Debug("Launching Editor on keyfile"); (undef, my $edit) = tempfile( "ks_XXXXXX", DIR => $tmpdir, SUFFIX => $vim_suffix, UNLINK => 1 ); unlink( $edit ); symlink("$ks/$f", $edit) or Fail("Key file symlink for edit failed: $!"); my $e = $ENV{VISUAL} || $ENV{EDITOR}; # text editor to use if ( $xw ) { # X window popup editor? $e = $ENV{XEDITOR} || # Xeditor to use (or xterm edit) "xterm -g 80x24 -name '$nm' -n '$nm' -T '$nm' -e $e"; } Command("$e $edit"); system( "$e $edit" ); # DANGER, this may be environment hackable unlink($edit); exit(0); } # # =========================================================================== # Debug("Get Password for the keyfile"); my $p = passwd_read "Password:"; $kf = decrypt_keyfile("$ks/$f", $p); # get contents of keyfile Debug("Extracting keyfile contents"); my ( $k, $cm, $ef, $et ); ( $cm ) = $kf =~ /^COMMAND=(.*).*/m; ( $k ) = $kf =~ /^PASSWORD=(.*).*/m; ( $ef ) = $kf =~ /^FROM=(.*).*/m; ( $et ) = $kf =~ /^TO=(.*)/m; Fail("Error, missing command or password in keyfile! (bad password?)") unless length($cm) && length($k); if ( $kl ) { # look at settings Debug("Look at the commands (not password)"); print "COMMAND=$cm\n"; print "FROM=$ef\n"; print "TO=$et\n"; exit(0); } if ( $km ) { # move from/to directories Debug("Replacing from/to directories"); $kf =~ s/^FROM=.*/FROM=$mf/m if defined $mf; $kf =~ s/^TO=.*/TO=$mt/m; } # Re-encrypt keyfile (to a new encryption format?) if ( $km || $kre ) { Debug("Re-encrypting keyfile"); encrypt_keyfile("$ks/$f", $kf, $p); exit(0); } $p = "Original password is no longer needed!!!"; if ( $kp ) { # Change KS File Encryption password (re-encrypt) my $p; while ( 1 ) { $p = passwd_read "New Password:"; my $p2 = passwd_read "Password Again:"; last if $p eq $p2; print STDERR "Password Mismatch -- Try Again!\n"; } encrypt_keyfile("$ks/$f", $kf, $p); exit(0); } chomp ( my $cwd = `pwd` ); # current working directory $ef = $mf||$ef; # EncFS from file system (full path) $et = $mt||$et||$nm; # EncFS to file system (just name) $et = "$cwd/$et" unless $et =~ /^\//; # if no path defined, mount in cwd Debug("Umount if requested"); umount($et) if length $et; exit 0 if $um; if ( length($ef) ) { Fail('Data Store not found') unless -d $ef || -f $ef; # directory or file Notify('Data Store is empty') if -d $ef && ! length(`ls -A '$ef'`); } # default command to run - reported later $cm = sprintf($cm, $ef, $et); # remove keyfile header, leaving just the data section for display $kf =~ s/^.*?\n__DATA__\n?//s; # ------------------------------------------------------- # DO IT -- Mount the encrypted data specified by the keyfile Debug("Figure out command to run"); # Is command a "$PAGER"? Setup command, and modification as appropriate if ( $cm =~ /^\$PAGER\b/ ) { $ENV{PAGER} = "cat" unless defined $ENV{PAGER}; #$ENV{PAGER} .= ' -f' if $ENV{PAGER} =~ /^[\/\w]\/less$/; # just "less" if ( $xw ) { if ( defined $ENV{XPAGER} ) { $ENV{PAGER} = $ENV{XPAGER}; # User defined X window pager } else { # stop gap measure, run PAGER in a terminal window $ENV{PAGER} = "xterm -g 80x50 -T pager -e $ENV{PAGER}"; } } } # Initialize data pipeline and mount points Debug("Create Data Pipeline"); ( undef, my $data_pipe ) = tempfile("ks_XXXXXX", DIR=>$tmpdir, UNLINK=>1 ); # Special handling for encrypted file systems if ( $cm =~ /^encfs\s/ ) { Fail("Source directory not found!") unless -d $ef; mkdir($et) unless -d $et; Fail("Unable to create mount point") unless -d $et; Fail("Mount directory not empty") if length(`ls -A '$et'`); # EncFS only reads it data once, so can read from a named pipe unlink($data_pipe); # we want a more secure named pipe, not a plain file system('mkfifo', '--mode=600', $data_pipe); #system('mknod', $data_pipe, 'p'); $ENV{ENCFS6_CONFIG} = $data_pipe; # where encfs reads its config } if ( $cm =~ /^cryfs\s/ ) { # cryfs does not read from a named pipe :-( # Write Data into a plain text file # This was originally a hack for EncFS File Bug (EncFS Issue #253) Fail("Source directory not found!") unless -d $ef; mkdir($et) unless -d $et; Fail("Unable to create mount point") unless -d $et; Fail("Mount directory not empty") if length(`ls -A '$et'`); if ( length($kf) > 1 ) { # copy data (cryfs config) to tmp file open(D, ">$data_pipe" ); print D $kf; close(D); } $ENV{CRYFS_FRONTEND}="noninteractive"; # CryFS config is set in command using "$DATA" variable } elsif ( length($kf) > 1 ) { # copy data to tmp pipe # Provide an actual named pipe for the DATA feed ($DATA unlink($data_pipe); # we want a more secure named pipe, not a plain file system('mkfifo', '--mode=600', $data_pipe); #system('mknod', $data_pipe, 'p'); } $ENV{DATA} = $data_pipe; # # Run the command # # The order for passing master password and data for "encfs" is very # important. First run command, then send DATA, and finally the password. # Debug("Run the command from keyfile"); Command($cm); open(E, '|-') or exec $cm or exit(1); # Send DATA into named pipe if ( length($kf) > 1 && $cm !~ /^cryfs\s/ ) { open(D, ">$data_pipe" ); print D $kf; close(D); } # send master password to stdin print E "$k\n"; close(E); my $stat = $?>>8; # Command has now backgrounded itself # cleanup data pipeline (be it a named pipe or file) Debug("Cleanup Pipeline"); system( 'shred', '-uz', $data_pipe ) if -f $data_pipe; unlink($data_pipe); # Check status if ($stat) { Fail("KS \"$nm\" failed") } else { Notify("KS \"$nm\" success"); } Debug("Exiting"); exit $stat