Added policy support for optional host key types, like certificates and smart card-based types.

This commit is contained in:
Joe Testa 2020-07-15 14:32:14 -04:00
parent 17f5eb0b38
commit 68a420ff00
7 changed files with 30 additions and 10 deletions

View File

@ -2,7 +2,7 @@
# Official policy for hardened OpenSSH on Ubuntu Server 16.04 LTS. # Official policy for hardened OpenSSH on Ubuntu Server 16.04 LTS.
# #
name = "Ubuntu Server 16.04 LTS" name = "Hardened Ubuntu Server 16.04 LTS"
version = 1 version = 1
# Group exchange DH modulus sizes. # Group exchange DH modulus sizes.
@ -11,6 +11,9 @@ dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
# The host key types that must match exactly (order matters). # The host key types that must match exactly (order matters).
host keys = ssh-ed25519 host keys = ssh-ed25519
# Host key types that may optionally appear.
optional host keys = sk-ssh-ed25519@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com
# The key exchange algorithms that must match exactly (order matters). # The key exchange algorithms that must match exactly (order matters).
key exchanges = curve25519-sha256@libssh.org, diffie-hellman-group-exchange-sha256 key exchanges = curve25519-sha256@libssh.org, diffie-hellman-group-exchange-sha256

View File

@ -2,7 +2,7 @@
# Official policy for hardened OpenSSH on Ubuntu Server 18.04 LTS. # Official policy for hardened OpenSSH on Ubuntu Server 18.04 LTS.
# #
name = "Ubuntu Server 18.04 LTS" name = "Hardened Ubuntu Server 18.04 LTS"
version = 1 version = 1
# Group exchange DH modulus sizes. # Group exchange DH modulus sizes.
@ -11,6 +11,9 @@ dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
# The host key types that must match exactly (order matters). # The host key types that must match exactly (order matters).
host keys = ssh-ed25519 host keys = ssh-ed25519
# Host key types that may optionally appear.
optional host keys = sk-ssh-ed25519@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com
# The key exchange algorithms that must match exactly (order matters). # The key exchange algorithms that must match exactly (order matters).
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256 key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256

View File

@ -2,7 +2,7 @@
# Official policy for hardened OpenSSH on Ubuntu Server 20.04 LTS. # Official policy for hardened OpenSSH on Ubuntu Server 20.04 LTS.
# #
name = "Ubuntu Server 20.04 LTS" name = "Hardened Ubuntu Server 20.04 LTS"
version = 1 version = 1
# RSA host key sizes. # RSA host key sizes.
@ -15,6 +15,9 @@ dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048
# The host key types that must match exactly (order matters). # The host key types that must match exactly (order matters).
host keys = rsa-sha2-512, rsa-sha2-256, ssh-ed25519 host keys = rsa-sha2-512, rsa-sha2-256, ssh-ed25519
# Host key types that may optionally appear.
optional host keys = sk-ssh-ed25519@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com
# The key exchange algorithms that must match exactly (order matters). # The key exchange algorithms that must match exactly (order matters).
key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256 key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256

View File

@ -101,6 +101,7 @@ class Policy:
self._banner = None # type: Optional[str] self._banner = None # type: Optional[str]
self._compressions = None # type: Optional[List[str]] self._compressions = None # type: Optional[List[str]]
self._host_keys = None # type: Optional[List[str]] self._host_keys = None # type: Optional[List[str]]
self._optional_host_keys = None # type: Optional[List[str]]
self._kex = None # type: Optional[List[str]] self._kex = None # type: Optional[List[str]]
self._ciphers = None # type: Optional[List[str]] self._ciphers = None # type: Optional[List[str]]
self._macs = None # type: Optional[List[str]] self._macs = None # type: Optional[List[str]]
@ -137,7 +138,7 @@ class Policy:
key = key.strip() key = key.strip()
val = val.strip() val = val.strip()
if key not in ['name', 'version', 'banner', 'compressions', 'host keys', 'key exchanges', 'ciphers', 'macs', 'client policy'] and not key.startswith('hostkey_size_') and not key.startswith('cakey_size_') and not key.startswith('dh_modulus_size_'): if key not in ['name', 'version', 'banner', 'compressions', 'host keys', 'optional host keys', 'key exchanges', 'ciphers', 'macs', 'client policy'] and not key.startswith('hostkey_size_') and not key.startswith('cakey_size_') and not key.startswith('dh_modulus_size_'):
raise ValueError("invalid field found in policy: %s" % line) raise ValueError("invalid field found in policy: %s" % line)
if key in ['name', 'banner']: if key in ['name', 'banner']:
@ -158,7 +159,7 @@ class Policy:
self._banner = val self._banner = val
elif key == 'version': elif key == 'version':
self._version = val self._version = val
elif key in ['compressions', 'host keys', 'key exchanges', 'ciphers', 'macs']: elif key in ['compressions', 'host keys', 'optional host keys', 'key exchanges', 'ciphers', 'macs']:
try: try:
algs = val.split(',') algs = val.split(',')
except ValueError: except ValueError:
@ -172,6 +173,8 @@ class Policy:
self._compressions = algs self._compressions = algs
elif key == 'host keys': elif key == 'host keys':
self._host_keys = algs self._host_keys = algs
elif key == 'optional host keys':
self._optional_host_keys = algs
elif key == 'key exchanges': elif key == 'key exchanges':
self._kex = algs self._kex = algs
elif key == 'ciphers': elif key == 'ciphers':
@ -273,6 +276,9 @@ version = 1
# The host key types that must match exactly (order matters). # The host key types that must match exactly (order matters).
host keys = %s host keys = %s
# Host key types that may optionally appear.
#optional host keys = ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com
# The key exchange algorithms that must match exactly (order matters). # The key exchange algorithms that must match exactly (order matters).
key exchanges = %s key exchanges = %s
@ -305,9 +311,14 @@ macs = %s
ret = False ret = False
errors.append('Compression types did not match. Expected: %s; Actual: %s' % (self._compressions, kex.server.compression)) errors.append('Compression types did not match. Expected: %s; Actual: %s' % (self._compressions, kex.server.compression))
if (self._host_keys is not None) and (kex.key_algorithms != self._host_keys): # If a list of optional host keys was given in the policy, remove any of its entries from the list retrieved from the server. This allows us to do an exact comparison with the expected list below.
pruned_host_keys = kex.key_algorithms
if self._optional_host_keys is not None:
pruned_host_keys = [x for x in kex.key_algorithms if x not in self._optional_host_keys]
if (self._host_keys is not None) and (pruned_host_keys != self._host_keys):
ret = False ret = False
errors.append('Host key types did not match. Expected: %s; Actual: %s' % (self._host_keys, kex.key_algorithms)) errors.append('Host key types did not match. Expected (required): %s; Expected (optional): %s; Actual: %s' % (self._host_keys, self._optional_host_keys, kex.key_algorithms))
if self._hostkey_sizes is not None: if self._hostkey_sizes is not None:
hostkey_types = list(self._hostkey_sizes.keys()) hostkey_types = list(self._hostkey_sizes.keys())

View File

@ -1 +1 @@
{"errors": ["Host key types did not match. Expected: ['ssh-rsa', 'ssh-dss', 'key_alg1']; Actual: ['ssh-rsa', 'ssh-dss']"], "host": "localhost", "passed": false, "policy": "Docker policy: test3 (version 1)"} {"errors": ["Host key types did not match. Expected (required): ['ssh-rsa', 'ssh-dss', 'key_alg1']; Expected (optional): None; Actual: ['ssh-rsa', 'ssh-dss']"], "host": "localhost", "passed": false, "policy": "Docker policy: test3 (version 1)"}

View File

@ -3,4 +3,4 @@ Policy: Docker policy: test3 (version 1)
Result: ❌ Failed! Result: ❌ Failed!
 
Errors: Errors:
* Host key types did not match. Expected: ['ssh-rsa', 'ssh-dss', 'key_alg1']; Actual: ['ssh-rsa', 'ssh-dss'] * Host key types did not match. Expected (required): ['ssh-rsa', 'ssh-dss', 'key_alg1']; Expected (optional): None; Actual: ['ssh-rsa', 'ssh-dss']

View File

@ -192,7 +192,7 @@ macs = mac_alg1, mac_alg2, mac_alg3'''
pol_data = pol_data.replace(date.today().strftime('%Y/%m/%d'), '[todays date]') pol_data = pol_data.replace(date.today().strftime('%Y/%m/%d'), '[todays date]')
# Instead of writing out the entire expected policy--line by line--just check that it has the expected hash. # Instead of writing out the entire expected policy--line by line--just check that it has the expected hash.
assert hashlib.sha256(pol_data.encode('ascii')).hexdigest() == '1765f236d765b1741e7006f601babf0a8e1628326341a3a00b1026c7f85f48ce' assert hashlib.sha256(pol_data.encode('ascii')).hexdigest() == '4af7777fb57a1dad0cf438c899a11d4f625fd9276ea3bb5ef5c9fe8806cb47dc'
def test_policy_evaluate_passing_1(self): def test_policy_evaluate_passing_1(self):