#!/bin/bash
# Copyright 2014 Quobyte Inc.

# Script to generate a CA which will be required for service certificates.

trap onexit EXIT
function onexit() {
  if [ -f "$OPENSSL_OUTPUT" ]; then
    rm "$OPENSSL_OUTPUT"
  fi
}

function print_usage_and_exit {
  printf "Usage: %s --registry=<registry hosts> [--ca <directory>] [--import_cert <ca_cert_path> [--import_cert_format DER|PEM|NET] [--import_key <ca_key_path>] [--import_key_format RSA|DSA]]\n" $(basename $0)
cat <<EOF

One-time initialization of a certificate authority (CA) used by all services.

Required Arguments:
  --registry List of registry services or DNS SRV name for host.cfg.

Optional Arguments:
  --ca                  Directory where all certificates will be stored.
                        If not specified, current working directory is used.
  --import_cert         CA certificate file to import.
                        Supported formats: X509, PKCS12.
                        If the file name ends with .p12, certificate and
                        private key are imported from a PKCS12 keystore.
                        If omitted, a new CA certificate is generated.
  --import_cert_format  Optional format for imported X509 certificate.
                        Supported options: DER, PEM, NET. Default is PEM.
                        Will be ignored for PKCS12 keystores.
  --import_key          CA private key file to import.
                        Required with --import_cert and X509 certificate file.
  --import_key_format   Optional format for imported private key file.
                        Supported options: RSA, DSA. Default is RSA.

EOF
  exit 1
}

# Parse arguments.
if [ $# -eq 0 ]; then
  print_usage_and_exit
fi
BINARY_NAME="$(basename "$0")"
OPTS=`getopt -n "$BINARY_NAME" -o h -l help::,ca:,registry:,import_cert:,import_key:,import_cert_format:,import_key_format: -- "$@"`
if [ $? -ne 0 ]; then
    echo "ERROR: Failed to parse command line options."
    exit 2
fi

unset -v CA_DIRECTORY
unset -v HELP
unset -v REGISTRY
unset -v IMPORT_CERT
unset -v IMPORT_KEY
unset -v IMPORT_CERT_FORMAT
unset -v IMPORT_KEY_FORMAT
eval set -- "$OPTS"
while true
do
    case "$1" in
        --ca) CA_DIRECTORY=$2; shift 2;;
        --registry) REGISTRY=$2; shift 2;;
        --import_cert) IMPORT_CERT=$2; shift 2;;
        --import_key) IMPORT_KEY=$2; shift 2;;
        --import_cert_format) IMPORT_CERT_FORMAT=$2; shift 2;;
        --import_key_format) IMPORT_KEY_FORMAT=$2; shift 2;;
        -h) HELP=1; shift;;
        --help) HELP=1; shift 2;;
        --) shift; break;;
    esac
done
if [ -n "$HELP" ]; then
  print_usage_and_exit
fi

if [ -z "$REGISTRY" ]; then
  echo "ERROR: No --registry specified. Example: --registry=localhost:7861"
  exit 4
fi

# Check options.
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
if [ "$(dirname `readlink --canonicalize "$0"`)" = "/usr/bin" ]; then
  source "/opt/quobyte/lib/quobyte_ca_lib.inc"
else
  source "${SCRIPT_DIR}/quobyte_ca_lib.inc"
fi
if [ $? -ne 0 ]; then
  echo "ERROR: Failed to include required library."
  exit 3
fi
create_ca_directory_if_not_exists
check_ca_directory
if [ -e "$CA_CSR" -o -e "$CA_KEY" -o -e "$CA_PEM" -o -e "$CA_SRL" ]; then
  echo "ERROR: Some CA files already exist. Delete them first or run the command from a different directory."
  exit 5
fi

# Init variables.
OPENSSL_OUTPUT="/tmp/quobyte_init_ca_openssl_output.txt"

# Main.
if [ "$CA_DIRECTORY" = "." ]; then
  echo "INFO: Use --ca <directory> to not use the current working directory."
fi

if [ -n "$IMPORT_CERT" ]; then
  # Import existing certificate.
  if [ ! -f "$IMPORT_CERT" ]; then
    echo "ERROR: File not found: $IMPORT_CERT"
    exit 2
  fi
  : ${IMPORT_KEY_FORMAT:="rsa"} # default key format: RSA
  IMPORT_KEY_FORMAT=`echo $IMPORT_KEY_FORMAT | tr '[:upper:]' '[:lower:]'`
  if [[ "$IMPORT_CERT" == *.p12 ]]; then
    # PKCS12 file: extract cert + key
    echo "INFO: Importing certificate from PKCS12 file $IMPORT_CERT ... "
    openssl pkcs12 -in "$IMPORT_CERT" -out "$CA_PEM" -cacerts -nokeys >"$OPENSSL_OUTPUT" 2>&1
    check_openssl_return_code_or_exit $? $OPENSSL_OUTPUT
    if [ ! -s "$CA_PEM" ]; then
      echo "ERROR: no CA certificate found in PKCS12 file $IMPORT_CERT."
      exit 2
    fi
    # Mark as TRUSTED CERTIFICATE and remove redundant header info.
    openssl x509 -in "$CA_PEM" -out "$CA_PEM" -trustout >"$OPENSSL_OUTPUT" 2>&1
    check_openssl_return_code_or_exit $? $OPENSSL_OUTPUT
    echo "done"
    echo "INFO: Importing key from PKCS12 file $IMPORT_CERT ... "
    openssl pkcs12 -in "$IMPORT_CERT" -out "$CA_KEY" -nocerts -nodes >"$OPENSSL_OUTPUT" 2>&1
    check_openssl_return_code_or_exit $? $OPENSSL_OUTPUT
    if [ ! -s "$CA_KEY" ]; then
      echo "ERROR: no private key found in PKCS12 file $IMPORT_CERT."
      exit 2
    fi
    # Remove redundant header info from output.
    openssl $IMPORT_KEY_FORMAT -in "$CA_KEY" -out "$CA_KEY" >"$OPENSSL_OUTPUT" 2>&1
    check_openssl_return_code_or_exit $? $OPENSSL_OUTPUT
    echo "done"
  else
    # No PKCS12 file: try to convert as X509 certificate.
    if [ -z "$IMPORT_KEY" ]; then
      echo "ERROR: Missing key file. Please execute with --import_key <CA_private_key_file>."
      exit 2
    fi
    INFORM=""
    if [ -n "$IMPORT_CERT_FORMAT" ]; then INFORM="-inform $IMPORT_CERT_FORMAT"; fi

    # 1. Try to parse cert file using OpenSSL.
    echo -n "INFO: Checking certificate file $IMPORT_CERT ... "
    openssl x509 -in "$IMPORT_CERT" $INFORM >"$OPENSSL_OUTPUT" 2>&1
    check_openssl_return_code_or_exit $? $OPENSSL_OUTPUT
    echo "done"
    # 2. If cert is parsable, import key file.
    echo -n "INFO: Importing key file $IMPORT_KEY ... "
    openssl $IMPORT_KEY_FORMAT -in "$IMPORT_KEY" -out "$CA_KEY" >"$OPENSSL_OUTPUT" 2>&1
    check_openssl_return_code_or_exit $? $OPENSSL_OUTPUT
    if [ ! -s "$CA_KEY" ]; then
      echo "ERROR: Could not import private key from file $IMPORT_KEY."
      exit 2
    fi
    echo "done"
    # 3. Convert cert file.
    echo -n "INFO: Importing certificate file $IMPORT_CERT ... "
    openssl x509 -in "$IMPORT_CERT" $INFORM -out "$CA_PEM" -trustout >"$OPENSSL_OUTPUT" 2>&1
    check_openssl_return_code_or_exit $? $OPENSSL_OUTPUT
    if [ ! -s "$CA_PEM" ]; then
      echo "ERROR: Could not import CA certificate from file $IMPORT_CERT."
      exit 2
    fi
    echo "done"
  fi
else
  # Generate new certificate.
  echo -n "INFO: Creating new certificate authority (CA) in ${CA_DIRECTORY_TEXT} ... "

  openssl req -addext basicConstraints=CA:TRUE -new -newkey rsa:$KEY_LENGTH -noenc -out "$CA_CSR" \
    -keyout "$CA_KEY" -subj "/CN=Quobyte CA" \
    >"$OPENSSL_OUTPUT" 2>&1
  check_openssl_return_code_or_exit $? $OPENSSL_OUTPUT

  openssl x509 -copy_extensions copyall -trustout -signkey "$CA_KEY" -days $EXPIRATION_TIME -req \
    -in "$CA_CSR" -out "$CA_PEM" >"$OPENSSL_OUTPUT" \
    >"$OPENSSL_OUTPUT" 2>&1
  check_openssl_return_code_or_exit $? $OPENSSL_OUTPUT
  echo "done"
fi
# Init serial number file.
echo "02" > "$CA_SRL"

BASENAME=$(basename "$path")
SCRIPT_NAME="quobyte_init_ca"
DATE=$(date)
cat <<EOF > "$HOST_CFG"
# Automatically generated file $BASENAME by $SCRIPT_NAME on $DATE.
#
# DIR hostname/IP address + port or SRV record domain.
registry=${REGISTRY}

# log level (4=ERROR, 5=WARN, 6=INFO, 7=DEBUG)
debug.level=6

EOF

echo "<$CA_OPTION>"     >> "$HOST_CFG"
cat "$CA_PEM"           >> "$HOST_CFG"
echo "</$CA_OPTION>"    >> "$HOST_CFG"

cat <<EOF
INFO: Generated host.cfg.

Run "quobyte_create_service" for each service in order
to create a service configuration and push the host.cfg and
service configuration to the host.
EOF
