#!/bin/bash -x
#
# Steps to create certs:
# #######################
# Generate  private key
# openssl genrsa -aes128 -out dsm01-key.pem 4096
# Removing password from key
# openssl rsa -in dsm01-key.pem -out dsm01-key-nopasswd.pem
# Creating sign request for CA
# openssl req -new -out dsm01-req.pem -key dsm01-key-nopasswd.pem -config "$gencert"
# Sign CA request
# openssl ca -in dsm01-req.pem -days "$exp_days" -cert ca-cert.pem -keyfile ca-key.pem -out dsm01-cert.pem -config "$gencert"
# 
# TODO:
# CertMng --CASetup /tmp/cert --Ext v3_req --Opt "MsgDigestAlg="md5",SSLKeySize=1024,ExpDays=365,DNS=casipgw01.networklab.ca,IP=192.168.10.69"
# Default value: SSLKeySize=4096; ExpDays="$exp_days", MsgDigestAlg="md5"
#
#DEF_DIR
def_dir="/etc/pki/adm_cert"
date=$(date +"%m-%d-%Y-%T")
ca_ext="-extensions v3_ca"
req_ext="-extensions v3_req"
usr_ext="-extensions usr_cert"
version='1.0.1'

declare -A opt_array

usage() {
  echo -ne "Valid options for: $0\n[ --CASetup | Setup new CA | Value: Installation directory full path ]\n[ --GenCert | Generate Cert | Value: CNF file name | File format 'myorg.cnf' ] \n[ --GenReq | Generate Cert Request | Value: CNF file name | File format 'myorg.cnf' ]\n[ --RevokeCert | Cancel Certificate | Value: CNF file name | File format 'myorg.cnf' ]\n[ --SignRequest | Self Sign CA Certificate | Value: full path to request file | File format 'shortname-req.pem' ]\n[ --Ext | Openssl Extensions  | Value: v3_ca,v3_req,usr_cert ]\n[ --Export | Export to PKSC12 or JKS  | Value: P12, JKS ]\n[ --Opt | If Opt specified it will override default value | Default Value: SSLKeySize=4096; ExpDays=1095; MsgDigestAlg=sha1 | Supported Option: SSLKeySize; ExpDays; MsgDigestAlg; DNS; IP; ]\nExample: CA Authority setup with alternative dns names:\nCertMng --CASetup /tmp/cert --Ext v3_ca --Opt \"MsgDigestAlg=md5,SSLKeySize=1024,ExpDays=365,DNS1=myhostname.mydomainname.org,IP1=10.10.10.10\"\nNote: DNS IP can be specified how match you need.\n"
}

GenCert=""
GenReq=""
RevokeCert=""
SignReq=""
CASetup=""
Opt=""
Ext=""
Export=""

val=$(getopt -o :h::v::o:: --long GenCert:,GenReq:,RevokeCert:,SignRequest:,CASetup:,Opt:,Ext:,Export:,version:: -n 'CertMng' -- "$@")
eval set -- "$val"

if (( $# == 1 )); then
      usage
      exit
fi

while :; do
    case "$1" in
        -h|--help) usage $0 && exit 0;;
        -v|--version) echo "${0##*/} $version" && exit 0;;
        -c|--CASetup) setup=$2; shift 2;;
        -o|--Opt) list=$2; IFS=, read -r -a opt_list <<< "$list"; shift 2;;
        -g|--GenCert) gencert=$2; shift 2;;
        -r|--GenReq) genreq=$2; shift 2;;
        -R|--RevokeCert) certrevoke=$2; shift 2;;
        -S|--SignRequest) req_file=$2; shift 2;;
        -e|--Ext) ext=$2; shift 2;;
        -E|--Export) con=$2; shift 2;;
        --) shift 1; break;;
        *) break; usage ;;
     esac
done

for c in "${opt_list[@]}"; do
  opt_array+=([${c%%=*}]=${c#*=})
done

key_size=${opt_array['SSLKeySize']:=4096}
exp_days=${opt_array['ExpDays']:=1095}
msg_alg=${opt_array['MsgDigestAlg']:=sha1}

if [[ "$ext" == v3_req ]]; then
   for r in "${!opt_array[@]}"; do
      if [[ $r = DNS* ]]; then
         sum=$((n++))
         new_dns=$(sed -r 's/[0-9]{1,10}$//' <<< "$r")
         str_dns+=("$new_dns.$sum = ${opt_array[$r]}")
      fi
      if [[ $r = IP* ]]; then
         num=$((m++))
         new_ip=$(sed -r 's/[0-9]{1,10}$//' <<< "$r")
         str_ip+=("$new_ip.$num = ${opt_array[$r]}")
      fi
   done
fi

con() {
   echo -n "Please enter user last name: "
   read name
if [[ -d "$def_dir"/"$name" ]]; then
   passwd
   case "$con" in
     P12)
     openssl pkcs12 -export -name "$name"-cert -in "$def_dir"/"$name"/"$name"-cert.pem -inkey "$def_dir"/"$name"/"$name"-key-nopasswd.pem -out "$def_dir"/"$name"/"$name".p12 -passout pass:"${password[*]}"
     ;;
     JKS)
      if [[ -f "$def_dir"/"$name"/"$name".p12 ]]; then
        keytool -importkeystore -srckeystore "$def_dir"/"$name"/"$name".p12 -srcstoretype pkcs12 -destkeystore "$def_dir"/"$name"/"$name".jks -srckeypass "${password[*]}" -destkeypass "${password[*]}" -deststorepass "${password[*]}" -srcstorepass "${password[*]}" -noprompt -alias "$name"-cert
      keytool -list -v -keystore "$def_dir"/"$name"/"$name".jks -storepass "${password[*]}"
      else
        openssl pkcs12 -export -name "$name"-cert -in "$def_dir"/"$name"/"$name"-cert.pem -inkey "$def_dir"/"$name"/"$name"-key-nopasswd.pem -out "$def_dir"/"$name"/"$name".p12 -passout pass:"${password[*]}"
        keytool -importkeystore -srckeystore "$def_dir"/"$name"/"$name".p12 -srcstoretype pkcs12 -destkeystore "$def_dir"/"$name"/"$name".jks -srckeypass "${password[*]}" -destkeypass "${password[*]}" -deststorepass "${password[*]}" -srcstorepass "${password[*]}" -noprompt -alias "$name"-cert
        keytool -list -v -keystore "$def_dir"/"$name"/"$name".jks -storepass "${password[*]}"
     fi
     ;;
     *)
      echo "Invalid Option $1 ..."
      exit 1
     ;;
  esac
fi
}

passwd() {
echo -n "Enter Global Password [private key/keystore]: "
unset password
while IFS= read -r -s -n1 pass; do
  if [[ -z $pass ]]; then
     echo
     break
  else
     echo -n '*'
     password+="$pass"
  fi
done

}

name_print() {
  printf '%s\n' '/^#NAMES/a' "${str_ip[@]}" . w a | ex -s $1
  printf '%s\n' '/^#NAMES/a' "${str_dns[@]}" . w a | ex -s $1
}

add_req_v3() {
sed -i '/#EXT_V3/a \
[ v3_req ] \
basicConstraints = CA:FALSE \
subjectKeyIdentifier = hash \
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment \
subjectAltName = @alt_names \
\
[alt_names] \
#NAMES' "$1"


sed -i '/#EXT_REQ/a \
req_extensions = v3_req' "$1"
}

add_ca_ext() {
sed -i '/#EXT_CA/a \
x509_extensions = v3_ca' "$1"
}

add_default_ext() {
sed -i '/#EXT_USR/a \ 
x509_extensions = usr_cert' "$1"
}

remove_ext() {
sed -i '/#EXT_V3/,/#END/{//!d}' "$1"
sed -i '/#EXT_REQ/,/#END/{//!d}' "$1"
sed -i '/#EXT_CA/,/#END/{//!d}' "$1"
sed -i '/#EXT_USR/,/#END/{//!d}' "$1"
}

sign_req() {
echo -n "Please enter short name: "
read name

passwd

echo "Signing request for $name ..."
if [[ ! -d "$def_dir"/"$name" ]]; then
   mkdir -p "$def_dir"/"$name"
fi

/bin/mv "$req_file" "$def_dir"/"$name"
openssl ca -in "$def_dir"/"$name"/"${req_file##*/}" -days "$exp_days" -cert "$def_dir"/ca-cert.pem -keyfile "$def_dir"/ca-key.pem -out "$def_dir"/"$name"/"$name"-cert.pem -passin pass:"${password[*]}"
echo "Done ..."
echo -n "Verifing created cert for $name"
openssl verify -CAfile "$def_dir"/ca-cert.pem "$def_dir"/"$name"/"$name"-cert.pem
echo "Done ..."
exit 0
}

gen_req() {
echo -n "Please enter user last name: "
read name

passwd

echo "Generating certificates request for $name ..."
if [[ ! -d "$def_dir"/"$name" ]]; then
   mkdir -p "$def_dir"/"$name"
fi

openssl genrsa -aes128 -passout pass:"${password[*]}" -out "$def_dir"/"$name"/"$name"-key.pem "$key_size"
openssl rsa -passin pass:"${password[*]}" -in "$def_dir"/"$name"/"$name"-key.pem -out "$def_dir"/"$name"/"$name"-key-nopasswd.pem

if [[ "$ext" == v3_req ]]; then
      add_req_v3 "$def_dir"/"$genreq"
      name_print "$def_dir"/"$genreq"
      openssl req -new -passin pass:"${password[*]}" -out "$def_dir"/"$name"/"$name"-req.pem -key "$def_dir"/"$name"/"$name"-key-nopasswd.pem -extensions v3_req -config "$def_dir"/"$genreq"
else
      openssl req -new -passin pass:"${password[*]}" -out "$def_dir"/"$name"/"$name"-req.pem -key "$def_dir"/"$name"/"$name"-key-nopasswd.pem -config "$def_dir"/"$genreq"
fi

cat "$def_dir"/"$name"/"$name"-req.pem
echo "Done ..." 
exit 0
}

gen_user() {
echo -n "Please enter user last name: "
read name

passwd

echo "Generating certificates for $name ..."
if [[ -d "$def_dir"/"$name" ]]; then
   echo -n "User with name $name already exist. Do you want revoke cert first? [Y/n]: "
   read answer
   if [[ "$answer" =~ ^([yY][eE][sS]|[yY])$ ]]; then
        openssl ca -cert "$def_dir"/ca-cert.pem -keyfile "$def_dir"/ca-key.pem -revoke "$def_dir"/"$name"/"$name"-cert.pem -passin pass:"${password[*]}" -config "$def_dir"/"$gencert"
        openssl ca  -cert "$def_dir"/ca-cert.pem -keyfile "$def_dir"/ca-key.pem -gencrl -out "$def_dir"/ca_rev-list.pem -passin pass:"${password[*]}" -config "$def_dir"/"$gencert"
        /bin/mv -f "$def_dir"/"$name" "$def_dir"/"$name"_rev-"$date"  
    else
         echo "Done. Exiting ..."
         exit 1
    fi
fi

if [[ ! -d "$def_dir"/"$name" ]]; then
     mkdir -p "$def_dir"/"$name"
fi
openssl genrsa -aes128 -passout pass:"${password[*]}" -out "$def_dir"/"$name"/"$name"-key.pem "$key_size"
openssl rsa -passin pass:"${password[*]}" -in "$def_dir"/"$name"/"$name"-key.pem -out "$def_dir"/"$name"/"$name"-key-nopasswd.pem

if [[ "$ext" == v3_req ]]; then
   add_req_v3 "$def_dir"/"$gencert"
   name_print "$def_dir"/"$gencert"
   openssl req -new -passin pass:"${password[*]}" -out "$def_dir"/"$name"/"$name"-req.pem -key "$def_dir"/"$name"/"$name"-key-nopasswd.pem -extensions v3_req -config "$def_dir"/"$gencert"
    openssl ca -in "$def_dir"/"$name"/"$name"-req.pem -days "$exp_days" -cert "$def_dir"/ca-cert.pem -keyfile "$def_dir"/ca-key.pem -out "$def_dir"/"$name"/"$name"-cert.pem -extensions v3_req -config "$def_dir"/"$gencert" -passin pass:"${password[*]}"
else
      openssl req -new -passin pass:"${password[*]}" -out "$def_dir"/"$name"/"$name"-req.pem -key "$def_dir"/"$name"/"$name"-key-nopasswd.pem -config "$def_dir"/"$gencert"
    openssl ca -in "$def_dir"/"$name"/"$name"-req.pem -days "$exp_days" -cert "$def_dir"/ca-cert.pem -keyfile "$def_dir"/ca-key.pem -out "$def_dir"/"$name"/"$name"-cert.pem -config "$def_dir"/"$gencert" -passin pass:"${password[*]}"
fi

cat "$def_dir"/"$name"/"$name"-cert.pem "$def_dir"/"$name"/"$name"-key-nopasswd.pem > "$def_dir"/"$name"/"$name"-bundle.pem
echo "Done ..." 

echo -n "Verifing created cert for $name"
openssl verify -CAfile "$def_dir"/ca-cert.pem "$def_dir"/"$name"/"$name"-cert.pem
remove_ext "$def_dir"/"$gencert"
echo "Done ..."
exit 0

}

certrev_user() {
echo -n "Please enter user last name: "
read name

passwd

openssl ca -cert "$def_dir"/ca-cert.pem -keyfile "$def_dir"/ca-key.pem -revoke "$def_dir"/"$name"/"$name"-cert.pem -passin pass:"${password[*]}" -config "$def_dir"/"$certrevoke"
openssl ca  -cert "$def_dir"/ca-cert.pem -keyfile "$def_dir"/ca-key.pem -gencrl -out "$def_dir"/ca_rev-list.pem -passin pass:"${password[*]}" -config "$def_dir"/"$certrevoke"
/bin/mv -f "$def_dir"/"$name" "$def_dir"/"$name"_rev-"$date"
 
}

ca_cnf() {
d='$dir'
printf "%sdefault_ca = CA_default
[ CA_default ]
dir = ca_dir
serial = $d/serial
database = $d/index.txt
new_certs_dir = $d/certs
certificate = $d/ca-cert.pem
crl = $d/ca_rev-list.pem
private_key = $d/ca-key.pem
default_days = "$exp_days"
default_crl_days = 15
default_md = "$msg_alg"
crl_extensions = crl_ext
#EXT_USR
#END
preserve = no
email_in_dn = no
unique_subject = no
nameopt = default_ca
certopt = default_ca
policy = policy_match

[ policy_match ]
countryName = match
stateOrProvinceName = match
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional

[ req ]
default_bits = "$key_size" # Size of keys
default_keyfile = ca_key.pem # name of generated keys
default_md = "$msg_alg" # message digest algorithm
string_mask = nombstr # permitted characters
distinguished_name = req_distinguished_name
#EXT_REQ
#END
#EXT_CA
#END

[ req_distinguished_name ]
# Variable name Prompt string
#------------------------- ----------------------------------
0.organizationName = Organization Name (company)
organizationalUnitName = Organizational Unit Name (department, division)
emailAddress = Email Address
emailAddress_max = 40
localityName = Locality Name (city, district)
stateOrProvinceName = State or Province Name (full name)
countryName = Country Name (2 letter code)
countryName_min = 2
countryName_max = 2
commonName = Common Name (hostname, IP, or your name)
commonName_max = 64
# Default values for the above, for consistency and less typing.
# Variable name Value
#------------------------ ------------------------------
0.organizationName_default = org_name
organizationalUnitName_default = org_unit
localityName_default = org_locality
stateOrProvinceName_default = org_state
countryName_default = org_country
emailAddress_default = org_email

[ usr_cert ]
# These extensions are added when 'ca' signs a request.
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
nsComment = \"OpenSSL Generated Certificate\"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer

[ v3_ca ]
# Extensions for a typical CA
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = CA:true
keyUsage = cRLSign, keyCertSign

#EXT_V3
#END

[ crl_ext ]
# issuerAltName=issuer:copy
authorityKeyIdentifier = keyid:always,issuer:always
"
}

ca_setup() {
echo -n "Specify Organization Name: "
read org_name
echo -n "Specify Organization Unit: "
read org_unit
echo -n "Specify Organization Locality: "
read org_locality
echo -n "Specify Organization State: "
read org_state
echo -n "Specify Organization Country: "
read org_country
echo -n "Specify Organization Email: "
read org_email

passwd

echo "Generating CNF file for Openssl .."
     mkdir -p "$setup"
     mkdir -p "$setup"/certs
     ca_cnf > "$setup"/${org_name,,}.cnf

     ca_dir="$(echo $setup | sed 's:/:\\\/:g')"

     sed -i -e "s/org_name/$org_name/g" \
            -e "s/org_unit/$org_unit/g" \
            -e "s/org_locality/$org_locality/g" \
            -e "s/org_state/$org_state/g" \
            -e "s/org_country/$org_country/g" \
            -e "s/ca_dir/$ca_dir/g" \
            -e "s/org_email/$org_email/g" "$setup"/"${org_name,,}".cnf

     echo "Done ..."
#fi

echo "01" >  "$setup"/serial
echo "01" >  "$setup"/crlnumber
touch "$setup"/index.txt
openssl genrsa -aes128 -passout pass:"${password[*]}" -out "$setup"/ca-key.pem "$key_size"

if [[ "$ext" == v3_req ]]; then
    add_req_v3 "$setup"/"${org_name,,}".cnf
    add_ca_ext "$setup"/"${org_name,,}".cnf
    name_print "$setup"/"${org_name,,}".cnf
    openssl req -new -x509 -extensions v3_ca -key "$setup"/ca-key.pem -out "$setup"/ca-cert.pem -days "$exp_days" -passin pass:"${password[*]}" -config "$setup"/"${org_name,,}".cnf
else
   add_ca_ext "$setup"/"${org_name,,}".cnf
   openssl req -new -x509 -key "$setup"/ca-key.pem -out "$setup"/ca-cert.pem -days "$exp_days" -passin pass:"${password[*]}" -config "$setup"/"${org_name,,}".cnf
fi
openssl ca -passin pass:"${password[*]}" -config "$setup"/"${org_name,,}".cnf -gencrl -out "$setup"/ca_rev-list.pem
echo "Done ..."
}

if [[ -n $gencert ]]; then
     gen_user
fi

if [[ -n $genreq ]]; then
      gen_req
fi

if [[ -n $certrevoke ]]; then
     certrev_user
fi

if [[ -f "$req_file" ]]; then
    sig_cert
fi

if [[ -n "$con" ]]; then
    con
    exit 0
fi

if [[ -d  $def_dir ]]; then
   ca_dir="$(echo $setup | sed 's:/:\\\/:g')"
   echo -ne "CA is aleady setup under this dir $setup ...\n"
   echo -ne "Do you want delete existing CA Authority in $def_dir [Y/n]: "
   read answer
   if [[ "$answer" =~ ^([yY][eE][sS]|[yY])$ ]]; then
      rm -rf "$def_dir"
      sed -i "/^def_dir/d" /usr/bin/CertMng
      sed -i "/^#DEF_DIR/a \def_dir="\"$ca_dir"\"" /usr/bin/CertMng
      ca_setup
   fi
 else
      sed -i "/^def_dir/d" /usr/bin/CertMng
      sed -i "/^#DEF_DIR/a \def_dir="\"$ca_dir"\"" /usr/bin/CertMng
      ca_setup
fi

remove_ext "$setup"/"${org_name,,}".cnf
