From 222b2a009d30f4390159b3eba9f530bee30be36a Mon Sep 17 00:00:00 2001 From: dmiller Date: Fri, 7 Nov 2014 16:39:26 +0000 Subject: [PATCH] Use internal cipher/handshake scoring system instead of static datafile --- nselib/tls.lua | 388 +++++++++++++++++++++++++++++------ scripts/ssl-enum-ciphers.nse | 271 ++++++++++++++---------- 2 files changed, 493 insertions(+), 166 deletions(-) diff --git a/nselib/tls.lua b/nselib/tls.lua index fcef5d7c7..9d64ca7c6 100644 --- a/nselib/tls.lua +++ b/nselib/tls.lua @@ -583,6 +583,18 @@ CIPHERS = { ["SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA"] = 0xFEFF, } +local function find_key(t, value) + local k, v + + for k, v in pairs(t) do + if v == value then + return k + end + end + + return nil +end + -- Keep this local to enforce use of the cipher_info function local cipher_info_cache = { -- pre-populate the special cases that break the parser below @@ -637,13 +649,323 @@ local cipher_info_cache = { }, } + +-- A couple helpers for server_key_exchange parsing +local function unpack_dhparams (blob, pos) + local p, g, y + pos, p, g, y = bin.unpack(">PPP", blob) + return pos, {p=p, g=g, y=y}, rsa_equiv("dh", #p) +end + +local function unpack_ecdhparams (blob, pos) + local eccurvetype + pos, eccurvetype = bin.unpack("C", blob, pos) + local ret = {} + local strength + if eccurvetype == 1 then + local p, a, b, base, order, cofactor + pos, p, a, b, base, order, cofactor = bin.unpack("pppppp", blob, pos) + strength = rsa_equiv("ec", #p) + ret.curve_params = { + ec_curve_type = "explicit_prime", + prime_p=p, curve={a=a, b=b}, base=base, order=order, cofactor=cofactor + } + elseif eccurvetype == 2 then + local p = {} + local m, basis + pos, m, basis = bin.unpack(">SC", blob, pos) + strength = rsa_equiv("ec", m) + if basis == 1 then -- ec_trinomial + pos, p.k = bin.unpack("p", blob, pos) + elseif basis == 2 then -- ec_pentanomial + pos, p.k1, p.k2, p.k3 = bin.unpack("ppp", blob, pos) + end + local a, b, base, order, cofactor + pos, a, b, base, order, cofactor = bin.unpack("ppppp", blob, pos) + ret.curve_params = { + ec_curve_type = "explicit_char2", + m=m, basis=basis, field=p, curve={a=a, b=b}, base=base, order=order, cofactor=cofactor + } + elseif eccurvetype == 3 then + local curve + pos, curve = bin.unpack(">S", blob, pos) + ret.curve_params = { + ec_curve_type = "namedcurve", + curve = find_key(ELLIPTIC_CURVES, curve) + } + local size = ret.curve_params.curve:match("(%d+)[rk]%d$") + if size then + strength = rsa_equiv("ec", tonumber(size)) + end + end + pos, ret.public = bin.unpack("p", blob, pos) + return pos, ret, strength +end + +local function unpack_signed (blob, pos, protocol) + if pos > #blob then -- not-signed + return pos, nil + end + local hash_alg, sig_alg, sig + -- TLSv1.2 changed to allow arbitrary hash and sig algorithms + if protocol and PROTOCOLS[protocol] >= 0x0303 then + pos, hash_alg, sig_alg, sig = bin.unpack("CC>P", blob, pos) + else + pos, sig = bin.unpack(">P", blob, pos) + end + return pos, {hash_algorithm=hash_alg, signature_algorithm=sig_alg, signature=sig} +end + +--- Get the strength-equivalent RSA key size +-- +-- Based on NIST SP800-57 part 1 rev 3 +-- @param ktype Key type ("dh", "ec", "rsa", "dsa") +-- @param bits Size of key in bits +-- @return Size in bits of RSA key with equivalent strength +function rsa_equiv (ktype, bits) + if ktype == "rsa" or ktype == "dsa" or ktype == "dh" then + return bits + elseif ktype == "ec" then + if bits < 160 then + return 512 -- Possibly down to 0, but details not published + elseif bits < 224 then + return 1024 + elseif bits < 256 then + return 2048 + elseif bits < 384 then + return 3072 + elseif bits < 512 then + return 7680 + else -- 512+ + return 15360 + end + end + return nil +end + +KEX_ALGORITHMS = {} + +-- RFC 5246 +KEX_ALGORITHMS.NULL = { anon = true } +KEX_ALGORITHMS.DH_anon = { + anon = true, + type = "dh", + server_key_exchange = function (blob, protocol) + local pos + local ret = {} + pos, ret.dhparams, ret.strength = unpack_dhparams(blob) + return ret + end +} +KEX_ALGORITHMS.DH_anon_EXPORT = { + anon=true, + export=true, + type = "dh", + server_key_exchange = KEX_ALGORITHMS.DH_anon.server_key_exchange +} +KEX_ALGORITHMS.ECDH_anon = { + anon=true, + type = "ecdh", + server_key_exchange = function (blob, protocol) + local pos + local ret = {} + pos, ret.ecdhparams, ret.strength = unpack_ecdhparams(blob) + return ret + end +} +KEX_ALGORITHMS.ECDH_anon_EXPORT = { + anon=true, + export=true, + type = "ecdh", + server_key_exchange = KEX_ALGORITHMS.ECDH_anon.server_key_exchange +} + +KEX_ALGORITHMS.RSA = { + pubkey="rsa", +} +-- http://www-archive.mozilla.org/projects/security/pki/nss/ssl/fips-ssl-ciphersuites.html +KEX_ALGORITHMS.RSA_FIPS = KEX_ALGORITHMS.RSA +KEX_ALGORITHMS.RSA_EXPORT = { + export=true, + pubkey="rsa", + type = "rsa", + server_key_exchange = function (blob, protocol) + local pos + local ret = {rsa={}} + pos, ret.rsa.modulus, ret.rsa.exponent = bin.unpack(">PP", blob) + pos, ret.signed = unpack_signed(blob, pos) + ret.strength = #ret.rsa.modulus + return ret + end +} +KEX_ALGORITHMS.RSA_EXPORT1024 = KEX_ALGORITHMS.RSA_EXPORT +KEX_ALGORITHMS.DHE_RSA={ + pubkey="rsa", + type = "dh", + server_key_exchange = function (blob, protocol) + local pos + local ret = {} + pos, ret.dhparams, ret.strength = unpack_dhparams(blob) + pos, ret.signed = unpack_signed(blob, pos) + return ret + end +} +KEX_ALGORITHMS.DHE_RSA_EXPORT={ + export=true, + pubkey="rsa", + type = "dh", + server_key_exchange = KEX_ALGORITHMS.DHE_RSA.server_key_exchange +} +KEX_ALGORITHMS.DHE_DSS={ + pubkey="dsa", + type = "dh", + server_key_exchange = KEX_ALGORITHMS.DHE_RSA.server_key_exchange +} +KEX_ALGORITHMS.DHE_DSS_EXPORT={ + export=true, + pubkey="dsa", + type = "dh", + server_key_exchange = KEX_ALGORITHMS.DHE_RSA.server_key_exchange +} +KEX_ALGORITHMS.DHE_DSS_EXPORT1024 = KEX_ALGORITHMS.DHE_DSS_EXPORT1024 + +KEX_ALGORITHMS.DH_DSS={ + pubkey="dh", +} +KEX_ALGORITHMS.DH_DSS_EXPORT={ + export=true, + pubkey="dh", +} +KEX_ALGORITHMS.DH_RSA={ + pubkey="dh", +} +KEX_ALGORITHMS.DH_RSA_EXPORT={ + export=true, + pubkey="dh", +} + +KEX_ALGORITHMS.ECDHE_RSA={ + pubkey="rsa", + type = "ecdh", + server_key_exchange = function (blob, protocol) + local pos + local ret = {} + pos, ret.ecdhparams, ret.strength = unpack_ecdhparams(blob) + pos, ret.signed = unpack_signed(blob, pos) + return ret + end +} +KEX_ALGORITHMS.ECDHE_ECDSA={ + pubkey="ec", + type = "ecdh", + server_key_exchange = KEX_ALGORITHMS.ECDHE_RSA.server_key_exchange +} +KEX_ALGORITHMS.ECDH_ECDSA={ + pubkey="ec", +} +KEX_ALGORITHMS.ECDH_RSA={ + pubkey="ec", +} + +-- draft-ietf-tls-ecc-00 +KEX_ALGORITHMS.ECDH_ECNRA={ + pubkey="ec", +} +KEX_ALGORITHMS.ECMQV_ECDSA={ + pubkey="ec", + type = "ecmqv", + server_key_exchange = function (blob, protocol) + local pos + local ret = {} + pos, ret.mqvparams = bin.unpack("p", blob) + return ret + end +} +KEX_ALGORITHMS.ECMQV_ECNRA={ + pubkey="ec", +} + +-- rfc4279 +KEX_ALGORITHMS.PSK = { + type = "psk", + server_key_exchange = function (blob, protocol) + local pos, hint = bin.unpack(">P", blob) + return {psk_identity_hint=hint} + end +} +KEX_ALGORITHMS.RSA_PSK = { + pubkey="rsa", + type = "psk", + server_key_exchange = KEX_ALGORITHMS.PSK.server_key_exchange +} +KEX_ALGORITHMS.DHE_PSK = { + type = "dh", + server_key_exchange = function (blob, protocol) + local pos + local ret = {} + pos, ret.psk_identity_hint = bin.unpack(">P", blob) + pos, ret.dhparams, ret.strength = unpack_dhparams(blob) + return ret + end +} +--nomenclature change +KEX_ALGORITHMS.PSK_DHE = KEX_ALGORITHMS.DHE_PSK + +--rfc5489 +KEX_ALGORITHMS.ECDHE_PSK={ + type = "ecdh", + server_key_exchange = function (blob, protocol) + local pos + local ret = {} + pos, ret.psk_identity_hint = bin.unpack(">P", blob) + pos, ret.ecdhparams, ret.strength = unpack_ecdhparams(blob) + return ret + end +} + +-- RFC 5054 +KEX_ALGORITHMS.SRP_SHA = { + type = "srp", + server_key_exchange = function (blob, protocol) + local pos + local ret = {srp={}} + pos, ret.srp.N, ret.srp.g, ret.srp.s, ret.srp.B = bin.unpack(">PPpP", blob) + pos, ret.signed = unpack_signed(blob, pos) + ret.strength = #ret.srp.N + return ret + end +} +KEX_ALGORITHMS.SRP_SHA_DSS = { + pubkey="dsa", + type = "srp", + server_key_exchange = KEX_ALGORITHMS.SRP_SHA.server_key_exchange +} +KEX_ALGORITHMS.SRP_SHA_RSA = { + pubkey="rsa", + type = "srp", + server_key_exchange = KEX_ALGORITHMS.SRP_SHA.server_key_exchange +} + +-- RFC 6101 +KEX_ALGORITHMS.FORTEZZA_KEA={} + +-- RFC 4491 +KEX_ALGORITHMS.GOSTR341001={} +KEX_ALGORITHMS.GOSTR341094={} + +-- RFC 2712 +KEX_ALGORITHMS.KRB5={} +KEX_ALGORITHMS.KRB5_EXPORT={ + export=true, +} + + --- Get info about a cipher suite -- --- Returned table has "kex", "server_auth", "cipher", "mode", "size", and --- "hash" keys, as well as boolean flags "dh", "ec", and "draft". The "draft" +-- Returned table has "kex", "cipher", "mode", "size", and +-- "hash" keys, as well as boolean flag "draft". The "draft" -- flag is only supported for some suites that have different enumeration --- values in draft versus final RFC. The "export" key may be present with --- value either "EXPORT" or "EXPORT1024". +-- values in draft versus final RFC. -- @param c The cipher suite name, e.g. TLS_RSA_WITH_AES_128_GCM_SHA256 -- @return A table of info as described above. function cipher_info (c) @@ -656,50 +978,12 @@ function cipher_info (c) stdnse.debug2("cipher_info: Not a TLS ciphersuite: %s", c) return nil end - -- kex, server_auth, cipher, size, mode, hash - -- flags: dh, ec, export + -- kex, cipher, size, mode, hash + i = i + 1 while tokens[i] and tokens[i] ~= "WITH" do i = i + 1 - local t = tokens[i] - if t == "RSA" or t == "DSS" then - info.server_auth = t - info.kex = info.kex or t - elseif t:sub(1,2) == "EC" then - info.ec = true - if t == "ECDH" or t == "ECDHE" then - info.dh = true - info.kex = t - elseif t == "ECDSA" or t == "ECNRA" then - info.server_auth = t - elseif t == "ECMQV" then - info.kex = t - end - elseif t == "DH" or t == "DHE" then - info.dh = true - info.kex = t - elseif t == "PSK" then - info.kex = (info.kex and info.kex .. "_" .. t) or t - info.server_auth = info.server_auth or t - elseif t == "EXPORT" or t == "EXPORT1024" then - info.export = t - elseif t == "SRP" then - info.kex = "SRP_SHA" - info.server_auth = "SRP_SHA" - i = i + 1 -- consume _SHA - elseif t == "FORTEZZA" then - info.kex = "FORTEZZA_KEA" - info.server_auth = "FORTEZZA" - i = i + 1 -- consume _KEA - elseif t == "NULL" then - info.kex = t - info.server_auth = "anon" - elseif t == "anon" then - info.server_auth = t - else - info.kex = info.kex or t - info.server_auth = info.server_auth or t - end end + info.kex = table.concat(tokens, "_", 2, i-1) if tokens[i] and tokens[i] ~= "WITH" then stdnse.debug2("cipher_info: Can't parse (no WITH): %s", c) @@ -777,18 +1061,6 @@ SCSVS = { ["TLS_FALLBACK_SCSV"] = 0x5600, -- draft-ietf-tls-downgrade-scsv-00 } -local function find_key(t, value) - local k, v - - for k, v in pairs(t) do - if v == value then - return k - end - end - - return nil -end - -- Helper function to unpack a 3-byte integer value local function unpack_3byte (buffer, pos) local low, high @@ -991,7 +1263,7 @@ function client_hello(t) if type(cipher) == "string" then cipher = CIPHERS[cipher] or SCSVS[cipher] end - if type(cipher) == "number" and cipher > 0 and cipher <= 0xffff then + if type(cipher) == "number" and cipher >= 0 and cipher <= 0xffff then table.insert(ciphers, bin.pack(">S", cipher)) else stdnse.debug1("Unknown cipher in client_hello: %s", cipher) diff --git a/scripts/ssl-enum-ciphers.nse b/scripts/ssl-enum-ciphers.nse index aa477fa3a..cec1d56ad 100644 --- a/scripts/ssl-enum-ciphers.nse +++ b/scripts/ssl-enum-ciphers.nse @@ -12,17 +12,13 @@ local tls = require "tls" description = [[ This script repeatedly initiates SSLv3/TLS connections, each time trying a new cipher or compressor while recording whether a host accepts or rejects it. The -end result is a list of all the ciphers and compressors that a server accepts. +end result is a list of all the ciphersuites and compressors that a server accepts. -Each cipher is shown with a strength rating: one of strong, -weak, or unknown strength. The output line -beginning with Least strength shows the strength of the -weakest cipher offered. If you are auditing for weak ciphers, you would -want to look more closely at any port where Least strength -is not strong. The cipher strength database is in the file -nselib/data/ssl-ciphers, or you can use a different file -through the script argument -ssl-enum-ciphers.rankedcipherlist. +Each ciphersuite is shown with a letter grade (A through F) indicating the +strength of the connection. The grade is based on the cryptographic strength of +the key exchange and of the stream cipher. The message integrity (hash) +algorithm choice is not a factor. The output line beginning with +Least strength shows the strength of the weakest cipher offered. SSLv3/TLSv1 requires more effort to determine which ciphers and compression methods a server supports than SSLv2. A client lists the ciphers and compressors @@ -44,46 +40,44 @@ and therefore is quite noisy. -- @usage -- nmap --script ssl-enum-ciphers -p 443 -- --- @args ssl-enum-ciphers.rankedcipherlist A path to a file of cipher names and strength ratings --- -- @output -- PORT STATE SERVICE REASON -- 443/tcp open https syn-ack -- | ssl-enum-ciphers: -- | SSLv3: -- | ciphers: --- | TLS_RSA_WITH_RC4_128_MD5 - strong --- | TLS_RSA_WITH_RC4_128_SHA - strong --- | TLS_RSA_WITH_3DES_EDE_CBC_SHA - strong +-- | TLS_RSA_WITH_RC4_128_MD5 - A +-- | TLS_RSA_WITH_RC4_128_SHA - A +-- | TLS_RSA_WITH_3DES_EDE_CBC_SHA - E -- | compressors: -- | NULL -- | cipher preference: server -- | TLSv1.0: -- | ciphers: --- | TLS_RSA_WITH_RC4_128_MD5 - strong --- | TLS_RSA_WITH_RC4_128_SHA - strong --- | TLS_RSA_WITH_3DES_EDE_CBC_SHA - strong --- | TLS_RSA_WITH_AES_256_CBC_SHA - strong --- | TLS_RSA_WITH_AES_128_CBC_SHA - strong +-- | TLS_RSA_WITH_RC4_128_MD5 - A +-- | TLS_RSA_WITH_RC4_128_SHA - A +-- | TLS_RSA_WITH_3DES_EDE_CBC_SHA - E +-- | TLS_RSA_WITH_AES_256_CBC_SHA - A +-- | TLS_RSA_WITH_AES_128_CBC_SHA - A -- | compressors: -- | NULL -- | cipher preference: server --- |_ least strength: strong +-- |_ least strength: E -- -- @xmloutput -- --
--
-- TLS_RSA_WITH_RC4_128_MD5 --- strong +-- A --
-- -- TLS_RSA_WITH_RC4_128_SHA --- strong +-- A --
-- -- TLS_RSA_WITH_3DES_EDE_CBC_SHA --- strong +-- E --
-- -- @@ -95,23 +89,23 @@ and therefore is quite noisy. --
--
-- TLS_RSA_WITH_RC4_128_MD5 --- strong +-- A --
-- -- TLS_RSA_WITH_RC4_128_SHA --- strong +-- A --
-- -- TLS_RSA_WITH_3DES_EDE_CBC_SHA --- strong +-- E --
-- -- TLS_RSA_WITH_AES_256_CBC_SHA --- strong +-- A --
-- -- TLS_RSA_WITH_AES_128_CBC_SHA --- strong +-- A --
-- -- @@ -119,7 +113,7 @@ and therefore is quite noisy. --
-- server -- --- strong +-- E author = "Mak Kolybabi , Gabriel Lawrence" @@ -133,18 +127,6 @@ categories = {"discovery", "intrusive"} -- http://seclists.org/nmap-dev/2010/q1/859 local CHUNK_SIZE = 64 - -cipherstrength = { - ["broken"] = 0, - ["weak"] = 1, - ["unknown strength"] = 2, - ["strong"] = 3 - } - -local rankedciphers={} -local mincipherstrength=9999 --artificial "highest value" -local rankedciphersfilename=false - -- Add additional context (protocol) to debug output local function ctx_log(level, protocol, fmt, ...) return stdnse.debug(level, "(%s) " .. fmt, protocol, ...) @@ -367,8 +349,59 @@ local function get_body(record, property, value) return nil end +-- Score a ciphersuite implementation (including key exchange info) +local function score_cipher (kex_strength, cipher_info) + local kex_score, cipher_score + if not kex_strength or not cipher_info.size then + return "unknown" + end + if kex_strength == 0 then + return 0 + elseif kex_strength < 512 then + kex_score = 0.2 + elseif kex_strength < 1024 then + kex_score = 0.4 + elseif kex_strength < 2048 then + kex_score = 0.8 + elseif kex_strength < 4096 then + kex_score = 0.9 + else + kex_score = 1.0 + end + + if cipher_info.size == 0 then + return 0 + elseif cipher_info.size < 128 then + cipher_score = 0.2 + elseif cipher_info.size < 256 then + cipher_score = 0.8 + else + cipher_score = 1.0 + end + + -- Based on SSL Labs' 30-30-40 rating without the first 30% (protocol support) + return 0.43 * kex_score + 0.57 * cipher_score +end + +local function letter_grade (score) + if not tonumber(score) then return "unknown" end + if score >= 0.80 then + return "A" + elseif score >= 0.65 then + return "B" + elseif score >= 0.50 then + return "C" + elseif score >= 0.35 then + return "D" + elseif score >= 0.20 then + return "E" + else + return "F" + end +end + -- Find which ciphers out of group are supported by the server. -local function find_ciphers_group(host, port, protocol, group) +local function find_ciphers_group(host, port, protocol, group, scores) local results = {} local t = { ["protocol"] = protocol, @@ -435,6 +468,72 @@ local function find_ciphers_group(host, port, protocol, group) else -- Add cipher to the list of accepted ciphers. table.insert(results, name) + if scores then + local info = tls.cipher_info(name) + -- Some warnings: + if info.hash and info.hash == "MD5" then + scores.warnings["Ciphersuite uses MD5 for message integrity"] = true + end + if protocol == "SSLv3" and info.mode and info.mode == "CBC" then + scores.warnings["CBC-mode cipher in SSLv3 (CVE-2014-3566)"] = true + elseif info.cipher == "RC4" and tls.PROTOCOLS[protocol] >= 0x0302 then + scores.warnings["Weak cipher RC4 in TLSv1.1 or newer not needed for BEAST mitigation"] = true + end + local kex = tls.KEX_ALGORITHMS[info.kex] + local extra, kex_strength + if kex.anon then + kex_strength = 0 + elseif kex.export then + if info.kex:find("1024$") then + kex_strength = 1024 + else + kex_strength = 512 + end + else + if kex.pubkey then + local certs = get_body(handshake, "type", "certificate") + -- Assume RFC compliance: + -- "The sender's certificate MUST come first in the list." + -- This may not always be the case, so + -- TODO: reorder certificates and validate entire chain + -- TODO: certificate validation (date, self-signed, etc) + local c = sslcert.parse_ssl_certificate(certs.certificates[1]) + if c.pubkey.type == kex.pubkey then + local sigalg = c.sig_algorithm:match("([mM][dD][245])") + if sigalg then + -- MD2 and MD5 are broken + kex_strength = 0 + scores.warnings["Insecure certificate signature: " .. string.upper(sigalg)] = true + else + sigalg = c.sig_algorithm:match("([sS][hH][aA]1)") + -- TODO: Update this when SHA-1 is deprecated in 2016 + -- kex_strength = 0 + scores.warnings["Weak certificate signature: SHA1"] = true + kex_strength = tls.rsa_equiv(kex.pubkey, c.pubkey.bits) + extra = string.format("%s %d", kex.pubkey, c.pubkey.bits) + end + end + end + local ske = get_body(handshake, "type", "server_key_exchange") + if kex.server_key_exchange and ske then + local kex_info = kex.server_key_exchange(ske.data) + if kex_info.strength then + if kex_strength and kex_strength > kex_info.strength then + kex_strength = kex_info.strength + scores.warnings["Key exchange parameters of lower strength than certificate key"] = true + end + kex_strength = kex_strength or kex_info.strength + extra = string.format("%s %d", kex.type, kex_info.strength) + end + end + end + scores[name] = { + cipher_strength=info.size, + kex_strength = kex_strength, + extra = extra, + letter_grade = letter_grade(score_cipher(kex_strength, info)) + } + end end end end @@ -449,10 +548,11 @@ local function find_ciphers(host, port, protocol) local ciphers = in_chunks(sorted_keys(tls.CIPHERS), CHUNK_SIZE) local results = {} + local scores = {warnings={}} -- Try every cipher. for _, group in ipairs(ciphers) do - local chunk, protocol_worked = find_ciphers_group(host, port, protocol, group) + local chunk, protocol_worked = find_ciphers_group(host, port, protocol, group, scores) if protocol_worked == nil then return nil end for _, name in ipairs(chunk) do table.insert(results, name) @@ -460,7 +560,7 @@ local function find_ciphers(host, port, protocol) end if not next(results) then return nil end - return results + return results, scores end local function find_compressors(host, port, protocol, good_ciphers) @@ -634,7 +734,7 @@ local function try_protocol(host, port, protocol, upresults) local results = stdnse.output_table() -- Find all valid ciphers. - local ciphers = find_ciphers(host, port, protocol) + local ciphers, scores = find_ciphers(host, port, protocol) if ciphers == nil then condvar "signal" return nil @@ -682,19 +782,15 @@ local function try_protocol(host, port, protocol, upresults) -- Add rankings to ciphers local cipherstr for i, name in ipairs(ciphers) do - if rankedciphersfilename and rankedciphers[name] then - cipherstr=rankedciphers[name] - else - cipherstr="unknown strength" - end - stdnse.debug2("Strength of %s rated %d.",cipherstr,cipherstrength[cipherstr]) - if mincipherstrength>cipherstrength[cipherstr] then - stdnse.debug2("Downgrading min cipher strength to %d.",cipherstrength[cipherstr]) - mincipherstrength=cipherstrength[cipherstr] - end - local outcipher = {name=name, strength=cipherstr} + local outcipher = {name=name, kex_info=scores[name].extra, strength=scores[name].letter_grade} setmetatable(outcipher,{ - __tostring=function(t) return string.format("%s - %s", t.name, t.strength) end + __tostring=function(t) + if t.kex_info then + return string.format("%s (%s) - %s", t.name, t.kex_info, t.strength) + else + return string.format("%s - %s", t.name, t.strength) + end + end }) ciphers[i]=outcipher end @@ -707,47 +803,15 @@ local function try_protocol(host, port, protocol, upresults) results["cipher preference"] = cipher_pref results["cipher preference error"] = cipher_pref_err + if next(scores.warnings) then + results["warnings"] = sorted_keys(scores.warnings) + end upresults[protocol] = results condvar "signal" return nil end --- Shamelessly stolen from nselib/unpwdb.lua and changed a bit. (Gabriel Lawrence) -local filltable = function(filename,table) - if #table ~= 0 then - return true - end - - local file = io.open(filename, "r") - - if not file then - return false - end - - while true do - local l = file:read() - - if not l then - break - end - - -- Comments takes up a whole line - if not l:match("#!comment:") then - local lsplit=stdnse.strsplit("%s+", l) - if cipherstrength[lsplit[2]] then - table[lsplit[1]] = lsplit[2] - else - stdnse.debug1("Strength not defined, ignoring: %s:%s",lsplit[1],lsplit[2]) - end - end - end - - file:close() - - return true -end - portrule = function (host, port) return shortport.ssl(host, port) or sslcert.getPrepareTLSWithoutReconnect(port) end @@ -774,15 +838,6 @@ end action = function(host, port) - rankedciphersfilename=stdnse.get_script_args("ssl-enum-ciphers.rankedcipherlist") - if rankedciphersfilename then - filltable(rankedciphersfilename,rankedciphers) - else - rankedciphersfilename = nmap.fetchfile( "nselib/data/ssl-ciphers" ) - stdnse.debug1("Ranked ciphers filename: %s", rankedciphersfilename) - filltable(rankedciphersfilename,rankedciphers) - end - local results = {} local condvar = nmap.condvar(results) @@ -807,14 +862,14 @@ action = function(host, port) return nil end - if rankedciphersfilename then - for k, v in pairs(cipherstrength) do - if v == mincipherstrength then - -- Should sort before or after SSLv3, TLSv* - results["least strength"] = k - end + local least = "A" + for p, r in pairs(results) do + for i, c in ipairs(r.ciphers) do + -- counter-intuitive: "A" < "B", so really looking for max + least = least < c.strength and c.strength or least end end + results["least strength"] = least return sorted_by_key(results) end