LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (http://www.linuxquestions.org/questions/programming-9/)
-   -   Check certificate of a server (http://www.linuxquestions.org/questions/programming-9/check-certificate-of-a-server-4175436532/)

frater 11-10-2012 03:56 AM

Check certificate of a server
 
To make it easy to troubleshoot an SSL certificate I wrote this little script. It connects to the server reading its certificate and gives the most important info giving warnings with colors. It also verifies the certificate against a downloadable CA bundle (apt-get install ca-certificates && update-ca-certificates)

There's an awkward part (the while loop) in the script where I spawn the openssl request. It's there because I developed this script from another script I wrote (certexpire) to be used on a Zabbix server (monitoring system like Nagios). For this script it was imperative to always have an output.

I also have a question regarding SSL.
I have a server where I'm running pound (reverse proxy) which has 2 certificates loaded. One for domain1 and another for domain2. It uses TLS and it works for IE, Chrome and Firefox.

Because I can't instruct openssl to use TLS my pound only gives the first certificate. TLS works for smtp, pop and imap.
Is it correct that I can't use TLS over http?
If so, is it a strange questions to ask the developers of openssl to support this?

Well, here's the script:

cat /usr/local/sbin/certinfo
Code:

#!/bin/bash
export PATH=${PATH}:/usr/local/sbin:/sbin:/usr/sbin:/bin:/usr/bin

TIMEOUT=10
RETVAL=3

# location on Debian based Linux, run "update-ca-certificates" if you don't have them
CAfile=/etc/ssl/certs/ca-certificates.crt
# Try Redhat based
[ -e "${CAfile}" ] || CAfile=/etc/pki/tls/certs/ca-bundle.crt
if [ ! -e "${CAfile}" ] ; then
  echo "No Certificate Authority Bundle found" >&2
  exit 1
fi

# If called by zabbix, handle some things different
if echo "${BASH_SOURCE}" | grep -q "zabbix" ; then
  # get rid of 1st parameter (on Zabbix 1.8x)
  # shift 1

  # Change TimeOut value to the one in /etc/zabbix/zabbix_server.conf
  ZABBIX_TIMEOUT=`grep -i '^Timeout' /etc/zabbix/zabbix_server.conf 2>/dev/null | awk -F= '{print $2}' | tr -cd '0-9'`
  if [ -z "${ZABBIX_TIMEOUT}" ] ; then
    TIMEOUT=3
  else
    # Let's take 1 second less than the one in /etc/zabbix/zabbix_server.conf and just hope to be in time
    TIMEOUT=$(( ${ZABBIX_TIMEOUT} - 1 ))
  fi
fi

# Zabbix 2.0 sends parameters quoted, where < 1.9 sends them unquoted
# This way it works on both
HOST=`echo "$*" | awk '{print $1}' | tr 'A-Z' 'a-z'`
PORT=`echo "$*" | awk '{print $2}' | tr -cd '0-9'`

SCRATCH=`mktemp`
TMP1=`mktemp`
TMP2=`mktemp`

esc="\033["
RED="31;40;1m"
GREEN="32;40;1m"

[ -z "${HOST}" ] && exit 1
[ -z "${PORT}" ] && PORT=443
HOSTWITHIP=${HOST}
IP=${HOST}
if echo "${HOST}" | grep -q '[a-z]' ; then
  IP=`host -t A ${HOST} | egrep -o 'has address [0-9.]+' | head -n1 | awk '{print $3}'`
  HOSTWITHIP="${HOST} (${IP})"
  if [ -z "${IP}" ] ; then
    echo "${esc}${RED}Error resolving ${HOST}${esc}0m"
    exit 1
  fi
fi

# openssl is able to check plain smtp/pop3/ftp/imap connections
# that use TLS to setup a secure connection
TLS=
echo "${PORT}" | egrep -q '^(25|587)$'  && TLS="-crlf -starttls smtp"
echo "${PORT}" | egrep -q '^110$'      && TLS="-starttls pop3"
echo "${PORT}" | egrep -q '^21$'        && TLS="-starttls ftp"
echo "${PORT}" | egrep -q '^143$'      && TLS="-starttls imap"

# Retrieve Certificate in background because it doesn't support TimeOuts
# exec 2>/dev/null doesn't seem to be necessary if called this way....
echo "" | openssl s_client  -verify 3 -CAfile ${CAfile} -connect ${IP}:${PORT} ${TLS} 2>/dev/null >${SCRATCH} &
sleep .1

# double the TIMEOUT and wait for half a second each time
let TIMEOUT*=2

# Wait for certificate
n=1
while [ ! -s ${SCRATCH} ] ; do
  sleep .48
  [ $n -ge ${TIMEOUT} ] && break
  let n++
done

# If we have retrieved the certificate, we'll process it and retrieve the domain names
if [ -s ${SCRATCH} ] ; then
  sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' ${SCRATCH} | openssl x509 -text -noout 2>/dev/null >${TMP1}

  #cat ${TMP1}
  REMARK=
  [ -z "${TLS}" ] || REMARK="(using TLS)"
  echo -e "\nCertificate info for host ${esc}${GREEN}${HOSTWITHIP}${esc}0m on port ${PORT} ${esc}${GREEN}${REMARK}${esc}0m\n"
  CN=`grep -i "Subject:" ${TMP1} | egrep -o 'CN=[A-Za-z0-9=:/. @_-]+' | awk -F= '{print $2}'`
  echo "      CN: ${CN}"
  echo -e '\n  Subject:'
  grep -i "Subject:" ${TMP1}  | egrep -o '[A-Z]+=[A-Za-z0-9=:/. @_-]+' | sed 's/.*/          &/'

  grep -i 'Verify return code' ${SCRATCH} | grep -qi '(ok)' || echo -e "          ${esc}${RED}Not certified by an Authority!!${esc}0m"

  echo '  Issuer:'
  # grep -i "Issuer:" ${TMP1}
  grep -i "Issuer:" ${TMP1}  | egrep -o '[A-Z]+=[A-Za-z0-9=:/. @_-]+' | sed 's/.*/          &/'

  echo -e "\n Validity:"
  FROM_DATE=`grep -io 'Not Before.*' ${TMP1} | head -n1 | awk -F: '{print $2":"$3":"$4}'`
  [ ! -z "${FROM_DATE}" ] && [ `date -d "${FROM_DATE}" +%s` -ge `date +%s` ] && echo -en "${esc}${RED}"
  echo -e "          Valid since: ${FROM_DATE}${esc}0m"
  EXPIRE_DATE=`grep -io 'Not After.*' ${TMP1} | head -n1 | awk -F: '{print $2":"$3":"$4}'`
  if [ ! -z "${EXPIRE_DATE}" ] ; then
    [ `date -d "${EXPIRE_DATE}" +%s` -lt `date -d "next month" +%s` ] && echo -en "${esc}${GREEN}"
    [ `date -d "${EXPIRE_DATE}" +%s` -lt `date +%s` ]                && echo -en "${esc}${RED}"
  fi

  echo -e "            Expires on: ${EXPIRE_DATE}${esc}0m"

  # Create a greplist with DNS names converted to regular expressions
  egrep -o 'DNS:[*a-z0-9.-]+' ${TMP1} | awk -F: '{print $2}' | sed 's/\./\\./g;s/*/.*/g;s/.*/^&$/g' >${TMP2}

  echo -e "\nDNS names: "
  if [ -s ${TMP2} ] ; then
    echo "${HOST}" | grep -qf ${TMP2} || echo -e "          ${esc}${RED}Name Mismatch!!${esc}0m no DNS name matches ${esc}${GREEN}${HOST}${esc}0m"
    egrep -o 'DNS:[*a-z0-9.-]+' ${TMP1} | awk -F: '{print $2}' | sed 's/.*/          &/'
  else
    # There are NO DNS names, put CN in the greplist
    echo -en "${CN}" | sed 's/\./\\./g;s/*/.*/g;s/.*/^&$/g' >${TMP2}
    echo -e "          ${esc}${RED}No DNS names in certificate${esc}0m\n"
    if echo "${HOST}" | grep -qf ${TMP2} ; then
      echo -e "          ${esc}${GREEN}${HOST} matches CN${esc}0m"
    else
      echo -e "          ${esc}${GREEN}${HOST} ${esc}${RED}does NOT match CN${esc}0m"
    fi
  fi
  echo -e '\n'
else
  # Too late you lazy bastard, I might as well kill you...
  kill -9 %1 2>/dev/null
fi

rm -f ${SCRATCH} 2>/dev/null
rm -f ${TMP1} 2>/dev/null
rm -f ${TMP2} 2>/dev/null


unSpawn 11-11-2012 10:37 AM

Quote:

Originally Posted by frater (Post 4826375)
I can't instruct openssl to use TLS

'man s_client'?


Quote:

Originally Posted by frater (Post 4826375)
Code:

# Zabbix 2.0 sends parameters quoted, where < 1.9 sends them unquoted
# This way it works on both
HOST=`echo "$*" | awk '{print $1}' | tr 'A-Z' 'a-z'`
PORT=`echo "$*" | awk '{print $2}' | tr -cd '0-9'`


To remove for example single quotes you could:
Code:

HOST="$1"; HOST=${HOST//\'/}

Quote:

Originally Posted by frater (Post 4826375)
Code:

SCRATCH=`mktemp`
TMP1=`mktemp`
TMP2=`mktemp`


If you need several temp files it could be easier to create a temporary directory and put them all inside that? I also prefer using I/O-less /dev/shm as temp root.
Code:

_MYTMPDIR=`mktemp -p /dev/shm -d scriptname.XXXXXXXXXX` && {
 output0 > "${_MYTMPDIR}/tempfile0"
 output1 > "${_MYTMPDIR}/tempfile1"
 rm -rf "${_MYTMPDIR}"
 } # All temporary files gone now.


Quote:

Originally Posted by frater (Post 4826375)
Code:

[ -z "${PORT}" ] && PORT=443

You could set a default at the beginning of the script:
Code:

PORT=443
or force the default if not declared:
Code:

${PORT:=443}

Quote:

Originally Posted by frater (Post 4826375)
Code:

HOSTWITHIP=${HOST}
IP=${HOST}
if echo "${HOST}" | grep -q '[a-z]' ; then
  IP=`host -t A ${HOST} | egrep -o 'has address [0-9.]+' | head -n1 | awk '{print $3}'`
  HOSTWITHIP="${HOST} (${IP})"
  if [ -z "${IP}" ] ; then
    echo "${esc}${RED}Error resolving ${HOST}${esc}0m"
    exit 1
  fi
fi


Some hosts may have multiple IP addresses. How about:
Code:

IP=${HOST}; if [ "${HOST}" != "${HOST//[a-z]/}" ]; then
 IPLIST=($(dig +nocomments +noquestion +nostats +nocmd +noauth +noadditional -t A ${HOST} 2>/dev/null| awk '/IN.*A/ {print $NF}'))
 if [ ${#IPLIST[@]} -ne 0 ]; then
  for (( i = 0 ; i < ${#IPLIST[@]}; i++ ));do
  echo "${HOST} (${IPLIST[$i]})"
  done
 else
  echo "${esc}${RED}Error resolving ${HOST}${esc}0m"; exit 1
 fi
fi


Quote:

Originally Posted by frater (Post 4826375)
Code:

# openssl is able to check plain smtp/pop3/ftp/imap connections
# that use TLS to setup a secure connection
TLS=
echo "${PORT}" | egrep -q '^(25|587)$'  && TLS="-crlf -starttls smtp"
echo "${PORT}" | egrep -q '^110$'      && TLS="-starttls pop3"
echo "${PORT}" | egrep -q '^21$'        && TLS="-starttls ftp"
echo "${PORT}" | egrep -q '^143$'      && TLS="-starttls imap"


How about a case statement instead:
Code:

case "${PORT}" in
 21) TLS="-starttls ftp";;
 25|587) TLS="-crlf -starttls smtp";;
 110) TLS="-starttls pop3";;
 143) TLS="-starttls imap";;
esac

Just suggestions, OK?

frater 11-13-2012 05:47 AM

Thanks (again) for all the tips...

But the main question remains unanswered.
I'm already using TLS and it is working for FTP, IMAP, POP3 and SMTP

I also would like to use it to test http with TLS

http is not mentioned as a protocol and I was wondering if I could write around it.....


BTW...
Your remark about hosts having more than 1 IP.
I solved it in the original script by using 'head -n1'
Does your code deliver a different IP each time (round robin??)?

linosaurusroot 11-13-2012 08:07 AM

http://www.madboa.com/geek/openssl/#cert-retrieve

unSpawn 11-13-2012 09:00 AM

Quote:

Originally Posted by frater (Post 4828273)
But the main question remains unanswered.
I'm already using TLS and it is working for FTP, IMAP, POP3 and SMTP
I also would like to use it to test http with TLS
http is not mentioned as a protocol and I was wondering if I could write around it.....

Ah, now I see where the misunderstanding came from in your OP:
Quote:

Originally Posted by frater (Post 4826375)
Because I can't instruct openssl to use TLS my pound only gives the first certificate. TLS works for smtp, pop and imap.
Is it correct that I can't use TLS over http?

You don't mean TLS as in:
Code:

~]$ echo -n | openssl s_client -connect www.google.com:443 2>&1|awk '/Protocol/ {print $3}'
TLSv1
 ~]$ echo -n | openssl s_client -no_tls1 -connect www.google.com:443 2>&1|awk '/Protocol/ {print $3}'
SSLv3

but STARTTLS, in other words upgrading a plain text protocol to an encrypted one.

For HTTP there's the theoretical RFC 2817, a suggestion Apache may use:
Code:

<VirtualHost _default_:80>
SSLEngine optional
</VirtualHost>

and mozilla 276813.
But that's about it.
(Short answer: no.)


Quote:

Originally Posted by frater (Post 4828273)
Your remark about hosts having more than 1 IP.
I solved it in the original script by using 'head -n1'
Does your code deliver a different IP each time (round robin??)?

You didn't "solve" it: you just chose to ignore the other IP addresses ;-p
My snippet just loops over all IPv4 addresses returned and using it in a RR or other fashion is, like they say, "left as an exercise for the reader".

frater 11-17-2012 04:31 AM

Quote:

Originally Posted by linosaurusroot (Post 4828382)

I didnᷰt find any info there I wasnᷰt already using...

I already have a web-server running on port 443 which can deliver the certificate for domain1.com if one connects with the hostname domain1.com and it gives the certificate for domain2.com if the browser connects using domain2.com. If the browser is connecting in the classical way, it will always give the certificate for domain1.com.

The mechanism is similar to the TLS with smtp, imap and other protocols. I donᷰt know the details of this mechanism. What I do know is that it is working with all popular browsers.


The problem I want to solve is this:
I wrote this script that tests certificates. If itᷰs connecting to the well known ports of plain smtp, pop3, ftp and imap, it will use the opensslᷰs option to use TLS

openssl doesnᷰt have support for http, although such a mechanism does exist.
Maybe it s something I could write around?

Anyone knows something about the ins and outs of this protocol and help me out?

frater 11-18-2012 07:00 AM

It turns out I asked the wrong question.
I should have asked about SNI (Server Name Indication).
It's not TLS at all what's doing this, which is good as I heard there are some security issues with TLS.

The https-server is supporting SNI and I merely have to give OpenSSL the extra option "-servername hostname"

Code:

echo "" | openssl s_client -verify 3 -CAfile ${CAfile} -servername ${HOST} -connect ${IP}:${PORT} ${TLS}

frater 11-18-2012 07:12 AM

@ unSpawn: Thanks for the suggestions. I very much appreciate your efforts to show me how I can improve my coding with bash.
Although I haven't followed all suggestions in this one, I will consider them in future scripts.

This script is now working for my site which uses SNI to present the proper certificate.


# cat /usr/local/sbin/certinfo
Code:

#!/bin/bash
export PATH=${PATH}:/usr/local/sbin:/sbin:/usr/sbin:/bin:/usr/bin

TIMEOUT=10
RETVAL=3

# location on Debian based Linux, run "update-ca-certificates" if you don't have them
CAfile=/etc/ssl/certs/ca-certificates.crt
# Try Redhat based
[ -e "${CAfile}" ] || CAfile=/etc/pki/tls/certs/ca-bundle.crt
if [ ! -e "${CAfile}" ] ; then
  echo "No Certificate Authority Bundle found" >&2
  exit 1
fi

# If called by zabbix, handle some things different
if echo "${BASH_SOURCE}" | grep -q "zabbix" ; then
  # get rid of 1st parameter (on Zabbix 1.8x)
  # shift 1

  # Change TimeOut value to the one in /etc/zabbix/zabbix_server.conf
  ZABBIX_TIMEOUT=`grep -i '^Timeout' /etc/zabbix/zabbix_server.conf 2>/dev/null | awk -F= '{print $2}' | tr -cd '0-9'`
  if [ -z "${ZABBIX_TIMEOUT}" ] ; then
    TIMEOUT=3
  else
    # Let's take 1 second less than the one in /etc/zabbix/zabbix_server.conf and just hope to be in time
    TIMEOUT=$(( ${ZABBIX_TIMEOUT} - 1 ))
  fi
fi

# Zabbix 2.0 sends parameters quoted, where < 1.9 sends them unquoted
# This way it works on both
HOST=`echo "$*" | awk '{print $1}' | tr 'A-Z' 'a-z'`
PORT=`echo "$*" | awk '{print $2}' | tr -cd '0-9'`

SCRATCH=`mktemp`
TMP1=`mktemp`
TMP2=`mktemp`

esc="\033["
RED="31;40;1m"
GREEN="32;40;1m"

[ -z "${HOST}" ] && exit 1
[ -z "${PORT}" ] && PORT=443
HOSTWITHIP=${HOST}
IP=${HOST}
if [ "${HOST}" != "${HOST//[a-z]/}" ]; then
  IP=`host -t A ${HOST} 2>/dev/null | egrep -o 'has address [0-9.]+' | head -n1 | awk '{print $3}'`
  HOSTWITHIP="${HOST} (${IP})"
  if [ -z "${IP}" ] ; then
    echo -e "${esc}${RED}Error resolving ${HOST}${esc}0m" >&2
    exit 1
  fi
fi

# openssl is able to check plain smtp/pop3/ftp/imap connections
# that use TLS to setup a secure connection
TLS=
case "${PORT}" in
 21)    TLS="-starttls ftp";;
 25|587) TLS="-crlf -starttls smtp";;
 110)    TLS="-starttls pop3";;
 143)    TLS="-starttls imap";;
esac

# Retrieve Certificate in background because it doesn't support TimeOuts
# exec 2>/dev/null doesn't seem to be necessary if called this way....
echo "" | openssl s_client -verify 3 -CAfile ${CAfile} -servername ${HOST} -connect ${IP}:${PORT} ${TLS} 2>/dev/null >${SCRATCH} &
sleep .1

# double the TIMEOUT and wait for half a second each time
let TIMEOUT*=2

# Wait for certificate
n=1
while [ ! -s ${SCRATCH} ] ; do
  sleep .48
  [ $n -ge ${TIMEOUT} ] && break
  let n++
done

# If we have retrieved the certificate, we'll process it and retrieve the domain names
if [ -s ${SCRATCH} ] ; then
  sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' ${SCRATCH} | openssl x509 -text -noout 2>/dev/null >${TMP1}

  #cat ${TMP1}
  REMARK=
  [ -z "${TLS}" ] || REMARK="(using TLS)"
  echo -e "\nCertificate info for host ${esc}${GREEN}${HOSTWITHIP}${esc}0m on port ${PORT} ${esc}${GREEN}${REMARK}${esc}0m\n"
  CN=`grep -i "Subject:" ${TMP1} | egrep -o 'CN=[A-Za-z0-9=:/. @_-]+' | awk -F= '{print $2}'`
  echo "      CN: ${CN}"
  echo -e '\n  Subject:'
  grep -i "Subject:" ${TMP1}  | egrep -o '[A-Z]+=[A-Za-z0-9=:/. @_-]+' | sed 's/.*/          &/'

  grep -i 'Verify return code' ${SCRATCH} | grep -qi '(ok)' || echo -e "          ${esc}${RED}Not certified by an Authority!!${esc}0m"

  echo '  Issuer:'
  # grep -i "Issuer:" ${TMP1}
  grep -i "Issuer:" ${TMP1}  | egrep -o '[A-Z]+=[A-Za-z0-9=:/. @_-]+' | sed 's/.*/          &/'

  echo -e "\n Validity:"
  FROM_DATE=`grep -io 'Not Before.*' ${TMP1} | head -n1 | awk -F: '{print $2":"$3":"$4}'`
  [ ! -z "${FROM_DATE}" ] && [ `date -d "${FROM_DATE}" +%s` -ge `date +%s` ] && echo -en "${esc}${RED}"
  echo -e "          Valid since: ${FROM_DATE}${esc}0m"
  EXPIRE_DATE=`grep -io 'Not After.*' ${TMP1} | head -n1 | awk -F: '{print $2":"$3":"$4}'`
  if [ ! -z "${EXPIRE_DATE}" ] ; then
    [ `date -d "${EXPIRE_DATE}" +%s` -lt `date -d "next month" +%s` ] && echo -en "${esc}${GREEN}"
    [ `date -d "${EXPIRE_DATE}" +%s` -lt `date +%s` ]                && echo -en "${esc}${RED}"
  fi

  echo -e "            Expires on: ${EXPIRE_DATE}${esc}0m"

  # Create a greplist with DNS names converted to regular expressions
  egrep -o 'DNS:[*A-Za-z0-9.-]+' ${TMP1} | awk -F: '{print $2}' | sed 's/\./\\./g;s/*/.*/g;s/.*/^&$/g' >${TMP2}

  echo -e "\nDNS names: "
  if [ -s ${TMP2} ] ; then
    echo "${HOST}" | grep -qif ${TMP2} || echo -e "          ${esc}${RED}Name Mismatch!!${esc}0m no DNS name matches ${esc}${GREEN}${HOST}${esc}0m"
    egrep -o 'DNS:[*a-zA-Z0-9.-]+' ${TMP1} | awk -F: '{print $2}' | sed 's/.*/          &/'
  else
    # There are NO DNS names, put CN in the greplist
    echo -en "${CN}" | tr 'A-Z' 'a-z' | sed 's/\./\\./g;s/*/.*/g;s/.*/^&$/g' >${TMP2}
    echo -e "          ${esc}${RED}No DNS names in certificate${esc}0m\n"
    if echo "${HOST}" | grep -qif ${TMP2} ; then
      echo -e "          ${esc}${GREEN}${HOST} matches CN${esc}0m"
    else
      echo -e "          ${esc}${GREEN}${HOST} ${esc}${RED}does NOT match CN ${CN}${esc}0m"
    fi
  fi
  echo -e '\n'
else
  # Too late you lazy bastard, I might as well kill you...
  kill -9 %1 2>/dev/null
fi

rm -f ${SCRATCH} 2>/dev/null
rm -f ${TMP1} 2>/dev/null
rm -f ${TMP2} 2>/dev/null



All times are GMT -5. The time now is 11:47 AM.