SSL Certificate Expiry

2004-12-03T00:00:00
ID SSL_CERT_EXPIRY.NASL
Type nessus
Reporter This script is Copyright (C) 2004-2020 George A. Theall
Modified 2004-12-03T00:00:00

Description

This plugin checks expiry dates of certificates associated with SSL- enabled services on the target and reports whether any have already expired.

                                        
                                            #TRUSTED ac8aeee04d38ce931cd0c24a8003e0ed67efd176b56524d2a20350e174ae986b3113696f92aaf0bb53900e44cea006b5ff122033f379ce99542f36d63eaf9d5be20209f19366dc40bcdbcab600ad04cd46dfb56ac0a2319606e7a46afd08f72c8bf580c88f9246a64f96471c92bc8c9bf0a4e644b6d97ba897ca443ed9b9f20d558c58622488216a49c2c0b56efc8f5c9f9bf223a44eab81cf66746ca30a5df26354dc473f38624d4d039a1538a8a331b3a5d0f02437f539e86b7efa1415c3711210f2909e5fbefb760e94ee6b91e29a27606943963cc676b0b346000c0e9b96fba449ab41327325ec2160a739b95ffe3a5b1f0c7076c9f60c5e2761ecbd9384341b9bebdaf445c9d931e1936e1ddb2133e86dd0d51b72e9b50abdcd339f1b512d7e5e4048fcf485545ce8462c3f45e72da196dbf0ab9c03110d157aeeb45b30634a9323f02f081beb4d5f4eab3c1b38c9e4562b204c538267391b7ff2356d72cf7244a43a84f0795c581660eca8b304ed15a356e771bab43f38dac891d8a2b5767770194c9768192f6dbd19874ca5a286289019419e1703094e080cd6dff6de0daa144f23406d9b11518fe047fc81ac77d8898f734696907e0144da6391480ded259ea1488be56624c44b6707c769e870797cdd229960f29bd640d4280b0ffbf7a9fe40c5b5bf22d7eb1c48c027d7d3dd9a8bef1e0a8b67e3794b7bd6ea39d3
#
# @PREFERENCES@
#
# This script was written by George A. Theall, <theall@tifaware.com>.
#
# See the Nessus Scripts License for details.
#
# Changes by Tenable :
# - Updated to use compat.inc, made report severity consistent (11/23/09)
# - Added CVSS score, KBs. (11/23/09)
# - Signed. (10/18/2013)
# - Made expiration warning period user-configurable. (05/12/15)
# - Added rsync. (2016/01/07)

if ( ! defined_func("localtime") ) exit(0);

include("compat.inc");

if (description)
{
  script_id(15901);
  script_version("1.48");
  script_set_attribute(attribute:"plugin_modification_date", value:"2020/06/12");

  script_name(english:"SSL Certificate Expiry");
  script_summary(english:"Checks the SSL certificate expiry.");

  script_set_attribute(attribute:"synopsis", value:
"The remote server's SSL certificate has already expired.");
  script_set_attribute(attribute:"description", value:
"This plugin checks expiry dates of certificates associated with SSL-
enabled services on the target and reports whether any have already
expired.");
  script_set_attribute(attribute:"solution", value:
"Purchase or generate a new SSL certificate to replace the existing
one.");
  script_set_cvss_base_vector("CVSS2#AV:N/AC:L/Au:N/C:N/I:P/A:N");
  script_set_cvss3_base_vector("CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N");
  script_set_attribute(attribute:"cvss_score_source", value:"manual");
  script_set_attribute(attribute:"cvss_score_rationale", value:"Expired certificates cannot be validated.");
  script_set_attribute(attribute:"plugin_publication_date", value:"2004/12/03");
  script_set_attribute(attribute:"plugin_type", value:"remote");
  script_end_attributes();

  script_category(ACT_GATHER_INFO);
  script_family(english:"General");

  script_copyright(english:"This script is Copyright (C) 2004-2020 George A. Theall");

  script_dependencies("ssl_supported_versions.nasl");
  script_require_keys("SSL/Supported");

  script_add_preference(name:"Identify certificates that expire within x days", type:"entry", value:"60");

  exit(0);
}

include("ftp_func.inc");
include("global_settings.inc");
include("datetime.inc");
include("ldap_func.inc");
include("nntp_func.inc");
include("smtp_func.inc");
include("telnet2_func.inc");
include("x509_func.inc");
include("audit.inc");
include("rsync.inc");

# How far (in days) to warn of certificate expiry.
# Default to 60, and allow the user to customize as long as a non-null, non-zero int is provided
lookahead = 60;
pref = script_get_preference("Identify certificates that expire within x days");
if (pref =~ "^\d+$")
{
  pref = int(pref);
  if (pref > 0)
    lookahead = pref;
}

set_kb_item(name:'SSL/settings/future_warning_days', value:lookahead);

# This function converts a date expressed as:
#   Year(4)|Month(2)|Day(2)|Hour(2)|Min(2)|Sec(2)
# and returns it in a more human-friendly format.
function x509time_to_gtime(x509time) {
  local_var gtime, i, mm, mon, mons, parts, year;
  mons = "JanFebMarAprMayJunJulAugSepOctNovDec";

  if (x509time && x509time =~ "^[0-9]{14}Z?$") {
    parts[0] = substr(x509time, 0, 3);
    for (i=1; i<= 6; ++i) {
      parts[i] = substr(x509time, 2+i*2, 2+i*2+1);
    }

    year = int(parts[0]);

    mm = int(parts[1]);
    if (mm >= 1 && mm <= 12) {
      --mm;
      mon = substr(mons, mm*3, mm*3+2);
    }
    else {
      mon = "unk";
    }
    parts[2] = ereg_replace(string:parts[2], pattern:"^0", replace:" ");

    gtime = mon + " " +
      parts[2] + " " +
      parts[3] + ":" + parts[4] + ":" + parts[5] + " " +
      year + " GMT";
  }
  return gtime;
}


function x509time_to_bn_epoch(x509time)
{
  local_var gtime, i, mon, parts, year, day, hour, min, sec;

  if (x509time && x509time =~ "^[0-9]{14}Z?$")
  {
    parts[0] = substr(x509time, 0, 3);
    for (i=1; i<= 6; ++i) {
      parts[i] = substr(x509time, 2+i*2, 2+i*2+1);
    }

    year = int(parts[0]);
    mon = int(parts[1]);
    day = int(parts[2]);
    hour = int(parts[3]);
    min = int(parts[4]);
    sec = int(parts[5]);

    gtime = calendar_to_bn_epoch(year:year, mon:mon, day:day, hour:hour, min:min, sec:sec);
  }

  return gtime;
}


get_kb_item_or_exit("SSL/Supported");

port = get_ssl_ports(fork:TRUE);
if (isnull(port))
  exit(1, "The host does not appear to have any SSL-based services.");

# Find out if the port is open.
if (!get_port_state(port))
  exit(0, "Port " + port + " is not open.");

# Allow the plugin to be tested with just certificates in a KB with -k,
# instead of having to have an SSL server somewhere.
if (!get_kb_item("TEST_ssl_cert_expiry_do_not_open_socket"))
{
  # Open socket, sending StartTLS commands if necessary.
  soc = open_sock_ssl(port);
  if (!soc)
    exit(1, "Failed to connect to port " + port + ".");
}

# Retrieve the certificate the server is using for this port.
cert = get_server_cert(socket:soc, port:port, encoding:"der");
if (isnull(cert))
  exit(1, "Failed to read server cert from port " + port + ".");

# nb: maybe someday I'll actually *parse* ASN.1.
v = stridx(cert, raw_string(0x30, 0x1e, 0x17, 0x0d));
if (v >= 0) {
  v += 4;
  valid_start = substr(cert, v, v+11);
  v += 15;
  valid_end = substr(cert, v, v+11);

  if (valid_start =~ "^[0-9]{12}$" && valid_end =~ "^[0-9]{12}$") {
    # nb: YY >= 50 => YYYY = 19YY per RFC 3280 (4.1.2.5.1)
    if (int(substr(valid_start, 0, 1)) >= 50) valid_start = "19" + valid_start;
    else valid_start = "20" + valid_start;

    if (int(substr(valid_end, 0, 1)) >= 50) valid_end = "19" + valid_end;
    else valid_end = "20" + valid_end;

    # Get dates, expressed in UTC, for checking certs.
    # - right now.
    tm = localtime(unixtime(), utc:TRUE);
    now = string(tm["year"]);
    foreach field (make_list("mon", "mday", "hour", "min", "sec")) {
      if (tm[field] < 10) now += "0";
      now += tm[field];
    }
    # - 'lookahead' days in the future.
    tm = localtime(unixtime() + lookahead*24*60*60, utc:TRUE);
    future = string(tm["year"]);
    foreach field (make_list("mon", "mday", "hour", "min", "sec")) {
      if (tm[field] < 10) future += "0";
      future += tm[field];
    }
    debug_print("now:    ", now, ".");
    debug_print("future: ", future, ".");

    valid_start_alt = x509time_to_gtime(x509time:valid_start);
    valid_end_alt = x509time_to_gtime(x509time:valid_end);
    debug_print("valid not before: ", valid_start_alt, " (", valid_start, "Z).");
    debug_print("valid not after:  ", valid_end_alt,   " (", valid_end, "Z).");

    key = 'Transport/SSL/' + port + '/';
    replace_kb_item(name:key + 'valid_end', value:valid_end_alt);
    replace_kb_item(name:key + 'valid_start', value:valid_start_alt);
    replace_kb_item(name:key + 'valid_end_alt', value:hexstr(x509time_to_bn_epoch(x509time:valid_end)));
    replace_kb_item(name:key + 'valid_start_alt', value:hexstr(x509time_to_bn_epoch(x509time:valid_start)));

    debug_print("The SSL certificate on port ", port, " is valid between ", valid_start_alt, " and ", valid_end_alt, ".", level:1);

    # Extract the issuer / subject.
    cert2 = parse_der_cert(cert:cert);
    if (isnull(cert2))
      exit(1, "Failed to parse the SSL certificate associated with the service on port " + port + ".");

    tbs = cert2["tbsCertificate"];
    if(is_subscriber_cert(tbs))
      set_kb_item(name:key + 'subscriber_cert', value:1);

    issuer_seq = tbs["issuer"];
    subject_seq = tbs["subject"];

    issuer = '';
    foreach seq (issuer_seq)
    {
      o = oid_name[seq[0]];
      if (isnull(o)) continue;

      attr = "";
      if (o == "Common Name") attr = "CN";
      else if (o == "Surname") attr = "SN";
      else if (o == "Country") attr = "C";
      else if (o == "Locality") attr = "L";
      else if (o == "State/Province") attr = "ST";
      else if (o == "Street") attr = "street";
      else if (o == "Organization") attr = "O";
      else if (o == "Organization Unit") attr = "OU";
      else if (o == "Email Address") attr = "emailAddress";

      if (attr) issuer += ', ' + attr + '=' + seq[1];
    }
    if (issuer) issuer = substr(issuer, 2);
    else issuer = 'n/a';

    subject = '';
    foreach seq (subject_seq)
    {
      o = oid_name[seq[0]];
      if (isnull(o)) continue;

      attr = "";
      if (o == "Common Name") attr = "CN";
      else if (o == "Surname") attr = "SN";
      else if (o == "Country") attr = "C";
      else if (o == "Locality") attr = "L";
      else if (o == "State/Province") attr = "ST";
      else if (o == "Street") attr = "street";
      else if (o == "Organization") attr = "O";
      else if (o == "Organization Unit") attr = "OU";
      else if (o == "Email Address") attr = "emailAddress";

      if (attr) subject += ', ' + attr + '=' + seq[1];
    }
    if (subject) subject = substr(subject, 2);
    else subject = 'n/a';

    replace_kb_item(name:key + 'issuer', value:issuer);
    replace_kb_item(name:key + 'subject', value:subject);

    if (valid_start > now)
    {
      replace_kb_item(name:key + 'future_validity_date', value:valid_start_alt);
    }
    else if (valid_end < now)
    {
      if (report_verbosity > 0)
      {
        report =
          '\n' + 'The SSL certificate has already expired :' +
          '\n' +
          '\n  Subject          : ' + subject +
          '\n  Issuer           : ' + issuer +
          '\n  Not valid before : ' + valid_start_alt +
          '\n  Not valid after  : ' + valid_end_alt + '\n';
        security_warning(port:port, extra:report);
      }
      else security_warning(port);

      replace_kb_item(name:key + 'expired_cert', value:TRUE);
    }
    else if (valid_end < future)
    {
      replace_kb_item(name:key + 'days_to_expire', value:lookahead);
      replace_kb_item(name:key + 'future_expiry_date', value:valid_end_alt);
    }
  }
}