Manage your SSL certificates with the ssl-on-demand script

Keep track of certificate expirations to prevent problems with the ssl-on-demand script.
105 readers like this.
Lock

JanBaby, via Pixabay CC0.

It happens all the time, to the largest of companies. An important certificate doesn't get renewed, and services become inaccessible. It happened to Microsoft Teams in early February 2020, awkwardly timed just after the launch of a major television campaign promoting it as a Slack competitor. Embarrassing as that may be, it's sure to happen to someone else in the future.

On the modern web, expired certificates can create major problems for websites, ranging from unhappy users who can't connect to a site to security threats from bad actors who take advantage of the failure to renew a certificate.

Ssl-on-demand is a set of SSL scripts to help site owners manage certificates. It is used for on-demand certificate generation and validation and it can create certificate signing requests (CSRs) and predict the expiration of existing certificates.

Automate SSL expiry checks

 USAGE: SSLexpiryPredictions.sh -[cdewh]

  DESCRIPTION: This script predicts the expiring SSL certificates based on the end date.

  OPTIONS:

  -c|   sets the value for configuration file which has server:port or host:port details.
        
  -d|   sets the value of directory containing the certificate files in crt or pem format.

  -e|   sets the value of certificate extention, e.g crt, pem, cert.
        crt: default [to be used with -d, if certificate file extention is other than .crt]

  -w|   sets the value for writing the script output to a file.

  -h|   prints this help and exit.

Examples:

To create a file with a list of all servers and their port numbers to make an SSL handshake, use:

cat > servers.list
         server1:port1
         server2:port2
         server3:port3
        (ctrl+d)
        
$ ./SSLexpiryPredictions.sh -c server.list

Run the script by providing the certificate location and extension (in case it is not .crt):

$ ./SSLexpiryPredictions.sh -d /path/to/certificates/dir -e pem

Automate CSR and private key creation

Usage: 	genSSLcsr.sh [options] -[cdmshx]
  [-c (common name)]
  [-d (domain name)]
  [-s (SSL certificate subject)]
  [-p (password)]
  [-m (email address)] *(Experimental)
  [-r (remove pasphrase) default:true]
  [-h (help)]
  [-x (optional)]

[OPTIONS]
  -c|   Sets the value for common name.
        A valid common name is something that ends with 'xyz.com'

  -d|   Sets the domain name.

  -s|   Sets the subject to be applied to the certificates.
        '/C=country/ST=state/L=locality/O=organization/OU=organizationalunit/emailAddress=email'

  -p|   Sets the password for private key.

  -r|   Sets the value of remove passphrase.
        true:[default] passphrase will be removed from key.
        false: passphrase will not be removed and key wont get printed.

  -m|   Sets the mailing capability to the script.
        (Experimental at this time and requires a lot of work)

  -x|   Creates the certificate request and key but do not print on screen.
        To be used when script is used just to create the key and CSR with no need
        + to generate the certficate on the go.

  -h|   Displays the usage. No further functions are performed.

  Example: genSSLcsr.sh -c mywebsite.xyz.com -m myemail@mydomain.com

The scripts

1. SSLexpiryPredictions.sh

#!/bin/bash
##############################################
#
#	PURPOSE: The script to predict expiring SSL certificates.
#
# 	AUTHOR: 'Abhishek.Tamrakar'
#
# 	VERSION: 0.0.1
#
# 	COMPANY: Self
#
# 	EMAIL: abhishek.tamrakar08@gmail.com
#
# 	GENERATED: on 2018-05-20
#
#	LICENSE: Copyright (C) 2018 Abhishek Tamrakar
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
##############################################

#your Variables go here
script=${0##/}
exitcode=''
WRITEFILE=0
CONFIG=0
DIR=0
# functions here
usage()
{
cat <<EOF

  USAGE: $script -[cdewh]"

  DESCRIPTION: This script predicts the expiring SSL certificates based on the end date.

  OPTIONS:

  -c|   sets the value for configuration file which has server:port or host:port details.

  -d|   sets the value of directory containing the certificate files in crt or pem format.

  -e|   sets the value of certificate extention, e.g crt, pem, cert.
        crt: default

  -w|   sets the value for writing the script output to a file.

  -h|   prints this help and exit.

EOF
exit 1
}
# print info messages
info()
{
  printf '\n%s: %6s\n' "INFO" "$@"
}
# print error messages
error()
{
  printf '\n%s: %6s\n' "ERROR" "$@"
  exit 1
}
# print warning messages
warn()
{
  printf '\n%s: %6s\n' "WARN" "$@"
}
# get expiry for the certificates
getExpiry()
{
  local expdate=$1
  local certname=$2
  today=$(date +%s)
  timetoexpire=$(( ($expdate - $today)/(60*60*24) ))

  expcerts=( ${expcerts[@]} "${certname}:$timetoexpire" )
}

# print all expiry that was found, typically if there is any.
printExpiry()
{
  local args=$#
  i=0
  if [[ $args -ne 0 ]]; then
    #statements
    printf '%s\n' "---------------------------------------------"
    printf '%s\n' "List of expiring SSL certificates"
    printf '%s\n' "---------------------------------------------"
    printf '%s\n' "$@"  | \
      sort -t':' -g -k2 | \
      column -s: -t     | \
      awk '{printf "%d.\t%s\n", NR, $0}'
    printf '%s\n' "---------------------------------------------"
  fi
}

# calculate the end date for the certificates first, finally to compare and predict when they are going to expire.
calcEndDate()
{
  sslcmd=$(which openssl)
  if [[ x$sslcmd = x ]]; then
    #statements
    error "$sslcmd command not found!"
  fi
  # when cert dir is given
  if [[ $DIR -eq 1 ]]; then
    #statements
    checkcertexists=$(ls -A $TARGETDIR| egrep "*.$EXT$")
    if [[ -z ${checkcertexists} ]]; then
      #statements
      error "no certificate files at $TARGETDIR with extention $EXT"
    fi
    for file in $TARGETDIR/*.${EXT:-crt}
    do
      expdate=$($sslcmd x509 -in $file -noout -enddate)
      expepoch=$(date -d "${expdate##*=}" +%s)
      certificatename=${file##*/}
      getExpiry $expepoch ${certificatename%%.*}
    done
  elif [[ $CONFIG -eq 1 ]]; then
    #statements
    while read line
    do
      if echo "$line" | \
      egrep -q '^[a-zA-Z0-9.]+:[0-9]+|^[a-zA-Z0-9]+_.*:[0-9]+';
      then
        expdate=$(echo | \
        openssl s_client -connect $line 2>/dev/null | \
        openssl x509 -noout -enddate 2>/dev/null);
        if [[ $expdate = '' ]]; then
          #statements
          warn "[error:0906D06C] Cannot fetch certificates for $line"
        else
          expepoch=$(date -d "${expdate##*=}" +%s);
          certificatename=${line%%:*};
          getExpiry $expepoch ${certificatename};
        fi
      else
        warn "[format error] $line is not in required format!"
      fi
    done < $CONFIGFILE
  fi
}
# your script goes here
while getopts ":c:d:w:e:h" options
do
case $options in
c )
  CONFIG=1
  CONFIGFILE="$OPTARG"
  if [[ ! -e $CONFIGFILE ]] || [[ ! -s $CONFIGFILE ]]; then
    #statements
    error "$CONFIGFILE does not exist or empty!"
  fi
	;;
e )
  EXT="$OPTARG"
  case $EXT in
    crt|pem|cert )
    info "Extention check complete."
    ;;
    * )
    error "invalid certificate extention $EXT!"
    ;;
  esac
  ;;
d )
  DIR=1
  TARGETDIR="$OPTARG"
  [ $TARGETDIR = '' ] && error "$TARGETDIR empty variable!"
  ;;
w )
  WRITEFILE=1
  OUTFILE="$OPTARG"
  ;;
h )
	usage
	;;
\? )
	usage
	;;
: )
	fatal "Argument required !!! see \'-h\' for help"
	;;
esac
done
shift $(($OPTIND - 1))
#
calcEndDate
#finally print the list
if [[ $WRITEFILE -eq 0 ]]; then
  #statements
  printExpiry ${expcerts[@]}
else
  printExpiry ${expcerts[@]} > $OUTFILE
fi

2. genSSLcsr.sh

#!/bin/bash -
#===============================================================================
#
#          FILE: genSSLcsr.sh
#
#         USAGE: ./genSSLcsr.sh [options]
#
#   DESCRIPTION: ++++version 1.0.2
# 		Fixed few bugs from previous script
#		+Removing passphrase after CSR generation
# 		Extended use of functions
# 		Checks for valid common name
#		++++1.0.3
# 		Fixed line breaks
# 		Work directory to be created at the start
# 		Used getopts for better code arrangements
#   ++++1.0.4
#     Added mail feature (experimental at this time and needs
#     a mail server running locally.)
#     Added domain input and certificate subject inputs
#
#       OPTIONS: ---
#  REQUIREMENTS: openssl, mailx
#          BUGS: ---
#         NOTES: ---
#        AUTHOR: Abhishek Tamrakar (), abhishek.tamrakar08@gmail.com
#  ORGANIZATION: Self
#       CREATED: 6/24/2016
#      REVISION: 4
# COPYRIGHT AND
#	LICENSE: Copyright (C) 2016 Abhishek Tamrakar
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#===============================================================================

#variables ges here
#set basename to scriptname
SCRIPT=${0##*/}

#set flags
TFOUND=0
CFOUND=0
MFOUND=0
XFOUND=0
SFOUND=0
logdir=/var/log
# edit these below values to replace with yours
homedir=''
yourdomain=''
country=IN
state=Maharashtra
locality=Pune
organization="your_organization"
organizationalunit="your_organizational_unit"
email=your_email@your_domain
password=your_ssl_password
# OS is declared and will be used in its next version
OS=$(egrep -io 'Redhat|centos|fedora|ubuntu' /etc/issue)

### function declarations ###

info()
{
  printf '\n%s\t%s\t' "INFO" "$@"
}

#exit on error with a custom error message
#the extra function was removed and replaced withonly one.
#using FAILED\n\e<message> is a way but not necessarily required.
#

fatal()
{
 printf '\n%s\t%s\n' "ERROR" "$@"
 exit 1
}

checkperms()
{
if [[ -z ${homedir} ]]; then
homedir=$(pwd)
fi
if [[ -w ${homedir} ]]; then
info "Permissions acquired for ${SCRIPT} on ${homedir}."
else
fatal "InSufficient permissions to run the ${SCRIPT}."
fi
}

checkDomain()
{
info "Initializing Domain ${cn} check ? "
if [[ ! -z ${yourdomain} ]]; then
workdir=${homedir}/${yourdomain}
echo -e "${cn}"|grep -E -i -q "${yourdomain}$" && echo -n "[OK]" || fatal "InValid domain in ${cn}"
else
workdir=${homedir}/${cn#*.}
echo -n "[NULL]"
info "WARNING: No domain declared to check."
confirmUserAction
fi
}	# end function checkDomain

usage()
{
cat << EOF

Usage: 	$SCRIPT [options] -[cdmshx]
  [-c (common name)]
  [-d (domain name)]
  [-s (SSL certificate subject)]
  [-p (password)]
  [-m (email address)] *(Experimental)
  [-r (remove pasphrase) default:true]
  [-h (help)]
  [-x (optional)]

[OPTIONS]
  -c|   Sets the value for common name.
        A valid common name is something that ends with 'xyz.com'

  -d|   Sets the domain name.

  -s|   Sets the subject to be applied to the certificates.
        '/C=country/ST=state/L=locality/O=organization/OU=organizationalunit/emailAddress=email'

  -p|   Sets the password for private key.

  -r|   Sets the value of remove passphrase.
        true:[default] passphrase will be removed from key.
        false: passphrase will not be removed and key wont get printed.

  -m|   Sets the mailing capability to the script.
        (Experimental at this time and requires a lot of work)

  -x|   Creates the certificate request and key but do not print on screen.
        To be used when script is used just to create the key and CSR with no need
        + to generate the certficate on the go.

  -h|   Displays the usage. No further functions are performed.

  Example: $SCRIPT -c mywebsite.xyz.com -m myemail@mydomain.com

EOF
exit 1
}	# end usage

confirmUserAction() {
while true; do
read -p "Do you wish to continue? ans: " yn
case $yn in
[Yy]* ) info "Initiating the process";
break;;
[Nn]* ) exit 1;;
* ) info "Please answer yes or no.";;
esac
done
}	# end function confirmUserAction

parseSubject()
{
  local subject="$1"
  parsedsubject=$(echo $subject|sed 's/\// /g;s/^ //g')
  for i in ${parsedsubject}; do
      case ${i%%=*} in
        'C' )
        country=${i##*=}
        ;;
        'ST' )
        state=${i##*=}
        ;;
        'L' )
        locality=${i##*=}
        ;;
        'O' )
        organization=${i##*=}
        ;;
        'OU' )
        organizationalunit=${i##*=}
        ;;
        'emailAddress' )
        email=${i##*=}
      ;;
    esac
  done
}

sendMail()
{
 mailcmd=$(which mailx)
 if [[ x"$mailcmd" = "x" ]]; then
   fatal "Cannot send email! please install mailutils for linux"
 else
   echo "SSL CSR attached." | $mailcmd -s "SSL certificate request" \
   -t $email $ccemail -A ${workdir}/${cn}.csr \
   && info "mail sent" \
   || fatal "error in sending mail."
 fi
}

genCSRfile()
{
info "Creating signed key request for ${cn}"
#Generate a key
openssl genrsa -des3 -passout pass:$password -out ${workdir}/${cn}.key 4096 -noout 2>/dev/null && echo -n "[DONE]" || fatal "unable to generate key"

#Create the request
info "Creating Certificate request for ${cn}"
openssl req -new -key ${workdir}/${cn}.key -passin pass:$password -sha1 -nodes \
	-subj "/C=$country/ST=$state/L=$locality/O=$organization/OU=$organizationalunit/CN=$cn/emailAddress=$email" \
	-out ${workdir}/${cn}.csr && echo -n "[DONE]" || fatal "unable to create request"

if [[ "${REMOVEPASSPHRASE:-true}" = 'true' ]]; then
  #statements
  #Remove passphrase from the key. Comment the line out to keep the passphrase
  info "Removing passphrase from ${cn}.key"
  openssl rsa -in ${workdir}/${cn}.key \
  -passin pass:$password \
  -out ${workdir}/${cn}.insecure 2>/dev/null \
  && echo -n "[DONE]" || fatal "unable to remove passphrase"
  #swap the filenames
  info "Swapping the ${cn}.key to secure"
  mv ${workdir}/${cn}.key ${workdir}/${cn}.secure \
  && echo -n "[DONE]" || fatal "unable to perfom move"
  info "Swapping insecure key to ${cn}.key"
  mv ${workdir}/${cn}.insecure ${workdir}/${cn}.key \
  && echo -n "[DONE]" || fatal "unable to perform move"
else
  info "Flag '-r' is set, passphrase will not be removed."
fi
}

printCSR()
{
if [[ -e ${workdir}/${cn}.csr ]] && [[ -e ${workdir}/${cn}.key ]]
then
echo -e "\n\n----------------------------CSR-----------------------------"
cat ${workdir}/${cn}.csr
echo -e "\n----------------------------KEY-----------------------------"
cat ${workdir}/${cn}.key
echo -e "------------------------------------------------------------\n"
else
fatal "CSR or KEY generation failed !!"
fi
}

### END Functions ###

#Check the number of arguments. If none are passed, print help and exit.
NUMARGS=$#
if [ $NUMARGS -eq 0 ]; then
fatal "$NUMARGS Arguments provided !!!! See usage with '-h'"
fi

#Organisational details

while getopts ":c:d:s:m:p:rhx" atype
do
case $atype in
c )
	CFOUND=1
	cn="$OPTARG"
	;;
d )
  yourdomain="$OPTARG"
  ;;
s )
  SFOUND=1
  subj="$OPTARG"
  ;;
p )
  password="$OPTARG"
  ;;
r )
  REMOVEPASSPHRASE='false'
  ;;
m )
  MFOUND=1
  ccemail="$OPTARG"
  ;;
x )
	XFOUND=1
  ;;
h )
	usage
	;;
\? )
	usage
	;;
: )
	fatal "Argument required !!! see \'-h\' for help"
	;;
esac
done
shift $(($OPTIND - 1))

#### END CASE #### START MAIN ####

if [ $CFOUND -eq 1 ]
then
# take current dir as homedir by default.
checkperms ${homedir}
checkDomain

  if [[ ! -d ${workdir} ]]
  then
    mkdir ${workdir:-${cn#*.}} 2>/dev/null && info "${workdir} created."
  else
    info "${workdir} exists."
  fi # end workdir check
  parseSubject "$subj"
  genCSRfile
  if [ $XFOUND -eq 0 ]
  then
    sleep 2
    printCSR
  fi	# end x check
  if [[ $MFOUND -eq 1 ]]; then
    sendMail
  fi
else
	fatal "Nothing to do!"
fi	# end common name check

##### END MAIN #####

This was originally published as the README in ssl-on-demand's GitHub repository and is reused with permission.

What to read next
iamabhi
I work as Lead DevOps, a programmer. I am an open source enthusiast, blogger, writer. You will find me helping people learn, mostly.

2 Comments

nyc article

Melbourne Cab

Creative Commons LicenseThis work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.