#!/bin/bash
#
# LTC Certification Authority
# Shell script client for Linux and Windows Git Bash
#
# Copyright 2018 LTC Sp. z o.o.
# All rights reserved.
#

# curl http://www.finn.pl/ltc-root-ca/ltc-root-ca-2-client -O; chmod u+x ltc-root-ca-2-client

PROGNAME=$(basename $0)

CAID=ltc-root-ca-2
CAOID=1.3.6.1.4.1.10853.1

WEB_NAME="LTC Root CA"
WEB_URL=http://ltc-root-ca.finn.pl/ltc-root-ca
WEB_CAREPO_URL=http://ltc-root-ca.finn.pl/ltc-root-ca

OPENVPN_HOSTNAME=

cnf_content () {
if [ "$1" == "" ]; then
  SANLINE=""
else
  SANLINE="subjectAltName=$1"
fi
cat << __CNF__

[ req ]
default_bits = 2048
default_md = sha256

distinguished_name = req_distinguished_name

attributes = req_attributes
string_mask = utf8only

[ req_distinguished_name ]

[ req_attributes ]

### Certificate Profile ###

[ v3_req_personal ]
basicConstraints=critical,CA:FALSE
keyUsage=critical,nonRepudiation,digitalSignature,keyEncipherment
certificatePolicies=@cert_policy_cps,@cert_policy_personal
subjectKeyIdentifier=hash
$SANLINE

[ v3_req_ssl ]
basicConstraints=critical,CA:FALSE
keyUsage=critical,digitalSignature,keyEncipherment
extendedKeyUsage=clientAuth,serverAuth
certificatePolicies=@cert_policy_cps,@cert_policy_ssl
subjectKeyIdentifier=hash
$SANLINE

[ v3_req_ipsec ]
basicConstraints=critical,CA:FALSE
keyUsage=critical,digitalSignature,keyEncipherment
extendedKeyUsage=clientAuth,serverAuth,1.3.6.1.5.5.8.2.2
certificatePolicies=@cert_policy_cps,@cert_policy_ipsec
subjectKeyIdentifier=hash
$SANLINE

[ v3_req_code ]
basicConstraints=critical,CA:FALSE
keyUsage=critical,digitalSignature
extendedKeyUsage=critical,codeSigning
certificatePolicies=@cert_policy_cps,@cert_policy_code
subjectKeyIdentifier=hash
$SANLINE

### Certificate Policies ###

[ cert_policy_cps ]
policyIdentifier=$CAOID.1.1
CPS.1=$WEB_URL/ltc-root-ca-cps.pdf
userNotice=@cert_policy_cps_notice

[ cert_policy_cps_notice ]
explicitText="Certificate Practice Statement / Kodeks Postepowania Certyfikacyjnego"

[ cert_policy_ocsp ]
policyIdentifier=$CAOID.2.1
userNotice=@cert_policy_ocsp_notice

[ cert_policy_ocsp_notice ]
explicitText="OCSP certificate / Certyfikat OCSP"

[ cert_policy_personal ]
policyIdentifier=$CAOID.2.2
userNotice=@cert_policy_personal_notice

[ cert_policy_personal_notice ]
explicitText="Personal certificate / Certyfikat osobowy"

[ cert_policy_code ]
policyIdentifier=$CAOID.2.3
userNotice=@cert_policy_code_notice

[ cert_policy_code_notice ]
explicitText="Code signing certificate / Certyfikat podpisywanie kodu"

[ cert_policy_ipsec ]
policyIdentifier=$CAOID.2.4
userNotice=@cert_policy_ipsec_notice

[ cert_policy_ipsec_notice ]
explicitText="IPsec certificate / Certyfikat IPsec"

[ cert_policy_ssl ]
policyIdentifier=$CAOID.2.5
userNotice=@cert_policy_ssl_notice

[ cert_policy_ssl_notice ]
explicitText="SSL certificate / Certyfikat SSL"

__CNF__
}

_contains() {
  _str="$1"
  _sub="$2"
  echo "$_str" | grep -- "$_sub" >/dev/null 2>&1
}

genpasswd() {
  if [ ! -f "$1" ]; then
    openssl rand -base64 16 | tr -cd 'ABCDEFGHJKLMNOPQRTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!#$+ \n' > "$1" || return
  fi
  if [ ! -f "$1" ]; then
    >&2 echo "No $1 file with password!"
    return 1
  fi
}

usage() {
  echo "Usage: $PROGNAME req|req-nodes personal|code|ipsec|ssl \"subject\" [altSubName] [name]"
  echo "       $PROGNAME p12 <name>"
  echo "       $PROGNAME ovpn <name>"
  echo "       $PROGNAME keystore <name> (deprecated old Java format)"
  echo ""
  echo "Examples:"
  echo "$PROGNAME req personal \"/C=PL/ST=łódzkie/L=Łódź/O=Example Ltd./OU=ICT Department/GN=Jan/SN=Kowalski/CN=Jan Kowalski/emailAddress=jkowalski@example.com/telephoneNumber=+48509990066\""
  echo "$PROGNAME req personal \"/C=PL/GN=Jan/SN=Kowalski/CN=Jan Kowalski/emailAddress=jkowalski@example.com\""
  echo "$PROGNAME req ssl \"/C=PL/ST=łódzkie/L=Łódź/O=Example Ltd./CN=example.com\" DNS:example.com,DNS:*.example.com"
  echo "$PROGNAME req-nodes ssl \"/C=PL/CN=edi.example.com\" DNS:edi.example.com,IP:1.2.3.4"
  echo "$PROGNAME req code \"/C=PL/ST=łódzkie/L=Łódź/O=Example Ltd./OU=ICT Department/CN=Example Software\""
  echo "$PROGNAME req ipsec \"/C=PL/ST=łódzkie/L=Łódź/O=Example Ltd./OU=VPN/CN=fw.example.com\" DNS:fw.example.com,IP:1.2.3.4"
  exit 1
}

if ! command -v openssl > /dev/null; then
  >&2 echo "openssl program not found!"
  exit 1
fi

ACTION="$1"


if [[ "$ACTION" =~ ^req ]] && [ "$2" != "" ] && [ "$3" != "" ]; then
  REQMODE=${ACTION#req}
  CERTPOLICY=$2
  SUBJECT=$3
  SAN=$4
  CN=`echo $SUBJECT | sed '/CN=/!d; s|.*CN=\([^/]*\).*|\1|'`
  EMAIL=`echo $SUBJECT | sed '/emailAddress=/!d; s|.*emailAddress=\([^/]*\).*|\1|'`
  TELEPHONE=`echo $SUBJECT | sed '/telephoneNumber=/!d; s|.*telephoneNumber=\([^/]*\).*|\1|'`

  if [ "$5" == "" ]; then
    BASENAME=$CN
    if [ "$CERTPOLICY" == "personal" ] && [ "$EMAIL" != "" ]; then
      BASENAME=$EMAIL
    fi
  else
    BASENAME="$5"
  fi

  if [ "$SAN" == "" ] && [ "$CERTPOLICY" == "personal" ] && [ "$EMAIL" != "" ]; then
    SAN="email:copy"
  fi

  if [ -f "$BASENAME.csr" ]; then
    >&2 echo "Request file $BASENAME.csr already exists!"
    echo ""
  else
    if [ -f "$BASENAME.key" ]; then
      >&2 echo "Private key file $BASENAME.key already exists. Reusing old key."
      KEYCMD="-key"
      if [ -f "$BASENAME.passwd" ]; then
        PASSCMD=" -passin file:$BASENAME.passwd"
      fi
    else
      KEYCMD="-keyout"
      if [[ ! "$REQMODE" =~ -nodes ]] && [ ! -f "$BASENAME.passwd" ]; then
        genpasswd "$BASENAME.passwd" || exit 1
      fi

      if [ -f "$BASENAME.passwd" ]; then
        if [[ "$REQMODE" =~ -nodes ]] ; then
          >&2 echo "Password file $BASENAME.passwd already exists. Key will be encrypted."
        fi
        PASSCMD=" -passout file:$BASENAME.passwd"
      else
        PASSCMD=" -nodes"
      fi
    fi

    if _contains "$(uname -a)" "MINGW"; then
      OPENSSL_CNF=$(mktemp $CAID-openssl-XXXXXXXX.cnf)
      trap "{ rm -f $OPENSSL_CNF; }" EXIT
      cnf_content $SAN > $OPENSSL_CNF
      MINGSUBJECT="//$(echo "${SUBJECT:1}" | tr '/' '\\')"
      openssl req -utf8 -config $OPENSSL_CNF -reqexts v3_req_$CERTPOLICY $PASSCMD -new -subj "$MINGSUBJECT" -text $KEYCMD "$BASENAME.key" -out "$BASENAME.csr" || exit 1
    else
      openssl req -utf8 -config <(cnf_content $SAN) -reqexts v3_req_$CERTPOLICY $PASSCMD -new -subj "$SUBJECT" -text $KEYCMD "$BASENAME.key" -out "$BASENAME.csr" || exit 1
    fi

    if [ "$KEYCMD" == "-keyout" ]; then
      echo "Generated for $WEB_NAME ($CAID) at $(date --rfc-3339=seconds) using:" >> $BASENAME.key
      echo "$PROGNAME $*" >> $BASENAME.key

      echo "Private key has been written to $BASENAME.key"
    fi

    echo "Certificate request has been written to $BASENAME.csr"
    echo ""
  fi

  if [[ "$REQMODE" =~ -sign ]] ; then
    CA=$(which CA 2>/dev/null)
    if [ "$CA" == "" ]; then
      >&2 echo "CA command is not available!"
      exit 1
    fi
    $CA -I "$BASENAME.csr" -o "$BASENAME.crt" || exit 1
  else
    echo "Request for $WEB_NAME ($CAID):"
    openssl req -utf8 -in "$BASENAME.csr" -subject || exit 1
  fi
  echo ""

  echo "Generate PKCS#12 file using: $PROGNAME p12 $BASENAME"
  exit 0
fi


if [ "$ACTION" == "p12" ] && [ "$2" != "" ]; then
  NAME=$2
  if [ ! -s "$NAME.key" ]; then
    >&2 echo "No $NAME.key file!"
    exit 1
  fi
  if [ ! -s "$NAME.crt" ]; then
    >&2 echo "No $NAME.crt file!"
    exit 1
  fi

  if [ -s "$NAME.p12" ]; then
    >&2 echo "Target file $NAME.p12 already exists."
    exit 1
  fi

  genpasswd "$NAME.p12.passwd" || exit 1

  if [ ! -s "$CAID-pem.crt" ]; then
    curl -s "$WEB_CAREPO_URL/$CAID-pem.crt" -o "$CAID-pem.crt" || exit 1
  fi

  openssl pkcs12 -password "file:$NAME.p12.passwd" -export -in "$NAME.crt" -inkey "$NAME.key" -certfile $CAID-pem.crt -name "$NAME" -out "$NAME.p12" || exit 1
  echo "Target file \"$NAME.p12\" has been generated and secured using password $(<"$NAME.p12.passwd")"
  echo "Deprecated: Generate Java Keystore (JKS) file using: $PROGNAME keystore $BASENAME"
  exit 0
fi


if [ "$ACTION" == "keystore" ] && [ "$2" != "" ]; then
  NAME=$2
  if [ ! -s "$NAME.p12" ]; then
    >&2 echo "No $NAME.p12 file!"
    exit 1
  fi
  if [ -s "$NAME.keystore" ]; then
    >&2 echo "Target file $NAME.keystore already exists."
    exit 1
  fi
  KEYTOOL_OPT=""
  if [ -f "$NAME.p12.passwd" ]; then
    KEYTOOL_OPT="$KEYTOOL_OPT -srcstorepass $(cat $NAME.p12.passwd)"
  fi

  genpasswd "$NAME.keystore.passwd" || exit 1

  keytool -importkeystore -destkeystore $NAME.keystore -deststoretype JKS -deststorepass $(<"$NAME.keystore.passwd") -destkeypass $(<"$NAME.keystore.passwd") -srckeystore $NAME.p12 -srcstoretype PKCS12 $KEYTOOL_OPT -alias "$NAME" || exit 1
  echo "Target file \"$NAME.keystore\" has been generated and secured using password $(<"$NAME.keystore.passwd")"
  exit 0
fi


if [ "$ACTION" == "ovpn" ] && [ "$2" != "" ]; then
  NAME=$2
  if [ ! -s "$NAME.key" ]; then
    >&2 echo "No $NAME.key file!"
    exit 1
  fi
  if [ ! -s "$NAME.crt" ]; then
    >&2 echo "No $NAME.crt file!"
    exit 1
  fi

  if [ -s "$NAME.ovpn" ]; then
    >&2 echo "Target file $NAME.ovpn already exists."
    exit 1
  fi

cat << __OVPN_END__ > $NAME.ovpn
client
dev tun
remote ${OPENVPN_HOSTNAME:-vpn.example.com} 1194
nobind
persist-key
persist-tun
cipher AES-256-CBC
remote-cert-tls server
verb 1
auth-nocache

# $WEB_CAREPO_URL/$CAID-pem.crt
<ca>
$(curl -s "$WEB_CAREPO_URL/$CAID-pem.crt")
</ca>

# Klucz prywatny i certyfikat w magazynie certyfikatow Windows (CryptoAPI)
#cryptoapicert "THUMB:$(openssl x509 -in $NAME.crt -fingerprint -noout| sed -r 's|^SHA1 Fingerprint=||')"

# Klucz prywatny i certyfikat w treści pliku konfiguracyjnego .OVPN
<cert>
$(cat $NAME.crt)
</cert>
<key>
$(cat $NAME.key)
</key>
__OVPN_END__

  echo "Target file \"$NAME.ovpn\" has been generated. Import it to your OpenVPN client."
  exit 0
fi


usage
exit 1
