~dricottone/my-utils

a5400e398c7c8f77d09b3e5966789d38f48a47a6 — Dominic Ricottone 1 year, 11 months ago 0996099 dev
Starting point for cryptographic utilities

Much of this came from my deep dive into digital signatures for my chat
application. More details in a recent blog post.

These tools are not yet mature enough to warrant an installation recipe.
The sign, verify, and verify-filter utilities are especially so.
8 files changed, 581 insertions(+), 0 deletions(-)

A crypto/keytype
A crypto/mkhtpasswd
A crypto/mkkey
A crypto/pgpls
A crypto/pgprm
A crypto/sign
A crypto/verify
A crypto/verify-filter
A crypto/keytype => crypto/keytype +151 -0
@@ 0,0 1,151 @@
#!/bin/bash

name="keytype"
version="1.0"
read -r -d '' help_message <<-EOF
	Inspect a cryptographic key for type and length
	Usage: keytype [OPTIONS] KEYFILE
	Options:
	 -h, --help     print this message and exit
	 -q, --quiet    suppress error messages and prompts
	 -v, --verbose  show additional messages
	 -V, --version  print version number and exit
EOF

source /usr/local/lib/mylib.bash

quiet=0
verbose=0
while [[ $# -gt 0 ]]; do
  case "$1" in

  -h|--help)
    help_msg
    shift
    ;;

  -q|--quiet)
    debug_msg "Setting quiet option to 1 (was ${quiet})"
    quiet=1
    shift
    ;;

  -v|--verbose)
    debug_msg "Setting verbose option to 1 (was ${verbose})"
    verbose=1
    shift
    ;;

  -V|--version)
    version_msg
    ;;

  *)
    debug_msg "Argument '${1}' added to positional array"
    positional+=("$1")
    shift
    ;;
  esac
done

try_openssh() {
  keylength=
  keyfingerprint=
  keytype=
  keycomment=

  keyinfo="$(/usr/bin/ssh-keygen -l -f "$keyfile" 2>/dev/null)"

  if [ $? -ne 0 ]; then
    debug_msg "file '${keyfile}' is not an OpenSSH key"
    return 1
  else
    keylength="$(/usr/bin/cut --delimiter=' ' --field=1 <<<"$keyinfo")"
    keyfingerprint="$(/usr/bin/cut --delimiter=' ' --field=2 <<<"$keyinfo")"
    keycomment="$(/usr/bin/awk '{$1=$2=$NF="";print substr($0,3,length($0)-3)}' <<<"$keyinfo")"
    keytype="$(/usr/bin/awk '{print substr($NF,2,length($NF)-2)}' <<<"$keyinfo")"

    /usr/bin/printf "%s: %s-bit %s OpenSSH key\n" "${keyfile}" "${keylength}" "${keytype}"
    return 0
  fi
}

try_openpgp() {
  keyinfo=
  keyprivacy=
  keylength=
  keytypenum=
  keytype=
  keyfingerprint=

  keyinfodump="$(/usr/bin/gpg2 --show-keys --with-colons "$keyfile" 2>/dev/null)"

  if [ $? -ne 0 ]; then
    debug_msg "file '${keyfile}' is not an OpenPGP key"
    return 1
  else
    if /usr/bin/grep -e "^pub:" <<<"$keyinfodump" >/dev/null 2>&1; then
      keyprivacy="public"
      keyinfo="$(/usr/bin/grep -e "^pub:" <<<"$keyinfodump")"
    elif /usr/bin/grep -e "^sec:" <<<"$keyinfodump" >/dev/null 2>&1; then
      keyprivacy="private"
      keyinfo="$(/usr/bin/grep -e "^sec:" <<<"$keyinfodump")"
    else
      debug_msg "file '${keyfile}' is an OpenPGP key, but it cannot be interpretted"
    fi

    keylength="$(/usr/bin/cut --delimiter=':' --field=3 <<<"$keyinfo")"
    keytypenum="$(/usr/bin/cut --delimiter=':' --field=4 <<<"$keyinfo")"

    case "$keytypenum" in
    1)
      keytype="RSA"
      ;;

    16)
      keytype="Elgamel"
      ;;

    17)
      keytype="DSA"
      ;;

    *)
      error_msg "Unknown algorythm on OpenPGP key"
      ;;

    #2 is deprecated as RSA Encrypt Only
    #3 is deprecated as RSA Sign Only
    #18 is reserved for Elliptic Curve but not part of the OpenPGP spec yet
    #19 is reserved for ECDSA but not part of the OpenPGP spec yet
    #20 is reserved; deprecated as Elgamel Encrypt or Sign
    #21 is reserved for Diffie-Hellman but not part of the OpenPGP spec yet
    #100-110 are reserved for experimental use
    #256+ are reserved for Libgcrypt to allocate
    esac

    keyfingerprint="$(/usr/bin/cut --delimiter=':' --field=5 <<<"$keyinfo")"

    /usr/bin/printf "%s: %s-bit %s %s OpenPGP key\n" "${keyfile}" "${keylength}" "${keyprivacy}" "${keytype}"
    return 0
  fi
}

try_openssl() {
  #TODO: implement OpenSSL checks
  :
}

code=0
for keyfile in "${positional[@]}"; do
  if ! try_openssh; then
    if ! try_openpgp; then
      if ! try_openssl; then
        code=1
      fi
    fi
  fi
done

exit "$code"


A crypto/mkhtpasswd => crypto/mkhtpasswd +39 -0
@@ 0,0 1,39 @@
#!/bin/bash

name="mkhtpasswd"
version="1.0"
read -r -d '' help_message <<-EOF
	Create an Apache htpasswd file
	Usage: mkhtpasswd [OPTIONS] >>.htpasswd
	Options:
	 -h, --help     print this message and exit
	 -q, --quiet    suppress error messages and prompts
	 -v, --verbose  show additional messages
	 -V, --version  print version number and exit
EOF

source /usr/local/lib/mylib.bash
. /usr/local/lib/myparse.bash

# Get username
user=
/usr/bin/printf "Enter username: " >&2
read user
if [[ -z "$user" ]]; then
  error_msg "expected non-empty username"
fi

# Get password
passwd=
/usr/bin/printf "Enter password:" >&2
read -s passwd
/usr/bin/printf "\n" >&2
if [[ -z "$passwd" ]]; then
  debug_msg "Using empty password"
else
  debug_msg "Using a non-empty password"
fi

# Print out the htpasswd file
/usr/bin/printf '%s:%s\n' "$user" "$(/usr/bin/openssl passwd -crypt "$passwd")"


A crypto/mkkey => crypto/mkkey +204 -0
@@ 0,0 1,204 @@
#!/bin/bash

name="mkkey"
version="1.0"
read -r -d '' help_message <<-EOF
	Create a cryptographic keypair
	Usage: mkkey [OPTIONS] [FILE]
	Options:
	 -a ALGO,            algorithm for keypair [rsa, ed25519]
	   --algorithm ALGO
	 -h, --help          print this message and exit
	 -q, --quiet         suppress error messages and prompts
	 --private NAME      private key file name
	 --public NAME       public key file name
	 -t TYPE,            type of keypair [ssh, pgp, ssl]
	   --type TYPE
	 -v, --verbose       show additional messages
	 -V, --version       print version number and exit
EOF

source /usr/local/lib/mylib.bash

positional=()
keytype=
keyalgo=
privkey=
pubkey=
quiet=
verbose=
while [[ $# -gt 0 ]]; do
  case "$1" in

  -a|--algorithm)
    debug_msg "Setting algorithm to '$2' (was ${keyalgo})"
    keyalgo="$2"
    shift; shift
    ;;

  -h|--help)
    help_msg
    shift
    ;;

  -q|--quiet)
    debug_msg "Setting quiet option to 1 (was ${quiet})"
    quiet=1
    shift
    ;;

  --private)
    debug_msg "Setting private key file name to '$2' (was ${privkey})"
    privkey="$2"
    shift; shift
    ;;

  --public)
    debug_msg "Setting public key file name to '$2' (was ${pubkey})"
    pubkey="$2"
    shift; shift
    ;;

  -t|--type)
    debug_msg "Setting type to '$2' (was ${keytype})"
    keytype="$2"
    shift; shift
    ;;

  -v|--verbose)
    debug_msg "Setting verbose option to 1 (was ${verbose})"
    verbose=1
    shift
    ;;

  -V|--version)
    version_msg
    ;;

  *)
    debug_msg "Argument '${1}' added to positional array"
    positional+=("$1")
    shift
    ;;
  esac
done

# handle output files
# NOTE: individual key types have additional requirements for output files
if [[ ${#positional[@]} -gt 0 ]] && [[ -z "$privkey" ]]; then
  debug_msg "Moving a positional argument into private key file name option"
  privkey="${positional[0]}"
  positional=("${positional[@]:1}")
fi
if [[ "$privkey" = "$pubkey" ]]; then
  error_msg "expected different file names for public and private key"
fi

try_openssh() {
  # if pubkey filename is specified, check if it complies with ssh-keygen
  if [[ ! -z "$pubkey" ]] && [[ "$pubkey" != "${privkey}.pub" ]]; then
    if [[ -z "$privkey" ]]; then
      privkey="$(/usr/bin/basename "$pubkey" ".pub")"
      if [[ "$privkey" = "$pubkey" ]]; then
        error_msg "public key must be written to a file named like the private key"
      fi
    else
      error_msg "public key must be written to a file named like the private key"
    fi
  fi

  # if privkey filename is not specified, use defaults
  if [[ -z "$privkey" ]]; then
    if [[ "$keyalgo" = "rsa" ]]; then
      privkey="${HOME}/.ssh/id_rsa"
      debug_msg "Default file for private RSA key is '${privkey}'"
      pubkey="${privkey}.pub"
      debug_msg "Default file for public RSA key is '${pubkey}'"
    elif [[ "$keyalgo" = "ed25519" ]]; then
      privkey="${HOME}/.ssh/id_ed25519"
      debug_msg "Default file for private Ed25519 key is '${privkey}'"
      pubkey="${privkey}.pub"
      debug_msg "Default file for public Ed25519 key is '${pubkey}'"
    else
      error_msg "key algorithm '${keyalgo}' not implemented"
    fi
  fi

  # check if keypair files already exist
  if [[ -f "$privkey" ]]; then
    error_msg "file '${privkey}' already exists"
  elif [[ -f "${pubkey}" ]]; then
    error_msg "file '${pubkey}' already exists"
  fi

  # make keypair
  if [[ "$keyalgo" = "rsa" ]]; then
    if ! /usr/bin/ssh-keygen -t rsa -b 4096 -f "$privkey"; then
      error_msg "failed to generate RSA keypair"
    fi
  elif [[ "$keyalgo" = "ed25519" ]]; then
    if ! /usr/bin/ssh-keygen -t ed25519 -f "$privkey"; then
      error_msg "failed to generate Ed25519 keypair"
    fi
  else
    error_msg "key algorithm '${keyalgo}' not implemented"
  fi
}

try_openpgp() {
  :
}

try_openssl() {
  # if privkey filename is not specified, try basename of pubkey
  if [[ -z "$privkey" ]]; then
    if [[ -z "$pubkey" ]]; then
      error_msg "expected private key file name"
    else
      privkey="$(/usr/bin/basename "$pubkey" ".pub")"
      if [[ "$privkey" = "$pubkey" ]]; then
        error_msg "expected private key file name"
      fi
    fi
  fi

  # if pubkey filename is not specified, use privkey as basename
  if [[ -z "$pubkey" ]] && [[ ! -z "$privkey" ]]; then
    pubkey="${privkey}.pub"
  fi

  # check if keypair files already exist
  if [[ -f "$privkey" ]]; then
    error_msg "file '${privkey}' already exists"
  elif [[ -f "${pubkey}" ]]; then
    error_msg "file '${pubkey}' already exists"
  fi

  # make keypair
  if [[ "$keyalgo" = "rsa" ]]; then
    if ! /usr/bin/openssl genrsa -out "$privkey" 4096; then
      error_msg "failed to generate RSA keypair"
    elif ! /usr/bin/openssl rsa -in "$privkey" -pubout -out "$pubkey"; then
      error_msg "failed to generate RSA public key"
    fi
  elif [[ "$keyalgo" = "ed25519" ]]; then
    if ! /usr/bin/openssl genpkey -algorithm Ed25519 -out "$privkey"; then
      error_msg "failed to generate Ed25519 keypair"
    elif ! /usr/bin/openssl pkey -in "$privkey" -pubout -out "$pubkey"; then
      error_msg "failed to generate Ed25519 public key"
    fi
  else
    error_msg "key algorithm '${keyalgo}' not implemented"
  fi
}

if [[ "$keytype" = "ssh" ]]; then
  try_openssh
elif [[ "$keytype" = "pgp" ]]; then
  try_openpgp
elif [[ "$keytype" = "ssl" ]]; then
  try_openssl
else
  error_msg "key type '${keytype}' not implemented"
fi


A crypto/pgpls => crypto/pgpls +25 -0
@@ 0,0 1,25 @@
#!/bin/sh

name="pgpls"
version="1.0"
usage_message="Usage: pgpls [OPTIONS] [KEY ..]"
help_message=$(/usr/bin/cat <<-EOF
	List OpenPGP keys on the keychain
	Usage: pgpls [OPTIONS]
	Options:
	 -h, --help     print this message and exit
	 -q, --quiet    suppress error messages
	 -v, --version  print version number and exit
EOF
)

. /usr/local/lib/myminiparse.sh

code=0
if ! /usr/bin/gpg2 –list-keys; then
  code=1
fi

# exit with stored code
exit "$code"


A crypto/pgprm => crypto/pgprm +37 -0
@@ 0,0 1,37 @@
#!/bin/sh

name="pgprm"
version="1.0"
usage_message="Usage: pgprm [OPTIONS] [KEY ..]"
help_message=$(/usr/bin/cat <<-EOF
	Delete an OpenPGP key from the keychain
	Usage: pgprm [OPTIONS] [KEY ..]
	Options:
	 -h, --help     print this message and exit
	 -q, --quiet    suppress error messages
	 -v, --version  print version number and exit
EOF
)

. /usr/local/lib/myminiparse.sh

# loop through arguments
code=0
for arg; do
  case "$arg" in
  -q|--quiet)
    #ignore these
    ;;

  *)
    # main routine
    if ! /usr/bin/gpg2 -delete-key "$arg"; then
      code=1
    fi
    ;;
  esac
done

# exit with stored code
exit "$code"


A crypto/sign => crypto/sign +5 -0
@@ 0,0 1,5 @@
# gpg2 --detach-sign example
# if ! gpg2 --verify example.sig example >/dev/null 2>&1; then code=1; fi

# gpg2 --clearsign example
# if ! gpg2 --verify example.asc >/dev/null 2>&1; then code=1; fi

A crypto/verify => crypto/verify +5 -0
@@ 0,0 1,5 @@
# gpg2 --detach-sign example
# if ! gpg2 --verify example.sig example >/dev/null 2>&1; then code=1; fi

# gpg2 --clearsign example
# if ! gpg2 --verify example.asc >/dev/null 2>&1; then code=1; fi

A crypto/verify-filter => crypto/verify-filter +115 -0
@@ 0,0 1,115 @@
#!/bin/bash

name="verify-filter"
version="1.0"
read -r -d '' help_message <<-EOF
	Verify a PGP-signed file and print it
	Usage: pgpcat [OPTIONS] FILE
	Options:
	 -e, --exit               exit without printing FILE if verification fails
	 -f FILE, --file FILE     file to verify and print
	 -h, --help               print this message and exit
	 -q, --quiet              suppress error messages
	 -s SIG, --signature SIG  detached signature file; if unspecified,
	                          FILE should be armored clear-signed message
	 -v, --verbose            show additional messages
	 -V, --version            print version number and exit
EOF

source /usr/local/lib/mylib.bash

positional=()
fail_immediately=0
msgfile=
quiet=0
sigfile=
verbose=0

while [[ $# -gt 0 ]]; do
  case $1 in

  -e|--exit)
    debug_msg "Setting exit option to 1 (was ${fail_immediately})"
    fail_immediately=1
    shift
    ;;

  -f|--file)
    debug_msg "Setting file option to ${2} (was ${msgfile})"
    msgfile="$2"
    shift; shift
    ;;

  -h|--help)
    help_msg
    shift
    ;;

  -q|--quiet)
    debug_msg "Setting quiet option to 1 (was ${quiet})"
    quiet=1
    shift
    ;;

  -s|--signature)
    debug_msg "Setting signature option to ${2} (was ${sigfile})"
    sigfile="$2"
    shift; shift
    ;;

  -v|--verbose)
    debug_msg "Setting verbose option to 1 (was ${verbose})"
    verbose=1
    shift
    ;;

  -V|--version)
    version_msg
    ;;

  *)
    debug_msg "Argument '${1}' added to positional array"
    positional+=("$1")
    shift
    ;;
  esac
done

# handle input files
if [[ -z "$msgfile" ]]; then
  if [[ ${#positional[@]} -lt 1 ]]; then
    error_msg "Expected input file (given none)"
  else
    debug_msg "Moving a positional argument into file option (${#positional[0]})"
    msgfile="${positional[0]}"
    positional=("${positional[@]:1}")
  fi
fi

code=0
if [[ -z "$sigfile" ]]; then
  debug_msg "'${msgfile}' is armored clearsigned"
  if ! /usr/bin/local/verify --file "$msgfile"; then
    code=1
    nonfatal_error_msg "Can not verify file '${msgfile}'"

    if [[ "$fail_immediately" -eq 1 ]]; then
      exit "$code"
    fi
  fi
  /usr/bin/cat "$msgfile"
else
  debug_msg "'${msgfile}' is signed, '${sigfile}' is detached signature"
  if ! /usr/bin/local/verify --file "$msgfile" --signature "${sigfile}"; then
    code=1
    nonfatal_error_msg "Can not verify file '${msgfile}'"

    if [[ "$fail_immediately" -eq 1 ]]; then
      exit "$code"
    fi
  fi
  /usr/bin/cat "$msgfile"
fi

exit "$code"