#!/bin/bash
# Implement HMAC functionality on top of the OpenSSL digest functions.
# licensed under the terms of the GNU GPL v2
# Copyright 2007 Victor Lowther <victor.lowther@gmail.com>

die() {
  echo $*
  exit 1
}

check_deps() {
  local res=0
  while [ $# -ne 0 ]; do
    which "${1}" >& /dev/null || { res=1; echo "${1} not found."; }
    shift
  done
  (( res == 0 )) || die "aborting."
}

# write a byte (passed as hex) to stdout
write_byte() {
  # $1 = byte to write
  printf "\\x$(printf "%x" ${1})"
}

# make an hmac pad out of a key.
# this is not the most secure way of doing it, but it is
# the most expedient.
make_hmac_pad() {
  # using key in file $1 and byte in $2, create the appropriate hmac pad
  # Pad keys out to $3 bytes
  # if key is longer than $3, use hash $4 to hash the key first.
  local x y a size remainder oifs
  (( remainder = ${3} ))
  # in case someone else was messing with IFS.
  for x in $(echo -n "${1}" | od -v -t u1 | cut -b 9-);
  do
    write_byte $((${x} ^ ${2}))
    (( remainder -= 1 ))
  done
  for ((y=0; remainder - y ;y++)); do
    write_byte $((0 ^ ${2}))
  done
}

# utility functions for making hmac pads
hmac_ipad() {
  make_hmac_pad "${1}" 0x36 ${2} "${3}"
}

hmac_opad() {
  make_hmac_pad "${1}" 0x5c ${2} "${3}"
}

# hmac something
do_hmac() {
  # $1 = algo to use.  Must be one that openssl knows about
  # $2 = keyfile to use
  # $3 = file to hash.  uses stdin if none is given.
  # accepts input on stdin, leaves it on stdout.
  # Output is binary, if you want something else pipe it accordingly.
  local blocklen keysize x
  case "${1}" in
    sha) blocklen=64 ;;
    sha1) blocklen=64 ;;
    md5) blocklen=64 ;;
    md4) blocklen=64 ;;
    sha256) blocklen=64 ;;
    sha512) blocklen=128 ;;
    *) die "Unknown hash ${1} passed to hmac!" ;;
  esac
  cat <(hmac_ipad ${2} ${blocklen} "${1}") "${3:--}" | openssl dgst "-${1}" -binary | \
  cat <(hmac_opad ${2} ${blocklen} "${1}") - | openssl dgst "-${1}" -binary
}

[[ ${1} ]] || die "Must pass the name of the hash function to use to ${0}".

check_deps od openssl
do_hmac "${@}"
