mirror of
https://github.com/jtesta/ssh-audit.git
synced 2025-09-03 18:38:32 +02:00
Added support for mixed host key/CA key types (i.e.: RSA host keys signed by ED25519 CAs) (#120).
This commit is contained in:
@ -28,7 +28,7 @@ from typing import Callable, Optional, Union, Any # noqa: F401
|
||||
|
||||
import traceback
|
||||
|
||||
from ssh_audit.kexdh import KexDH, KexGroup1, KexGroup14_SHA1, KexGroup14_SHA256, KexCurve25519_SHA256, KexGroup16_SHA512, KexGroup18_SHA512, KexGroupExchange_SHA1, KexGroupExchange_SHA256, KexNISTP256, KexNISTP384, KexNISTP521
|
||||
from ssh_audit.kexdh import KexDH, KexDHException, KexGroup1, KexGroup14_SHA1, KexGroup14_SHA256, KexCurve25519_SHA256, KexGroup16_SHA512, KexGroup18_SHA512, KexGroupExchange_SHA1, KexGroupExchange_SHA256, KexNISTP256, KexNISTP384, KexNISTP521
|
||||
from ssh_audit.ssh2_kex import SSH2_Kex
|
||||
from ssh_audit.ssh2_kexdb import SSH2_KexDB
|
||||
from ssh_audit.ssh_socket import SSH_Socket
|
||||
@ -55,6 +55,7 @@ class HostKeyTest:
|
||||
}
|
||||
|
||||
TWO2K_MODULUS_WARNING = '2048-bit modulus only provides 112-bits of symmetric strength'
|
||||
SMALL_ECC_MODULUS_WARNING = '224-bit ECC modulus only provides 112-bits of symmetric strength'
|
||||
|
||||
|
||||
@staticmethod
|
||||
@ -82,7 +83,7 @@ class HostKeyTest:
|
||||
for server_kex_alg in server_kex.kex_algorithms:
|
||||
if server_kex_alg in KEX_TO_DHGROUP:
|
||||
kex_str = server_kex_alg
|
||||
kex_group = KEX_TO_DHGROUP[kex_str]()
|
||||
kex_group = KEX_TO_DHGROUP[kex_str](out)
|
||||
break
|
||||
|
||||
if kex_str is not None and kex_group is not None:
|
||||
@ -110,7 +111,6 @@ class HostKeyTest:
|
||||
out.d('Preparing to obtain ' + host_key_type + ' host key...', write_now=True)
|
||||
|
||||
cert = host_key_types[host_key_type]['cert']
|
||||
variable_key_len = host_key_types[host_key_type]['variable_key_len']
|
||||
|
||||
# If the connection is closed, re-open it and get the kex again.
|
||||
if not s.is_connected():
|
||||
@ -131,7 +131,7 @@ class HostKeyTest:
|
||||
try:
|
||||
# Parse the server's KEX.
|
||||
_, payload = s.read_packet()
|
||||
SSH2_Kex.parse(payload)
|
||||
SSH2_Kex.parse(out, payload)
|
||||
except Exception:
|
||||
out.v("Failed to parse server's kex. Stack trace:\n%s" % str(traceback.format_exc()), write_now=True)
|
||||
return
|
||||
@ -139,15 +139,29 @@ class HostKeyTest:
|
||||
# Do the initial DH exchange. The server responds back
|
||||
# with the host key and its length. Bingo. We also get back the host key fingerprint.
|
||||
kex_group.send_init(s)
|
||||
raw_hostkey_bytes = b''
|
||||
try:
|
||||
host_key = kex_group.recv_reply(s, variable_key_len)
|
||||
if host_key is not None:
|
||||
server_kex.set_host_key(host_key_type, host_key)
|
||||
except Exception:
|
||||
pass
|
||||
kex_reply = kex_group.recv_reply(s)
|
||||
raw_hostkey_bytes = kex_reply if kex_reply is not None else b''
|
||||
except KexDHException:
|
||||
out.v("Failed to parse server's host key. Stack trace:\n%s" % str(traceback.format_exc()), write_now=True)
|
||||
|
||||
# Since parsing this host key failed, there's nothing more to do but close the socket and move on to the next host key type.
|
||||
s.close()
|
||||
continue
|
||||
|
||||
hostkey_modulus_size = kex_group.get_hostkey_size()
|
||||
ca_key_type = kex_group.get_ca_type()
|
||||
ca_modulus_size = kex_group.get_ca_size()
|
||||
out.d("Hostkey type: [%s]; hostkey size: %u; CA type: [%s]; CA modulus size: %u" % (host_key_type, hostkey_modulus_size, ca_key_type, ca_modulus_size), write_now=True)
|
||||
|
||||
# Record all the host key info.
|
||||
server_kex.set_host_key(host_key_type, raw_hostkey_bytes, hostkey_modulus_size, ca_key_type, ca_modulus_size)
|
||||
|
||||
# Set the hostkey size for all RSA key types since 'ssh-rsa', 'rsa-sha2-256', etc. are all using the same host key. Note, however, that this may change in the future.
|
||||
if cert is False and host_key_type in HostKeyTest.RSA_FAMILY:
|
||||
for rsa_type in HostKeyTest.RSA_FAMILY:
|
||||
server_kex.set_host_key(rsa_type, raw_hostkey_bytes, hostkey_modulus_size, ca_key_type, ca_modulus_size)
|
||||
|
||||
# Close the socket, as the connection has
|
||||
# been put in a state that later tests can't use.
|
||||
@ -155,43 +169,53 @@ class HostKeyTest:
|
||||
|
||||
# If the host key modulus or CA modulus was successfully parsed, check to see that its a safe size.
|
||||
if hostkey_modulus_size > 0 or ca_modulus_size > 0:
|
||||
# Set the hostkey size for all RSA key types since 'ssh-rsa',
|
||||
# 'rsa-sha2-256', etc. are all using the same host key.
|
||||
# Note, however, that this may change in the future.
|
||||
if cert is False and host_key_type in HostKeyTest.RSA_FAMILY:
|
||||
for rsa_type in HostKeyTest.RSA_FAMILY:
|
||||
server_kex.set_rsa_key_size(rsa_type, hostkey_modulus_size)
|
||||
elif cert is True:
|
||||
server_kex.set_rsa_key_size(host_key_type, hostkey_modulus_size, ca_modulus_size)
|
||||
# The minimum good modulus size for RSA host keys is 3072. However, since ECC cryptosystems are fundamentally different, the minimum good is 256.
|
||||
hostkey_min_good = cakey_min_good = 3072
|
||||
hostkey_min_warn = cakey_min_warn = 2048
|
||||
hostkey_warn_str = cakey_warn_str = HostKeyTest.TWO2K_MODULUS_WARNING
|
||||
if host_key_type.startswith('ssh-ed25519') or host_key_type.startswith('ecdsa-sha2-nistp'):
|
||||
hostkey_min_good = 256
|
||||
hostkey_min_warn = 224
|
||||
hostkey_warn_str = HostKeyTest.SMALL_ECC_MODULUS_WARNING
|
||||
if ca_key_type.startswith('ssh-ed25519') or host_key_type.startswith('ecdsa-sha2-nistp'):
|
||||
cakey_min_good = 256
|
||||
cakey_min_warn = 224
|
||||
cakey_warn_str = HostKeyTest.SMALL_ECC_MODULUS_WARNING
|
||||
|
||||
# Keys smaller than 2048 result in a failure. Keys smaller 3072 result in a warning. Update the database accordingly.
|
||||
if (cert is False) and (hostkey_modulus_size < 3072):
|
||||
for rsa_type in HostKeyTest.RSA_FAMILY:
|
||||
alg_list = SSH2_KexDB.ALGORITHMS['key'][rsa_type]
|
||||
|
||||
# Ensure that failure & warning lists exist.
|
||||
while len(alg_list) < 3:
|
||||
alg_list.append([])
|
||||
|
||||
# If the key is under 2048, add to the failure list.
|
||||
if hostkey_modulus_size < 2048:
|
||||
alg_list[1].append('using small %d-bit modulus' % hostkey_modulus_size)
|
||||
elif HostKeyTest.TWO2K_MODULUS_WARNING not in alg_list[2]: # Issue a warning about 2048-bit moduli.
|
||||
alg_list[2].append(HostKeyTest.TWO2K_MODULUS_WARNING)
|
||||
|
||||
elif (cert is True) and ((hostkey_modulus_size < 3072) or (ca_modulus_size > 0 and ca_modulus_size < 3072)): # pylint: disable=chained-comparison
|
||||
if (cert is False) and (hostkey_modulus_size < hostkey_min_good):
|
||||
alg_list = SSH2_KexDB.ALGORITHMS['key'][host_key_type]
|
||||
min_modulus = min(hostkey_modulus_size, ca_modulus_size)
|
||||
min_modulus = min_modulus if min_modulus > 0 else max(hostkey_modulus_size, ca_modulus_size)
|
||||
|
||||
# Ensure that failure & warning lists exist.
|
||||
while len(alg_list) < 3:
|
||||
alg_list.append([])
|
||||
|
||||
if (hostkey_modulus_size < 2048) or (ca_modulus_size > 0 and ca_modulus_size < 2048): # pylint: disable=chained-comparison
|
||||
alg_list[1].append('using small %d-bit modulus' % min_modulus)
|
||||
elif HostKeyTest.TWO2K_MODULUS_WARNING not in alg_list[2]:
|
||||
alg_list[2].append(HostKeyTest.TWO2K_MODULUS_WARNING)
|
||||
# If the key is under 2048, add to the failure list.
|
||||
if hostkey_modulus_size < hostkey_min_warn:
|
||||
alg_list[1].append('using small %d-bit modulus' % hostkey_modulus_size)
|
||||
elif hostkey_warn_str not in alg_list[2]: # Issue a warning about 2048-bit moduli.
|
||||
alg_list[2].append(hostkey_warn_str)
|
||||
|
||||
elif (cert is True) and ((hostkey_modulus_size < hostkey_min_good) or (0 < ca_modulus_size < cakey_min_good)):
|
||||
alg_list = SSH2_KexDB.ALGORITHMS['key'][host_key_type]
|
||||
|
||||
# Ensure that failure & warning lists exist.
|
||||
while len(alg_list) < 3:
|
||||
alg_list.append([])
|
||||
|
||||
# If the host key is smaller than 2048-bit/224-bit, flag this as a failure.
|
||||
if hostkey_modulus_size < hostkey_min_warn:
|
||||
alg_list[1].append('using small %d-bit hostkey modulus' % hostkey_modulus_size)
|
||||
# Otherwise, this is just a warning.
|
||||
elif (hostkey_modulus_size < hostkey_min_good) and (hostkey_warn_str not in alg_list[2]):
|
||||
alg_list[2].append(hostkey_warn_str)
|
||||
|
||||
# If the CA key is smaller than 2048-bit/224-bit, flag this as a failure.
|
||||
if 0 < ca_modulus_size < cakey_min_warn:
|
||||
alg_list[1].append('using small %d-bit CA key modulus' % ca_modulus_size)
|
||||
# Otherwise, this is just a warning.
|
||||
elif (0 < ca_modulus_size < cakey_min_good) and (cakey_warn_str not in alg_list[2]):
|
||||
alg_list[2].append(cakey_warn_str)
|
||||
|
||||
# If this host key type is in the RSA family, then mark them all as parsed (since results in one are valid for them all).
|
||||
if host_key_type in HostKeyTest.RSA_FAMILY:
|
||||
|
Reference in New Issue
Block a user