mirror of
https://github.com/nmap/nmap.git
synced 2025-12-07 21:21:31 +00:00
TLS 1.3 support for NSE. Fixes #1691
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
#Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o [NSE][GH#1691] TLS 1.3 now supported by most scripts for which it is
|
||||
relevant, such as ssl-enum-ciphers. Some functions like ssl tunnel
|
||||
connections and certificate parsing will require OpenSSL 1.1.1 or later to
|
||||
fully support TLS 1.3. [Daniel Miller]
|
||||
|
||||
o Setting --host-timeout=0 will disable the host timeout, which is set by -T5
|
||||
to 15 minutes. Earlier versions of Nmap require the user to specify a very
|
||||
long timeout instead.
|
||||
|
||||
@@ -942,7 +942,9 @@ end
|
||||
|
||||
local function handshake_cert (socket)
|
||||
-- logic mostly lifted from ssl-enum-ciphers
|
||||
local hello = tls.client_hello()
|
||||
-- TODO: implement TLSv1.3 handshake encryption so we can decrypt the
|
||||
-- Certificate message. Until then, we don't attempt TLSv1.3
|
||||
local hello = tls.client_hello({protocol="TLSv1.2"})
|
||||
local status, err = socket:send(hello)
|
||||
if not status then
|
||||
return false, "Failed to send to server"
|
||||
|
||||
442
nselib/tls.lua
442
nselib/tls.lua
@@ -28,9 +28,10 @@ PROTOCOLS = {
|
||||
["SSLv3"] = 0x0300,
|
||||
["TLSv1.0"] = 0x0301,
|
||||
["TLSv1.1"] = 0x0302,
|
||||
["TLSv1.2"] = 0x0303
|
||||
["TLSv1.2"] = 0x0303,
|
||||
["TLSv1.3"] = 0x0304,
|
||||
}
|
||||
HIGHEST_PROTOCOL = "TLSv1.2"
|
||||
HIGHEST_PROTOCOL = "TLSv1.3"
|
||||
local TLS_PROTOCOL_VERSIONS = tableaux.invert(PROTOCOLS)
|
||||
|
||||
--
|
||||
@@ -85,12 +86,15 @@ TLS_ALERT_REGISTRY = {
|
||||
["inappropriate_fallback"] = 86,
|
||||
["user_canceled"] = 90,
|
||||
["no_renegotiation"] = 100,
|
||||
["missing_extension"] = 109,
|
||||
["unsupported_extension"] = 110,
|
||||
["certificate_unobtainable"] = 111,
|
||||
["unrecognized_name"] = 112,
|
||||
["bad_certificate_status_response"] = 113,
|
||||
["bad_certificate_hash_value"] = 114,
|
||||
["unknown_psk_identity"] = 115
|
||||
["unknown_psk_identity"] = 115,
|
||||
["certificate_required"] = 116,
|
||||
["no_application_protocol"] = 120,
|
||||
}
|
||||
|
||||
--
|
||||
@@ -102,6 +106,9 @@ TLS_HANDSHAKETYPE_REGISTRY = {
|
||||
["server_hello"] = 2,
|
||||
["hello_verify_request"] = 3,
|
||||
["NewSessionTicket"] = 4,
|
||||
["end_of_early_data"] = 5,
|
||||
["hello_retry_request"] = 6,
|
||||
["encrypted_extensions"] = 8,
|
||||
["certificate"] = 11,
|
||||
["server_key_exchange"] = 12,
|
||||
["certificate_request"] = 13,
|
||||
@@ -112,7 +119,9 @@ TLS_HANDSHAKETYPE_REGISTRY = {
|
||||
["certificate_url"] = 21,
|
||||
["certificate_status"] = 22,
|
||||
["supplemental_data"] = 23,
|
||||
["key_update"] = 24,
|
||||
["next_protocol"] = 67,
|
||||
["message_hash"] = 254,
|
||||
}
|
||||
|
||||
--
|
||||
@@ -156,13 +165,24 @@ ELLIPTIC_CURVES = {
|
||||
brainpoolP256r1 = 26, --RFC7027
|
||||
brainpoolP384r1 = 27,
|
||||
brainpoolP512r1 = 28,
|
||||
ecdh_x25519 = 29, -- draft rfc4492
|
||||
ecdh_x448 = 30, --draft rfc4492
|
||||
ffdhe2048 = 256, --RFC7919
|
||||
ffdhe3072 = 257, --RFC7919
|
||||
ffdhe4096 = 258, --RFC7919
|
||||
ffdhe6144 = 259, --RFC7919
|
||||
ffdhe8192 = 260, --RFC7919
|
||||
ecdh_x25519 = 29, -- rfc8422
|
||||
ecdh_x448 = 30, -- rfc8422
|
||||
brainpoolP256r1tls13 = 31, --RFC8734
|
||||
brainpoolP384r1tls13 = 32,
|
||||
brainpoolP512r1tls13 = 33,
|
||||
GC256A = 34, -- draft-smyshlyaev-tls12-gost-suites
|
||||
GC256B = 35,
|
||||
GC256C = 36,
|
||||
GC256D = 37,
|
||||
GC512A = 38,
|
||||
GC512B = 39,
|
||||
GC512C = 40,
|
||||
curveSM2 = 41, -- RFC 8998
|
||||
ffdhe2048 = 0x0100, --RFC7919
|
||||
ffdhe3072 = 0x0101, --RFC7919
|
||||
ffdhe4096 = 0x0102, --RFC7919
|
||||
ffdhe6144 = 0x0103, --RFC7919
|
||||
ffdhe8192 = 0x0104, --RFC7919
|
||||
arbitrary_explicit_prime_curves = 0xFF01,
|
||||
arbitrary_explicit_char2_curves = 0xFF02,
|
||||
}
|
||||
@@ -173,6 +193,7 @@ DEFAULT_ELLIPTIC_CURVES = {
|
||||
"secp384r1",
|
||||
"secp521r1",
|
||||
"ecdh_x25519",
|
||||
"ffdhe2048", -- added for TLSv1.3
|
||||
}
|
||||
|
||||
---
|
||||
@@ -204,6 +225,35 @@ SignatureAlgorithms = {
|
||||
ed448 = 8,
|
||||
}
|
||||
|
||||
---
|
||||
-- TLS v1.3 Signature Algorithms
|
||||
SignatureSchemes = {
|
||||
-- RSASSA-PKCS1-v1_5 algorithms
|
||||
rsa_pkcs1_sha256 = 0x0401,
|
||||
rsa_pkcs1_sha384 = 0x0501,
|
||||
rsa_pkcs1_sha512 = 0x0601,
|
||||
-- ECDSA algorithms
|
||||
ecdsa_secp256r1_sha256 = 0x0403,
|
||||
ecdsa_secp384r1_sha384 = 0x0503,
|
||||
ecdsa_secp521r1_sha512 = 0x0603,
|
||||
-- RSASSA-PSS algorithms with public key OID rsaEncryption
|
||||
rsa_pss_rsae_sha256 = 0x0804,
|
||||
rsa_pss_rsae_sha384 = 0x0805,
|
||||
rsa_pss_rsae_sha512 = 0x0806,
|
||||
-- EdDSA algorithms
|
||||
ed25519 = 0x0807,
|
||||
ed448 = 0x0808,
|
||||
-- RSASSA-PSS algorithms with public key OID RSASSA-PSS
|
||||
rsa_pss_pss_sha256 = 0x0809,
|
||||
rsa_pss_pss_sha384 = 0x080a,
|
||||
rsa_pss_pss_sha512 = 0x080b,
|
||||
-- Legacy algorithms
|
||||
rsa_pkcs1_sha1 = 0x0201,
|
||||
ecdsa_sha1 = 0x0203,
|
||||
-- RFC 8998
|
||||
sm2sig_sm3 = 0x0708,
|
||||
}
|
||||
|
||||
---
|
||||
-- Extensions
|
||||
-- RFC 6066, draft-agl-tls-nextprotoneg-03
|
||||
@@ -224,6 +274,9 @@ EXTENSIONS = {
|
||||
["ec_point_formats"] = 11,
|
||||
["srp"] = 12,
|
||||
["signature_algorithms"] = 13,
|
||||
-- TLSv1.3 changed the format for this extension. It's just more convenient
|
||||
-- to call it something else.
|
||||
["signature_algorithms_13"] = 13,
|
||||
["use_srtp"] = 14,
|
||||
["heartbeat"] = 15,
|
||||
["application_layer_protocol_negotiation"] = 16,
|
||||
@@ -237,6 +290,18 @@ EXTENSIONS = {
|
||||
["token_binding"] = 24, -- Temporary, expires 2018-02-04
|
||||
["cached_info"] = 25, -- rfc7924
|
||||
["SessionTicket TLS"] = 35,
|
||||
-- TLSv1.3
|
||||
["pre_shared_key"] = 41,
|
||||
["early_data"] = 42,
|
||||
["supported_versions"] = 43,
|
||||
["cookie"] = 44,
|
||||
["psk_key_exchange_modes"] = 45,
|
||||
["certificate_authorities"] = 47,
|
||||
["oid_filters"] = 48,
|
||||
["post_handshake_auth"] = 49,
|
||||
["signature_algorithms_cert"] = 50,
|
||||
["key_share"] = 51,
|
||||
--
|
||||
["next_protocol_negotiation"] = 13172,
|
||||
["renegotiation_info"] = 65281,
|
||||
}
|
||||
@@ -279,6 +344,13 @@ EXTENSION_HELPERS = {
|
||||
end
|
||||
return pack(">s2", table.concat(list))
|
||||
end,
|
||||
["signature_algorithms_13"] = function (signature_schemes)
|
||||
local list = {}
|
||||
for _, name in ipairs(signature_schemes) do
|
||||
list[#list+1] = pack(">I2", SignatureSchemes[name])
|
||||
end
|
||||
return pack(">s2", table.concat(list))
|
||||
end,
|
||||
["application_layer_protocol_negotiation"] = function(protocols)
|
||||
local list = {}
|
||||
for _, proto in ipairs(protocols) do
|
||||
@@ -287,6 +359,13 @@ EXTENSION_HELPERS = {
|
||||
return pack(">s2", table.concat(list))
|
||||
end,
|
||||
["next_protocol_negotiation"] = tostring,
|
||||
["supported_versions"] = function(versions)
|
||||
local list = {}
|
||||
for _, name in ipairs(versions) do
|
||||
list[#list+1] = pack(">I2", PROTOCOLS[name])
|
||||
end
|
||||
return pack(">s1", table.concat(list))
|
||||
end,
|
||||
}
|
||||
|
||||
--
|
||||
@@ -654,6 +733,12 @@ CIPHERS = {
|
||||
["TLS_ECDHE_ECDSA_WITH_AES_256_CCM"] = 0xC0AD,
|
||||
["TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8"] = 0xC0AE,
|
||||
["TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8"] = 0xC0AF,
|
||||
["TLS_ECCPWD_WITH_AES_128_GCM_SHA256"] = 0xC0B0, -- RFC8492
|
||||
["TLS_ECCPWD_WITH_AES_256_GCM_SHA384"] = 0xC0B1, -- RFC8492
|
||||
["TLS_ECCPWD_WITH_AES_128_CCM_SHA256"] = 0xC0B2, -- RFC8492
|
||||
["TLS_ECCPWD_WITH_AES_256_CCM_SHA384"] = 0xC0B3, -- RFC8492
|
||||
["TLS_AKE_WITH_NULL_SHA256"] = 0xC0B4, -- draft-camwinget-tls-ts13-macciphersuites
|
||||
["TLS_AKE_WITH_NULL_SHA384"] = 0xC0B5, -- draft-camwinget-tls-ts13-macciphersuites
|
||||
["TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256-draft"] = 0xCC13, -- RFC7905 superseded
|
||||
["TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256-draft"] = 0xCC14, -- RFC7905 superseded
|
||||
["TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256-draft"] = 0xCC15, -- RFC7905 superseded
|
||||
@@ -664,21 +749,49 @@ CIPHERS = {
|
||||
["TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256"] = 0xCCAC,
|
||||
["TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256"] = 0xCCAD,
|
||||
["TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256"] = 0xCCAE,
|
||||
["TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256"] = 0xD001, -- draft-ietf-tls-ecdhe-psk-aead-05
|
||||
["TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384"] = 0xD002, -- draft-ietf-tls-ecdhe-psk-aead-05
|
||||
["TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256"] = 0xD003, -- draft-ietf-tls-ecdhe-psk-aead-05
|
||||
["TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256"] = 0xD005, -- draft-ietf-tls-ecdhe-psk-aead-05
|
||||
["TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256"] = 0xD001, -- RFC 8442
|
||||
["TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384"] = 0xD002, -- RFC 8442
|
||||
["TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256"] = 0xD003, -- RFC 8442
|
||||
["TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256"] = 0xD005, -- RFC 8442
|
||||
["SSL_RSA_FIPS_WITH_DES_CBC_SHA"] = 0xFEFE,
|
||||
["SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA"] = 0xFEFF,
|
||||
-- TLSv1.3:
|
||||
-- "Although TLS 1.3 uses the same cipher suite space as previous versions of
|
||||
-- TLS, TLS 1.3 cipher suites are defined differently, only specifying the
|
||||
-- symmetric ciphers, and cannot be used for TLS 1.2. Similarly, TLS 1.2 and
|
||||
-- lower cipher suites cannot be used with TLS 1.3."
|
||||
-- We designate these as AKE (Authenticated Key Exchange) ciphersuites, in
|
||||
-- order to simplify use of the cipher_info function.
|
||||
TLS_AKE_WITH_AES_128_GCM_SHA256 = 0x1301,
|
||||
TLS_AKE_WITH_AES_256_GCM_SHA384 = 0x1302,
|
||||
TLS_AKE_WITH_CHACHA20_POLY1305_SHA256 = 0x1303,
|
||||
TLS_AKE_WITH_AES_128_CCM_SHA256 = 0x1304,
|
||||
TLS_AKE_WITH_AES_128_CCM_8_SHA256 = 0x1305,
|
||||
TLS_AKE_WITH_SM4_GCM_SM3 = 0x00C6, -- RFC 8998
|
||||
TLS_AKE_WITH_SM4_CCM_SM3 = 0x00C7, -- RFC 8998
|
||||
}
|
||||
|
||||
DEFAULT_CIPHERS = {
|
||||
-- Default ciphers sent by tls.client_hello for TLSv1.2 or earlier
|
||||
DEFAULT_TLS12_CIPHERS = {
|
||||
"TLS_RSA_WITH_AES_128_CBC_SHA", -- mandatory TLSv1.2
|
||||
"TLS_RSA_WITH_3DES_EDE_CBC_SHA", -- mandatory TLSv1.1
|
||||
"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", -- mandatory TLSv1.0
|
||||
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", -- DHE with strong AES
|
||||
"TLS_RSA_WITH_RC4_128_MD5", -- Weak and old, but likely supported on old stuff
|
||||
}
|
||||
-- Same, but for TLSv1.3
|
||||
DEFAULT_TLS13_CIPHERS = {
|
||||
"TLS_AKE_WITH_AES_128_GCM_SHA256", -- mandatory TLSv1.3
|
||||
"TLS_AKE_WITH_AES_256_GCM_SHA384", -- stronger TLSv1.3
|
||||
"TLS_AKE_WITH_CHACHA20_POLY1305_SHA256", -- alternate TLSv1.3
|
||||
}
|
||||
-- Same, but for handshakes compatible with any TLS version
|
||||
local DEFAULT_CIPHERS = {
|
||||
table.unpack(DEFAULT_TLS13_CIPHERS)
|
||||
}
|
||||
for _, c in ipairs(DEFAULT_TLS12_CIPHERS) do
|
||||
table.insert(DEFAULT_CIPHERS, c)
|
||||
end
|
||||
|
||||
local function find_key(t, value)
|
||||
local found, v = tableaux.contains(t, value)
|
||||
@@ -747,6 +860,24 @@ local function unpack_dhparams (blob, pos)
|
||||
return pos, {p=p, g=g, y=y}, #p * 8
|
||||
end
|
||||
|
||||
local function named_group_info (group)
|
||||
if group:match("^arbitrary") then
|
||||
return "ec"
|
||||
end
|
||||
local ktype, size = group:match("^(%D+)(%d+)")
|
||||
assert(ktype and size, ("Invalid named group: %s"):format(group))
|
||||
size = tonumber(size)
|
||||
if ktype == "ffdhe" then
|
||||
ktype = "dh"
|
||||
else
|
||||
if group == "ecdh_x25519" or group == "curveSM2" then
|
||||
size = 256
|
||||
end
|
||||
ktype = "ec"
|
||||
end
|
||||
return ktype, size
|
||||
end
|
||||
|
||||
local function unpack_ecdhparams (blob, pos)
|
||||
local eccurvetype
|
||||
eccurvetype, pos = unpack("B", blob, pos)
|
||||
@@ -783,14 +914,8 @@ local function unpack_ecdhparams (blob, pos)
|
||||
ec_curve_type = "namedcurve",
|
||||
curve = find_key(ELLIPTIC_CURVES, curve)
|
||||
}
|
||||
local size = ret.curve_params.curve:match("(%d+)[rk]%d$")
|
||||
if size then
|
||||
strength = tonumber(size)
|
||||
elseif ret.curve_params.curve == "ecdh_x25519" then
|
||||
strength = 256
|
||||
elseif ret.curve_params.curve == "ecdh_x448" then
|
||||
strength = 448
|
||||
end
|
||||
local _
|
||||
_, strength = named_group_info(ret.curve_params.curve)
|
||||
end
|
||||
ret.public, pos = unpack("s1", blob, pos)
|
||||
return pos, ret, strength
|
||||
@@ -1064,7 +1189,51 @@ KEX_ALGORITHMS.KRB5_EXPORT={
|
||||
export=true,
|
||||
}
|
||||
|
||||
-- TLSv1.3
|
||||
KEX_ALGORITHMS.AKE = {
|
||||
tls13ok=true,
|
||||
tls13only=true,
|
||||
pfs=true,
|
||||
-- TLSv1.3 swaps the ServerKeyExchange message for the key_share extension.
|
||||
-- We'll just pretend that's what this is:
|
||||
server_key_exchange = function (blob, protocol)
|
||||
local named_group, pos = unpack(">I2", blob)
|
||||
stdnse.debug1("named_group = %d", named_group)
|
||||
named_group = find_key(ELLIPTIC_CURVES, named_group)
|
||||
local gtype, strength = named_group_info(named_group)
|
||||
return {
|
||||
type = gtype,
|
||||
strength = strength,
|
||||
ecdhparams={ -- Not always ECC, but reusing structure simplifies things
|
||||
curve_params={
|
||||
ec_curve_type = "namedcurve",
|
||||
curve = named_group,
|
||||
}
|
||||
}
|
||||
}
|
||||
end,
|
||||
}
|
||||
-- RFC 8492
|
||||
KEX_ALGORITHMS.ECCPWD = {
|
||||
tls13ok=true,
|
||||
tls13only=false,
|
||||
}
|
||||
|
||||
local algorithms = {
|
||||
["3DES"] = {s=112, b=64}, --NIST SP 800-57
|
||||
CHACHA20 = {s=256, b=128},
|
||||
IDEA = {s=128, b=64},
|
||||
SEED = {s=128, b=128},
|
||||
FORTEZZA = {s=80, b=64},
|
||||
DES = {s=56, b=64},
|
||||
RC2 = {s=40, b=64},
|
||||
DES40 = {s=40, b=64},
|
||||
NULL = {s=0},
|
||||
CAMELLIA = {b=128},
|
||||
ARIA = {b=128},
|
||||
AES = {b=128},
|
||||
SM4 = {s=128, b=128},
|
||||
}
|
||||
--- Get info about a cipher suite
|
||||
--
|
||||
-- Returned table has "kex", "cipher", "mode", "size", and
|
||||
@@ -1076,7 +1245,6 @@ KEX_ALGORITHMS.KRB5_EXPORT={
|
||||
function cipher_info (c)
|
||||
local info = cipher_info_cache[c]
|
||||
if info then return info end
|
||||
info = {}
|
||||
local tokens = stringaux.strsplit("_", c)
|
||||
local i = 1
|
||||
if tokens[i] ~= "TLS" and tokens[i] ~= "SSL" then
|
||||
@@ -1088,7 +1256,14 @@ function cipher_info (c)
|
||||
while tokens[i] and tokens[i] ~= "WITH" do
|
||||
i = i + 1
|
||||
end
|
||||
info.kex = table.concat(tokens, "_", 2, i-1)
|
||||
local kex = table.concat(tokens, "_", 2, i-1)
|
||||
info = KEX_ALGORITHMS[kex]
|
||||
if info then
|
||||
info = tableaux.tcopy(info)
|
||||
info.kex = kex
|
||||
else
|
||||
info = {kex = kex}
|
||||
end
|
||||
|
||||
if tokens[i] and tokens[i] ~= "WITH" then
|
||||
stdnse.debug2("cipher_info: Can't parse (no WITH): %s", c)
|
||||
@@ -1104,34 +1279,16 @@ function cipher_info (c)
|
||||
end
|
||||
|
||||
-- key size
|
||||
if t == "3DES" then -- NIST SP 800-57
|
||||
info.size = 112
|
||||
elseif t == "CHACHA20" then
|
||||
info.size = 256
|
||||
elseif t == "IDEA" then
|
||||
info.size = 128
|
||||
elseif t == "SEED" then
|
||||
info.size = 128
|
||||
elseif t == "FORTEZZA" then
|
||||
info.size = 80
|
||||
elseif t == "DES" then
|
||||
info.size = 56
|
||||
elseif t == "RC2" or t == "DES40" then
|
||||
info.size = 40
|
||||
elseif t == "NULL" then
|
||||
info.size = 0
|
||||
else
|
||||
local tmp = algorithms[t]
|
||||
if tmp then
|
||||
info.size = tmp.s
|
||||
info.block_size = tmp.b
|
||||
end
|
||||
if info.size == nil then
|
||||
i = i + 1
|
||||
info.size = tonumber(tokens[i])
|
||||
end
|
||||
|
||||
-- block size (bits)
|
||||
if t == "3DES" or t == "RC2" or t == "IDEA" or t == "DES" or t == "FORTEZZA" or t == "DES40" then
|
||||
info.block_size = 64
|
||||
elseif t == "AES" or t == "CAMELLIA" or t == "ARIA" or t == "SEED" then
|
||||
info.block_size = 128
|
||||
end
|
||||
|
||||
-- stream ciphers don't have a mode
|
||||
if info.cipher == "RC4" then
|
||||
info.mode = "stream"
|
||||
@@ -1158,15 +1315,28 @@ function cipher_info (c)
|
||||
-- hash
|
||||
if info.mode == "CCM" then
|
||||
info.hash = "SHA256"
|
||||
else
|
||||
i = i + 1
|
||||
t = (tokens[i]):match("(.*)%-draft$")
|
||||
if t then
|
||||
info.draft = true
|
||||
else
|
||||
t = tokens[i]
|
||||
end
|
||||
i = i + 1
|
||||
if i <= #tokens then
|
||||
if tokens[i] == "8" and info.mode == "CCM" then
|
||||
info.mode = "CCM_8"
|
||||
i = i + 1
|
||||
elseif info.export and (tokens[i]):match("^%d+$") then
|
||||
info.size = tonumber(tokens[i])
|
||||
i = i + 1
|
||||
end
|
||||
if i <= #tokens then
|
||||
local t, w = (tokens[i]):match("(.+)%-([a-z]+)")
|
||||
if t then
|
||||
if w == "draft" then
|
||||
info.draft = true
|
||||
end
|
||||
-- else "or"
|
||||
else
|
||||
t = tokens[i]
|
||||
end
|
||||
info.hash = t
|
||||
end
|
||||
info.hash = t
|
||||
end
|
||||
|
||||
cipher_info_cache[c] = info
|
||||
@@ -1203,6 +1373,22 @@ handshake_parse = {
|
||||
b["cipher"] = find_key(CIPHERS, b["cipher"])
|
||||
b["compressor"] = find_key(COMPRESSORS, b["compressor"])
|
||||
|
||||
-- RFC 8446: HelloRetryRequest message uses the same structure as the
|
||||
-- ServerHello, but with Random set to the special value of the SHA-256
|
||||
-- of "HelloRetryRequest"
|
||||
if b.protocol == "TLSv1.2" -- TLSv1.3 legacy version
|
||||
and b.random == "\xCF\x21\xAD\x74\xE5\x9A\x61\x11\xBE\x1D\x8C\x02\x1E\x65\xB8\x91\xC2\xA2\x11\x16\x7A\xBB\x8C\x5E\x07\x9E\x09\xE2\xC8\xA8\x33\x9C"
|
||||
then
|
||||
b.helloretry = true
|
||||
end
|
||||
|
||||
-- RFC 8446: "the legacy_version field MUST be set to 0x0303,
|
||||
-- which is the version number for TLS 1.2"
|
||||
if (b.protocol == "TLSv1.2" and b.extensions
|
||||
and b.extensions.supported_versions == "\x03\x04") then
|
||||
b.protocol = "TLSv1.3"
|
||||
end
|
||||
|
||||
return b, j
|
||||
end,
|
||||
|
||||
@@ -1395,6 +1581,27 @@ function record_read(buffer, i, fragment)
|
||||
return j, h, true
|
||||
end
|
||||
|
||||
---
|
||||
-- Get the record version field appropriate for the protocol version
|
||||
--
|
||||
-- TLSv1.3 introduced a change in the interpretation of the record version
|
||||
-- field. Previously, this was an indication of the TLS protocol, but now it is
|
||||
-- frozen at TLSv1.2.
|
||||
-- @param proto_version The numeric value of the protocol, e.g. 0x0303 for TLSv1.2
|
||||
-- @return The numeric value that should be used in the record layer version field
|
||||
local function legacy_version (proto_version)
|
||||
-- TLSv1.2 was the last version where protocol version was negotiated via the
|
||||
-- record layer version. Later versions use the supported_versions extension
|
||||
return proto_version <= 0x0303 and proto_version or 0x0303
|
||||
end
|
||||
|
||||
function record_version_ok(received_version, proto_version)
|
||||
if proto_version == "TLSv1.3" then
|
||||
return received_version == "TLSv1.2"
|
||||
end
|
||||
return proto_version == received_version
|
||||
end
|
||||
|
||||
---
|
||||
-- Build a SSL/TLS record
|
||||
-- @param type The type of record ("handshake", "change_cipher_spec", etc.)
|
||||
@@ -1406,7 +1613,7 @@ function record_write(type, protocol, b)
|
||||
-- Set the header as a handshake.
|
||||
pack("B", TLS_CONTENTTYPE_REGISTRY[type]),
|
||||
-- Set the protocol.
|
||||
pack(">I2", PROTOCOLS[protocol]),
|
||||
pack(">I2", legacy_version(PROTOCOLS[protocol])),
|
||||
-- Set the length of the header body.
|
||||
pack(">s2", b)
|
||||
})
|
||||
@@ -1435,6 +1642,26 @@ do
|
||||
}
|
||||
DEFAULT_SIGALGS = EXTENSION_HELPERS["signature_algorithms"](sigalgs)
|
||||
end
|
||||
-- Equivalent for TLSv1.3 is SignatureScheme
|
||||
-- We'll offer all the sha256 and sha512 variants, plus a few extra
|
||||
local DEFAULT_SIGSCHEMES
|
||||
do
|
||||
local sigalgs = {
|
||||
"rsa_pkcs1_sha256",
|
||||
"rsa_pkcs1_sha512",
|
||||
"ecdsa_secp256r1_sha256",
|
||||
"ecdsa_secp521r1_sha512",
|
||||
"rsa_pss_rsae_sha256",
|
||||
"rsa_pss_rsae_sha512",
|
||||
"ed25519",
|
||||
"ed448",
|
||||
"rsa_pss_pss_sha256",
|
||||
"rsa_pss_pss_sha512",
|
||||
"rsa_pkcs1_sha1",
|
||||
"ecdsa_sha1",
|
||||
}
|
||||
DEFAULT_SIGSCHEMES = EXTENSION_HELPERS["signature_algorithms_13"](sigalgs)
|
||||
end
|
||||
|
||||
---
|
||||
-- Build a client_hello message
|
||||
@@ -1459,10 +1686,11 @@ function client_hello(t)
|
||||
-- Set the protocol.
|
||||
local protocol = t["protocol"] or HIGHEST_PROTOCOL
|
||||
table.insert(b, pack(">I2 I4",
|
||||
PROTOCOLS[protocol],
|
||||
legacy_version(PROTOCOLS[protocol]),
|
||||
-- Set the random data.
|
||||
os.time()
|
||||
))
|
||||
local record_proto = t.record_protocol
|
||||
|
||||
-- Set the random data.
|
||||
table.insert(b, rand.random_string(28))
|
||||
@@ -1471,11 +1699,24 @@ function client_hello(t)
|
||||
local sid = t["session_id"] or ""
|
||||
table.insert(b, pack(">s1", sid))
|
||||
|
||||
local eccpwd = false
|
||||
local shangmi = false
|
||||
-- Cipher suites.
|
||||
ciphers = {}
|
||||
-- Add specified ciphers.
|
||||
for _, cipher in pairs(t["ciphers"] or DEFAULT_CIPHERS) do
|
||||
for _, cipher in pairs(t.ciphers -- user-specified list
|
||||
or (record_proto == "TLSv1.3" and DEFAULT_TLS13_CIPHERS) -- TLSv1.3 only
|
||||
or (PROTOCOLS[protocol] < PROTOCOLS["TLSv1.3"] and DEFAULT_TLS12_CIPHERS) -- non-TLSv1.3
|
||||
or DEFAULT_CIPHERS) -- combined/compatible handshake
|
||||
do
|
||||
if type(cipher) == "string" then
|
||||
if cipher:match("^TLS_ECCPWD_") then
|
||||
-- RFC 8492 has specific requirements
|
||||
eccpwd = true
|
||||
elseif protocol == "TLSv1.3" and cipher:match("_SM3$") then
|
||||
-- RFC 8998 has specific requirements
|
||||
shangmi = true
|
||||
end
|
||||
cipher = CIPHERS[cipher] or SCSVS[cipher]
|
||||
end
|
||||
if type(cipher) == "number" and cipher >= 0 and cipher <= 0xffff then
|
||||
@@ -1501,27 +1742,75 @@ function client_hello(t)
|
||||
table.insert(b, pack("s1", table.concat(compressors)))
|
||||
|
||||
-- TLS extensions
|
||||
if PROTOCOLS[protocol] and protocol ~= "SSLv3" then
|
||||
local proto_ver = PROTOCOLS[protocol]
|
||||
if proto_ver and protocol ~= "SSLv3" then
|
||||
local extensions = {}
|
||||
if t["extensions"] ~= nil then
|
||||
-- Do we need to add the signature_algorithms extension?
|
||||
local need_sigalg = (protocol == "TLSv1.2")
|
||||
-- Add specified extensions.
|
||||
-- TLSv1.3 requires supported_versions and key_share extensions
|
||||
-- OpenSSL also appears to want supported_groups in some cases?
|
||||
local need_supported_versions = (proto_ver >= PROTOCOLS["TLSv1.3"])
|
||||
local need_key_share = need_supported_versions
|
||||
local need_elliptic_curves = need_supported_versions
|
||||
-- Do we need to add the signature_algorithms extension?
|
||||
local need_sigalg = (proto_ver >= PROTOCOLS["TLSv1.2"])
|
||||
-- Add specified extensions.
|
||||
if t.extensions then
|
||||
for extension, data in pairs(t["extensions"]) do
|
||||
if type(extension) == "number" then
|
||||
table.insert(extensions, pack(">I2", extension))
|
||||
else
|
||||
if extension == "signature_algorithms" then
|
||||
if extension == "signature_algorithms" or extension == "signature_algorithms_13" then
|
||||
need_sigalg = false
|
||||
if shangmi then
|
||||
local sm2sig_sm3 = pack(">I2", SignatureSchemes.sm2sig_sm3)
|
||||
if not data:match("^..(..)*" .. sm2sig_sm3) then
|
||||
data = pack(">s2", data:sub(3) .. sm2sig_sm3)
|
||||
end
|
||||
end
|
||||
elseif extension == "supported_versions" then
|
||||
need_supported_versions = false
|
||||
elseif extension == "key_share" then
|
||||
need_key_share = false
|
||||
elseif extension == "elliptic_curves" then
|
||||
need_elliptic_curves = false
|
||||
if shangmi then
|
||||
-- For now, RFC 8998 is the only one that enforces particular curves
|
||||
local curveSM2 = pack(">I2", ELLIPTIC_CURVES.curveSM2)
|
||||
if not data:match("^..(..)*" .. curveSM2) then
|
||||
data = pack(">s2", data:sub(3) .. curveSM2)
|
||||
end
|
||||
end
|
||||
end
|
||||
table.insert(extensions, pack(">I2", EXTENSIONS[extension]))
|
||||
end
|
||||
table.insert(extensions, pack(">s2", data))
|
||||
end
|
||||
if need_sigalg then
|
||||
table.insert(extensions, pack(">I2", EXTENSIONS["signature_algorithms"]))
|
||||
table.insert(extensions, pack(">s2", DEFAULT_SIGALGS))
|
||||
end
|
||||
if need_supported_versions then
|
||||
table.insert(extensions, pack(">I2", EXTENSIONS["supported_versions"]))
|
||||
-- We'd prefer TLS 1.2 or 1.1, since we've tested our scripts on those.
|
||||
table.insert(extensions, pack(">s2", EXTENSION_HELPERS["supported_versions"]({"TLSv1.2", "TLSv1.1", "TLSv1.3", "SSLv3"})))
|
||||
end
|
||||
if need_sigalg then
|
||||
table.insert(extensions, pack(">I2", EXTENSIONS["signature_algorithms"]))
|
||||
local data = proto_ver >= PROTOCOLS["TLSv1.3"] and DEFAULT_SIGSCHEMES or DEFAULT_SIGALGS
|
||||
if shangmi then
|
||||
data = pack(">s2", data:sub(3) .. pack(">I2", SignatureSchemes.sm2sig_sm3))
|
||||
end
|
||||
table.insert(extensions, pack(">s2", data))
|
||||
end
|
||||
if need_key_share then
|
||||
-- RFC 8446: Clients MAY send an empty client_shares vector in order to request
|
||||
-- group selection from the server, at the cost of an additional round trip
|
||||
table.insert(extensions, pack(">I2", EXTENSIONS["key_share"]))
|
||||
table.insert(extensions, pack(">s2", "\0\0"))
|
||||
end
|
||||
if need_elliptic_curves then
|
||||
local curves = {table.unpack(DEFAULT_ELLIPTIC_CURVES)}
|
||||
if shangmi then
|
||||
curves[#curves+1] = "curveSM2"
|
||||
end
|
||||
table.insert(extensions, pack(">I2", EXTENSIONS["elliptic_curves"]))
|
||||
table.insert(extensions, pack(">s2", EXTENSION_HELPERS["elliptic_curves"](curves)))
|
||||
end
|
||||
-- Extensions are optional
|
||||
if #extensions ~= 0 then
|
||||
@@ -1548,9 +1837,14 @@ function client_hello(t)
|
||||
-- be downgraded by a MITM to SSLv3. So we use TLSv1.0 unless the caller
|
||||
-- explicitly tries to set SSLv3.0 somewhere (t.record_protocol or
|
||||
-- t.protocol)
|
||||
local record_proto = t.record_protocol
|
||||
if not record_proto then
|
||||
record_proto = (t.protocol == "SSLv3") and "SSLv3" or "TLSv1.0"
|
||||
elseif record_proto == "TLSv1.3" then
|
||||
-- RFC 8446: "MUST be set to 0x0303 for all records generated by a TLS 1.3
|
||||
-- implementation other than an initial ClientHello (i.e., one not generated
|
||||
-- after a HelloRetryRequest), where it MAY also be 0x0301 for compatibility
|
||||
-- purposes.
|
||||
record_proto = "TLSv1.2"
|
||||
end
|
||||
return record_write("handshake", record_proto, table.concat(h))
|
||||
end
|
||||
@@ -1624,4 +1918,12 @@ function servername(host)
|
||||
end
|
||||
end
|
||||
|
||||
local unittest = require "unittest"
|
||||
if not unittest.testing() then
|
||||
return _ENV
|
||||
end
|
||||
test_suite = unittest.TestSuite:new()
|
||||
for name, code in pairs(CIPHERS) do
|
||||
test_suite:add_test(unittest.not_nil(cipher_info(name).kex), name .. ".kex")
|
||||
end
|
||||
return _ENV;
|
||||
|
||||
@@ -75,7 +75,7 @@ local client_hello = function(host, port)
|
||||
status, err = sock:connect(host, port)
|
||||
if not status then
|
||||
sock:close()
|
||||
stdnse.debug("Can't send: %s", err)
|
||||
stdnse.debug("Can't connect: %s", err)
|
||||
return false
|
||||
end
|
||||
else
|
||||
@@ -86,23 +86,24 @@ local client_hello = function(host, port)
|
||||
end
|
||||
|
||||
|
||||
-- Send Client Hello to the target server
|
||||
status, err = sock:send(cli_h)
|
||||
if not status then
|
||||
stdnse.debug("Couldn't send: %s", err)
|
||||
sock:close()
|
||||
return false
|
||||
end
|
||||
repeat -- only once
|
||||
-- Send Client Hello to the target server
|
||||
status, err = sock:send(cli_h)
|
||||
if not status then
|
||||
stdnse.debug("Couldn't send: %s", err)
|
||||
break
|
||||
end
|
||||
|
||||
-- Read response
|
||||
status, response, err = tls.record_buffer(sock)
|
||||
if not status then
|
||||
stdnse.debug("Couldn't receive: %s", err)
|
||||
sock:close()
|
||||
return false
|
||||
end
|
||||
-- Read response
|
||||
status, response, err = tls.record_buffer(sock)
|
||||
if not status then
|
||||
stdnse.debug("Couldn't receive: %s", err)
|
||||
break
|
||||
end
|
||||
until true
|
||||
|
||||
return true, response
|
||||
sock:close()
|
||||
return status, response
|
||||
end
|
||||
|
||||
-- extract time from ServerHello response
|
||||
|
||||
@@ -883,6 +883,11 @@ parameters.]],
|
||||
}
|
||||
|
||||
for protocol in pairs(tls.PROTOCOLS) do
|
||||
if protocol == "TLSv1.3" then
|
||||
-- TLSv1.3 does not allow anonymous key exchange and only allows specific
|
||||
-- DHE groups named in RFC 7919
|
||||
goto NEXT_PROTOCOL
|
||||
end
|
||||
-- Try anonymous DH ciphersuites
|
||||
cipher, dhparams = get_dhe_params(host, port, protocol, dh_anons)
|
||||
-- Explicit test for false needed because nil just means no ciphers supported.
|
||||
|
||||
@@ -400,7 +400,10 @@ local function try_params(host, port, t)
|
||||
for j = 1, #record.body do -- no ipairs because we append below
|
||||
local b = record.body[j]
|
||||
done = ((record.type == "alert" and b.level == "fatal") or
|
||||
(record.type == "handshake" and b.type == "server_hello_done"))
|
||||
(record.type == "handshake" and (b.type == "server_hello_done" or
|
||||
-- TLSv1.3 does not have server_hello_done
|
||||
(t.protocol == "TLSv1.3" and b.type == "server_hello")))
|
||||
)
|
||||
table.insert(records[record.type].body, b)
|
||||
end
|
||||
if done then
|
||||
@@ -544,7 +547,7 @@ local function score_cipher (kex_strength, cipher_info)
|
||||
if not kex_strength or not cipher_info.size then
|
||||
return "unknown"
|
||||
end
|
||||
if kex_strength == 0 then
|
||||
if kex_strength <= 0 then
|
||||
return 0
|
||||
elseif kex_strength < 512 then
|
||||
kex_score = 0.2
|
||||
@@ -558,7 +561,7 @@ local function score_cipher (kex_strength, cipher_info)
|
||||
kex_score = 1.0
|
||||
end
|
||||
|
||||
if cipher_info.size == 0 then
|
||||
if cipher_info.size <= 0 then
|
||||
return 0
|
||||
elseif cipher_info.size < 128 then
|
||||
cipher_score = 0.2
|
||||
@@ -589,14 +592,27 @@ local function letter_grade (score)
|
||||
end
|
||||
end
|
||||
|
||||
local tls13proto = tls.PROTOCOLS["TLSv1.3"]
|
||||
local tls13supported = tls.EXTENSION_HELPERS.supported_versions({"TLSv1.3"})
|
||||
local function get_hello_table(host, protocol)
|
||||
local t = {
|
||||
protocol = protocol,
|
||||
record_protocol = protocol, -- improve chances of immediate rejection
|
||||
extensions = base_extensions(host),
|
||||
}
|
||||
|
||||
-- supported_versions extension required for TLSv1.3
|
||||
if (tls.PROTOCOLS[protocol] >= tls13proto) then
|
||||
t.extensions.supported_versions = tls13supported
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
-- Find which ciphers out of group are supported by the server.
|
||||
local function find_ciphers_group(host, port, protocol, group, scores)
|
||||
local results = {}
|
||||
local t = {
|
||||
["protocol"] = protocol,
|
||||
["record_protocol"] = protocol, -- improve chances of immediate rejection
|
||||
["extensions"] = base_extensions(host),
|
||||
}
|
||||
local t = get_hello_table(host, protocol)
|
||||
|
||||
-- This is a hacky sort of tristate variable. There are three conditions:
|
||||
-- 1. false = either ciphers or protocol is bad. Keep trying with new ciphers
|
||||
@@ -616,14 +632,15 @@ local function find_ciphers_group(host, port, protocol, group, scores)
|
||||
local alert = records.alert
|
||||
if alert then
|
||||
ctx_log(2, protocol, "Got alert: %s", alert.body[1].description)
|
||||
if alert["protocol"] ~= protocol then
|
||||
if not tls.record_version_ok(alert["protocol"], protocol) then
|
||||
ctx_log(1, protocol, "Protocol mismatch (received %s)", alert.protocol)
|
||||
-- Sometimes this is not an actual rejection of the protocol. Check specifically:
|
||||
if get_body(alert, "description", "protocol_version") then
|
||||
protocol_worked = nil
|
||||
end
|
||||
break
|
||||
elseif get_body(alert, "description", "handshake_failure") then
|
||||
elseif get_body(alert, "description", "handshake_failure")
|
||||
or get_body(alert, "description", "insufficient_security") then
|
||||
protocol_worked = true
|
||||
ctx_log(2, protocol, "%d ciphers rejected.", #group)
|
||||
break
|
||||
@@ -680,98 +697,97 @@ local function find_ciphers_group(host, port, protocol, group, scores)
|
||||
elseif info.cipher == "RC4" then
|
||||
scores.warnings["Broken cipher RC4 is deprecated by RFC 7465"] = true
|
||||
end
|
||||
if protocol == "TLSv1.3" and not info.tls13ok then
|
||||
scores.warnings["Non-TLSv1.3 ciphersuite chosen for TLSv1.3"] = true
|
||||
end
|
||||
local kex = tls.KEX_ALGORITHMS[info.kex]
|
||||
scores.any_pfs_ciphers = kex.pfs or scores.any_pfs_ciphers
|
||||
local extra, kex_strength
|
||||
if kex.anon then
|
||||
kex_strength = 0
|
||||
elseif kex.export then
|
||||
if kex.export then
|
||||
scores.warnings["Export key exchange"] = true
|
||||
if info.kex:find("1024$") then
|
||||
kex_strength = 1024
|
||||
else
|
||||
kex_strength = 512
|
||||
end
|
||||
else
|
||||
if have_ssl and 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, err
|
||||
if certs == nil then
|
||||
err = "no certificate message"
|
||||
else
|
||||
c, err = sslcert.parse_ssl_certificate(certs.certificates[1])
|
||||
end
|
||||
if kex.anon then
|
||||
scores.warnings["Anonymous key exchange, score capped at F"] = true
|
||||
kex_strength = 0
|
||||
elseif have_ssl and 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, err
|
||||
if certs == nil then
|
||||
err = "no certificate message"
|
||||
else
|
||||
c, err = sslcert.parse_ssl_certificate(certs.certificates[1])
|
||||
end
|
||||
if not c then
|
||||
ctx_log(1, protocol, "Failed to parse certificate: %s", err)
|
||||
elseif c.pubkey.type == kex.pubkey then
|
||||
local sigalg = c.sig_algorithm:match("([mM][dD][245])") or c.sig_algorithm:match("([sS][hH][aA]1)")
|
||||
if sigalg then
|
||||
kex_strength = 0
|
||||
scores.warnings[("Insecure certificate signature (%s), score capped at F"):format(string.upper(sigalg))] = true
|
||||
end
|
||||
if not c then
|
||||
stdnse.debug1("Failed to parse certificate: %s", err)
|
||||
elseif c.pubkey.type == kex.pubkey then
|
||||
local sigalg = c.sig_algorithm:match("([mM][dD][245])")
|
||||
if sigalg then
|
||||
-- MD2 and MD5 are broken
|
||||
local rsa_bits = tls.rsa_equiv(kex.pubkey, c.pubkey.bits)
|
||||
kex_strength = math.min(kex_strength or rsa_bits, rsa_bits)
|
||||
if c.pubkey.exponent then
|
||||
if openssl.bignum_bn2dec(c.pubkey.exponent) == "1" then
|
||||
kex_strength = 0
|
||||
scores.warnings["Insecure certificate signature: " .. string.upper(sigalg)] = true
|
||||
else
|
||||
sigalg = c.sig_algorithm:match("([sS][hH][aA]1)")
|
||||
if sigalg then
|
||||
-- TODO: Update this when SHA-1 is fully deprecated in 2017
|
||||
if type(c.notBefore) == "table" and c.notBefore.year >= 2016 then
|
||||
kex_strength = 0
|
||||
scores.warnings["Deprecated SHA1 signature in certificate issued after January 1, 2016"] = true
|
||||
end
|
||||
scores.warnings["Weak certificate signature: SHA1"] = true
|
||||
end
|
||||
kex_strength = tls.rsa_equiv(kex.pubkey, c.pubkey.bits)
|
||||
if c.pubkey.exponent then
|
||||
if openssl.bignum_bn2dec(c.pubkey.exponent) == "1" then
|
||||
kex_strength = 0
|
||||
scores.warnings["Certificate RSA exponent is 1, score capped at F"] = true
|
||||
end
|
||||
end
|
||||
if c.pubkey.ecdhparams then
|
||||
if c.pubkey.ecdhparams.curve_params.ec_curve_type == "namedcurve" then
|
||||
extra = c.pubkey.ecdhparams.curve_params.curve
|
||||
else
|
||||
extra = string.format("%s %d", c.pubkey.ecdhparams.curve_params.ec_curve_type, c.pubkey.bits)
|
||||
end
|
||||
else
|
||||
extra = string.format("%s %d", kex.pubkey, c.pubkey.bits)
|
||||
end
|
||||
scores.warnings["Certificate RSA exponent is 1, score capped at F"] = true
|
||||
end
|
||||
end
|
||||
if c.pubkey.ecdhparams then
|
||||
if c.pubkey.ecdhparams.curve_params.ec_curve_type == "namedcurve" then
|
||||
extra = c.pubkey.ecdhparams.curve_params.curve
|
||||
else
|
||||
extra = string.format("%s %d", c.pubkey.ecdhparams.curve_params.ec_curve_type, c.pubkey.bits)
|
||||
end
|
||||
else
|
||||
extra = string.format("%s %d", kex.pubkey, c.pubkey.bits)
|
||||
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, protocol)
|
||||
if kex_info.strength then
|
||||
local rsa_bits = tls.rsa_equiv(kex.type, kex_info.strength)
|
||||
local low_strength_warning = false
|
||||
if kex_strength and kex_strength > rsa_bits then
|
||||
kex_strength = rsa_bits
|
||||
low_strength_warning = true
|
||||
end
|
||||
kex_strength = kex_strength or rsa_bits
|
||||
if kex_info.ecdhparams then
|
||||
if kex_info.ecdhparams.curve_params.ec_curve_type == "namedcurve" then
|
||||
extra = kex_info.ecdhparams.curve_params.curve
|
||||
else
|
||||
extra = string.format("%s %d", kex_info.ecdhparams.curve_params.ec_curve_type, kex_info.strength)
|
||||
end
|
||||
end
|
||||
local ske
|
||||
if protocol == "TLSv1.3" then
|
||||
ske = server_hello.extensions.key_share
|
||||
elseif kex.server_key_exchange then
|
||||
ske = get_body(handshake, "type", "server_key_exchange")
|
||||
if ske then
|
||||
ske = ske.data
|
||||
end
|
||||
end
|
||||
if ske then
|
||||
local kex_info = kex.server_key_exchange(ske, protocol)
|
||||
if kex_info.strength then
|
||||
local kex_type = kex_info.type or kex.type
|
||||
if kex_info.ecdhparams then
|
||||
if kex_info.ecdhparams.curve_params.ec_curve_type == "namedcurve" then
|
||||
extra = kex_info.ecdhparams.curve_params.curve
|
||||
else
|
||||
extra = string.format("%s %d", kex.type, kex_info.strength)
|
||||
end
|
||||
if low_strength_warning then
|
||||
scores.warnings[(
|
||||
"Key exchange (%s) of lower strength than certificate key"
|
||||
):format(extra)] = true
|
||||
extra = string.format("%s %d", kex_info.ecdhparams.curve_params.ec_curve_type, kex_info.strength)
|
||||
end
|
||||
else
|
||||
extra = string.format("%s %d", kex_type, kex_info.strength)
|
||||
end
|
||||
if kex_info.rsa and kex_info.rsa.exponent == 1 then
|
||||
kex_strength = 0
|
||||
scores.warnings["Certificate RSA exponent is 1, score capped at F"] = true
|
||||
local rsa_bits = tls.rsa_equiv(kex_type, kex_info.strength)
|
||||
if kex_strength and kex_strength > rsa_bits then
|
||||
kex_strength = rsa_bits
|
||||
scores.warnings[(
|
||||
"Key exchange (%s) of lower strength than certificate key"
|
||||
):format(extra)] = true
|
||||
end
|
||||
kex_strength = math.min(kex_strength or rsa_bits, rsa_bits)
|
||||
end
|
||||
if kex_info.rsa and kex_info.rsa.exponent == 1 then
|
||||
kex_strength = 0
|
||||
scores.warnings["Certificate RSA exponent is 1, score capped at F"] = true
|
||||
end
|
||||
end
|
||||
scores[name] = {
|
||||
@@ -791,11 +807,8 @@ end
|
||||
local function get_chunk_size(host, protocol)
|
||||
-- Try to make sure we don't send too big of a handshake
|
||||
-- https://github.com/ssllabs/research/wiki/Long-Handshake-Intolerance
|
||||
local len_t = {
|
||||
protocol = protocol,
|
||||
ciphers = {},
|
||||
extensions = base_extensions(host),
|
||||
}
|
||||
local len_t = get_hello_table(host, protocol)
|
||||
len_t.ciphers = {}
|
||||
local cipher_len_remaining = 255 - #tls.client_hello(len_t)
|
||||
-- if we're over 255 anyway, just go for it.
|
||||
-- Each cipher adds 2 bytes
|
||||
@@ -809,7 +822,17 @@ end
|
||||
-- each chunk.
|
||||
local function find_ciphers(host, port, protocol)
|
||||
|
||||
local ciphers = in_chunks(sorted_keys(tls.CIPHERS), get_chunk_size(host, protocol))
|
||||
local candidates = {}
|
||||
-- TLSv1.3 ciphers are different, though some are shared (ECCPWD)
|
||||
local tls13 = protocol == "TLSv1.3"
|
||||
for _, c in ipairs(sorted_keys(tls.CIPHERS)) do
|
||||
local info = tls.cipher_info(c)
|
||||
if (not tls13 and not info.tls13only)
|
||||
or (tls13 and info.tls13ok) then
|
||||
candidates[#candidates+1] = c
|
||||
end
|
||||
end
|
||||
local ciphers = in_chunks(candidates, get_chunk_size(host, protocol))
|
||||
|
||||
local results = {}
|
||||
local scores = {warnings={}}
|
||||
@@ -830,11 +853,8 @@ end
|
||||
|
||||
local function find_compressors(host, port, protocol, good_ciphers)
|
||||
local compressors = sorted_keys(tls.COMPRESSORS)
|
||||
local t = {
|
||||
["protocol"] = protocol,
|
||||
["ciphers"] = good_ciphers,
|
||||
["extensions"] = base_extensions(host),
|
||||
}
|
||||
local t = get_hello_table(host, protocol)
|
||||
t.ciphers = good_ciphers
|
||||
|
||||
local results = {}
|
||||
|
||||
@@ -852,7 +872,7 @@ local function find_compressors(host, port, protocol, good_ciphers)
|
||||
local alert = records.alert
|
||||
if alert then
|
||||
ctx_log(2, protocol, "Got alert: %s", alert.body[1].description)
|
||||
if alert["protocol"] ~= protocol then
|
||||
if not tls.record_version_ok(alert["protocol"], protocol) then
|
||||
ctx_log(1, protocol, "Protocol rejected.")
|
||||
protocol_worked = nil
|
||||
break
|
||||
@@ -908,11 +928,8 @@ end
|
||||
-- Offer two ciphers and return the one chosen by the server. Returns nil and
|
||||
-- an error message in case of a server error.
|
||||
local function compare_ciphers(host, port, protocol, cipher_a, cipher_b)
|
||||
local t = {
|
||||
["protocol"] = protocol,
|
||||
["ciphers"] = {cipher_a, cipher_b},
|
||||
["extensions"] = base_extensions(host),
|
||||
}
|
||||
local t = get_hello_table(host, protocol)
|
||||
t.ciphers = {cipher_a, cipher_b}
|
||||
local records = try_params(host, port, t)
|
||||
local server_hello = records.handshake and get_body(records.handshake, "type", "server_hello")
|
||||
if server_hello then
|
||||
@@ -1010,14 +1027,18 @@ local function try_protocol(host, port, protocol, upresults)
|
||||
end
|
||||
-- Find all valid compression methods.
|
||||
local compressors
|
||||
-- Reduce chunk size by 1 to allow extra room for the extra compressors (2 bytes)
|
||||
for _, c in ipairs(in_chunks(ciphers, get_chunk_size(host, protocol) - 1)) do
|
||||
compressors = find_compressors(host, port, protocol, c)
|
||||
-- I observed a weird interaction between ECDSA ciphers and DEFLATE compression.
|
||||
-- Some servers would reject the handshake if no non-ECDSA ciphers were available.
|
||||
-- Sending 64 ciphers at a time should be sufficient, but we'll try them all if necessary.
|
||||
if compressors and #compressors ~= 0 then
|
||||
break
|
||||
-- RFC 8446: "For every TLS 1.3 ClientHello, this vector MUST contain exactly
|
||||
-- one byte, set to zero"
|
||||
if (tls.PROTOCOLS[protocol] < tls13proto) then
|
||||
-- Reduce chunk size by 1 to allow extra room for the extra compressors (2 bytes)
|
||||
for _, c in ipairs(in_chunks(ciphers, get_chunk_size(host, protocol) - 1)) do
|
||||
compressors = find_compressors(host, port, protocol, c)
|
||||
-- I observed a weird interaction between ECDSA ciphers and DEFLATE compression.
|
||||
-- Some servers would reject the handshake if no non-ECDSA ciphers were available.
|
||||
-- Sending 64 ciphers at a time should be sufficient, but we'll try them all if necessary.
|
||||
if compressors and #compressors ~= 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
||||
categories = { "vuln", "safe" }
|
||||
dependencies = {"https-redirect"}
|
||||
|
||||
-- TLSv1.3 was not implemented by affected versions of OpenSSL.
|
||||
local arg_protocols = stdnse.get_script_args(SCRIPT_NAME .. ".protocols") or {'TLSv1.0', 'TLSv1.1', 'TLSv1.2'}
|
||||
|
||||
portrule = function(host, port)
|
||||
|
||||
@@ -48,6 +48,7 @@ your TLS ciphersuites.
|
||||
-- | https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-3566
|
||||
-- |_ https://www.openssl.org/~bodo/ssl-poodle.pdf
|
||||
--
|
||||
-- @see ssl-enum-ciphers.nse
|
||||
|
||||
author = "Daniel Miller"
|
||||
|
||||
|
||||
@@ -56,10 +56,13 @@ local client_hello = function(host, port, protos)
|
||||
local sock, status, response, err, cli_h
|
||||
|
||||
cli_h = tls.client_hello({
|
||||
["extensions"] = {
|
||||
[ALPN_NAME] = tls.EXTENSION_HELPERS[ALPN_NAME](protos)
|
||||
},
|
||||
})
|
||||
-- TLSv1.3 does not send this extension plaintext.
|
||||
-- TODO: implement key exchange crypto to retrieve encrypted extensions
|
||||
protocol = "TLSv1.2",
|
||||
["extensions"] = {
|
||||
[ALPN_NAME] = tls.EXTENSION_HELPERS[ALPN_NAME](protos)
|
||||
},
|
||||
})
|
||||
|
||||
-- Connect to the target server
|
||||
local status, err
|
||||
|
||||
@@ -58,6 +58,9 @@ local client_hello = function(host, port)
|
||||
local sock, status, response, err, cli_h
|
||||
|
||||
cli_h = tls.client_hello({
|
||||
-- TLSv1.3 does not send this extension plaintext.
|
||||
-- TODO: implement key exchange crypto to retrieve encrypted extensions
|
||||
protocol = "TLSv1.2",
|
||||
["extensions"] = {
|
||||
["next_protocol_negotiation"] = "",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user