Verifique la fecha de vencimiento del certificado SSL para múltiples servidores remotos

17

Puedo encontrar la fecha de vencimiento de los certificados SSL usando este comando OpenSSL:

openssl x509 -noout -in <filename> -enddate

Pero si los certificados están dispersos en diferentes servidores web, ¿cómo encuentra las fechas de vencimiento de todos estos certificados en todos los servidores?

Parece que hay una manera de conectarse a otro host, pero no estoy seguro de cómo obtener la fecha de caducidad con esto:

openssl s_client -connect host:port
user32262
fuente

Respuestas:

15

Tuve el mismo problema y escribí esto ... Es rápido y sucio, pero debería funcionar. Registrará (e imprimirá en la pantalla con depuración) cualquier certificado que aún no sea válido o que caduque en los próximos 90 días. Puede contener algunos errores, pero no dude en arreglarlo.

#!/bin/sh

DEBUG=false
warning_days=90 # Number of days to warn about soon-to-expire certs
certs_to_check='serverA.test.co.uk:443
serverB.test.co.uk:8140
serverC.test.co.uk:443'

for CERT in $certs_to_check
do
  $DEBUG && echo "Checking cert: [$CERT]"

  output=$(echo | openssl s_client -connect ${CERT} 2>/dev/null |\
  sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' |\
  openssl x509 -noout -subject -dates 2>/dev/null) 

  if [ "$?" -ne 0 ]; then
    $DEBUG && echo "Error connecting to host for cert [$CERT]"
    logger -p local6.warn "Error connecting to host for cert [$CERT]"
    continue
  fi

  start_date=$(echo $output | sed 's/.*notBefore=\(.*\).*not.*/\1/g')
  end_date=$(echo $output | sed 's/.*notAfter=\(.*\)$/\1/g')

  start_epoch=$(date +%s -d "$start_date")
  end_epoch=$(date +%s -d "$end_date")

  epoch_now=$(date +%s)

  if [ "$start_epoch" -gt "$epoch_now" ]; then
    $DEBUG && echo "Certificate for [$CERT] is not yet valid"
    logger -p local6.warn "Certificate for $CERT is not yet valid"
  fi

  seconds_to_expire=$(($end_epoch - $epoch_now))
  days_to_expire=$(($seconds_to_expire / 86400))

  $DEBUG && echo "Days to expiry: ($days_to_expire)"

  warning_seconds=$((86400 * $warning_days))

  if [ "$seconds_to_expire" -lt "$warning_seconds" ]; then
    $DEBUG && echo "Cert [$CERT] is soon to expire ($seconds_to_expire seconds)"
    logger -p local6.warn "cert [$CERT] is soon to expire ($seconds_to_expire seconds)"
  fi
done

Si se usa en OS X, es posible que el datecomando no funcione correctamente. Esto se debe a las diferencias en la versión Unix y Linux de esta utilidad. La publicación vinculada tiene opciones para hacer que esto funcione.

Sirex
fuente
Modifiqué / expandí su script un poco para poder verificar los certificados del servidor de correo y dar una buena descripción general sobre el estado de todos los certificados. Puede encontrar el script modificado en: gist.github.com/lkiesow/c9c5d96ecb71822b82cd9d194c581cc8
Lars Kiesow
1
Si el servidor está usando SNI, debe incluir el -servernameargumento, de esta manera:openssl s_client -servername example.com -connect example.com:443
Flimm
11

Simplemente ejecute el comando a continuación y proporcionará la fecha de vencimiento:

echo q | openssl s_client -connect google.com.br:443 | openssl x509 -noout -enddate

Puede usar este comando en un archivo por lotes para recopilar esta información para más servidores remotos.

Vinício
fuente
2
De la forma en que tiene esto escrito, debe presionar CTRL-C para finalizarlo. Puede solucionarlo con: openssl s_client -connect google.com.br:443 </ dev / null 2> & 1 | openssl x509 -noout -enddate Solo un pensamiento.
numberwhun
1
Si el servidor usa SNI, debe usar el -servernameargumento, de esta manera:openssl s_client -servername google.com.br -connect google.com.br:443
Flimm
6

A continuación se muestra mi script que como un cheque dentro de nagios. Se conecta a un host específico, verifica que el certificado sea válido dentro de un umbral establecido por las opciones -c / -w. Puede verificar que el CN ​​del certificado coincida con el nombre que espera.

Necesita la biblioteca python openssl, e hice todas las pruebas con python 2.7.

Sería trivial que un script de shell llame a esto varias veces. El script devuelve los valores de salida de nagios estándar para el estado crítico / advertencia / ok.

Una simple verificación del certificado de Google se puede realizar de esta manera.

./check_ssl_certificate -H www.google.com -p 443 -n www.google.com

Expire OK[108d] - CN OK - cn:www.google.com

check_ssl_certificate

#!/usr/bin/python

"""
Usage: check_ssl_certificate -H <host> -p <port> [-m <method>] 
                      [-c <days>] [-w <days>]
  -h show the help
  -H <HOST>    host/ip to check
  -p <port>    port number
  -m <method>  (SSLv2|SSLv3|SSLv23|TLSv1) defaults to SSLv23
  -c <days>    day threshold for critical
  -w <days>    day threshold for warning
  -n name      Check CN value is valid
"""

import getopt,sys
import __main__
from OpenSSL import SSL
import socket
import datetime

# On debian Based systems requires python-openssl

def get_options():
  "get options"

  options={'host':'',
           'port':'',
           'method':'SSLv23',
           'critical':5,
           'warning':15,
           'cn':''}

  try:
    opts, args = getopt.getopt(sys.argv[1:], "hH:p:m:c:w:n:", ['help', "host", 'port', 'method'])
  except getopt.GetoptError as err:
    # print help information and exit:
    print str(err) # will print something like "option -a not recognized"
    usage()
    sys.exit(2)
  for o, a in opts:
    if o in ("-h", "--help"):
      print __main__.__doc__
      sys.exit()
    elif o in ("-H", "--host"):
      options['host'] = a
      pass
    elif o in ("-p", "--port"):
      options['port'] = a
    elif o in ("-m", "--method"):
      options['method'] = a
    elif o == '-c':
      options['critical'] = int(a)
    elif o == '-w':
      options['warning'] = int(a)
    elif o == '-n':
      options['cn'] = a
    else:
      assert False, "unhandled option"

  if (''==options['host'] or 
      ''==options['port']):
    print __main__.__doc__
    sys.exit()

  if options['critical'] >= options['warning']:
    print "Critical must be smaller then warning"
    print __main__.__doc__
    sys.exit()

  return options

def main():
  options = get_options()

  # Initialize context
  if options['method']=='SSLv3':
    ctx = SSL.Context(SSL.SSLv3_METHOD)
  elif options['method']=='SSLv2':
    ctx = SSL.Context(SSL.SSLv2_METHOD)
  elif options['method']=='SSLv23':
    ctx = SSL.Context(SSL.SSLv23_METHOD)
  else:
    ctx = SSL.Context(SSL.TLSv1_METHOD)

  # Set up client
  sock = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
  sock.connect((options['host'], int(options['port'])))
  # Send an EOF
  try:
    sock.send("\x04")
    sock.shutdown()
    peer_cert=sock.get_peer_certificate()
    sock.close()
  except SSL.Error,e:
    print e

  exit_status=0
  exit_message=[]

  cur_date = datetime.datetime.utcnow()
  cert_nbefore = datetime.datetime.strptime(peer_cert.get_notBefore(),'%Y%m%d%H%M%SZ')
  cert_nafter = datetime.datetime.strptime(peer_cert.get_notAfter(),'%Y%m%d%H%M%SZ')

  expire_days = int((cert_nafter - cur_date).days)

  if cert_nbefore > cur_date:
    if exit_status < 2: 
      exit_status = 2
    exit_message.append('C: cert is not valid')
  elif expire_days < 0:
    if exit_status < 2: 
      exit_status = 2
    exit_message.append('Expire critical (expired)')
  elif options['critical'] > expire_days:
    if exit_status < 2: 
      exit_status = 2
    exit_message.append('Expire critical')
  elif options['warning'] > expire_days:
    if exit_status < 1: 
      exit_status = 1
    exit_message.append('Expire warning')
  else:
    exit_message.append('Expire OK')

  exit_message.append('['+str(expire_days)+'d]')

  for part in peer_cert.get_subject().get_components():
    if part[0]=='CN':
      cert_cn=part[1]

  if options['cn']!='' and options['cn'].lower()!=cert_cn.lower():
    if exit_status < 2:
      exit_status = 2
    exit_message.append(' - CN mismatch')
  else:
    exit_message.append(' - CN OK')

  exit_message.append(' - cn:'+cert_cn)

  print ''.join(exit_message)
  sys.exit(exit_status)

if __name__ == "__main__":
  main()
Zoredache
fuente
2

get_pem

Conéctese al host: puerto, extraiga el certificado con sed y escríbalo en /tmp/host.port.pem.

get_expiration_date

Lea el archivo pem dado y evalúe la clave notAfter como una variable bash. Luego imprima el nombre del archivo y la fecha en que caduca en un lugar determinado.

get_pem_expiration_dates

Itere algún archivo de entrada y ejecute las funciones anteriores.

check.pems.sh

#!/bin/bash
get_pem () {
    openssl s_client -connect $1:$2 < /dev/null |& \
    sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/w'/tmp/$1.$2.pem
}
get_expiration_date () {
    local pemfile=$1 notAfter
    if [ -s $pemfile ]; then
        eval `
          openssl x509 -noout -enddate -in /tmp/$pemfile |
          sed -E 's/=(.*)/="\1"/'
        `
        printf "%40s: " $pemfile
        LC_ALL=ru_RU.utf-8 date -d "$notAfter" +%c
    else
        printf "%40s: %s\n" $pemfile '???'
    fi
}

get_pem_expiration_dates () {
    local pemfile server port
    while read host; do
        pemfile=${host/ /.}.pem
        server=${host% *}
        port=${host#* }
        if [ ! -f /tmp/$pemfile ]; then get_pem $server $port; fi
        if [   -f /tmp/$pemfile ]; then get_expiration_date $pemfile; fi
    done < ${1:-input.txt}
}

if [ -f "$1" ]; then
    get_pem_expiration_dates "$1" ; fi

salida de muestra

 $ sh check.pems.sh input.txt
             www.google.com.443.pem: Пн. 30 дек. 2013 01:00:00
              superuser.com.443.pem: Чт. 13 марта 2014 13:00:00
               slashdot.org.443.pem: Сб. 24 мая 2014 00:49:50
          movielens.umn.edu.443.pem: ???
 $ cat input.txt
 www.google.com 443
 superuser.com 443
 slashdot.org 443
 movielens.umn.edu 443

Y para responder a tu pregunta:

$ openssl s_client -connect www.google.com:443 </dev/null |& \
sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' | \
openssl x509 -noout -enddate |& \
grep ^notAfter
Ярослав Рахматуллин
fuente
Si el servidor está utilizando SNI, debe incluir el -servernameargumento, de esta manera:openssl s_client -servername example.com -connect example.com:443
Flimm
1

Aquí hay una versión de una sola línea de la respuesta aceptada, que solo muestra el número restante de días:

( export DOMAIN=example.com; echo $(( ( $(date +%s -d "$( echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:443 2>/dev/null | openssl x509 -noout -enddate | sed 's/.*notAfter=\(.*\)$/\1/g' )" ) - $(date +%s) ) / 86400 )) )

Ejemplo con www.github.com:

$ ( export DOMAIN=www.github.com; echo $(( ( $(date +%s -d "$( echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:443 2>/dev/null | openssl x509 -noout -enddate | sed 's/.*notAfter=\(.*\)$/\1/g' )" ) - $(date +%s) ) / 86400 )) )
210
Mathieu Rey
fuente
0

Proporcione la lista de nombres de host con el puerto 443 en el formato nombre de host: puerto en el archivo y asígnelo como nombre de archivo.

! / bin / bash

filename = / root / kns / certs

fecha1 = $ (fecha | corte -d "" -f2,3,6)

currentDate = $ (fecha -d "$ fecha1" + "% Y% m% d")

mientras lee -r line do

dcert = $ (echo | openssl s_client -servername $ line -connect $ line 2> / dev / null | openssl x509 -noout -dates | grep notAfter | cut -d = -f2)

echo Nombre de host: $ line endDate = $ (fecha -d "$ dcert" + "% Y% m% d")

d1 = $ (fecha -d "$ endDate" +% s) d2 = $ (fecha -d "$ currentDate" +% s) echo Nombre de host: $ línea - Días restantes $ (((d1 - d2) / 86400))

echo $ dcert hecho <"$ nombre de archivo"

Karthik
fuente