# (C) 2003 Tenable Network Security
# $Id


# This function generates an SSL / TLS Client Hello message. For specs, see:
#   - http://wp.netscape.com/eng/security/SSL_2.html (SSLv2)
#   - http://wp.netscape.com/eng/ssl3/draft302.txt (SSLv3)
#   - http://www.ietf.org/rfc/rfc2246.txt (TLSv1)
#
# Args:
#   o mlen = message length after this byte; 1 byte.
#   o mtype = handshake message type; 1 byte.
#     nb: 0x01 => Client Hello (default).
#   o version = client SSL version; 2 bytes.
#     nb: 0x0002 => SSLv2, 0x0300 => SSLv3 (default), 0x0301 => TLSv1.
#   o v2hello = whether to use v2 client hello format.
#     nb: FALSE (use protocol-specific format), TRUE (default).
#   o cipherspec = which ciphers are supported.
#   o cspeclen = cipher spec len; 2 bytes.
#   o sessionid = session ID (defaults to empty string
#     or random bytes if sessionidlen is non-zero).
#   o sessionidlen = session ID length; 2 bytes for SSLv2, 1 byte otherwise.
#   o challenge = random bytes of challenge data.
#     nb: for SSLv2, defaults to "NESSUSNESSUSNESS";
#         for SSLv3 or TLSv1, defaults to current unix datetime
#         followed by "NESSUSNESSUSNESSUSNESSUSNESS".
#   o challengelen = length of challenge; 2 bytes.
#     nb: this is used only with SSLv2 format hellos.
#   o compmeths = list of compression methods supported by client,
#     1 byte each (defaults to 0x00 => no compression).
#     nb: this is not used with SSLv2.
#   o compmethslen = length of compression methods.
# Return:
#   o a raw string representing the Client Hello message.
#
# updated: 16-Nov-2004, George A. Theall
#
function client_hello(mlen, mtype, version, v2hello, cipherspec, cspeclen, sessionid, sessionidlen, challenge, challengelen, compmeths, compmethslen) {
  local_var chello, chellolen, handshake, myhello;

  # Assign some defaults.
  if (isnull(mtype)) mtype = raw_string(0x01);
  if (isnull(version)) version = raw_string(0x03, 0x00);
  if (isnull(v2hello)) v2hello = TRUE;

  # Generate the hello.
  #
  # - SSLv2, whether it's explicitly SSLv2 or in v2 compatability mode.
  if (version == raw_string(0x00, 0x02) || v2hello == TRUE) {
    # Assign other defaults.
    #
    # - ciphers.
    if (isnull(cipherspec)) {
      if (isnull(cspeclen)) 
        cipherspec = 
          # TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA
          raw_string(0x00, 0x00, 0x62) +
          # SSL2_RC2_CBC128_CBC_WITH_MD5
          raw_string(0x04, 0x00, 0x80) +
          # TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA
          raw_string(0x00, 0x00, 0x63) +
          # TLS_RSA_EXPORT_WITH_RC4_40_MD5
          raw_string(0x00, 0x00, 0x03) +
          # TLS_RSA_EXPORT1024_WITH_RC4_56_SHA
          raw_string(0x00, 0x00, 0x64) +
          # SSL2_RC4_128_EXPORT40_WITH_MD5
          raw_string(0x02, 0x00, 0x80);
      else 
        # nb: fill it with random bytes.
        while (strlen(cipherspec) < cspeclen)
          cipherspec = cipherspec + (rand() % 256);
    }
    if (isnull(cspeclen)) {
      cspeclen = strlen(cipherspec); 
      cspeclen = raw_string(cspeclen / 256, cspeclen % 256);
    }
    # - session ID.
    if (isnull(sessionid)) {
      if (sessionidlen) 
        # nb: fill it with random bytes.
        while (strlen(sessionid) < sessionidlen)
          sessionid = sessionid + (rand() % 256);
      else sessionid = "";
    }
    if (isnull(sessionidlen)) {
      sessionidlen = strlen(sessionid);
      sessionidlen = raw_string(sessionidlen / 256, sessionidlen % 256);
    }
    # - challenge.
    if (isnull(challenge)) challenge = "NESSUSNESSUSNESS";
    if (isnull(challengelen)) {
      challengelen = strlen(challenge);
      challengelen = raw_string(challengelen / 256, challengelen % 256);
    }

    # Assemble the message.
    # nb: 2 byte length code is used since we don't need any padding.
    handshake = mtype +
                version +
                cspeclen +
                sessionidlen +
                challengelen +
                cipherspec;
    if (sessionid) handshake = handshake + sessionid;
    handshake = handshake + challenge;
    if (isnull(mlen)) mlen = strlen(handshake);
    myhello = raw_string(0x80 | (mlen / 256), mlen % 256) + handshake;
  }
  # - SSLv3 or TLSv1.
  else if (version == raw_string(0x03, 0x00) || version == raw_string(0x03, 0x01)) {
    # Assign other defaults.
    #
    # - ciphers.
    if (isnull(cipherspec)) {
      if (isnull(cspeclen)) 
        # nb: this is what openssl s_client uses by default.
        cipherspec = 
          # TLS_DHE_RSA_WITH_AES_256_CBC_SHA
          raw_string(0x00, 0x39) +
          # TLS_DHE_DSS_WITH_AES_256_CBC_SHA
          raw_string(0x00, 0x38) +
          # TLS_RSA_WITH_AES_256_CBC_SHA
          raw_string(0x00, 0x35) +
          # TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA
          raw_string(0x00, 0x16) +
          # TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA
          raw_string(0x00, 0x13) +
          # TLS_RSA_WITH_3DES_EDE_CBC_SHA
          raw_string(0x00, 0x0a) +
          # TLS_DHE_RSA_WITH_AES_128_CBC_SHA
          raw_string(0x00, 0x33) +
          # TLS_DHE_DSS_WITH_AES_128_CBC_SHA
          raw_string(0x00, 0x32) +
          # TLS_RSA_WITH_AES_128_CBC_SHA
          raw_string(0x00, 0x2f) +
          # TLS_RSA_WITH_IDEA_CBC_SHA
          raw_string(0x00, 0x07) +
          # TLS_DHE_DSS_WITH_RC4_128_SHA
          raw_string(0x00, 0x66) +
          # TLS_RSA_WITH_RC4_128_SHA
          raw_string(0x00, 0x05) +
          # TLS_RSA_WITH_RC4_128_MD5
          raw_string(0x00, 0x04) +
          # TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA
          raw_string(0x00, 0x63) +
          # TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA
          raw_string(0x00, 0x62) +
          # TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5
          raw_string(0x00, 0x61) +
          # TLS_DHE_RSA_WITH_DES_CBC_SHA
          raw_string(0x00, 0x15) +
          # TLS_DHE_DSS_WITH_DES_CBC_SHA
          raw_string(0x00, 0x12) +
          # TLS_RSA_WITH_DES_CBC_SHA
          raw_string(0x00, 0x09) +
          # TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA
          raw_string(0x00, 0x65) +
          # TLS_RSA_EXPORT1024_WITH_RC4_56_SHA
          raw_string(0x00, 0x64) +
          # TLS_RSA_EXPORT1024_WITH_RC4_56_MD5
          raw_string(0x00, 0x60) +
          # TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
          raw_string(0x00, 0x14) +
          # TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
          raw_string(0x00, 0x11) +
          # TLS_RSA_EXPORT_WITH_DES40_CBC_SHA
          raw_string(0x00, 0x08) +
          # TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5
          raw_string(0x00, 0x06) +
          # TLS_RSA_EXPORT_WITH_RC4_40_MD5
          raw_string(0x00, 0x03);
      else 
        # nb: fill it with random bytes.
        while (strlen(cipherspec) < cspeclen)
          cipherspec = cipherspec + (rand() % 256);
    }
    if (isnull(cspeclen)) {
      cspeclen = strlen(cipherspec);
      cspeclen = raw_string(cspeclen / 256, cspeclen % 256);
    }
    # - session ID.
    if (isnull(sessionid)) {
      if (sessionidlen) {
        # nb: fill out field with random bytes.
        while (strlen(sessionid) < sessionidlen)
          sessionid = sessionid + (rand() % 256);
      }
      else sessionid = "";
    }
    if (isnull(sessionidlen)) sessionidlen = raw_string(strlen(sessionid));
    # - challenge.
    if (isnull(challenge)) {
      challenge = dec2hex(num:unixtime()) + "NESSUSNESSUSNESSUSNESSUSNESS";
    }
    # - compression methods
    if (isnull(compmeths)) {
      compmeths = raw_string(0x00);
      # nb: fill out field with random bytes.
      while (strlen(compmeths) < compmethslen)
        compmeths = compmeths + (rand() % 256);
    }
    if (isnull(compmethslen)) compmethslen = raw_string(strlen(compmeths));

    # Assemble the message.
    chello = version +
             challenge + 
             sessionidlen + sessionid +
             cspeclen + cipherspec +
             compmethslen + compmeths;
    chellolen = strlen(chello);
    handshake = mtype +
              raw_string(0, chellolen / 256, chellolen % 256) +
              chello;
    if (isnull(mlen)) mlen = strlen(handshake);
    myhello = raw_string(0x16) +
              version +
              raw_string(mlen / 256, mlen % 256) +
              handshake;
  }

  return(myhello);
}


function client_send_cert (contenttype, version, length, hshaketype, hlength, clength, clength2, certificate) {
  # contenttype = content type (1 byte) -- handshake is 0x16
  # version = version (2 bytes) -- SSL v3 is 0x03 0x00
  # length = length (2 bytes) of record following this length field
  # hshaketype = handshake type (1 byte) -- certificate is 0x0B
  
  # Note to self:  3 bytes is a *lot* of space...

  # hlength = handshake length (3 bytes) (also equal to length - 4)
  # clength = certificate length (3 bytes) (or hlength - 3)
  # clength2 = actual cert length (3 bytes) (just subtract another 3 from clength)
  # certificate = clength2 bytes of certificate
  

  if (!certificate) exit(0);                      # must have a cert... 

  certlen = strlen(certificate);

  if (!contenttype) contenttype = raw_string(0x16);
  if (!version) version = raw_string(0x03, 0x00);
  if (!hshaketype) hshaketype = raw_string(0x0B);

  if (!clength2) {
    clength2 = certlen; 
    if (clength2 <= 0xFF) clength2 = raw_string(0x00, 0x00, clength2);
    else clength2 = raw_string(0x00, clength2 / 256, clength2 % 256); 
  }

  if (!clength) {
      tlen = certlen + 3;
      if (tlen <= 0xFF) clength = raw_string(0x00, 0x00, tlen);
      else clength = raw_string(0x00, tlen / 256, tlen % 256); 
  }

  if (!hlength) {
      tlen = certlen + 6;
      if (certlen <= 0xFF) hlength = raw_string(0x00, 0x00, tlen);
      else hlength = raw_string(0x00, tlen / 256, tlen % 256);
  }

  if (!length) {
    length = certlen + 10;
    if (length <= 0xFF) length = raw_string(0x00, strlen(length));
    else length = raw_string(length / 256, length % 256);
  }

 
    
  client_cert = contenttype + version + length + hshaketype + hlength + clength + clength2 + certificate;
  return (client_cert); 
}


# This function returns the message digest (fingerprint) of a string.
#
# Args:
#   o string - the string to fingerprint.
#   o type - the type of message digest to use, either MD5 (default) or SHA1.
#
# Return:
#   o a string of hex numbers, each separated by a colon, representing 
#     the string's fingerprint.
#
# updated: 16-Nov-2004, George A. Theall
#
function fingerprint_string(str, type) {
  local_var digest;

  if (isnull(str)) return(NULL);
  if (isnull(type)) type = "md5";

  if (type =~ "md5") {
    digest = MD5(str);
  }
  else if (type =~ "sha1") {
    digest = SHA1(str);
  }
  else {
    # unsupported digest type.
    return(NULL);
  }

  digest = ereg_replace(string:hexstr(digest), pattern:"(..)", replace:"\1:");
  digest = substr(digest, 0, strlen(digest)-2);

  return(digest);
}


# This function returns the message digest (fingerprint) of an SSL 
# certificate.
#
# Args:
#   o cert - the certificate (either PEM or DER-encoded).
#   o type - the type of digest to use, either MD5 (default) or SHA1.
#
# Return:
#   o a string of hex numbers, each separated by a colon, representing 
#     the certificate's fingerprint.
#
# updated: 16-Nov-2004, George A. Theall
#
function fingerprint_cert(cert, type) {
  local_var der;

  if (isnull(type)) type = "md5";

  # If the cert is PEM-encoded, convert it to DER-encoding.
  if (egrep(string:cert, pattern:"(BEGIN CERTIFICATE|^.{64}$)")) {
    der = "";
    foreach line (split(cert, keep:TRUE)) {
      if (line !~ "^-+(BEGIN|END) CERTIFICATE-+$") {
        der += line;
      }
    }
    cert = base64_decode(der);
  }

  return(fingerprint_string(str:cert, type:type));
}


# This function returns the server certificate for the SSL-enabled
# service on a given port.
#
# Args:
#   o port - a port number.
#   o encoding - format of certificate, either PEM (default) or DER.
#
# Return:
#   o a string representing the SSL certificate.
#
# updated: 16-Nov-2004, George A. Theall
#
function get_server_cert(port, encoding) {
  local_var cert;

  if (isnull(port)) return(NULL);
  if (isnull(encoding)) encoding = "pem";

  # Try to get cert from the KB.
  cert = get_kb_item("SSL/Certificate/" + port);

  # If that didn't work, retrieve it directly.
  if (!cert) {
    local_var buf, hello, done, encaps, hexmsg, host, soc, ssl;
    local_var msg, msg_len, msg_type;
    local_var hand, hand_len, hand_type;
    local_var cert, cert_len;
    local_var alert_desc, alert_lvl, err_code;

    host = get_host_name();
    if (!get_port_state(port)) return(NULL);
    soc = open_sock_tcp(port, transport:ENCAPS_IP);
    if (!soc) exit(0);

    encaps = get_kb_item("Transports/TCP/"+port);
	
    if (!encaps) return(NULL);
    if (debug_level) display("debug: encapsulation type is ", encaps, ".\n");
    # nb: see nessus-libraries/include/libnessus.h for defines
    #     mapping encapsulations to SSL / TLS version.
    if (encaps == 3) ssl = "SSLv2";
    else if (encaps == 4) ssl = "SSLv3";
    else if (encaps == 5) ssl = "TLSv1";
    else return(NULL);
    if (debug_level) display("debug: fingerprinting ", ssl, " certificate on ", host, ":", port, ".\n");

    # Send client hello.
    if (ssl == 'SSLv2') {
      hello = client_hello(version:raw_string(0x00, 0x02));
    }
    else if (ssl == 'SSLv3') {
      hello = client_hello(v2hello:FALSE, version:raw_string(0x03, 0x00));
    }
    else if (ssl == 'TLSv1') {
      hello = client_hello(v2hello:FALSE, version:raw_string(0x03, 0x01));
    }
    if (debug_level) {
      display("debug: sending ", ssl, " Client Hello:\n");
      hexmsg = ereg_replace(string:hexstr(hello), pattern:"(..)", replace:"0x\1 ");
      if (!hexmsg) hexmsg = "-- empty --";
      display("debug:   ", hexmsg, "\n");
    }
    send(socket:soc, data:hello);
    buf = recv(socket:soc, length:8192); 
    close(soc);

    # Process server message(s). 
    done = 0;
    if (ssl == 'SSLv2') {
      # Isolate a message.
      while (!done && strlen(buf) > 3) {
        msg_type = ord(buf[2]);
        msg_len = ((ord(buf[0]) & 0x7f) << 8) | ord(buf[1]);
        msg = substr(buf, 0, msg_len+2-1);
        if (debug_level) {
          display("debug: message type: ", msg_type, ".\n");
          display("debug:   length:     ", msg_len, ".\n");
          hexmsg = ereg_replace(string:hexstr(msg), pattern:"(..)", replace:"0x\1 ");
          if (!hexmsg) hexmsg = "-- empty --";
          display("debug:   contents:   ", hexmsg, "\n");
        }
        buf = substr(buf, msg_len);

        # Handshake message.
        if (msg_type == 4 && strlen(msg) > 14) {
          # X.509 certificate.
          if (ord(msg[4]) == 1) {
            cert_len = ord(msg[7])*256 + ord(msg[8]);
            cert = substr(msg, 13, cert_len+13-1);
            if (debug_level) {
              display("debug: cert length: ", cert_len, ".\n");
              hexmsg = ereg_replace(string:hexstr(cert), pattern:"(..)", replace:"0x\1 ");
              if (!hexmsg) hexmsg = "-- empty --";
              display("debug:   contents:  ", hexmsg, "\n");
            }
            done = 1;
          }
        }
        # Error message.
        else if (msg_type == 0 && strlen(msg) == 2) {
          err_code = ord(msg[1]) * 256 + ord(msg[1]);
          if (debug_level) display("debug: error code: ", err_code, "\n");
        }
        # Something else.
        else {
          if (log_verbosity > 1) display("Message of type ", msg_type, " received from ", host, ":", port, "!\n");
        }
        if (debug_level) display("debug:\n");
      }
    }
    else if (ssl == 'SSLv3' || ssl == 'TLSv1') {
      # We *should* see the Server Hello handshake first, followed by the Server
      # Certificate handshake, but not depending on the order is more robust.
      while (!done && strlen(buf) > 5) {
        msg_type = ord(buf[0]);
        msg_len = ord(buf[3])*256 + ord(buf[4]);
        # nb: msg_len doesn't include the first 5 bytes.
        msg = substr(buf, 0, msg_len+5-1);
        if (debug_level) {
          display("debug: message type: ", msg_type, ".\n");
          display("debug:   length:     ", msg_len, ".\n");
          hexmsg = ereg_replace(string:hexstr(msg), pattern:"(..)", replace:"0x\1 ");
          if (!hexmsg) hexmsg = "-- empty --";
          display("debug:   contents:   ", hexmsg, "\n");
        }
        buf = substr(buf, msg_len+5);

        # Handshake message.
        if (msg_type == 22 && strlen(msg) > 3) {
          while (!done && strlen(msg) > 4) {
            hand_type = ord(msg[5]);
            hand_len = ord(msg[6])*65536 + ord(msg[7])*256 + ord(msg[8]);
            # nb: hand_len doesn't include the first 4 bytes.
            hand = substr(msg, 5, hand_len+4+5-1);
            if (debug_level) {
              display("debug: handshake type: ", hand_type, ".\n");
              display("debug:   length:       ", hand_len, ".\n");
              hexmsg = ereg_replace(string:hexstr(hand), pattern:"(..)", replace:"0x\1 ");
              if (!hexmsg) hexmsg = "-- empty --";
              display("debug:   contents:     ", hexmsg, "\n");
            }

            # Certificate handshake.
            if (hand_type == 11 && strlen(hand) > 7) {
              # First cert belongs to the server itself.
              cert_len = ord(hand[7])*65536 + ord(hand[8])*256 + ord(hand[9]);
              cert = substr(hand, 10, cert_len+10-1);
              if (debug_level) {
                display("debug: cert length: ", cert_len, ".\n");
                hexmsg = ereg_replace(string:hexstr(cert), pattern:"(..)", replace:"0x\1 ");
                if (!hexmsg) hexmsg = "-- empty --";
                display("debug: server cert: ", hexmsg, "\n");
              }
              done = 1;
            }
            msg = substr(msg, hand_len+4);
          }
        }
        # Alert message.
        else if (msg_type == 21 && strlen(msg) == 2) {
          alert_lvl = ord(msg[0]);
          alert_desc = ord(msg[1]);
          if (debug_level) {
            display("debug: alert level: ", alert_lvl, "\n");
            display("debug:   desc:      ", alert_desc, "\n");
          }
        }
        # Something else.
        else {
          if (log_verbosity > 1) display("Non-handshake message of type ", msg_type, " received from ", host, ":", port, "!\n");
        }
        if (debug_level) display("debug:\n");
      }
    }

    if (done) {
      cert = string(
        "-----BEGIN CERTIFICATE-----\n",
        ereg_replace(
          string:base64(str:cert),
          pattern:"(.{64})", 
          replace:"\1" + raw_string("\n")
        ),
        "\n",
        "-----END CERTIFICATE-----\n"
      );

      set_kb_item(name:"SSL/Certificate/" + port, value:cert);
    }
    else {
      # The port was open but we couldn't get the certificate for some reason
      # so let the user know if we're verbosely logging.
      if (log_verbosity > 1) display("Can't get SSL certificate on ", host, ":", port, "!\n");
      return(NULL);
    }
  }

  if (encoding =~ "der") {
    local_var der, line;
    der = "";
    foreach line (split(cert, keep:FALSE)) {
      if (line !~ "^-+(BEGIN|END) CERTIFICATE-+$") {
        der += line;
      }
    }
    return(base64_decode(str:der));
  }
  else if (encoding =~ "pem") {
    return(cert);
  }
  else {
    # unsupported encoding.
    return(NULL);
  }
}
