Index: nse_nsock.cc
===================================================================
--- nse_nsock.cc	(revision 14761)
+++ nse_nsock.cc	(working copy)
@@ -70,6 +70,8 @@
 
 static int l_nsock_pcap_receive(lua_State * L);
 
+static int l_get_ssl_certificate(lua_State *L);
+
 void l_nsock_connect_handler(nsock_pool nsp, nsock_event nse, void *yield);
 
 void l_nsock_send_handler(nsock_pool nsp, nsock_event nse, void *yield);
@@ -395,6 +397,7 @@
     {"pcap_close", l_nsock_ncap_close},
     {"pcap_register", l_nsock_ncap_register},
     {"pcap_receive", l_nsock_pcap_receive},
+    {"get_ssl_certificate", l_get_ssl_certificate},
     // {"callback_test", l_nsock_pcap_callback_test},
     {NULL, NULL}
   };
@@ -1736,6 +1739,284 @@
   return 0;
 }
 
+/* SSL */
+
+#if HAVE_OPENSSL
+static const SSL *nse_nsock_get_ssl_certificate(lua_State *L)
+{
+  const l_nsock_udata *udata;
+
+  udata = (l_nsock_udata *) luaL_checkudata(L, 1, "nsock");
+  if (!nsi_checkssl(udata->nsiod))
+    error("Socket is not an SSL socket");
+
+  return (SSL *) nsi_getssl(udata->nsiod);
+}
+
+/* This is a helper function for x509_name_to_table. It takes the ASN1_OBJECT
+   passed as an argument, turns it into a table key, and pushes it on the stack.
+   The key is a string (like "commonName") if the object has an NID known by
+   OBJ_obj2nid; otherwise it is an array containing the OID components as
+   strings: { "2", "5", "4", "3" }. */
+static void obj_to_key(lua_State *L, const ASN1_OBJECT *obj)
+{
+  int nid;
+
+  nid = OBJ_obj2nid(obj);
+  if (nid == NID_undef) {
+    size_t size = 0;
+    char *buf = NULL;
+    const char *p, *q;
+    int i, n;
+
+    while ((n = OBJ_obj2txt(buf, size, obj, 1)) < 0 || (unsigned) n >= size) {
+      size = size * 2 + 1;
+      buf = (char *) safe_realloc(buf, size);
+    }
+
+    lua_newtable(L);
+
+    i = 1;
+    p = buf;
+    q = p;
+    while (*q != '\0') {
+      q = strchr(p, '.');
+      if (q == NULL)
+        q = strchr(p, '\0');
+      lua_pushlstring(L, p, q - p);
+      lua_rawseti(L, -2, i++);
+      p = q + 1;
+    }
+    free(buf);
+  } else {
+    lua_pushstring(L, OBJ_nid2ln(nid));
+  }
+}
+
+/* This is a helper function for l_get_ssl_certificate. It builds a table from
+   the given X509_NAME, using keys returned from obj_to_key as keys. The result
+   is pushed on the stack. */
+static void x509_name_to_table(lua_State *L, X509_NAME *name)
+{
+  int i;
+
+  lua_newtable(L);
+
+  for (i = 0; i < X509_NAME_entry_count(name); i++) {
+    X509_NAME_ENTRY *entry;
+    const ASN1_OBJECT *obj;
+    const ASN1_STRING *value;
+
+    entry = X509_NAME_get_entry(name, i);
+    obj = X509_NAME_ENTRY_get_object(entry);
+    value = X509_NAME_ENTRY_get_data(entry);
+
+    obj_to_key(L, obj);
+    lua_pushlstring(L, (const char *) value->data, value->length);
+
+    lua_settable(L, -3);
+  }
+}
+
+/* Parse as a decimal integer the len characters starting at s. This function
+   can only process positive numbers; if the return value is negative then a
+   parsing error occurred. */
+static int parse_int(const unsigned char *s, size_t len)
+{
+  char buf[32];
+  char *tail;
+  long v;
+
+  if (len == 0)
+    return -1;
+  if (!isdigit((int) (unsigned char) s[0]))
+    return -1;
+  if (len > sizeof(buf) - 1)
+    return -1;
+  memcpy(buf, s, len);
+  buf[len] = '\0';
+
+  errno = 0;
+  v = strtol(buf, &tail, 10);
+  if (errno != 0 || *tail != '\0')
+    return -1;
+  if ((int) v != v || v < 0)
+    return -1;
+
+  return (int) v;
+}
+
+/* This is a helper function for asn1_time_to_obj. It parses a tectual ASN1_TIME
+   value and stores the time in the given struct tm. It returns 0 on success and
+   -1 on a parse error. */
+static int time_to_tm(const ASN1_TIME *t, struct tm *result)
+{
+  const unsigned char *p;
+
+  p = t->data;
+  if (t->length == 13 && t->data[t->length - 1] == 'Z') {
+    /* yymmddhhmmssZ */
+    int year;
+
+    year = parse_int(t->data, 2);
+    if (year < 0)
+      return -1;
+    /* "In coming up with the worlds least efficient machine-readable time
+       encoding format, the ISO nevertheless decided to forgo the encoding of
+       centuries, a problem which has been kludged around by redefining the time
+       as UTCTime if the date is 2049 or ealier, and GeneralizedTime if the date
+       is 2050 or later."
+       http://www.cs.auckland.ac.nz/~pgut001/pubs/x509guide.txt */
+    if (year < 50)
+      result->tm_year = 2000 + year;
+    else
+      result->tm_year = 1900 + year;
+    p = t->data + 2;
+  } else if (t->length == 14) {
+    /* yyyymmddhhmmss */
+    result->tm_year = parse_int(t->data, 4);
+    if (result->tm_year < 0)
+      return -1;
+    p = t->data + 4;
+  } else {
+    return -1;
+  }
+
+  result->tm_mon = parse_int(p, 2);
+  result->tm_mday = parse_int(p + 2, 2);
+  result->tm_hour = parse_int(p + 4, 2);
+  result->tm_min = parse_int(p + 6, 2);
+  result->tm_sec = parse_int(p + 8, 2);
+
+  if (result->tm_mon < 0 || result->tm_mday < 0 || result->tm_hour < 0
+      || result->tm_min < 0 || result->tm_sec < 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+/* This is a helper function for asn1_time_to_obj. It converts a struct tm into
+   a date table as returned by the Lua date os.date("!*t"), with the exception
+   that the wday and yday fields are not present. */
+static void tm_to_table(lua_State *L, const struct tm *tm)
+{
+  lua_newtable(L);
+
+  lua_pushnumber(L, tm->tm_year);
+  lua_setfield(L, -2, "year");
+  lua_pushnumber(L, tm->tm_mon + 1);
+  lua_setfield(L, -2, "month");
+  lua_pushnumber(L, tm->tm_mday);
+  lua_setfield(L, -2, "day");
+  lua_pushnumber(L, tm->tm_hour);
+  lua_setfield(L, -2, "hour");
+  lua_pushnumber(L, tm->tm_min);
+  lua_setfield(L, -2, "min");
+  lua_pushnumber(L, tm->tm_sec);
+  lua_setfield(L, -2, "sec");
+  /* Omit tm_wday and tm_yday. */
+}
+
+/* This is a helper functino for x509_validity_to_table. It takes teh given
+   ASN1_TIME and covnerts it to a value on the stack, which is one of
+     nil, if the time is NULL;
+     a date table, if the date can be parsed; and
+     a string of the raw bytes, if the date cannot be parsed. */
+static void asn1_time_to_obj(lua_State *L, const ASN1_TIME *s)
+{
+  struct tm tm;
+
+  if (s == NULL) {
+      lua_pushnil(L);
+  } else if (time_to_tm(s, &tm) == 0) {
+      tm_to_table(L, &tm);
+  } else {
+      lua_pushlstring(L, (const char *) s->data, s->length);
+  }
+}
+
+/* This is a helper functino for x509_validity_to_table. It builds a table with
+   the two members "notBefore" and "notAfter", whose values are what is returned
+   from asn1_time_to_obj. */
+static void x509_validity_to_table(lua_State *L, const X509 *cert)
+{
+  lua_newtable(L);
+
+  asn1_time_to_obj(L, X509_get_notBefore(cert));
+  lua_setfield(L, -2, "notBefore");
+  asn1_time_to_obj(L, X509_get_notAfter(cert));
+  lua_setfield(L, -2, "notAfter");
+}
+
+/* This is a helper function for l_get_ssl_certificate. It converts the
+   certificate into a PEM-encoded string on the stack. */
+static void cert_pem_to_string(lua_State *L, X509 *cert)
+{
+  BIO *bio;
+  char *buf;
+  long size;
+
+  bio = BIO_new(BIO_s_mem());
+  assert(bio != NULL);
+
+  assert(PEM_write_bio_X509(bio, cert));
+
+  size = BIO_get_mem_data(bio, &buf);
+  lua_pushlstring(L, buf, size);
+
+  BIO_vfree(bio);
+}
+
+/* Gets the peer SSL certificate and returns it in a table. The structure of the
+   table is described in the NSEDoc in nselib/nmap.luadoc. */
+static int l_get_ssl_certificate(lua_State *L)
+{
+  const SSL *ssl;
+  X509 *cert;
+  X509_NAME *subject, *issuer;
+
+  ssl = nse_nsock_get_ssl_certificate(L);
+  cert = SSL_get_peer_certificate(ssl);
+  if (cert == NULL) {
+    lua_pushnil(L);
+    return 1;
+  }
+
+  /* Cert table. */
+  lua_newtable(L);
+
+  subject = X509_get_subject_name(cert);
+  if (subject != NULL) {
+    x509_name_to_table(L, subject);
+    lua_setfield(L, -2, "subject");
+  }
+
+  issuer = X509_get_issuer_name(cert);
+  if (issuer != NULL) {
+    x509_name_to_table(L, issuer);
+    lua_setfield(L, -2, "issuer");
+  }
+
+  x509_validity_to_table(L, cert);
+  lua_setfield(L, -2, "validity");
+
+  cert_pem_to_string(L, cert);
+  lua_setfield(L, -2, "pem");
+
+  X509_free(cert);
+
+  return 1;
+}
+#else
+static int l_get_ssl_certificate(lua_State *L)
+{
+  error("Socket is not an SSL socket");
+
+  return 0;
+}
+#endif
+
 /****************** DNET ******************************************************/
 static int l_dnet_open_ethernet(lua_State * L);
 
Index: nselib/nmap.luadoc
===================================================================
--- nselib/nmap.luadoc	(revision 14799)
+++ nselib/nmap.luadoc	(working copy)
@@ -467,6 +467,47 @@
 -- @usage socket:pcap_close()
 function pcap_close()
 
+---
+-- Retrieves the SSL certificate of the peer and returns it in the form of a
+-- table. The table has this structure:
+--
+-- <code>
+-- {
+--     subject = { commonName = "...", countryName = "...",
+--                 { "2", "5", "4", "15" } = "...", ... },
+--     issuer = { commonName = "...", ... },
+--     validity = { notBefore = { year = 2020, month = 5, day = 5,
+--                                hour = 0, min = 0, sec = 0 },
+--                  notAfter = { year = 2021, month = 5, day = 5,
+--                               hour = 0, min = 0, sec = 0 } },
+--     pem = "-----BEGIN CERTIFICATE-----\nMIIFxzCCBK+gAwIBAgIQX02QuADDB7CVj..."
+-- }
+-- </code>
+--
+-- The <code>"subject"</code> and <code>"issuer"</code> fields hold each
+-- distinguished name. Fields with an unknown OID are represented as an array
+-- whose elements are the numeric components of the OID, encoded as strings.
+--
+-- The <code>"validity"</code> table has the members <code>"notBefore"</code>
+-- and <code>"notAfter"</code>. Each of these is a table as returned by
+-- <code>os.date("!*t")</code> if the date in the certificate could be parsed,
+-- except that they lack the <code>"wday"</code> and <code>"yday"</code>
+-- members. If the date could not be parsed, the value will be a string
+-- containing the raw byte values of the field. If absent, the value will be
+-- <code>nil</code>.
+--
+-- The <code>"pem"</code> field contains a PEM-encoded string of the entire
+-- contents of the certificate.
+-- @return A table as described above.
+-- @usage
+-- local s = nmap.new_socket()
+-- local status, error = s:connect(host.ip, port.number, "ssl")
+-- if status then
+--     local cert = s:get_ssl_certificate()
+-- end
+
+function get_ssl_certificate()
+
 --- Creates a new dnet object, used to send raw packets.
 -- @usage local dnet = nmap.new_dnet()
 function new_dnet()
