#!/bin/bash # # function "argument" # # What function does. # # Password source. # # --- # # encrypted_function [options] [arguments...] # # Encrypted Shell function. The function is stored in the script in # encrypted form. When executed a password is requested to decrypt and # execute the function. # # Options are provided to make it easy to replace, view or change the function # or encrypting password. All you need to do is copy this script and use the # options to set the appropraite function to store in the new script. # # Function Encryption Options (no arguments needed for these) # -fe Encode a function and store it in this script. (DANGER) # -fd Decode and output the actual function stored in this script! # -fp Change the password of the stored function # -fc Clear and cached password saved for function # # Without any of the above options the 'function' is decrypted using the # a password requested from the user. Any 'argument' is provided to the # encoded function as "$1", etc... # # Example: Convert 'argument' to a password to use... # Argument encrypted (without salt) using the built-in 'phrase'. The first # 8 characters is then base64 encoded, and prefixed by the string 'Pwd'. # Do NOT use this exact example for your own password generator. # # ( echo 'phrase'; echo -n "$1" ) | # openssl enc -aes-256-cbc -nosalt -pass stdin -base64 | # s/^\(........\)/Pwd\1/' # ### # # Any changes or improvements should be passed back to author... # # Created: 30 Jun 2017 - Anthony Thyssen # Updated: 4 Jul 2017 - Version 1.2 # Updated: 15 May 2018 - Version 1.3 - add passwd caching # ### # Discover where the shell script resides PROGNAME=`type "$0" | awk '{print $3}'` # search for executable on path PROGDIR=`dirname "$PROGNAME"` # extract directory of program PROGNAME=`basename "$PROGNAME"` # base name of program unset FUNCTION # ensure these are not an environment variables unset function # FUNCTION START FUNCTION='Encrypted Function will be stored here by the script options' # FUNCTION END Usage() { # Report error and Synopsis line only echo >&2 "$PROGNAME:" "$@" sed >&2 -n '1,2d; /^###/q; /^#/!q; /^#$/q; s/^# */Usage: /p;' \ "$PROGDIR/$PROGNAME" echo >&2 "For help use option '-help'" exit 10; } Help() { # Output Full header comments as documentation sed >&2 -n '1d; /^###/q; /^#/!q; s/^#//; s/^ //; p' \ "$PROGDIR/$PROGNAME" exit 10; } Error() { # Just output an error condition and exit (no usage) echo >&2 "$PROGNAME:" "$@" exit 2 } # -------------------------------------------------------------------------- KEYNAME="encfn_$PROGNAME" # store password using this key TIMEOUT=600 # timeout password (in seconds) 10 minutes #CLEAR_CACHE=true # force password on every use unset passwd # ensure these are not environment variables unset passwd2 clear_cached_password() { # Called to clear any previously cached password # We really want the user to actually type a new password. case "$TTY_ASKPASS" in ''|*/systemd-ask-password) keyctl purge user "$KEYNAME" 2>/dev/null ;; */askpass_stars) $TTY_ASKPASS --keyname "$KEYNAME" --clear-cached ;; *) : No Action needed ;; esac } read_password_noecho() { # A 'no-echo' TTY Password Reader (BASH) unset passwd # ensure it is not an environment variable! read -r -s -p "$1" passwd tty >/dev/null && echo '' } read_password() { # read the password from a askpass 'helper' script. # If 'helper' is known to allow password caching, make use of it. # Otherwise fall back to a system default, or read from the user. [[ $CLEAR_CACHE ]] && clear_cached_password case "$TTY_ASKPASS" in '') # No TTY_ASKPASS set, try system default, and fall back if [ -x /bin/systemd-ask-password ]; then # FALLBACK: Using the systemd password reader (with cache) passwd=$( /bin/systemd-ask-password \ --keyname "$KEYNAME" --accept-cached "$1" ) keyctl timeout "$KEYNAME" $TIMEOUT 2>/dev/null # 10 minute cache else read_password_noecho "$1" fi ;; */systemd-ask-password) # systemd passwd 'helper' with caching options passwd=$( $TTY_ASKPASS --keyname "$KEYNAME" --accept-cached "$1" ) keyctl timeout "$KEYNAME" $TIMEOUT 2>/dev/null # 10 minute cache ;; */askpass_stars) # a passwd 'helper' with set timeout option passwd=$( $TTY_ASKPASS --timeout=$TIMEOUT \ --keyname "$KEYNAME" --accept-cached "$1" ) ;; *) # Unknown user defined password 'helper' (no password cache) passwd=$( $TTY_ASKPASS "$1" ) ;; esac # Final checks [[ -z $passwd ]] && Error "Zero length password not allowed." } # -------------------------------------------------------------------------- # Program Option Actions decrypt_function() { # decrypt the function stored in this script function=$( ( echo "$passwd"; echo "$FUNCTION"; ) | openssl enc -d -aes-256-cbc -nosalt -pbkdf2 -pass stdin -base64 ) [ $? -eq 0 ] && return # clear password cache - in case it was used (see above) clear_cached_password Error "Failed to Decrypt Function. Invalid Password?" } encrypt_function() { # encrypt the function function=$( ( echo "$passwd"; echo -n "$function"; ) | openssl enc -aes-256-cbc -nosalt -pbkdf2 -pass stdin -base64 ) } store_function() { # DANGER -- Save the function into this script -- DANGER perl -i -pe '$_.="FUNCTION='\'"$function"\''\n# FUNCTION END\n",next if /^# FUNCTION START$/; $_="" if /^FUNCTION=/.../^# FUNCTION END$/; ' "$PROGDIR/$PROGNAME" } encode_function() { # read in a shell function and encode it into a base64 string read_password "Encoding Password:" passwd2="$passwd" read_password "Type Passwd Again:" [[ $passwd != $passwd2 ]] && Error "Password Mismatch!" echo "Type Function (Ctrl-D to finish):" function=`cat` encrypt_function store_function exit 0 } output_function() { # ask the password and decode the function read_password "Function Password:" decrypt_function echo "$function" exit 0 } change_password() { # ask the password and decode the function read_password "Old Password:" decrypt_function # ask new password and encode function again read_password "New Password:" passwd2="$passwd" read_password "Passwd Again:" [[ $passwd != $passwd2 ]] && Error "Password Mismatch!" encrypt_function store_function exit 0 } # --------------------------------------------------------------------------- # Option Handling while [ $# -gt 0 ]; do case "$1" in # Standard help option. -\?|-help|--help|--doc*) Help ;; -fe) encode_function ;; # encode a new function into this script -fd) output_function ;; # output the function stored in this script -fp) change_password ;; # change the password of the stored function -fc) clear_cached_password; exit 0 ;; # clear any cached password --) shift; break ;; # forced end of user options -*) Usage "Unknown option \"$1\"" ;; *) break ;; # unforced end of user options esac shift # next option done [ $# -gt 1 ] && Usage "Too many Arguments" [ $# -ne 1 ] && Usage "Missing Argument" # Optional Argument (adjust to suit) # --------------------------------------------------------------------------- # Execute Encrypted Function (normal use) # # Decrypt and execute the function. read_password "Password for Function \"$PROGNAME\":" decrypt_function $SHELL -c "$function" - "$@" # ---------------------------------------------------------------------------