summaryrefslogtreecommitdiffstats
path: root/scripts/ssl-cert.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/ssl-cert.nse')
-rw-r--r--scripts/ssl-cert.nse320
1 files changed, 320 insertions, 0 deletions
diff --git a/scripts/ssl-cert.nse b/scripts/ssl-cert.nse
new file mode 100644
index 0000000..0e4ab77
--- /dev/null
+++ b/scripts/ssl-cert.nse
@@ -0,0 +1,320 @@
+local datetime = require "datetime"
+local nmap = require "nmap"
+local outlib = require "outlib"
+local shortport = require "shortport"
+local sslcert = require "sslcert"
+local stdnse = require "stdnse"
+local string = require "string"
+local table = require "table"
+local tls = require "tls"
+local unicode = require "unicode"
+local have_openssl, openssl = pcall(require, "openssl")
+
+description = [[
+Retrieves a server's SSL certificate. The amount of information printed
+about the certificate depends on the verbosity level. With no extra
+verbosity, the script prints the validity period and the commonName,
+organizationName, stateOrProvinceName, and countryName of the subject.
+
+<code>
+443/tcp open https
+| ssl-cert: Subject: commonName=www.paypal.com/organizationName=PayPal, Inc.\
+/stateOrProvinceName=California/countryName=US
+| Not valid before: 2011-03-23 00:00:00
+|_Not valid after: 2013-04-01 23:59:59
+</code>
+
+With <code>-v</code> it adds the issuer name and fingerprints.
+
+<code>
+443/tcp open https
+| ssl-cert: Subject: commonName=www.paypal.com/organizationName=PayPal, Inc.\
+/stateOrProvinceName=California/countryName=US
+| Issuer: commonName=VeriSign Class 3 Extended Validation SSL CA\
+/organizationName=VeriSign, Inc./countryName=US
+| Public Key type: rsa
+| Public Key bits: 2048
+| Signature Algorithm: sha1WithRSAEncryption
+| Not valid before: 2011-03-23 00:00:00
+| Not valid after: 2013-04-01 23:59:59
+| MD5: bf47 ceca d861 efa7 7d14 88ad 4a73 cb5b
+|_SHA-1: d846 5221 467a 0d15 3df0 9f2e af6d 4390 0213 9a68
+</code>
+
+With <code>-vv</code> it adds the PEM-encoded contents of the entire
+certificate.
+
+<code>
+443/tcp open https
+| ssl-cert: Subject: commonName=www.paypal.com/organizationName=PayPal, Inc.\
+/stateOrProvinceName=California/countryName=US/1.3.6.1.4.1.311.60.2.1.2=Delaware\
+/postalCode=95131-2021/localityName=San Jose/serialNumber=3014267\
+/streetAddress=2211 N 1st St/1.3.6.1.4.1.311.60.2.1.3=US\
+/organizationalUnitName=PayPal Production/businessCategory=Private Organization
+| Issuer: commonName=VeriSign Class 3 Extended Validation SSL CA\
+/organizationName=VeriSign, Inc./countryName=US\
+/organizationalUnitName=Terms of use at https://www.verisign.com/rpa (c)06
+| Public Key type: rsa
+| Public Key bits: 2048
+| Signature Algorithm: sha1WithRSAEncryption
+| Not valid before: 2011-03-23 00:00:00
+| Not valid after: 2013-04-01 23:59:59
+| MD5: bf47 ceca d861 efa7 7d14 88ad 4a73 cb5b
+| SHA-1: d846 5221 467a 0d15 3df0 9f2e af6d 4390 0213 9a68
+| -----BEGIN CERTIFICATE-----
+| MIIGSzCCBTOgAwIBAgIQLjOHT2/i1B7T//819qTJGDANBgkqhkiG9w0BAQUFADCB
+...
+| 9YDR12XLZeQjO1uiunCsJkDIf9/5Mqpu57pw8v1QNA==
+|_-----END CERTIFICATE-----
+</code>
+]]
+
+---
+-- @see ssl-cert-intaddr.nse
+--
+-- @output
+-- 443/tcp open https
+-- | ssl-cert: Subject: commonName=www.paypal.com/organizationName=PayPal, Inc.\
+-- /stateOrProvinceName=California/countryName=US
+-- | Not valid before: 2011-03-23 00:00:00
+-- |_Not valid after: 2013-04-01 23:59:59
+--
+-- @xmloutput
+-- <table key="subject">
+-- <elem key="1.3.6.1.4.1.311.60.2.1.2">Delaware</elem>
+-- <elem key="1.3.6.1.4.1.311.60.2.1.3">US</elem>
+-- <elem key="postalCode">95131-2021</elem>
+-- <elem key="localityName">San Jose</elem>
+-- <elem key="serialNumber">3014267</elem>
+-- <elem key="countryName">US</elem>
+-- <elem key="stateOrProvinceName">California</elem>
+-- <elem key="streetAddress">2211 N 1st St</elem>
+-- <elem key="organizationalUnitName">PayPal Production</elem>
+-- <elem key="commonName">www.paypal.com</elem>
+-- <elem key="organizationName">PayPal, Inc.</elem>
+-- <elem key="businessCategory">Private Organization</elem>
+-- </table>
+-- <table key="issuer">
+-- <elem key="organizationalUnitName">Terms of use at https://www.verisign.com/rpa (c)06</elem>
+-- <elem key="organizationName">VeriSign, Inc.</elem>
+-- <elem key="commonName">VeriSign Class 3 Extended Validation SSL CA</elem>
+-- <elem key="countryName">US</elem>
+-- </table>
+-- <table key="pubkey">
+-- <elem key="type">rsa</elem>
+-- <elem key="bits">2048</elem>
+-- <elem key="modulus">DF40CCF2C50A0D65....35B5927DF25D4DE5</elem>
+-- <elem key="exponent">65537</elem>
+-- </table>
+-- <elem key="sig_algo">sha1WithRSAEncryption</elem>
+-- <table key="validity">
+-- <elem key="notBefore">2011-03-23T00:00:00+00:00</elem>
+-- <elem key="notAfter">2013-04-01T23:59:59+00:00</elem>
+-- </table>
+-- <elem key="md5">bf47cecad861efa77d1488ad4a73cb5b</elem>
+-- <elem key="sha1">d8465221467a0d153df09f2eaf6d439002139a68</elem>
+-- <elem key="pem">-----BEGIN CERTIFICATE-----
+-- MIIGSzCCBTOgAwIBAgIQLjOHT2/i1B7T//819qTJGDANBgkqhkiG9w0BAQUFADCB
+-- ...
+-- 9YDR12XLZeQjO1uiunCsJkDIf9/5Mqpu57pw8v1QNA==
+-- -----END CERTIFICATE-----
+-- </elem>
+
+author = "David Fifield"
+
+license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
+
+categories = { "default", "safe", "discovery" }
+dependencies = {"https-redirect"}
+
+portrule = function(host, port)
+ return shortport.ssl(host, port) or sslcert.isPortSupported(port) or sslcert.getPrepareTLSWithoutReconnect(port)
+end
+
+-- Find the index of a value in an array.
+function table_find(t, value)
+ local i, v
+ for i, v in ipairs(t) do
+ if v == value then
+ return i
+ end
+ end
+ return nil
+end
+
+function date_to_string(date)
+ if not date then
+ return "MISSING"
+ end
+ if type(date) == "string" then
+ return string.format("Can't parse; string is \"%s\"", date)
+ else
+ return datetime.format_timestamp(date)
+ end
+end
+
+-- These are the subject/issuer name fields that will be shown, in this order,
+-- without a high verbosity.
+local NON_VERBOSE_FIELDS = { "commonName", "organizationName",
+"stateOrProvinceName", "countryName" }
+
+-- Test to see if the string is UTF-16 and transcode it if possible
+local function maybe_decode(str)
+ -- If length is not even, then return as-is
+ if #str < 2 or #str % 2 == 1 then
+ return str
+ end
+ if str:byte(1) > 0 and str:byte(2) == 0 then
+ -- little-endian UTF-16
+ return unicode.transcode(str, unicode.utf16_dec, unicode.utf8_enc, false, nil)
+ elseif str:byte(1) == 0 and str:byte(2) > 0 then
+ -- big-endian UTF-16
+ return unicode.transcode(str, unicode.utf16_dec, unicode.utf8_enc, true, nil)
+ else
+ return str
+ end
+end
+
+function stringify_name(name)
+ local fields = {}
+ local _, k, v
+ if not name then
+ return nil
+ end
+ for _, k in ipairs(NON_VERBOSE_FIELDS) do
+ v = name[k]
+ if v then
+ fields[#fields + 1] = string.format("%s=%s", k, maybe_decode(v) or '')
+ end
+ end
+ if nmap.verbosity() > 1 then
+ for k, v in pairs(name) do
+ -- Don't include a field twice.
+ if not table_find(NON_VERBOSE_FIELDS, k) then
+ if type(k) == "table" then
+ k = table.concat(k, ".")
+ end
+ fields[#fields + 1] = string.format("%s=%s", k, maybe_decode(v) or '')
+ end
+ end
+ end
+ return table.concat(fields, "/")
+end
+
+local function name_to_table(name)
+ local output = {}
+ for k, v in pairs(name) do
+ if type(k) == "table" then
+ k = table.concat(k, ".")
+ end
+ output[k] = v
+ end
+ return outlib.sorted_by_key(output)
+end
+
+local function output_tab(cert)
+ if not have_openssl then
+ -- OpenSSL is required to parse the cert, so just dump the PEM
+ return {pem = cert.pem}
+ end
+ local o = stdnse.output_table()
+ o.subject = name_to_table(cert.subject)
+ o.issuer = name_to_table(cert.issuer)
+
+ o.pubkey = stdnse.output_table()
+ o.pubkey.type = cert.pubkey.type
+ o.pubkey.bits = cert.pubkey.bits
+ -- The following fields are set in nse_ssl_cert.cc and mirror those in tls.lua
+ if cert.pubkey.type == "rsa" then
+ o.pubkey.modulus = openssl.bignum_bn2hex(cert.pubkey.modulus)
+ o.pubkey.exponent = openssl.bignum_bn2dec(cert.pubkey.exponent)
+ elseif cert.pubkey.type == "ec" then
+ local params = stdnse.output_table()
+ o.pubkey.ecdhparams = {curve_params=params}
+ params.ec_curve_type = cert.pubkey.ecdhparams.curve_params.ec_curve_type
+ params.curve = cert.pubkey.ecdhparams.curve_params.curve
+ end
+
+ if cert.extensions and #cert.extensions > 0 then
+ o.extensions = {}
+ for i, v in ipairs(cert.extensions) do
+ local ext = stdnse.output_table()
+ ext.name = v.name
+ ext.value = v.value
+ ext.critical = v.critical
+ o.extensions[i] = ext
+ end
+ end
+ o.sig_algo = cert.sig_algorithm
+
+ o.validity = stdnse.output_table()
+ for i, k in ipairs({"notBefore", "notAfter"}) do
+ local v = cert.validity[k]
+ if type(v)=="string" then
+ o.validity[k] = v
+ else
+ o.validity[k] = datetime.format_timestamp(v)
+ end
+ end
+ o.md5 = stdnse.tohex(cert:digest("md5"))
+ o.sha1 = stdnse.tohex(cert:digest("sha1"))
+ o.pem = cert.pem
+ return o
+end
+
+local function output_str(cert)
+ if not have_openssl then
+ -- OpenSSL is required to parse the cert, so just dump the PEM
+ return "OpenSSL required to parse certificate.\n" .. cert.pem
+ end
+ local lines = {}
+
+ lines[#lines + 1] = "Subject: " .. stringify_name(cert.subject)
+ if cert.extensions then
+ for _, e in ipairs(cert.extensions) do
+ if e.name == "X509v3 Subject Alternative Name" then
+ lines[#lines + 1] = "Subject Alternative Name: " .. e.value
+ break
+ end
+ end
+ end
+
+ if nmap.verbosity() > 0 then
+ lines[#lines + 1] = "Issuer: " .. stringify_name(cert.issuer)
+ end
+
+ if nmap.verbosity() > 0 then
+ lines[#lines + 1] = "Public Key type: " .. cert.pubkey.type
+ lines[#lines + 1] = "Public Key bits: " .. cert.pubkey.bits
+ lines[#lines + 1] = "Signature Algorithm: " .. cert.sig_algorithm
+ end
+
+ lines[#lines + 1] = "Not valid before: " ..
+ date_to_string(cert.validity.notBefore)
+ lines[#lines + 1] = "Not valid after: " ..
+ date_to_string(cert.validity.notAfter)
+
+ if nmap.verbosity() > 0 then
+ lines[#lines + 1] = "MD5: " .. stdnse.tohex(cert:digest("md5"), { separator = " ", group = 4 })
+ lines[#lines + 1] = "SHA-1: " .. stdnse.tohex(cert:digest("sha1"), { separator = " ", group = 4 })
+ end
+
+ if nmap.verbosity() > 1 then
+ lines[#lines + 1] = cert.pem
+ end
+ return table.concat(lines, "\n")
+end
+
+action = function(host, port)
+ host.targetname = tls.servername(host)
+ local status, cert = sslcert.getCertificate(host, port)
+ if ( not(status) ) then
+ stdnse.debug1("getCertificate error: %s", cert or "unknown")
+ return
+ end
+
+ return output_tab(cert), output_str(cert)
+end
+
+
+