1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-07 13:11:28 +00:00

TLS 1.3 support for NSE. Fixes #1691

This commit is contained in:
dmiller
2021-07-02 20:01:30 +00:00
parent 61f17067a3
commit 7c61f7c9c3
10 changed files with 545 additions and 201 deletions

View File

@@ -1,5 +1,10 @@
#Nmap Changelog ($Id$); -*-text-*- #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 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 to 15 minutes. Earlier versions of Nmap require the user to specify a very
long timeout instead. long timeout instead.

View File

@@ -942,7 +942,9 @@ end
local function handshake_cert (socket) local function handshake_cert (socket)
-- logic mostly lifted from ssl-enum-ciphers -- 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) local status, err = socket:send(hello)
if not status then if not status then
return false, "Failed to send to server" return false, "Failed to send to server"

View File

@@ -28,9 +28,10 @@ PROTOCOLS = {
["SSLv3"] = 0x0300, ["SSLv3"] = 0x0300,
["TLSv1.0"] = 0x0301, ["TLSv1.0"] = 0x0301,
["TLSv1.1"] = 0x0302, ["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) local TLS_PROTOCOL_VERSIONS = tableaux.invert(PROTOCOLS)
-- --
@@ -85,12 +86,15 @@ TLS_ALERT_REGISTRY = {
["inappropriate_fallback"] = 86, ["inappropriate_fallback"] = 86,
["user_canceled"] = 90, ["user_canceled"] = 90,
["no_renegotiation"] = 100, ["no_renegotiation"] = 100,
["missing_extension"] = 109,
["unsupported_extension"] = 110, ["unsupported_extension"] = 110,
["certificate_unobtainable"] = 111, ["certificate_unobtainable"] = 111,
["unrecognized_name"] = 112, ["unrecognized_name"] = 112,
["bad_certificate_status_response"] = 113, ["bad_certificate_status_response"] = 113,
["bad_certificate_hash_value"] = 114, ["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, ["server_hello"] = 2,
["hello_verify_request"] = 3, ["hello_verify_request"] = 3,
["NewSessionTicket"] = 4, ["NewSessionTicket"] = 4,
["end_of_early_data"] = 5,
["hello_retry_request"] = 6,
["encrypted_extensions"] = 8,
["certificate"] = 11, ["certificate"] = 11,
["server_key_exchange"] = 12, ["server_key_exchange"] = 12,
["certificate_request"] = 13, ["certificate_request"] = 13,
@@ -112,7 +119,9 @@ TLS_HANDSHAKETYPE_REGISTRY = {
["certificate_url"] = 21, ["certificate_url"] = 21,
["certificate_status"] = 22, ["certificate_status"] = 22,
["supplemental_data"] = 23, ["supplemental_data"] = 23,
["key_update"] = 24,
["next_protocol"] = 67, ["next_protocol"] = 67,
["message_hash"] = 254,
} }
-- --
@@ -156,13 +165,24 @@ ELLIPTIC_CURVES = {
brainpoolP256r1 = 26, --RFC7027 brainpoolP256r1 = 26, --RFC7027
brainpoolP384r1 = 27, brainpoolP384r1 = 27,
brainpoolP512r1 = 28, brainpoolP512r1 = 28,
ecdh_x25519 = 29, -- draft rfc4492 ecdh_x25519 = 29, -- rfc8422
ecdh_x448 = 30, --draft rfc4492 ecdh_x448 = 30, -- rfc8422
ffdhe2048 = 256, --RFC7919 brainpoolP256r1tls13 = 31, --RFC8734
ffdhe3072 = 257, --RFC7919 brainpoolP384r1tls13 = 32,
ffdhe4096 = 258, --RFC7919 brainpoolP512r1tls13 = 33,
ffdhe6144 = 259, --RFC7919 GC256A = 34, -- draft-smyshlyaev-tls12-gost-suites
ffdhe8192 = 260, --RFC7919 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_prime_curves = 0xFF01,
arbitrary_explicit_char2_curves = 0xFF02, arbitrary_explicit_char2_curves = 0xFF02,
} }
@@ -173,6 +193,7 @@ DEFAULT_ELLIPTIC_CURVES = {
"secp384r1", "secp384r1",
"secp521r1", "secp521r1",
"ecdh_x25519", "ecdh_x25519",
"ffdhe2048", -- added for TLSv1.3
} }
--- ---
@@ -204,6 +225,35 @@ SignatureAlgorithms = {
ed448 = 8, 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 -- Extensions
-- RFC 6066, draft-agl-tls-nextprotoneg-03 -- RFC 6066, draft-agl-tls-nextprotoneg-03
@@ -224,6 +274,9 @@ EXTENSIONS = {
["ec_point_formats"] = 11, ["ec_point_formats"] = 11,
["srp"] = 12, ["srp"] = 12,
["signature_algorithms"] = 13, ["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, ["use_srtp"] = 14,
["heartbeat"] = 15, ["heartbeat"] = 15,
["application_layer_protocol_negotiation"] = 16, ["application_layer_protocol_negotiation"] = 16,
@@ -237,6 +290,18 @@ EXTENSIONS = {
["token_binding"] = 24, -- Temporary, expires 2018-02-04 ["token_binding"] = 24, -- Temporary, expires 2018-02-04
["cached_info"] = 25, -- rfc7924 ["cached_info"] = 25, -- rfc7924
["SessionTicket TLS"] = 35, ["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, ["next_protocol_negotiation"] = 13172,
["renegotiation_info"] = 65281, ["renegotiation_info"] = 65281,
} }
@@ -279,6 +344,13 @@ EXTENSION_HELPERS = {
end end
return pack(">s2", table.concat(list)) return pack(">s2", table.concat(list))
end, 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) ["application_layer_protocol_negotiation"] = function(protocols)
local list = {} local list = {}
for _, proto in ipairs(protocols) do for _, proto in ipairs(protocols) do
@@ -287,6 +359,13 @@ EXTENSION_HELPERS = {
return pack(">s2", table.concat(list)) return pack(">s2", table.concat(list))
end, end,
["next_protocol_negotiation"] = tostring, ["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_256_CCM"] = 0xC0AD,
["TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8"] = 0xC0AE, ["TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8"] = 0xC0AE,
["TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8"] = 0xC0AF, ["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_RSA_WITH_CHACHA20_POLY1305_SHA256-draft"] = 0xCC13, -- RFC7905 superseded
["TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256-draft"] = 0xCC14, -- RFC7905 superseded ["TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256-draft"] = 0xCC14, -- RFC7905 superseded
["TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256-draft"] = 0xCC15, -- 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_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256"] = 0xCCAC,
["TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256"] = 0xCCAD, ["TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256"] = 0xCCAD,
["TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256"] = 0xCCAE, ["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_128_GCM_SHA256"] = 0xD001, -- RFC 8442
["TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384"] = 0xD002, -- draft-ietf-tls-ecdhe-psk-aead-05 ["TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384"] = 0xD002, -- RFC 8442
["TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256"] = 0xD003, -- draft-ietf-tls-ecdhe-psk-aead-05 ["TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256"] = 0xD003, -- RFC 8442
["TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256"] = 0xD005, -- draft-ietf-tls-ecdhe-psk-aead-05 ["TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256"] = 0xD005, -- RFC 8442
["SSL_RSA_FIPS_WITH_DES_CBC_SHA"] = 0xFEFE, ["SSL_RSA_FIPS_WITH_DES_CBC_SHA"] = 0xFEFE,
["SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA"] = 0xFEFF, ["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_AES_128_CBC_SHA", -- mandatory TLSv1.2
"TLS_RSA_WITH_3DES_EDE_CBC_SHA", -- mandatory TLSv1.1 "TLS_RSA_WITH_3DES_EDE_CBC_SHA", -- mandatory TLSv1.1
"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", -- mandatory TLSv1.0 "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", -- mandatory TLSv1.0
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", -- DHE with strong AES "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 "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 function find_key(t, value)
local found, v = tableaux.contains(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 return pos, {p=p, g=g, y=y}, #p * 8
end 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 function unpack_ecdhparams (blob, pos)
local eccurvetype local eccurvetype
eccurvetype, pos = unpack("B", blob, pos) eccurvetype, pos = unpack("B", blob, pos)
@@ -783,14 +914,8 @@ local function unpack_ecdhparams (blob, pos)
ec_curve_type = "namedcurve", ec_curve_type = "namedcurve",
curve = find_key(ELLIPTIC_CURVES, curve) curve = find_key(ELLIPTIC_CURVES, curve)
} }
local size = ret.curve_params.curve:match("(%d+)[rk]%d$") local _
if size then _, strength = named_group_info(ret.curve_params.curve)
strength = tonumber(size)
elseif ret.curve_params.curve == "ecdh_x25519" then
strength = 256
elseif ret.curve_params.curve == "ecdh_x448" then
strength = 448
end
end end
ret.public, pos = unpack("s1", blob, pos) ret.public, pos = unpack("s1", blob, pos)
return pos, ret, strength return pos, ret, strength
@@ -1064,7 +1189,51 @@ KEX_ALGORITHMS.KRB5_EXPORT={
export=true, 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 --- Get info about a cipher suite
-- --
-- Returned table has "kex", "cipher", "mode", "size", and -- Returned table has "kex", "cipher", "mode", "size", and
@@ -1076,7 +1245,6 @@ KEX_ALGORITHMS.KRB5_EXPORT={
function cipher_info (c) function cipher_info (c)
local info = cipher_info_cache[c] local info = cipher_info_cache[c]
if info then return info end if info then return info end
info = {}
local tokens = stringaux.strsplit("_", c) local tokens = stringaux.strsplit("_", c)
local i = 1 local i = 1
if tokens[i] ~= "TLS" and tokens[i] ~= "SSL" then 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 while tokens[i] and tokens[i] ~= "WITH" do
i = i + 1 i = i + 1
end 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 if tokens[i] and tokens[i] ~= "WITH" then
stdnse.debug2("cipher_info: Can't parse (no WITH): %s", c) stdnse.debug2("cipher_info: Can't parse (no WITH): %s", c)
@@ -1104,34 +1279,16 @@ function cipher_info (c)
end end
-- key size -- key size
if t == "3DES" then -- NIST SP 800-57 local tmp = algorithms[t]
info.size = 112 if tmp then
elseif t == "CHACHA20" then info.size = tmp.s
info.size = 256 info.block_size = tmp.b
elseif t == "IDEA" then end
info.size = 128 if info.size == nil then
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
i = i + 1 i = i + 1
info.size = tonumber(tokens[i]) info.size = tonumber(tokens[i])
end 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 -- stream ciphers don't have a mode
if info.cipher == "RC4" then if info.cipher == "RC4" then
info.mode = "stream" info.mode = "stream"
@@ -1158,15 +1315,28 @@ function cipher_info (c)
-- hash -- hash
if info.mode == "CCM" then if info.mode == "CCM" then
info.hash = "SHA256" info.hash = "SHA256"
else end
i = i + 1 i = i + 1
t = (tokens[i]):match("(.*)%-draft$") if i <= #tokens then
if t then if tokens[i] == "8" and info.mode == "CCM" then
info.draft = true info.mode = "CCM_8"
else i = i + 1
t = tokens[i] 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 end
info.hash = t
end end
cipher_info_cache[c] = info cipher_info_cache[c] = info
@@ -1203,6 +1373,22 @@ handshake_parse = {
b["cipher"] = find_key(CIPHERS, b["cipher"]) b["cipher"] = find_key(CIPHERS, b["cipher"])
b["compressor"] = find_key(COMPRESSORS, b["compressor"]) 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 return b, j
end, end,
@@ -1395,6 +1581,27 @@ function record_read(buffer, i, fragment)
return j, h, true return j, h, true
end 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 -- Build a SSL/TLS record
-- @param type The type of record ("handshake", "change_cipher_spec", etc.) -- @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. -- Set the header as a handshake.
pack("B", TLS_CONTENTTYPE_REGISTRY[type]), pack("B", TLS_CONTENTTYPE_REGISTRY[type]),
-- Set the protocol. -- Set the protocol.
pack(">I2", PROTOCOLS[protocol]), pack(">I2", legacy_version(PROTOCOLS[protocol])),
-- Set the length of the header body. -- Set the length of the header body.
pack(">s2", b) pack(">s2", b)
}) })
@@ -1435,6 +1642,26 @@ do
} }
DEFAULT_SIGALGS = EXTENSION_HELPERS["signature_algorithms"](sigalgs) DEFAULT_SIGALGS = EXTENSION_HELPERS["signature_algorithms"](sigalgs)
end 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 -- Build a client_hello message
@@ -1459,10 +1686,11 @@ function client_hello(t)
-- Set the protocol. -- Set the protocol.
local protocol = t["protocol"] or HIGHEST_PROTOCOL local protocol = t["protocol"] or HIGHEST_PROTOCOL
table.insert(b, pack(">I2 I4", table.insert(b, pack(">I2 I4",
PROTOCOLS[protocol], legacy_version(PROTOCOLS[protocol]),
-- Set the random data. -- Set the random data.
os.time() os.time()
)) ))
local record_proto = t.record_protocol
-- Set the random data. -- Set the random data.
table.insert(b, rand.random_string(28)) table.insert(b, rand.random_string(28))
@@ -1471,11 +1699,24 @@ function client_hello(t)
local sid = t["session_id"] or "" local sid = t["session_id"] or ""
table.insert(b, pack(">s1", sid)) table.insert(b, pack(">s1", sid))
local eccpwd = false
local shangmi = false
-- Cipher suites. -- Cipher suites.
ciphers = {} ciphers = {}
-- Add specified 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 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] cipher = CIPHERS[cipher] or SCSVS[cipher]
end end
if type(cipher) == "number" and cipher >= 0 and cipher <= 0xffff then 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))) table.insert(b, pack("s1", table.concat(compressors)))
-- TLS extensions -- TLS extensions
if PROTOCOLS[protocol] and protocol ~= "SSLv3" then local proto_ver = PROTOCOLS[protocol]
if proto_ver and protocol ~= "SSLv3" then
local extensions = {} local extensions = {}
if t["extensions"] ~= nil then -- TLSv1.3 requires supported_versions and key_share extensions
-- Do we need to add the signature_algorithms extension? -- OpenSSL also appears to want supported_groups in some cases?
local need_sigalg = (protocol == "TLSv1.2") local need_supported_versions = (proto_ver >= PROTOCOLS["TLSv1.3"])
-- Add specified extensions. 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 for extension, data in pairs(t["extensions"]) do
if type(extension) == "number" then if type(extension) == "number" then
table.insert(extensions, pack(">I2", extension)) table.insert(extensions, pack(">I2", extension))
else else
if extension == "signature_algorithms" then if extension == "signature_algorithms" or extension == "signature_algorithms_13" then
need_sigalg = false 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 end
table.insert(extensions, pack(">I2", EXTENSIONS[extension])) table.insert(extensions, pack(">I2", EXTENSIONS[extension]))
end end
table.insert(extensions, pack(">s2", data)) table.insert(extensions, pack(">s2", data))
end end
if need_sigalg then end
table.insert(extensions, pack(">I2", EXTENSIONS["signature_algorithms"])) if need_supported_versions then
table.insert(extensions, pack(">s2", DEFAULT_SIGALGS)) 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 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 end
-- Extensions are optional -- Extensions are optional
if #extensions ~= 0 then 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 -- 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 -- explicitly tries to set SSLv3.0 somewhere (t.record_protocol or
-- t.protocol) -- t.protocol)
local record_proto = t.record_protocol
if not record_proto then if not record_proto then
record_proto = (t.protocol == "SSLv3") and "SSLv3" or "TLSv1.0" 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 end
return record_write("handshake", record_proto, table.concat(h)) return record_write("handshake", record_proto, table.concat(h))
end end
@@ -1624,4 +1918,12 @@ function servername(host)
end end
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; return _ENV;

View File

@@ -75,7 +75,7 @@ local client_hello = function(host, port)
status, err = sock:connect(host, port) status, err = sock:connect(host, port)
if not status then if not status then
sock:close() sock:close()
stdnse.debug("Can't send: %s", err) stdnse.debug("Can't connect: %s", err)
return false return false
end end
else else
@@ -86,23 +86,24 @@ local client_hello = function(host, port)
end end
-- Send Client Hello to the target server repeat -- only once
status, err = sock:send(cli_h) -- Send Client Hello to the target server
if not status then status, err = sock:send(cli_h)
stdnse.debug("Couldn't send: %s", err) if not status then
sock:close() stdnse.debug("Couldn't send: %s", err)
return false break
end end
-- Read response -- Read response
status, response, err = tls.record_buffer(sock) status, response, err = tls.record_buffer(sock)
if not status then if not status then
stdnse.debug("Couldn't receive: %s", err) stdnse.debug("Couldn't receive: %s", err)
sock:close() break
return false end
end until true
return true, response sock:close()
return status, response
end end
-- extract time from ServerHello response -- extract time from ServerHello response

View File

@@ -883,6 +883,11 @@ parameters.]],
} }
for protocol in pairs(tls.PROTOCOLS) do 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 -- Try anonymous DH ciphersuites
cipher, dhparams = get_dhe_params(host, port, protocol, dh_anons) cipher, dhparams = get_dhe_params(host, port, protocol, dh_anons)
-- Explicit test for false needed because nil just means no ciphers supported. -- Explicit test for false needed because nil just means no ciphers supported.

View File

@@ -400,7 +400,10 @@ local function try_params(host, port, t)
for j = 1, #record.body do -- no ipairs because we append below for j = 1, #record.body do -- no ipairs because we append below
local b = record.body[j] local b = record.body[j]
done = ((record.type == "alert" and b.level == "fatal") or 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) table.insert(records[record.type].body, b)
end end
if done then 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 if not kex_strength or not cipher_info.size then
return "unknown" return "unknown"
end end
if kex_strength == 0 then if kex_strength <= 0 then
return 0 return 0
elseif kex_strength < 512 then elseif kex_strength < 512 then
kex_score = 0.2 kex_score = 0.2
@@ -558,7 +561,7 @@ local function score_cipher (kex_strength, cipher_info)
kex_score = 1.0 kex_score = 1.0
end end
if cipher_info.size == 0 then if cipher_info.size <= 0 then
return 0 return 0
elseif cipher_info.size < 128 then elseif cipher_info.size < 128 then
cipher_score = 0.2 cipher_score = 0.2
@@ -589,14 +592,27 @@ local function letter_grade (score)
end end
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. -- Find which ciphers out of group are supported by the server.
local function find_ciphers_group(host, port, protocol, group, scores) local function find_ciphers_group(host, port, protocol, group, scores)
local results = {} local results = {}
local t = { local t = get_hello_table(host, protocol)
["protocol"] = protocol,
["record_protocol"] = protocol, -- improve chances of immediate rejection
["extensions"] = base_extensions(host),
}
-- This is a hacky sort of tristate variable. There are three conditions: -- 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 -- 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 local alert = records.alert
if alert then if alert then
ctx_log(2, protocol, "Got alert: %s", alert.body[1].description) 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) ctx_log(1, protocol, "Protocol mismatch (received %s)", alert.protocol)
-- Sometimes this is not an actual rejection of the protocol. Check specifically: -- Sometimes this is not an actual rejection of the protocol. Check specifically:
if get_body(alert, "description", "protocol_version") then if get_body(alert, "description", "protocol_version") then
protocol_worked = nil protocol_worked = nil
end end
break 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 protocol_worked = true
ctx_log(2, protocol, "%d ciphers rejected.", #group) ctx_log(2, protocol, "%d ciphers rejected.", #group)
break break
@@ -680,98 +697,97 @@ local function find_ciphers_group(host, port, protocol, group, scores)
elseif info.cipher == "RC4" then elseif info.cipher == "RC4" then
scores.warnings["Broken cipher RC4 is deprecated by RFC 7465"] = true scores.warnings["Broken cipher RC4 is deprecated by RFC 7465"] = true
end 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] local kex = tls.KEX_ALGORITHMS[info.kex]
scores.any_pfs_ciphers = kex.pfs or scores.any_pfs_ciphers scores.any_pfs_ciphers = kex.pfs or scores.any_pfs_ciphers
local extra, kex_strength local extra, kex_strength
if kex.anon then if kex.export then
kex_strength = 0 scores.warnings["Export key exchange"] = true
elseif kex.export then
if info.kex:find("1024$") then if info.kex:find("1024$") then
kex_strength = 1024 kex_strength = 1024
else else
kex_strength = 512 kex_strength = 512
end end
else end
if have_ssl and kex.pubkey then if kex.anon then
local certs = get_body(handshake, "type", "certificate") scores.warnings["Anonymous key exchange, score capped at F"] = true
-- Assume RFC compliance: kex_strength = 0
-- "The sender's certificate MUST come first in the list." elseif have_ssl and kex.pubkey then
-- This may not always be the case, so local certs = get_body(handshake, "type", "certificate")
-- TODO: reorder certificates and validate entire chain -- Assume RFC compliance:
-- TODO: certificate validation (date, self-signed, etc) -- "The sender's certificate MUST come first in the list."
local c, err -- This may not always be the case, so
if certs == nil then -- TODO: reorder certificates and validate entire chain
err = "no certificate message" -- TODO: certificate validation (date, self-signed, etc)
else local c, err
c, err = sslcert.parse_ssl_certificate(certs.certificates[1]) 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 end
if not c then local rsa_bits = tls.rsa_equiv(kex.pubkey, c.pubkey.bits)
stdnse.debug1("Failed to parse certificate: %s", err) kex_strength = math.min(kex_strength or rsa_bits, rsa_bits)
elseif c.pubkey.type == kex.pubkey then if c.pubkey.exponent then
local sigalg = c.sig_algorithm:match("([mM][dD][245])") if openssl.bignum_bn2dec(c.pubkey.exponent) == "1" then
if sigalg then
-- MD2 and MD5 are broken
kex_strength = 0 kex_strength = 0
scores.warnings["Insecure certificate signature: " .. string.upper(sigalg)] = true scores.warnings["Certificate RSA exponent is 1, score capped at F"] = 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
end end
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 end
local ske = get_body(handshake, "type", "server_key_exchange") end
if kex.server_key_exchange and ske then local ske
local kex_info = kex.server_key_exchange(ske.data, protocol) if protocol == "TLSv1.3" then
if kex_info.strength then ske = server_hello.extensions.key_share
local rsa_bits = tls.rsa_equiv(kex.type, kex_info.strength) elseif kex.server_key_exchange then
local low_strength_warning = false ske = get_body(handshake, "type", "server_key_exchange")
if kex_strength and kex_strength > rsa_bits then if ske then
kex_strength = rsa_bits ske = ske.data
low_strength_warning = true end
end end
kex_strength = kex_strength or rsa_bits if ske then
if kex_info.ecdhparams then local kex_info = kex.server_key_exchange(ske, protocol)
if kex_info.ecdhparams.curve_params.ec_curve_type == "namedcurve" then if kex_info.strength then
extra = kex_info.ecdhparams.curve_params.curve local kex_type = kex_info.type or kex.type
else if kex_info.ecdhparams then
extra = string.format("%s %d", kex_info.ecdhparams.curve_params.ec_curve_type, kex_info.strength) if kex_info.ecdhparams.curve_params.ec_curve_type == "namedcurve" then
end extra = kex_info.ecdhparams.curve_params.curve
else else
extra = string.format("%s %d", kex.type, kex_info.strength) extra = string.format("%s %d", kex_info.ecdhparams.curve_params.ec_curve_type, kex_info.strength)
end
if low_strength_warning then
scores.warnings[(
"Key exchange (%s) of lower strength than certificate key"
):format(extra)] = true
end end
else
extra = string.format("%s %d", kex_type, kex_info.strength)
end end
if kex_info.rsa and kex_info.rsa.exponent == 1 then local rsa_bits = tls.rsa_equiv(kex_type, kex_info.strength)
kex_strength = 0 if kex_strength and kex_strength > rsa_bits then
scores.warnings["Certificate RSA exponent is 1, score capped at F"] = true kex_strength = rsa_bits
scores.warnings[(
"Key exchange (%s) of lower strength than certificate key"
):format(extra)] = true
end 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
end end
scores[name] = { scores[name] = {
@@ -791,11 +807,8 @@ end
local function get_chunk_size(host, protocol) local function get_chunk_size(host, protocol)
-- Try to make sure we don't send too big of a handshake -- Try to make sure we don't send too big of a handshake
-- https://github.com/ssllabs/research/wiki/Long-Handshake-Intolerance -- https://github.com/ssllabs/research/wiki/Long-Handshake-Intolerance
local len_t = { local len_t = get_hello_table(host, protocol)
protocol = protocol, len_t.ciphers = {}
ciphers = {},
extensions = base_extensions(host),
}
local cipher_len_remaining = 255 - #tls.client_hello(len_t) local cipher_len_remaining = 255 - #tls.client_hello(len_t)
-- if we're over 255 anyway, just go for it. -- if we're over 255 anyway, just go for it.
-- Each cipher adds 2 bytes -- Each cipher adds 2 bytes
@@ -809,7 +822,17 @@ end
-- each chunk. -- each chunk.
local function find_ciphers(host, port, protocol) 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 results = {}
local scores = {warnings={}} local scores = {warnings={}}
@@ -830,11 +853,8 @@ end
local function find_compressors(host, port, protocol, good_ciphers) local function find_compressors(host, port, protocol, good_ciphers)
local compressors = sorted_keys(tls.COMPRESSORS) local compressors = sorted_keys(tls.COMPRESSORS)
local t = { local t = get_hello_table(host, protocol)
["protocol"] = protocol, t.ciphers = good_ciphers
["ciphers"] = good_ciphers,
["extensions"] = base_extensions(host),
}
local results = {} local results = {}
@@ -852,7 +872,7 @@ local function find_compressors(host, port, protocol, good_ciphers)
local alert = records.alert local alert = records.alert
if alert then if alert then
ctx_log(2, protocol, "Got alert: %s", alert.body[1].description) 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.") ctx_log(1, protocol, "Protocol rejected.")
protocol_worked = nil protocol_worked = nil
break break
@@ -908,11 +928,8 @@ end
-- Offer two ciphers and return the one chosen by the server. Returns nil and -- Offer two ciphers and return the one chosen by the server. Returns nil and
-- an error message in case of a server error. -- an error message in case of a server error.
local function compare_ciphers(host, port, protocol, cipher_a, cipher_b) local function compare_ciphers(host, port, protocol, cipher_a, cipher_b)
local t = { local t = get_hello_table(host, protocol)
["protocol"] = protocol, t.ciphers = {cipher_a, cipher_b}
["ciphers"] = {cipher_a, cipher_b},
["extensions"] = base_extensions(host),
}
local records = try_params(host, port, t) local records = try_params(host, port, t)
local server_hello = records.handshake and get_body(records.handshake, "type", "server_hello") local server_hello = records.handshake and get_body(records.handshake, "type", "server_hello")
if server_hello then if server_hello then
@@ -1010,14 +1027,18 @@ local function try_protocol(host, port, protocol, upresults)
end end
-- Find all valid compression methods. -- Find all valid compression methods.
local compressors local compressors
-- Reduce chunk size by 1 to allow extra room for the extra compressors (2 bytes) -- RFC 8446: "For every TLS 1.3 ClientHello, this vector MUST contain exactly
for _, c in ipairs(in_chunks(ciphers, get_chunk_size(host, protocol) - 1)) do -- one byte, set to zero"
compressors = find_compressors(host, port, protocol, c) if (tls.PROTOCOLS[protocol] < tls13proto) then
-- I observed a weird interaction between ECDSA ciphers and DEFLATE compression. -- Reduce chunk size by 1 to allow extra room for the extra compressors (2 bytes)
-- Some servers would reject the handshake if no non-ECDSA ciphers were available. for _, c in ipairs(in_chunks(ciphers, get_chunk_size(host, protocol) - 1)) do
-- Sending 64 ciphers at a time should be sufficient, but we'll try them all if necessary. compressors = find_compressors(host, port, protocol, c)
if compressors and #compressors ~= 0 then -- I observed a weird interaction between ECDSA ciphers and DEFLATE compression.
break -- 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
end end

View File

@@ -43,6 +43,7 @@ license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = { "vuln", "safe" } categories = { "vuln", "safe" }
dependencies = {"https-redirect"} 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'} local arg_protocols = stdnse.get_script_args(SCRIPT_NAME .. ".protocols") or {'TLSv1.0', 'TLSv1.1', 'TLSv1.2'}
portrule = function(host, port) portrule = function(host, port)

View File

@@ -48,6 +48,7 @@ your TLS ciphersuites.
-- | https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-3566 -- | https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-3566
-- |_ https://www.openssl.org/~bodo/ssl-poodle.pdf -- |_ https://www.openssl.org/~bodo/ssl-poodle.pdf
-- --
-- @see ssl-enum-ciphers.nse
author = "Daniel Miller" author = "Daniel Miller"

View File

@@ -56,10 +56,13 @@ local client_hello = function(host, port, protos)
local sock, status, response, err, cli_h local sock, status, response, err, cli_h
cli_h = tls.client_hello({ cli_h = tls.client_hello({
["extensions"] = { -- TLSv1.3 does not send this extension plaintext.
[ALPN_NAME] = tls.EXTENSION_HELPERS[ALPN_NAME](protos) -- 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 -- Connect to the target server
local status, err local status, err

View File

@@ -58,6 +58,9 @@ local client_hello = function(host, port)
local sock, status, response, err, cli_h local sock, status, response, err, cli_h
cli_h = tls.client_hello({ 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"] = { ["extensions"] = {
["next_protocol_negotiation"] = "", ["next_protocol_negotiation"] = "",
}, },