From 5bd925ffc60b077105900832496c7ff22be8b062 Mon Sep 17 00:00:00 2001 From: yannik1015 <34042618+yannik1015@users.noreply.github.com> Date: Mon, 18 Mar 2024 22:41:17 +0100 Subject: [PATCH 1/6] [WIP] Adding allowed algorithms (#252) * Added allowed policy fields Added allowed fields for host keys kex ciphers and macs * Adapted policy.py to newest dev version * Added allow_algorithm_subset_and_reordering flag * Removed allowed policy entries as they are redundant now * Fixed call to append_error --- src/ssh_audit/policy.py | 89 ++++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 24 deletions(-) diff --git a/src/ssh_audit/policy.py b/src/ssh_audit/policy.py index 8e73360..591feec 100644 --- a/src/ssh_audit/policy.py +++ b/src/ssh_audit/policy.py @@ -54,6 +54,7 @@ class Policy: self._hostkey_sizes: Optional[Dict[str, Dict[str, Union[int, str, bytes]]]] = None self._dh_modulus_sizes: Optional[Dict[str, int]] = None self._server_policy = True + self._allow_algorithm_subset_and_reordering = False self._name_and_version: str = '' @@ -112,7 +113,7 @@ class Policy: key = key.strip() val = val.strip() - if key not in ['name', 'version', 'banner', 'compressions', 'host keys', 'optional host keys', 'key exchanges', 'ciphers', 'macs', 'client policy', 'host_key_sizes', 'dh_modulus_sizes'] 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', 'host_key_sizes', 'dh_modulus_sizes', 'allow_algorithm_subset_and_reordering'] 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) if key in ['name', 'banner']: @@ -150,7 +151,7 @@ class Policy: elif key == 'host keys': self._host_keys = algs elif key == 'optional host keys': - self._optional_host_keys = algs + self._optional_host_keys = algs elif key == 'key exchanges': self._kex = algs elif key == 'ciphers': @@ -205,7 +206,8 @@ class Policy: elif key.startswith('client policy') and val.lower() == 'true': self._server_policy = False - + elif key == 'allow_algorithm_subset_and_reordering' and val.lower() == 'true': + self._allow_algorithm_subset_and_reordering = True if self._name is None: raise ValueError('The policy does not have a name field.') @@ -332,7 +334,7 @@ macs = %s # All subsequent tests require a valid kex, so end here if we don't have one. if kex is None: - return ret, errors, self._get_error_str(errors) + return ret, errors, self._get_error_str(errors, self._allow_algorithm_subset_and_reordering) if (self._compressions is not None) and (kex.server.compression != self._compressions): ret = False @@ -342,11 +344,20 @@ macs = %s 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] + + # Checking Hostkeys + if self._host_keys is not None: + if self._allow_algorithm_subset_and_reordering: + for hostkey_t in kex.key_algorithms: + if hostkey_t not in self._host_keys: + ret = False + self._append_error(errors, 'Host keys', self._host_keys, None, kex.key_algorithms) + break + elif pruned_host_keys != self._host_keys: + ret = False + self._append_error(errors, 'Host keys', self._host_keys, None, kex.key_algorithms) - if (self._host_keys is not None) and (pruned_host_keys != self._host_keys): - ret = False - self._append_error(errors, 'Host keys', self._host_keys, self._optional_host_keys, kex.key_algorithms) - + # Checking Host Key Sizes if self._hostkey_sizes is not None: hostkey_types = list(self._hostkey_sizes.keys()) hostkey_types.sort() # Sorted to make testing output repeatable. @@ -374,18 +385,42 @@ macs = %s elif actual_ca_key_size != expected_ca_key_size: ret = False self._append_error(errors, 'CA signature size (%s)' % actual_ca_key_type, [str(expected_ca_key_size)], None, [str(actual_ca_key_size)]) - - if kex.kex_algorithms != self._kex: - ret = False - self._append_error(errors, 'Key exchanges', self._kex, None, kex.kex_algorithms) - - if (self._ciphers is not None) and (kex.server.encryption != self._ciphers): - ret = False - self._append_error(errors, 'Ciphers', self._ciphers, None, kex.server.encryption) - - if (self._macs is not None) and (kex.server.mac != self._macs): - ret = False - self._append_error(errors, 'MACs', self._macs, None, kex.server.mac) + + # Checking KEX + if self._kex is not None: + if self._allow_algorithm_subset_and_reordering: + for kex_t in kex.kex_algorithms: + if kex_t not in self._kex: + ret = False + self._append_error(errors, 'Key exchanges', self._kex, None, kex.kex_algorithms) + break + elif kex.kex_algorithms != self._kex: # Requires perfect match + ret = False + self._append_error(errors, 'Key exchanges', self._kex, None, kex.kex_algorithms) + + # Checking Ciphers + if self._ciphers is not None: + if self._allow_algorithm_subset_and_reordering: + for cipher_t in kex.server.encryption: + if cipher_t not in self._ciphers: + ret = False + self._append_error(errors, 'Ciphers', self._ciphers, None, kex.server.encryption) + break + elif kex.server.encryption != self._ciphers: # Requires perfect match + ret = False + self._append_error(errors, 'Ciphers', self._ciphers, None, kex.server.encryption) + + # Checking MACs + if self._macs is not None: + if self._allow_algorithm_subset_and_reordering: + for mac_t in kex.server.mac: + if mac_t not in self._macs: + ret = False + self._append_error(errors, 'MACs', self._macs, None, kex.server.mac) + break + elif kex.server.mac != self._macs: # Requires perfect match + ret = False + self._append_error(errors, 'MACs', self._macs, None, kex.server.mac) if self._dh_modulus_sizes is not None: dh_modulus_types = list(self._dh_modulus_sizes.keys()) @@ -398,22 +433,28 @@ macs = %s ret = False self._append_error(errors, 'Group exchange (%s) modulus sizes' % dh_modulus_type, [str(expected_dh_modulus_size)], None, [str(actual_dh_modulus_size)]) - return ret, errors, self._get_error_str(errors) + return ret, errors, self._get_error_str(errors, self._allow_algorithm_subset_and_reordering) @staticmethod - def _get_error_str(errors: List[Any]) -> str: + def _get_error_str(errors: List[Any], allow_algorithm_subset_and_reordering: bool = False) -> str: '''Transforms an error struct to a flat string of error messages.''' + if allow_algorithm_subset_and_reordering: + expected_str = 'allowed' + else: + expected_str = 'required' + error_list = [] spacer = '' for e in errors: e_str = " * %s did not match.\n" % e['mismatched_field'] + if ('expected_optional' in e) and (e['expected_optional'] != ['']): - e_str += " - Expected (required): %s\n - Expected (optional): %s\n" % (Policy._normalize_error_field(e['expected_required']), Policy._normalize_error_field(e['expected_optional'])) + e_str += " - Expected (" + expected_str + "): %s\n - Expected (optional): %s\n" % (Policy._normalize_error_field(e['expected_required']), Policy._normalize_error_field(e['expected_optional'])) spacer = ' ' else: - e_str += " - Expected: %s\n" % Policy._normalize_error_field(e['expected_required']) + e_str += " - Expected (" + expected_str + "): %s\n" % Policy._normalize_error_field(e['expected_required']) spacer = ' ' e_str += " - Actual:%s%s\n" % (spacer, Policy._normalize_error_field(e['actual'])) error_list.append(e_str) From 3c31934ac7b5ab816bb31640cd34f57b1c2f9b00 Mon Sep 17 00:00:00 2001 From: Joe Testa Date: Mon, 18 Mar 2024 17:48:50 -0400 Subject: [PATCH 2/6] Added tests and other cleanups resulting from merging PR #252. --- README.md | 1 + docker_test.sh | 6 ++ src/ssh_audit/policy.py | 102 ++++++++++-------- .../openssh_8.0p1_custom_policy_test15.json | 6 ++ .../openssh_8.0p1_custom_policy_test15.txt | 3 + .../openssh_8.0p1_custom_policy_test16.json | 86 +++++++++++++++ .../openssh_8.0p1_custom_policy_test16.txt | 21 ++++ test/docker/policies/policy_test15.txt | 13 +++ test/docker/policies/policy_test16.txt | 13 +++ test/test_policy.py | 97 ++++++++++++++++- 10 files changed, 302 insertions(+), 46 deletions(-) create mode 100644 test/docker/expected_results/openssh_8.0p1_custom_policy_test15.json create mode 100644 test/docker/expected_results/openssh_8.0p1_custom_policy_test15.txt create mode 100644 test/docker/expected_results/openssh_8.0p1_custom_policy_test16.json create mode 100644 test/docker/expected_results/openssh_8.0p1_custom_policy_test16.txt create mode 100644 test/docker/policies/policy_test15.txt create mode 100644 test/docker/policies/policy_test16.txt diff --git a/README.md b/README.md index 2fd7ac3..0047412 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,7 @@ For convenience, a web front-end on top of the command-line tool is available at - The built-in man page (`-m`, `--manual`) is now available on Docker, PyPI, and Snap builds, in addition to the Windows build. - Snap builds are now architecture-independent. - Changed Docker base image from `python:3-slim` to `python:3-alpine`, resulting in a 59% reduction in image size; credit [Daniel Thamdrup](https://github.com/dallemon). + - Custom policies now support the `allow_algorithm_subset_and_reordering` directive to allow targets to pass with a subset and/or re-ordered list of host keys, kex, ciphers, and MACs. This allows for the creation of a baseline policy where targets can optionally implement stricter controls; partial credit [yannik1015](https://github.com/yannik1015). - Added 1 new key exchange algorithm: `gss-nistp384-sha384-*`. ### v3.1.0 (2023-12-20) diff --git a/docker_test.sh b/docker_test.sh index 99c1155..f169b64 100755 --- a/docker_test.sh +++ b/docker_test.sh @@ -784,6 +784,12 @@ run_custom_policy_test "config2" "test13" "${PROGRAM_RETVAL_GOOD}" # Failing test with DH modulus test. run_custom_policy_test "config2" "test14" "${PROGRAM_RETVAL_FAILURE}" +# Passing test with algorithm subset matching. +run_custom_policy_test "config2" "test15" "${PROGRAM_RETVAL_GOOD}" + +# Failing test with algorithm subset matching. +run_custom_policy_test "config2" "test16" "${PROGRAM_RETVAL_FAILURE}" + # Failing test for built-in OpenSSH 8.0p1 server policy (RSA host key size is 3072 instead of 4096). run_builtin_policy_test "Hardened OpenSSH Server v8.0 (version 4)" "8.0p1" "test1" "-o HostKeyAlgorithms=rsa-sha2-512,rsa-sha2-256,ssh-ed25519 -o KexAlgorithms=curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256 -o Ciphers=chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr -o MACs=hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com" "${PROGRAM_RETVAL_FAILURE}" diff --git a/src/ssh_audit/policy.py b/src/ssh_audit/policy.py index 591feec..9918147 100644 --- a/src/ssh_audit/policy.py +++ b/src/ssh_audit/policy.py @@ -55,6 +55,7 @@ class Policy: self._dh_modulus_sizes: Optional[Dict[str, int]] = None self._server_policy = True self._allow_algorithm_subset_and_reordering = False + self._errors: List[Any] = [] self._name_and_version: str = '' @@ -151,7 +152,7 @@ class Policy: elif key == 'host keys': self._host_keys = algs elif key == 'optional host keys': - self._optional_host_keys = algs + self._optional_host_keys = algs elif key == 'key exchanges': self._kex = algs elif key == 'ciphers': @@ -217,13 +218,12 @@ class Policy: self._name_and_version = "%s (version %s)" % (self._name, self._version) - @staticmethod - def _append_error(errors: List[Any], mismatched_field: str, expected_required: Optional[List[str]], expected_optional: Optional[List[str]], actual: List[str]) -> None: + def _append_error(self, mismatched_field: str, expected_required: Optional[List[str]], expected_optional: Optional[List[str]], actual: List[str]) -> None: if expected_required is None: expected_required = [''] if expected_optional is None: expected_optional = [''] - errors.append({'mismatched_field': mismatched_field, 'expected_required': expected_required, 'expected_optional': expected_optional, 'actual': actual}) + self._errors.append({'mismatched_field': mismatched_field, 'expected_required': expected_required, 'expected_optional': expected_optional, 'actual': actual}) def _normalize_hostkey_sizes(self) -> None: @@ -296,6 +296,9 @@ name = "Custom Policy (based on %s on %s)" # The version of this policy (displayed in the output during scans). Not parsed, and may be any value, including strings. version = 1 +# When false, host keys, kex, ciphers, and MAC lists must match exactly. When true, the target host may support a subset of the specified algorithms and/or algorithms may appear in a different order; this is useful for specifying a baseline and allowing some hosts the option to implement stricter controls. +allow_algorithm_subset_and_reordering = false + # The banner that must match exactly. Commented out to ignore banners, since minor variability in the banner is sometimes normal. # banner = "%s" @@ -325,39 +328,41 @@ macs = %s '''Evaluates a server configuration against this policy. Returns a tuple of a boolean (True if server adheres to policy) and an array of strings that holds error messages.''' ret = True - errors: List[Any] = [] banner_str = str(banner) if (self._banner is not None) and (banner_str != self._banner): ret = False - self._append_error(errors, 'Banner', [self._banner], None, [banner_str]) + self._append_error('Banner', [self._banner], None, [banner_str]) # All subsequent tests require a valid kex, so end here if we don't have one. if kex is None: - return ret, errors, self._get_error_str(errors, self._allow_algorithm_subset_and_reordering) + error_list, error_str = self._get_errors() + return ret, error_list, error_str if (self._compressions is not None) and (kex.server.compression != self._compressions): ret = False - self._append_error(errors, 'Compression', self._compressions, None, kex.server.compression) + self._append_error('Compression', self._compressions, None, kex.server.compression) # 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] - - # Checking Hostkeys + + # Check host keys. if self._host_keys is not None: + # If the policy allows subsets and re-ordered algorithms... if self._allow_algorithm_subset_and_reordering: for hostkey_t in kex.key_algorithms: if hostkey_t not in self._host_keys: ret = False - self._append_error(errors, 'Host keys', self._host_keys, None, kex.key_algorithms) + self._append_error('Host keys', self._host_keys, self._optional_host_keys, kex.key_algorithms) break + # The policy requires exact matching of algorithms. elif pruned_host_keys != self._host_keys: ret = False - self._append_error(errors, 'Host keys', self._host_keys, None, kex.key_algorithms) + self._append_error('Host keys', self._host_keys, self._optional_host_keys, kex.key_algorithms) - # Checking Host Key Sizes + # Check host key sizes. if self._hostkey_sizes is not None: hostkey_types = list(self._hostkey_sizes.keys()) hostkey_types.sort() # Sorted to make testing output repeatable. @@ -368,7 +373,7 @@ macs = %s actual_hostkey_size = server_host_keys[hostkey_type]['hostkey_size'] if actual_hostkey_size != expected_hostkey_size: ret = False - self._append_error(errors, 'Host key (%s) sizes' % hostkey_type, [str(expected_hostkey_size)], None, [str(actual_hostkey_size)]) + self._append_error('Host key (%s) sizes' % hostkey_type, [str(expected_hostkey_size)], None, [str(actual_hostkey_size)]) # If we have expected CA signatures set, check them against what the server returned. if self._hostkey_sizes is not None and len(cast(str, self._hostkey_sizes[hostkey_type]['ca_key_type'])) > 0 and cast(int, self._hostkey_sizes[hostkey_type]['ca_key_size']) > 0: @@ -380,47 +385,59 @@ macs = %s # Ensure that the CA signature type is what's expected (i.e.: the server doesn't have an RSA sig when we're expecting an ED25519 sig). if actual_ca_key_type != expected_ca_key_type: ret = False - self._append_error(errors, 'CA signature type', [expected_ca_key_type], None, [actual_ca_key_type]) + self._append_error('CA signature type', [expected_ca_key_type], None, [actual_ca_key_type]) # Ensure that the actual and expected signature sizes match. elif actual_ca_key_size != expected_ca_key_size: ret = False - self._append_error(errors, 'CA signature size (%s)' % actual_ca_key_type, [str(expected_ca_key_size)], None, [str(actual_ca_key_size)]) - - # Checking KEX + self._append_error('CA signature size (%s)' % actual_ca_key_type, [str(expected_ca_key_size)], None, [str(actual_ca_key_size)]) + + # Check key exchanges. if self._kex is not None: + # If the policy allows subsets and re-ordered algorithms... if self._allow_algorithm_subset_and_reordering: for kex_t in kex.kex_algorithms: if kex_t not in self._kex: ret = False - self._append_error(errors, 'Key exchanges', self._kex, None, kex.kex_algorithms) + self._append_error('Key exchanges', self._kex, None, kex.kex_algorithms) break - elif kex.kex_algorithms != self._kex: # Requires perfect match + # If kex-strict-?-v00@openssh.com is in the policy (i.e. the Terrapin vulnerability countermeasure), then it must appear in the server's list, regardless of the "allow_algorithm_subset_and_reordering" flag. + if ('kex-strict-s-v00@openssh.com' in self._kex and 'kex-strict-s-v00@openssh.com' not in kex.kex_algorithms) or \ + ('kex-strict-c-v00@openssh.com' in self._kex and 'kex-strict-c-v00@openssh.com' not in kex.kex_algorithms): + ret = False + self._append_error('Key exchanges', self._kex, None, kex.kex_algorithms) + + # The policy requires exact matching of algorithms. + elif kex.kex_algorithms != self._kex: ret = False - self._append_error(errors, 'Key exchanges', self._kex, None, kex.kex_algorithms) - + self._append_error('Key exchanges', self._kex, None, kex.kex_algorithms) + # Checking Ciphers if self._ciphers is not None: + # If the policy allows subsets and re-ordered algorithms... if self._allow_algorithm_subset_and_reordering: for cipher_t in kex.server.encryption: if cipher_t not in self._ciphers: ret = False - self._append_error(errors, 'Ciphers', self._ciphers, None, kex.server.encryption) + self._append_error('Ciphers', self._ciphers, None, kex.server.encryption) break - elif kex.server.encryption != self._ciphers: # Requires perfect match + # The policy requires exact matching of algorithms. + elif kex.server.encryption != self._ciphers: ret = False - self._append_error(errors, 'Ciphers', self._ciphers, None, kex.server.encryption) - + self._append_error('Ciphers', self._ciphers, None, kex.server.encryption) + # Checking MACs if self._macs is not None: + # If the policy allows subsets and re-ordered algorithms... if self._allow_algorithm_subset_and_reordering: for mac_t in kex.server.mac: if mac_t not in self._macs: ret = False - self._append_error(errors, 'MACs', self._macs, None, kex.server.mac) + self._append_error('MACs', self._macs, None, kex.server.mac) break - elif kex.server.mac != self._macs: # Requires perfect match + # The policy requires exact matching of algorithms. + elif kex.server.mac != self._macs: ret = False - self._append_error(errors, 'MACs', self._macs, None, kex.server.mac) + self._append_error('MACs', self._macs, None, kex.server.mac) if self._dh_modulus_sizes is not None: dh_modulus_types = list(self._dh_modulus_sizes.keys()) @@ -431,30 +448,27 @@ macs = %s actual_dh_modulus_size = kex.dh_modulus_sizes()[dh_modulus_type] if expected_dh_modulus_size != actual_dh_modulus_size: ret = False - self._append_error(errors, 'Group exchange (%s) modulus sizes' % dh_modulus_type, [str(expected_dh_modulus_size)], None, [str(actual_dh_modulus_size)]) + self._append_error('Group exchange (%s) modulus sizes' % dh_modulus_type, [str(expected_dh_modulus_size)], None, [str(actual_dh_modulus_size)]) - return ret, errors, self._get_error_str(errors, self._allow_algorithm_subset_and_reordering) + error_list, error_str = self._get_errors() + return ret, error_list, error_str - @staticmethod - def _get_error_str(errors: List[Any], allow_algorithm_subset_and_reordering: bool = False) -> str: - '''Transforms an error struct to a flat string of error messages.''' - - if allow_algorithm_subset_and_reordering: - expected_str = 'allowed' - else: - expected_str = 'required' + def _get_errors(self) -> Tuple[List[Any], str]: + '''Returns the list of errors, along with the string representation of those errors.''' + subset_and_reordering_semicolon = "; subset and/or reordering allowed" if self._allow_algorithm_subset_and_reordering else "; exact match" + subset_and_reordering_parens = " (subset and/or reordering allowed)" if self._allow_algorithm_subset_and_reordering else "" error_list = [] spacer = '' - for e in errors: + for e in self._errors: e_str = " * %s did not match.\n" % e['mismatched_field'] if ('expected_optional' in e) and (e['expected_optional'] != ['']): - e_str += " - Expected (" + expected_str + "): %s\n - Expected (optional): %s\n" % (Policy._normalize_error_field(e['expected_required']), Policy._normalize_error_field(e['expected_optional'])) + e_str += " - Expected (required%s): %s\n - Expected (optional): %s\n" % (subset_and_reordering_semicolon, Policy._normalize_error_field(e['expected_required']), Policy._normalize_error_field(e['expected_optional'])) spacer = ' ' else: - e_str += " - Expected (" + expected_str + "): %s\n" % Policy._normalize_error_field(e['expected_required']) + e_str += " - Expected%s: %s\n" % (subset_and_reordering_parens, Policy._normalize_error_field(e['expected_required'])) spacer = ' ' e_str += " - Actual:%s%s\n" % (spacer, Policy._normalize_error_field(e['actual'])) error_list.append(e_str) @@ -465,7 +479,7 @@ macs = %s if len(error_list) > 0: error_str = "\n".join(error_list) - return error_str + return self._errors, error_str def get_name_and_version(self) -> str: @@ -581,4 +595,4 @@ macs = %s if self._dh_modulus_sizes is not None: dh_modulus_sizes_str = str(self._dh_modulus_sizes) - return "Name: %s\nVersion: %s\nBanner: %s\nCompressions: %s\nHost Keys: %s\nOptional Host Keys: %s\nKey Exchanges: %s\nCiphers: %s\nMACs: %s\nHost Key Sizes: %s\nDH Modulus Sizes: %s\nServer Policy: %r" % (name, version, banner, compressions_str, host_keys_str, optional_host_keys_str, kex_str, ciphers_str, macs_str, hostkey_sizes_str, dh_modulus_sizes_str, self._server_policy) + return "Name: %s\nVersion: %s\nAllow Algorithm Subset and/or Reordering: %r\nBanner: %s\nCompressions: %s\nHost Keys: %s\nOptional Host Keys: %s\nKey Exchanges: %s\nCiphers: %s\nMACs: %s\nHost Key Sizes: %s\nDH Modulus Sizes: %s\nServer Policy: %r" % (name, version, self._allow_algorithm_subset_and_reordering, banner, compressions_str, host_keys_str, optional_host_keys_str, kex_str, ciphers_str, macs_str, hostkey_sizes_str, dh_modulus_sizes_str, self._server_policy) diff --git a/test/docker/expected_results/openssh_8.0p1_custom_policy_test15.json b/test/docker/expected_results/openssh_8.0p1_custom_policy_test15.json new file mode 100644 index 0000000..f10cdb6 --- /dev/null +++ b/test/docker/expected_results/openssh_8.0p1_custom_policy_test15.json @@ -0,0 +1,6 @@ +{ + "errors": [], + "host": "localhost", + "passed": true, + "policy": "Docker policy: test15 (version 1)" +} diff --git a/test/docker/expected_results/openssh_8.0p1_custom_policy_test15.txt b/test/docker/expected_results/openssh_8.0p1_custom_policy_test15.txt new file mode 100644 index 0000000..b43d89d --- /dev/null +++ b/test/docker/expected_results/openssh_8.0p1_custom_policy_test15.txt @@ -0,0 +1,3 @@ +Host: localhost:2222 +Policy: Docker policy: test15 (version 1) +Result: ✔ Passed diff --git a/test/docker/expected_results/openssh_8.0p1_custom_policy_test16.json b/test/docker/expected_results/openssh_8.0p1_custom_policy_test16.json new file mode 100644 index 0000000..c6900dc --- /dev/null +++ b/test/docker/expected_results/openssh_8.0p1_custom_policy_test16.json @@ -0,0 +1,86 @@ +{ + "errors": [ + { + "actual": [ + "rsa-sha2-512", + "rsa-sha2-256", + "ssh-rsa", + "ecdsa-sha2-nistp256", + "ssh-ed25519" + ], + "expected_optional": [ + "" + ], + "expected_required": [ + "rsa-sha2-512", + "extra_hostkey_alg" + ], + "mismatched_field": "Host keys" + }, + { + "actual": [ + "curve25519-sha256", + "curve25519-sha256@libssh.org", + "ecdh-sha2-nistp256", + "ecdh-sha2-nistp384", + "ecdh-sha2-nistp521", + "diffie-hellman-group-exchange-sha256", + "diffie-hellman-group16-sha512", + "diffie-hellman-group18-sha512", + "diffie-hellman-group14-sha256", + "diffie-hellman-group14-sha1" + ], + "expected_optional": [ + "" + ], + "expected_required": [ + "curve25519-sha256", + "extra_kex_alg" + ], + "mismatched_field": "Key exchanges" + }, + { + "actual": [ + "chacha20-poly1305@openssh.com", + "aes128-ctr", + "aes192-ctr", + "aes256-ctr", + "aes128-gcm@openssh.com", + "aes256-gcm@openssh.com" + ], + "expected_optional": [ + "" + ], + "expected_required": [ + "chacha20-poly1305@openssh.com", + "extra_cipher_alg" + ], + "mismatched_field": "Ciphers" + }, + { + "actual": [ + "umac-64-etm@openssh.com", + "umac-128-etm@openssh.com", + "hmac-sha2-256-etm@openssh.com", + "hmac-sha2-512-etm@openssh.com", + "hmac-sha1-etm@openssh.com", + "umac-64@openssh.com", + "umac-128@openssh.com", + "hmac-sha2-256", + "hmac-sha2-512", + "hmac-sha1" + ], + "expected_optional": [ + "" + ], + "expected_required": [ + "umac-64-etm@openssh.com", + "extra_mac_alg" + ], + "mismatched_field": "MACs" + } + ], + "host": "localhost", + "passed": false, + "policy": "Docker policy: test16 (version 1)" +} diff --git a/test/docker/expected_results/openssh_8.0p1_custom_policy_test16.txt b/test/docker/expected_results/openssh_8.0p1_custom_policy_test16.txt new file mode 100644 index 0000000..3d52efa --- /dev/null +++ b/test/docker/expected_results/openssh_8.0p1_custom_policy_test16.txt @@ -0,0 +1,21 @@ +Host: localhost:2222 +Policy: Docker policy: test16 (version 1) +Result: ❌ Failed! + +Errors: + * Ciphers did not match. + - Expected (subset and/or reordering allowed): chacha20-poly1305@openssh.com, extra_cipher_alg + - Actual: chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com + + * Host keys did not match. + - Expected (subset and/or reordering allowed): rsa-sha2-512, extra_hostkey_alg + - Actual: rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519 + + * Key exchanges did not match. + - Expected (subset and/or reordering allowed): curve25519-sha256, extra_kex_alg + - Actual: curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group-exchange-sha256, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1 + + * MACs did not match. + - Expected (subset and/or reordering allowed): umac-64-etm@openssh.com, extra_mac_alg + - Actual: umac-64-etm@openssh.com, umac-128-etm@openssh.com, hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha1-etm@openssh.com, umac-64@openssh.com, umac-128@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1 + diff --git a/test/docker/policies/policy_test15.txt b/test/docker/policies/policy_test15.txt new file mode 100644 index 0000000..6f25df1 --- /dev/null +++ b/test/docker/policies/policy_test15.txt @@ -0,0 +1,13 @@ +# +# Docker policy: test15 +# + +name = "Docker policy: test15" +version = 1 +allow_algorithm_subset_and_reordering = true +banner = "SSH-2.0-OpenSSH_8.0" +compressions = none, zlib@openssh.com +host keys = rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519, extra_hostkey_alg +key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group-exchange-sha256, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1, extra_kex_alg +ciphers = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com, extra_cipher_alg +macs = umac-64-etm@openssh.com, umac-128-etm@openssh.com, hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha1-etm@openssh.com, umac-64@openssh.com, umac-128@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1, extra_mac_alg diff --git a/test/docker/policies/policy_test16.txt b/test/docker/policies/policy_test16.txt new file mode 100644 index 0000000..817e9bb --- /dev/null +++ b/test/docker/policies/policy_test16.txt @@ -0,0 +1,13 @@ +# +# Docker policy: test16 +# + +name = "Docker policy: test16" +version = 1 +allow_algorithm_subset_and_reordering = true +banner = "SSH-2.0-OpenSSH_8.0" +compressions = none, zlib@openssh.com +host keys = rsa-sha2-512, extra_hostkey_alg +key exchanges = curve25519-sha256, extra_kex_alg +ciphers = chacha20-poly1305@openssh.com, extra_cipher_alg +macs = umac-64-etm@openssh.com, extra_mac_alg diff --git a/test/test_policy.py b/test/test_policy.py index cf4fe90..9cb9385 100644 --- a/test/test_policy.py +++ b/test/test_policy.py @@ -150,7 +150,7 @@ ciphers = cipher_alg1, cipher_alg2, cipher_alg3 macs = mac_alg1, mac_alg2, mac_alg3''' policy = self.Policy(policy_data=policy_data) - assert str(policy) == "Name: [Test Policy]\nVersion: [1]\nBanner: {undefined}\nCompressions: comp_alg1\nHost Keys: key_alg1\nOptional Host Keys: {undefined}\nKey Exchanges: kex_alg1, kex_alg2\nCiphers: cipher_alg1, cipher_alg2, cipher_alg3\nMACs: mac_alg1, mac_alg2, mac_alg3\nHost Key Sizes: {undefined}\nDH Modulus Sizes: {undefined}\nServer Policy: True" + assert str(policy) == "Name: [Test Policy]\nVersion: [1]\nAllow Algorithm Subset and/or Reordering: False\nBanner: {undefined}\nCompressions: comp_alg1\nHost Keys: key_alg1\nOptional Host Keys: {undefined}\nKey Exchanges: kex_alg1, kex_alg2\nCiphers: cipher_alg1, cipher_alg2, cipher_alg3\nMACs: mac_alg1, mac_alg2, mac_alg3\nHost Key Sizes: {undefined}\nDH Modulus Sizes: {undefined}\nServer Policy: True" def test_policy_invalid_1(self): @@ -297,7 +297,7 @@ macs = mac_alg1, mac_alg2, mac_alg3''' 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. - assert hashlib.sha256(pol_data.encode('ascii')).hexdigest() == '4af7777fb57a1dad0cf438c899a11d4f625fd9276ea3bb5ef5c9fe8806cb47dc' + assert hashlib.sha256(pol_data.encode('ascii')).hexdigest() == '4b504b799f6b964a20ccbe8af7edd26c7b5f0e0b98070e754ea41dccdace33b4' def test_policy_evaluate_passing_1(self): @@ -440,3 +440,96 @@ macs = mac_alg1, mac_alg2, XXXmismatchedXXX, mac_alg3''' assert len(errors) == 2 assert error_str.find('Host keys did not match.') != -1 assert error_str.find('MACs did not match.') != -1 + + + def test_policy_evaluate_subset_passing_1(self): + '''Ensure that exact algorithm matches work even when subset parsing is enabled.''' + + policy_data = '''name = "Test Policy" +version = 1 +allow_algorithm_subset_and_reordering = true +compressions = comp_alg1, comp_alg2 +host keys = key_alg1, key_alg2 +key exchanges = kex_alg1, kex_alg2 +ciphers = cipher_alg1, cipher_alg2, cipher_alg3 +macs = mac_alg1, mac_alg2, mac_alg3''' + policy = self.Policy(policy_data=policy_data) + ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex()) + assert ret is True + assert len(errors) == 0 + assert error_str == "" + + + def test_policy_evaluate_subset_passing_2(self): + '''Ensure that subset parsing works.''' + + policy_data = '''name = "Test Policy" +version = 1 +allow_algorithm_subset_and_reordering = true +compressions = comp_alg1, comp_alg2 +host keys = key_alg2, key_alg1, key_alg0 +key exchanges = kex_alg3, kex_alg1, kex_alg2 +ciphers = cipher_alg0, cipher_alg3, cipher_alg2, cipher_alg1 +macs = mac_alg2, mac_alg1, mac_alg3, mac_alg0''' + policy = self.Policy(policy_data=policy_data) + ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex()) + assert ret is True + assert len(errors) == 0 + assert error_str == "" + + + def test_policy_evaluate_subset_failing_1(self): + '''Ensure that subset parsing returns a failure.''' + + policy_data = '''name = "Test Policy" +version = 1 +allow_algorithm_subset_and_reordering = true +compressions = comp_alg1, comp_alg2 +host keys = key_alg7, key_alg8, key_alg9 +key exchanges = kex_alg7, kex_alg8, kex_alg9 +ciphers = cipher_alg7, cipher_alg8, cipher_alg9, cipher_alg10 +macs = mac_alg7, mac_alg8, mac_alg9, mac_alg10''' + policy = self.Policy(policy_data=policy_data) + ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex()) + assert ret is False + assert len(errors) == 4 + assert error_str.find("Ciphers did not match.") != -1 + assert error_str.find("Host keys did not match.") != -1 + assert error_str.find("MACs did not match") != -1 + assert error_str.find("Key exchanges did not match.") != -1 + + + def test_policy_evaluate_subset_failing_2(self): + '''Ensure that subset parsing returns a failure when policy includes kex-strict-s-v00@openssh.com, but target does not.''' + + policy_data = '''name = "Test Policy" +version = 1 +allow_algorithm_subset_and_reordering = true +compressions = comp_alg1, comp_alg2 +host keys = key_alg2, key_alg1, key_alg0 +key exchanges = kex_alg3, kex_alg1, kex_alg2, kex-strict-s-v00@openssh.com +ciphers = cipher_alg0, cipher_alg3, cipher_alg2, cipher_alg1 +macs = mac_alg2, mac_alg1, mac_alg3, mac_alg0''' + policy = self.Policy(policy_data=policy_data) + ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex()) + assert ret is False + assert len(errors) == 1 + assert error_str.find("Key exchanges did not match.") != -1 + + + def test_policy_evaluate_subset_failing_3(self): + '''Ensure that subset parsing returns a failure when policy includes kex-strict-c-v00@openssh.com, but target does not.''' + + policy_data = '''name = "Test Policy" +version = 1 +allow_algorithm_subset_and_reordering = true +compressions = comp_alg1, comp_alg2 +host keys = key_alg2, key_alg1, key_alg0 +key exchanges = kex_alg3, kex_alg1, kex_alg2, kex-strict-c-v00@openssh.com +ciphers = cipher_alg0, cipher_alg3, cipher_alg2, cipher_alg1 +macs = mac_alg2, mac_alg1, mac_alg3, mac_alg0''' + policy = self.Policy(policy_data=policy_data) + ret, errors, error_str = policy.evaluate('SSH Server 1.0', self._get_kex()) + assert ret is False + assert len(errors) == 1 + assert error_str.find("Key exchanges did not match.") != -1 From 20873db5964550314f26d77bddfe9c995dd9e6c8 Mon Sep 17 00:00:00 2001 From: Damian Szuberski Date: Wed, 20 Mar 2024 04:38:27 +1000 Subject: [PATCH 3/6] use less-than instead of not-equal when comparing key sizes (#242) When evaluating policy compliance, use less-than operator so keys bigger than expected (and hence very often better) don't fail policy evaulation. This change reduces the amount of false-positives and allows for more flexibility when hardening SSH installations. Signed-off-by: szubersk --- src/ssh_audit/policy.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ssh_audit/policy.py b/src/ssh_audit/policy.py index 9918147..ef3b155 100644 --- a/src/ssh_audit/policy.py +++ b/src/ssh_audit/policy.py @@ -367,11 +367,11 @@ macs = %s hostkey_types = list(self._hostkey_sizes.keys()) hostkey_types.sort() # Sorted to make testing output repeatable. for hostkey_type in hostkey_types: - expected_hostkey_size = self._hostkey_sizes[hostkey_type]['hostkey_size'] + expected_hostkey_size = cast(int, self._hostkey_sizes[hostkey_type]['hostkey_size']) server_host_keys = kex.host_keys() if hostkey_type in server_host_keys: - actual_hostkey_size = server_host_keys[hostkey_type]['hostkey_size'] - if actual_hostkey_size != expected_hostkey_size: + actual_hostkey_size = cast(int, server_host_keys[hostkey_type]['hostkey_size']) + if actual_hostkey_size < expected_hostkey_size: ret = False self._append_error('Host key (%s) sizes' % hostkey_type, [str(expected_hostkey_size)], None, [str(actual_hostkey_size)]) @@ -387,7 +387,7 @@ macs = %s ret = False self._append_error('CA signature type', [expected_ca_key_type], None, [actual_ca_key_type]) # Ensure that the actual and expected signature sizes match. - elif actual_ca_key_size != expected_ca_key_size: + elif actual_ca_key_size < expected_ca_key_size: ret = False self._append_error('CA signature size (%s)' % actual_ca_key_type, [str(expected_ca_key_size)], None, [str(actual_ca_key_size)]) @@ -446,7 +446,7 @@ macs = %s expected_dh_modulus_size = self._dh_modulus_sizes[dh_modulus_type] if dh_modulus_type in kex.dh_modulus_sizes(): actual_dh_modulus_size = kex.dh_modulus_sizes()[dh_modulus_type] - if expected_dh_modulus_size != actual_dh_modulus_size: + if expected_dh_modulus_size > actual_dh_modulus_size: ret = False self._append_error('Group exchange (%s) modulus sizes' % dh_modulus_type, [str(expected_dh_modulus_size)], None, [str(actual_dh_modulus_size)]) From 9fae870260ee0067fd09840bc696b9459c4dba52 Mon Sep 17 00:00:00 2001 From: Joe Testa Date: Tue, 19 Mar 2024 14:45:19 -0400 Subject: [PATCH 4/6] Added allow_larger_keys flag to custom policies to control whether targets can have larger keys, and added Docker tests to complete work started in PR #242. --- README.md | 1 + docker_test.sh | 3 +++ src/ssh_audit/policy.py | 19 ++++++++++++++----- .../openssh_8.0p1_custom_policy_test17.json | 6 ++++++ .../openssh_8.0p1_custom_policy_test17.txt | 3 +++ test/docker/policies/policy_test17.txt | 15 +++++++++++++++ test/test_policy.py | 2 +- 7 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 test/docker/expected_results/openssh_8.0p1_custom_policy_test17.json create mode 100644 test/docker/expected_results/openssh_8.0p1_custom_policy_test17.txt create mode 100644 test/docker/policies/policy_test17.txt diff --git a/README.md b/README.md index 0047412..5a630c8 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,7 @@ For convenience, a web front-end on top of the command-line tool is available at - Snap builds are now architecture-independent. - Changed Docker base image from `python:3-slim` to `python:3-alpine`, resulting in a 59% reduction in image size; credit [Daniel Thamdrup](https://github.com/dallemon). - Custom policies now support the `allow_algorithm_subset_and_reordering` directive to allow targets to pass with a subset and/or re-ordered list of host keys, kex, ciphers, and MACs. This allows for the creation of a baseline policy where targets can optionally implement stricter controls; partial credit [yannik1015](https://github.com/yannik1015). + - Custom policies now support the `allow_larger_keys` directive to allow targets to pass with larger host keys, CA keys, and Diffie-Hellman keys. This allows for the creation of a baseline policy where targets can optionally implement stricter controls; partial credit [Damian Szuberski](https://github.com/szubersk). - Added 1 new key exchange algorithm: `gss-nistp384-sha384-*`. ### v3.1.0 (2023-12-20) diff --git a/docker_test.sh b/docker_test.sh index f169b64..3c14ef0 100755 --- a/docker_test.sh +++ b/docker_test.sh @@ -790,6 +790,9 @@ run_custom_policy_test "config2" "test15" "${PROGRAM_RETVAL_GOOD}" # Failing test with algorithm subset matching. run_custom_policy_test "config2" "test16" "${PROGRAM_RETVAL_FAILURE}" +# Passing test with larger key matching. +run_custom_policy_test "config2" "test17" "${PROGRAM_RETVAL_GOOD}" + # Failing test for built-in OpenSSH 8.0p1 server policy (RSA host key size is 3072 instead of 4096). run_builtin_policy_test "Hardened OpenSSH Server v8.0 (version 4)" "8.0p1" "test1" "-o HostKeyAlgorithms=rsa-sha2-512,rsa-sha2-256,ssh-ed25519 -o KexAlgorithms=curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256 -o Ciphers=chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr -o MACs=hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com" "${PROGRAM_RETVAL_FAILURE}" diff --git a/src/ssh_audit/policy.py b/src/ssh_audit/policy.py index ef3b155..bd0bee6 100644 --- a/src/ssh_audit/policy.py +++ b/src/ssh_audit/policy.py @@ -55,6 +55,7 @@ class Policy: self._dh_modulus_sizes: Optional[Dict[str, int]] = None self._server_policy = True self._allow_algorithm_subset_and_reordering = False + self._allow_larger_keys = False self._errors: List[Any] = [] self._name_and_version: str = '' @@ -114,7 +115,7 @@ class Policy: key = key.strip() val = val.strip() - if key not in ['name', 'version', 'banner', 'compressions', 'host keys', 'optional host keys', 'key exchanges', 'ciphers', 'macs', 'client policy', 'host_key_sizes', 'dh_modulus_sizes', 'allow_algorithm_subset_and_reordering'] 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', 'host_key_sizes', 'dh_modulus_sizes', 'allow_algorithm_subset_and_reordering', 'allow_larger_keys'] 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) if key in ['name', 'banner']: @@ -209,6 +210,8 @@ class Policy: self._server_policy = False elif key == 'allow_algorithm_subset_and_reordering' and val.lower() == 'true': self._allow_algorithm_subset_and_reordering = True + elif key == 'allow_larger_keys' and val.lower() == 'true': + self._allow_larger_keys = True if self._name is None: raise ValueError('The policy does not have a name field.') @@ -296,9 +299,12 @@ name = "Custom Policy (based on %s on %s)" # The version of this policy (displayed in the output during scans). Not parsed, and may be any value, including strings. version = 1 -# When false, host keys, kex, ciphers, and MAC lists must match exactly. When true, the target host may support a subset of the specified algorithms and/or algorithms may appear in a different order; this is useful for specifying a baseline and allowing some hosts the option to implement stricter controls. +# When false, host keys, kex, ciphers, and MAC lists must match exactly. When true, the target host may support a subset of the specified algorithms and/or algorithms may appear in a different order; this feature is useful for specifying a baseline and allowing some hosts the option to implement stricter controls. allow_algorithm_subset_and_reordering = false +# When false, host keys, CA keys, and Diffie-Hellman key sizes must exactly match what's specified in this policy. When true, target systems are allowed to have larger keys; this feature is useful for specifying a baseline and allowing some hosts the option to implement stricter controls. +allow_larger_keys = false + # The banner that must match exactly. Commented out to ignore banners, since minor variability in the banner is sometimes normal. # banner = "%s" @@ -371,7 +377,8 @@ macs = %s server_host_keys = kex.host_keys() if hostkey_type in server_host_keys: actual_hostkey_size = cast(int, server_host_keys[hostkey_type]['hostkey_size']) - if actual_hostkey_size < expected_hostkey_size: + if (self._allow_larger_keys and actual_hostkey_size < expected_hostkey_size) or \ + (not self._allow_larger_keys and actual_hostkey_size != expected_hostkey_size): ret = False self._append_error('Host key (%s) sizes' % hostkey_type, [str(expected_hostkey_size)], None, [str(actual_hostkey_size)]) @@ -387,7 +394,8 @@ macs = %s ret = False self._append_error('CA signature type', [expected_ca_key_type], None, [actual_ca_key_type]) # Ensure that the actual and expected signature sizes match. - elif actual_ca_key_size < expected_ca_key_size: + elif (self._allow_larger_keys and actual_ca_key_size < expected_ca_key_size) or \ + (not self._allow_larger_keys and actual_ca_key_size != expected_ca_key_size): ret = False self._append_error('CA signature size (%s)' % actual_ca_key_type, [str(expected_ca_key_size)], None, [str(actual_ca_key_size)]) @@ -446,7 +454,8 @@ macs = %s expected_dh_modulus_size = self._dh_modulus_sizes[dh_modulus_type] if dh_modulus_type in kex.dh_modulus_sizes(): actual_dh_modulus_size = kex.dh_modulus_sizes()[dh_modulus_type] - if expected_dh_modulus_size > actual_dh_modulus_size: + if (self._allow_larger_keys and actual_dh_modulus_size < expected_dh_modulus_size) or \ + (not self._allow_larger_keys and actual_dh_modulus_size != expected_dh_modulus_size): ret = False self._append_error('Group exchange (%s) modulus sizes' % dh_modulus_type, [str(expected_dh_modulus_size)], None, [str(actual_dh_modulus_size)]) diff --git a/test/docker/expected_results/openssh_8.0p1_custom_policy_test17.json b/test/docker/expected_results/openssh_8.0p1_custom_policy_test17.json new file mode 100644 index 0000000..b7286af --- /dev/null +++ b/test/docker/expected_results/openssh_8.0p1_custom_policy_test17.json @@ -0,0 +1,6 @@ +{ + "errors": [], + "host": "localhost", + "passed": true, + "policy": "Docker policy: test17 (version 1)" +} diff --git a/test/docker/expected_results/openssh_8.0p1_custom_policy_test17.txt b/test/docker/expected_results/openssh_8.0p1_custom_policy_test17.txt new file mode 100644 index 0000000..7957c73 --- /dev/null +++ b/test/docker/expected_results/openssh_8.0p1_custom_policy_test17.txt @@ -0,0 +1,3 @@ +Host: localhost:2222 +Policy: Docker policy: test17 (version 1) +Result: ✔ Passed diff --git a/test/docker/policies/policy_test17.txt b/test/docker/policies/policy_test17.txt new file mode 100644 index 0000000..6cfc9d9 --- /dev/null +++ b/test/docker/policies/policy_test17.txt @@ -0,0 +1,15 @@ +# +# Docker policy: test17 +# + +name = "Docker policy: test17" +version = 1 +allow_larger_keys = true +banner = "SSH-2.0-OpenSSH_8.0" +compressions = none, zlib@openssh.com +host keys = rsa-sha2-512, rsa-sha2-256, ssh-rsa, ecdsa-sha2-nistp256, ssh-ed25519 +key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group-exchange-sha256, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1 +ciphers = chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com +macs = umac-64-etm@openssh.com, umac-128-etm@openssh.com, hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha1-etm@openssh.com, umac-64@openssh.com, umac-128@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha1 +host_key_sizes = {"ssh-rsa": {"hostkey_size": 2048}, "rsa-sha2-256": {"hostkey_size": 2048}, "rsa-sha2-512": {"hostkey_size": 2048}, "ssh-ed25519": {"hostkey_size": 256}} +dh_modulus_sizes = {"diffie-hellman-group-exchange-sha256": 2048} diff --git a/test/test_policy.py b/test/test_policy.py index 9cb9385..cca9e3e 100644 --- a/test/test_policy.py +++ b/test/test_policy.py @@ -297,7 +297,7 @@ macs = mac_alg1, mac_alg2, mac_alg3''' 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. - assert hashlib.sha256(pol_data.encode('ascii')).hexdigest() == '4b504b799f6b964a20ccbe8af7edd26c7b5f0e0b98070e754ea41dccdace33b4' + assert hashlib.sha256(pol_data.encode('ascii')).hexdigest() == 'fb84bce442cff2bce9bf653d6373a8a938e3bfcfbd1e876f51a08c1842df3cff' def test_policy_evaluate_passing_1(self): From 3d403b1d708d636244c5ad124fd252350f363366 Mon Sep 17 00:00:00 2001 From: Joe Testa Date: Tue, 19 Mar 2024 15:47:09 -0400 Subject: [PATCH 5/6] Updated availability of algorithms in Dropbear. (#257) --- src/ssh_audit/ssh2_kexdb.py | 6 +++--- test/docker/expected_results/openssh_8.0p1_test1.json | 6 +++--- test/docker/expected_results/openssh_8.0p1_test1.txt | 8 ++++---- test/docker/expected_results/openssh_8.0p1_test2.json | 4 ++-- test/docker/expected_results/openssh_8.0p1_test2.txt | 6 +++--- test/docker/expected_results/openssh_8.0p1_test3.json | 4 ++-- test/docker/expected_results/openssh_8.0p1_test3.txt | 6 +++--- test/docker/expected_results/tinyssh_20190101_test1.json | 4 ++-- test/docker/expected_results/tinyssh_20190101_test1.txt | 6 +++--- 9 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/ssh_audit/ssh2_kexdb.py b/src/ssh_audit/ssh2_kexdb.py index 94f628d..f1ec908 100644 --- a/src/ssh_audit/ssh2_kexdb.py +++ b/src/ssh_audit/ssh2_kexdb.py @@ -223,7 +223,7 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods 'null': [[], [FAIL_PLAINTEXT]], 'pgp-sign-dss': [[], [FAIL_1024BIT_MODULUS]], 'pgp-sign-rsa': [[], [FAIL_1024BIT_MODULUS]], - 'rsa-sha2-256': [['7.2']], + 'rsa-sha2-256': [['7.2,d2020.79']], 'rsa-sha2-256-cert-v01@openssh.com': [['7.8']], 'rsa-sha2-512': [['7.2']], 'rsa-sha2-512-cert-v01@openssh.com': [['7.8']], @@ -242,7 +242,7 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods 'ssh-dss-sha256@ssh.com': [[], [FAIL_1024BIT_MODULUS]], 'ssh-dss-sha384@ssh.com': [[], [FAIL_1024BIT_MODULUS]], 'ssh-dss-sha512@ssh.com': [[], [FAIL_1024BIT_MODULUS]], - 'ssh-ed25519': [['6.5,l10.7.0']], + 'ssh-ed25519': [['6.5,d2020.79,l10.7.0']], 'ssh-ed25519-cert-v01@openssh.com': [['6.5']], 'ssh-ed448': [[]], 'ssh-ed448-cert-v01@openssh.com': [[], [], [], [INFO_NEVER_IMPLEMENTED_IN_OPENSSH]], @@ -335,7 +335,7 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods 'cast128-ecb': [[], [FAIL_CAST], [WARN_CIPHER_MODE]], 'cast128-ofb': [[], [FAIL_CAST], [WARN_CIPHER_MODE]], 'chacha20-poly1305': [[], [], [], [INFO_DEFAULT_OPENSSH_CIPHER]], - 'chacha20-poly1305@openssh.com': [['6.5'], [], [], [INFO_DEFAULT_OPENSSH_CIPHER]], + 'chacha20-poly1305@openssh.com': [['6.5,d2020.79'], [], [], [INFO_DEFAULT_OPENSSH_CIPHER]], 'crypticore128@ssh.com': [[], [FAIL_UNPROVEN]], 'des-cbc': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]], 'des-cfb': [[], [FAIL_DES], [WARN_CIPHER_MODE, WARN_BLOCK_SIZE]], diff --git a/test/docker/expected_results/openssh_8.0p1_test1.json b/test/docker/expected_results/openssh_8.0p1_test1.json index 809b9b5..7f627df 100644 --- a/test/docker/expected_results/openssh_8.0p1_test1.json +++ b/test/docker/expected_results/openssh_8.0p1_test1.json @@ -40,7 +40,7 @@ "notes": { "info": [ "default cipher since OpenSSH 6.9", - "available since OpenSSH 6.5" + "available since OpenSSH 6.5, Dropbear SSH 2020.79" ], "warn": [ "vulnerable to the Terrapin attack (CVE-2023-48795), allowing message prefix truncation" @@ -229,7 +229,7 @@ "keysize": 3072, "notes": { "info": [ - "available since OpenSSH 7.2" + "available since OpenSSH 7.2, Dropbear SSH 2020.79" ] } }, @@ -264,7 +264,7 @@ "algorithm": "ssh-ed25519", "notes": { "info": [ - "available since OpenSSH 6.5" + "available since OpenSSH 6.5, Dropbear SSH 2020.79" ] } } diff --git a/test/docker/expected_results/openssh_8.0p1_test1.txt b/test/docker/expected_results/openssh_8.0p1_test1.txt index f154b89..272d3e0 100644 --- a/test/docker/expected_results/openssh_8.0p1_test1.txt +++ b/test/docker/expected_results/openssh_8.0p1_test1.txt @@ -1,7 +1,7 @@ # general (gen) banner: SSH-2.0-OpenSSH_8.0 (gen) software: OpenSSH 8.0 -(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2018.76+ +(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2020.79+ (gen) compression: enabled (zlib@openssh.com) # security @@ -33,18 +33,18 @@ # host-key algorithms (key) rsa-sha2-512 (3072-bit) -- [info] available since OpenSSH 7.2 -(key) rsa-sha2-256 (3072-bit) -- [info] available since OpenSSH 7.2 +(key) rsa-sha2-256 (3072-bit) -- [info] available since OpenSSH 7.2, Dropbear SSH 2020.79 (key) ssh-rsa (3072-bit) -- [fail] using broken SHA-1 hash algorithm `- [info] available since OpenSSH 2.5.0, Dropbear SSH 0.28 `- [info] deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8 (key) ecdsa-sha2-nistp256 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency  `- [warn] using weak random number generator could reveal the key `- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 -(key) ssh-ed25519 -- [info] available since OpenSSH 6.5 +(key) ssh-ed25519 -- [info] available since OpenSSH 6.5, Dropbear SSH 2020.79 # encryption algorithms (ciphers) (enc) chacha20-poly1305@openssh.com -- [warn] vulnerable to the Terrapin attack (CVE-2023-48795), allowing message prefix truncation - `- [info] available since OpenSSH 6.5 + `- [info] available since OpenSSH 6.5, Dropbear SSH 2020.79 `- [info] default cipher since OpenSSH 6.9 (enc) aes128-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52 (enc) aes192-ctr -- [info] available since OpenSSH 3.7 diff --git a/test/docker/expected_results/openssh_8.0p1_test2.json b/test/docker/expected_results/openssh_8.0p1_test2.json index b434e6e..3101f46 100644 --- a/test/docker/expected_results/openssh_8.0p1_test2.json +++ b/test/docker/expected_results/openssh_8.0p1_test2.json @@ -40,7 +40,7 @@ "notes": { "info": [ "default cipher since OpenSSH 6.9", - "available since OpenSSH 6.5" + "available since OpenSSH 6.5, Dropbear SSH 2020.79" ], "warn": [ "vulnerable to the Terrapin attack (CVE-2023-48795), allowing message prefix truncation" @@ -209,7 +209,7 @@ "algorithm": "ssh-ed25519", "notes": { "info": [ - "available since OpenSSH 6.5" + "available since OpenSSH 6.5, Dropbear SSH 2020.79" ] } }, diff --git a/test/docker/expected_results/openssh_8.0p1_test2.txt b/test/docker/expected_results/openssh_8.0p1_test2.txt index 8c792e9..d2059d2 100644 --- a/test/docker/expected_results/openssh_8.0p1_test2.txt +++ b/test/docker/expected_results/openssh_8.0p1_test2.txt @@ -1,7 +1,7 @@ # general (gen) banner: SSH-2.0-OpenSSH_8.0 (gen) software: OpenSSH 8.0 -(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2018.76+ +(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2020.79+ (gen) compression: enabled (zlib@openssh.com) # security @@ -32,12 +32,12 @@ `- [info] available since OpenSSH 3.9, Dropbear SSH 0.53 # host-key algorithms -(key) ssh-ed25519 -- [info] available since OpenSSH 6.5 +(key) ssh-ed25519 -- [info] available since OpenSSH 6.5, Dropbear SSH 2020.79 (key) ssh-ed25519-cert-v01@openssh.com (256-bit cert/256-bit ssh-ed25519 CA) -- [info] available since OpenSSH 6.5 # encryption algorithms (ciphers) (enc) chacha20-poly1305@openssh.com -- [warn] vulnerable to the Terrapin attack (CVE-2023-48795), allowing message prefix truncation - `- [info] available since OpenSSH 6.5 + `- [info] available since OpenSSH 6.5, Dropbear SSH 2020.79 `- [info] default cipher since OpenSSH 6.9 (enc) aes128-ctr -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52 (enc) aes192-ctr -- [info] available since OpenSSH 3.7 diff --git a/test/docker/expected_results/openssh_8.0p1_test3.json b/test/docker/expected_results/openssh_8.0p1_test3.json index f5380e0..04fb66d 100644 --- a/test/docker/expected_results/openssh_8.0p1_test3.json +++ b/test/docker/expected_results/openssh_8.0p1_test3.json @@ -40,7 +40,7 @@ "notes": { "info": [ "default cipher since OpenSSH 6.9", - "available since OpenSSH 6.5" + "available since OpenSSH 6.5, Dropbear SSH 2020.79" ], "warn": [ "vulnerable to the Terrapin attack (CVE-2023-48795), allowing message prefix truncation" @@ -135,7 +135,7 @@ "algorithm": "ssh-ed25519", "notes": { "info": [ - "available since OpenSSH 6.5" + "available since OpenSSH 6.5, Dropbear SSH 2020.79" ] } } diff --git a/test/docker/expected_results/openssh_8.0p1_test3.txt b/test/docker/expected_results/openssh_8.0p1_test3.txt index ad45556..7ab90f7 100644 --- a/test/docker/expected_results/openssh_8.0p1_test3.txt +++ b/test/docker/expected_results/openssh_8.0p1_test3.txt @@ -1,7 +1,7 @@ # general (gen) banner: SSH-2.0-OpenSSH_8.0 (gen) software: OpenSSH 8.0 -(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2018.76+ +(gen) compatibility: OpenSSH 7.4+, Dropbear SSH 2020.79+ (gen) compression: enabled (zlib@openssh.com) # security @@ -19,11 +19,11 @@  `- [info] OpenSSH's GEX fallback mechanism was triggered during testing. Very old SSH clients will still be able to create connections using a 2048-bit modulus, though modern clients will use 4096. This can only be disabled by recompiling the code (see https://github.com/openssh/openssh-portable/blob/V_9_4/dh.c#L477). # host-key algorithms -(key) ssh-ed25519 -- [info] available since OpenSSH 6.5 +(key) ssh-ed25519 -- [info] available since OpenSSH 6.5, Dropbear SSH 2020.79 # encryption algorithms (ciphers) (enc) chacha20-poly1305@openssh.com -- [warn] vulnerable to the Terrapin attack (CVE-2023-48795), allowing message prefix truncation - `- [info] available since OpenSSH 6.5 + `- [info] available since OpenSSH 6.5, Dropbear SSH 2020.79 `- [info] default cipher since OpenSSH 6.9 (enc) aes256-gcm@openssh.com -- [info] available since OpenSSH 6.2 (enc) aes128-gcm@openssh.com -- [info] available since OpenSSH 6.2 diff --git a/test/docker/expected_results/tinyssh_20190101_test1.json b/test/docker/expected_results/tinyssh_20190101_test1.json index 40b00e9..52bb5d8 100644 --- a/test/docker/expected_results/tinyssh_20190101_test1.json +++ b/test/docker/expected_results/tinyssh_20190101_test1.json @@ -18,7 +18,7 @@ "notes": { "info": [ "default cipher since OpenSSH 6.9", - "available since OpenSSH 6.5" + "available since OpenSSH 6.5, Dropbear SSH 2020.79" ], "warn": [ "vulnerable to the Terrapin attack (CVE-2023-48795), allowing message prefix truncation" @@ -75,7 +75,7 @@ "algorithm": "ssh-ed25519", "notes": { "info": [ - "available since OpenSSH 6.5" + "available since OpenSSH 6.5, Dropbear SSH 2020.79" ] } } diff --git a/test/docker/expected_results/tinyssh_20190101_test1.txt b/test/docker/expected_results/tinyssh_20190101_test1.txt index db5678a..7137eb6 100644 --- a/test/docker/expected_results/tinyssh_20190101_test1.txt +++ b/test/docker/expected_results/tinyssh_20190101_test1.txt @@ -1,6 +1,6 @@ # general (gen) software: TinySSH noversion -(gen) compatibility: OpenSSH 8.0-8.4, Dropbear SSH 2018.76+ +(gen) compatibility: OpenSSH 8.0-8.4, Dropbear SSH 2020.79+ (gen) compression: disabled # key exchange algorithms @@ -13,11 +13,11 @@ `- [info] the sntrup4591761 algorithm was withdrawn, as it may not provide strong post-quantum security # host-key algorithms -(key) ssh-ed25519 -- [info] available since OpenSSH 6.5 +(key) ssh-ed25519 -- [info] available since OpenSSH 6.5, Dropbear SSH 2020.79 # encryption algorithms (ciphers) (enc) chacha20-poly1305@openssh.com -- [warn] vulnerable to the Terrapin attack (CVE-2023-48795), allowing message prefix truncation - `- [info] available since OpenSSH 6.5 + `- [info] available since OpenSSH 6.5, Dropbear SSH 2020.79 `- [info] default cipher since OpenSSH 6.9 # message authentication code algorithms From d7f8bf3e6d120d2dfbe4cb3a72e57ea1e7be5289 Mon Sep 17 00:00:00 2001 From: Joe Testa Date: Tue, 19 Mar 2024 18:24:22 -0400 Subject: [PATCH 6/6] Updated notes on OpenSSH default key exchanges. (#258) --- src/ssh_audit/ssh2_kexdb.py | 7 ++++--- test/docker/expected_results/dropbear_2019.78_test1.json | 4 ++-- test/docker/expected_results/dropbear_2019.78_test1.txt | 4 ++-- test/docker/expected_results/openssh_8.0p1_test1.json | 4 ++-- test/docker/expected_results/openssh_8.0p1_test1.txt | 4 ++-- test/docker/expected_results/openssh_8.0p1_test2.json | 4 ++-- test/docker/expected_results/openssh_8.0p1_test2.txt | 4 ++-- test/docker/expected_results/openssh_8.0p1_test3.json | 4 ++-- test/docker/expected_results/openssh_8.0p1_test3.txt | 4 ++-- test/docker/expected_results/tinyssh_20190101_test1.json | 4 ++-- test/docker/expected_results/tinyssh_20190101_test1.txt | 4 ++-- 11 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/ssh_audit/ssh2_kexdb.py b/src/ssh_audit/ssh2_kexdb.py index f1ec908..4d18bf0 100644 --- a/src/ssh_audit/ssh2_kexdb.py +++ b/src/ssh_audit/ssh2_kexdb.py @@ -62,7 +62,8 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods WARN_TAG_SIZE_96 = 'using small 96-bit tag size' INFO_DEFAULT_OPENSSH_CIPHER = 'default cipher since OpenSSH 6.9' - INFO_DEFAULT_OPENSSH_KEX_64_TO_89 = 'default key exchange from OpenSSH 6.4 to 8.9' + INFO_DEFAULT_OPENSSH_KEX_65_TO_73 = 'default key exchange from OpenSSH 6.5 to 7.3' + INFO_DEFAULT_OPENSSH_KEX_74_TO_89 = 'default key exchange from OpenSSH 7.4 to 8.9' INFO_DEFAULT_OPENSSH_KEX_90 = 'default key exchange since OpenSSH 9.0' INFO_DEPRECATED_IN_OPENSSH88 = 'deprecated in OpenSSH 8.8: https://www.openssh.com/txt/release-8.8' INFO_DISABLED_IN_DBEAR67 = 'disabled in Dropbear SSH 2015.67' @@ -82,8 +83,8 @@ class SSH2_KexDB: # pylint: disable=too-few-public-methods # Format: 'algorithm_name': [['version_first_appeared_in'], [reason_for_failure1, reason_for_failure2, ...], [warning1, warning2, ...], [info1, info2, ...]] 'kex': { 'Curve25519SHA256': [[]], - 'curve25519-sha256': [['7.4,d2018.76'], [], [], [INFO_DEFAULT_OPENSSH_KEX_64_TO_89]], - 'curve25519-sha256@libssh.org': [['6.4,d2013.62,l10.6.0'], [], [], [INFO_DEFAULT_OPENSSH_KEX_64_TO_89]], + 'curve25519-sha256': [['7.4,d2018.76'], [], [], [INFO_DEFAULT_OPENSSH_KEX_74_TO_89]], + 'curve25519-sha256@libssh.org': [['6.4,d2013.62,l10.6.0'], [], [], [INFO_DEFAULT_OPENSSH_KEX_65_TO_73]], 'curve448-sha512': [[]], 'curve448-sha512@libssh.org': [[]], 'diffie-hellman-group14-sha1': [['3.9,d0.53,l10.6.0'], [FAIL_SHA1], [WARN_2048BIT_MODULUS]], diff --git a/test/docker/expected_results/dropbear_2019.78_test1.json b/test/docker/expected_results/dropbear_2019.78_test1.json index 04d410a..c8a6650 100644 --- a/test/docker/expected_results/dropbear_2019.78_test1.json +++ b/test/docker/expected_results/dropbear_2019.78_test1.json @@ -96,7 +96,7 @@ "algorithm": "curve25519-sha256", "notes": { "info": [ - "default key exchange from OpenSSH 6.4 to 8.9", + "default key exchange from OpenSSH 7.4 to 8.9", "available since OpenSSH 7.4, Dropbear SSH 2018.76" ] } @@ -105,7 +105,7 @@ "algorithm": "curve25519-sha256@libssh.org", "notes": { "info": [ - "default key exchange from OpenSSH 6.4 to 8.9", + "default key exchange from OpenSSH 6.5 to 7.3", "available since OpenSSH 6.4, Dropbear SSH 2013.62" ] } diff --git a/test/docker/expected_results/dropbear_2019.78_test1.txt b/test/docker/expected_results/dropbear_2019.78_test1.txt index c01a568..d735dfb 100644 --- a/test/docker/expected_results/dropbear_2019.78_test1.txt +++ b/test/docker/expected_results/dropbear_2019.78_test1.txt @@ -6,9 +6,9 @@ # key exchange algorithms (kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76 - `- [info] default key exchange from OpenSSH 6.4 to 8.9 + `- [info] default key exchange from OpenSSH 7.4 to 8.9 (kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62 - `- [info] default key exchange from OpenSSH 6.4 to 8.9 + `- [info] default key exchange from OpenSSH 6.5 to 7.3 (kex) ecdh-sha2-nistp521 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency `- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 (kex) ecdh-sha2-nistp384 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency diff --git a/test/docker/expected_results/openssh_8.0p1_test1.json b/test/docker/expected_results/openssh_8.0p1_test1.json index 7f627df..af08c5b 100644 --- a/test/docker/expected_results/openssh_8.0p1_test1.json +++ b/test/docker/expected_results/openssh_8.0p1_test1.json @@ -115,7 +115,7 @@ "algorithm": "curve25519-sha256", "notes": { "info": [ - "default key exchange from OpenSSH 6.4 to 8.9", + "default key exchange from OpenSSH 7.4 to 8.9", "available since OpenSSH 7.4, Dropbear SSH 2018.76" ] } @@ -124,7 +124,7 @@ "algorithm": "curve25519-sha256@libssh.org", "notes": { "info": [ - "default key exchange from OpenSSH 6.4 to 8.9", + "default key exchange from OpenSSH 6.5 to 7.3", "available since OpenSSH 6.4, Dropbear SSH 2013.62" ] } diff --git a/test/docker/expected_results/openssh_8.0p1_test1.txt b/test/docker/expected_results/openssh_8.0p1_test1.txt index 272d3e0..15cdad7 100644 --- a/test/docker/expected_results/openssh_8.0p1_test1.txt +++ b/test/docker/expected_results/openssh_8.0p1_test1.txt @@ -12,9 +12,9 @@ # key exchange algorithms (kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76 - `- [info] default key exchange from OpenSSH 6.4 to 8.9 + `- [info] default key exchange from OpenSSH 7.4 to 8.9 (kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62 - `- [info] default key exchange from OpenSSH 6.4 to 8.9 + `- [info] default key exchange from OpenSSH 6.5 to 7.3 (kex) ecdh-sha2-nistp256 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency `- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 (kex) ecdh-sha2-nistp384 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency diff --git a/test/docker/expected_results/openssh_8.0p1_test2.json b/test/docker/expected_results/openssh_8.0p1_test2.json index 3101f46..1263a8d 100644 --- a/test/docker/expected_results/openssh_8.0p1_test2.json +++ b/test/docker/expected_results/openssh_8.0p1_test2.json @@ -105,7 +105,7 @@ "algorithm": "curve25519-sha256", "notes": { "info": [ - "default key exchange from OpenSSH 6.4 to 8.9", + "default key exchange from OpenSSH 7.4 to 8.9", "available since OpenSSH 7.4, Dropbear SSH 2018.76" ] } @@ -114,7 +114,7 @@ "algorithm": "curve25519-sha256@libssh.org", "notes": { "info": [ - "default key exchange from OpenSSH 6.4 to 8.9", + "default key exchange from OpenSSH 6.5 to 7.3", "available since OpenSSH 6.4, Dropbear SSH 2013.62" ] } diff --git a/test/docker/expected_results/openssh_8.0p1_test2.txt b/test/docker/expected_results/openssh_8.0p1_test2.txt index d2059d2..08caf4a 100644 --- a/test/docker/expected_results/openssh_8.0p1_test2.txt +++ b/test/docker/expected_results/openssh_8.0p1_test2.txt @@ -12,9 +12,9 @@ # key exchange algorithms (kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76 - `- [info] default key exchange from OpenSSH 6.4 to 8.9 + `- [info] default key exchange from OpenSSH 7.4 to 8.9 (kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62 - `- [info] default key exchange from OpenSSH 6.4 to 8.9 + `- [info] default key exchange from OpenSSH 6.5 to 7.3 (kex) ecdh-sha2-nistp256 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency `- [info] available since OpenSSH 5.7, Dropbear SSH 2013.62 (kex) ecdh-sha2-nistp384 -- [fail] using elliptic curves that are suspected as being backdoored by the U.S. National Security Agency diff --git a/test/docker/expected_results/openssh_8.0p1_test3.json b/test/docker/expected_results/openssh_8.0p1_test3.json index 04fb66d..6e96ae2 100644 --- a/test/docker/expected_results/openssh_8.0p1_test3.json +++ b/test/docker/expected_results/openssh_8.0p1_test3.json @@ -105,7 +105,7 @@ "algorithm": "curve25519-sha256", "notes": { "info": [ - "default key exchange from OpenSSH 6.4 to 8.9", + "default key exchange from OpenSSH 7.4 to 8.9", "available since OpenSSH 7.4, Dropbear SSH 2018.76" ] } @@ -114,7 +114,7 @@ "algorithm": "curve25519-sha256@libssh.org", "notes": { "info": [ - "default key exchange from OpenSSH 6.4 to 8.9", + "default key exchange from OpenSSH 6.5 to 7.3", "available since OpenSSH 6.4, Dropbear SSH 2013.62" ] } diff --git a/test/docker/expected_results/openssh_8.0p1_test3.txt b/test/docker/expected_results/openssh_8.0p1_test3.txt index 7ab90f7..62d5dd4 100644 --- a/test/docker/expected_results/openssh_8.0p1_test3.txt +++ b/test/docker/expected_results/openssh_8.0p1_test3.txt @@ -12,9 +12,9 @@ # key exchange algorithms (kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76 - `- [info] default key exchange from OpenSSH 6.4 to 8.9 + `- [info] default key exchange from OpenSSH 7.4 to 8.9 (kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62 - `- [info] default key exchange from OpenSSH 6.4 to 8.9 + `- [info] default key exchange from OpenSSH 6.5 to 7.3 (kex) diffie-hellman-group-exchange-sha256 (4096-bit) -- [info] available since OpenSSH 4.4  `- [info] OpenSSH's GEX fallback mechanism was triggered during testing. Very old SSH clients will still be able to create connections using a 2048-bit modulus, though modern clients will use 4096. This can only be disabled by recompiling the code (see https://github.com/openssh/openssh-portable/blob/V_9_4/dh.c#L477). diff --git a/test/docker/expected_results/tinyssh_20190101_test1.json b/test/docker/expected_results/tinyssh_20190101_test1.json index 52bb5d8..4b54a86 100644 --- a/test/docker/expected_results/tinyssh_20190101_test1.json +++ b/test/docker/expected_results/tinyssh_20190101_test1.json @@ -43,7 +43,7 @@ "algorithm": "curve25519-sha256", "notes": { "info": [ - "default key exchange from OpenSSH 6.4 to 8.9", + "default key exchange from OpenSSH 7.4 to 8.9", "available since OpenSSH 7.4, Dropbear SSH 2018.76" ] } @@ -52,7 +52,7 @@ "algorithm": "curve25519-sha256@libssh.org", "notes": { "info": [ - "default key exchange from OpenSSH 6.4 to 8.9", + "default key exchange from OpenSSH 6.5 to 7.3", "available since OpenSSH 6.4, Dropbear SSH 2013.62" ] } diff --git a/test/docker/expected_results/tinyssh_20190101_test1.txt b/test/docker/expected_results/tinyssh_20190101_test1.txt index 7137eb6..2dcc036 100644 --- a/test/docker/expected_results/tinyssh_20190101_test1.txt +++ b/test/docker/expected_results/tinyssh_20190101_test1.txt @@ -5,9 +5,9 @@ # key exchange algorithms (kex) curve25519-sha256 -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76 - `- [info] default key exchange from OpenSSH 6.4 to 8.9 + `- [info] default key exchange from OpenSSH 7.4 to 8.9 (kex) curve25519-sha256@libssh.org -- [info] available since OpenSSH 6.4, Dropbear SSH 2013.62 - `- [info] default key exchange from OpenSSH 6.4 to 8.9 + `- [info] default key exchange from OpenSSH 6.5 to 7.3 (kex) sntrup4591761x25519-sha512@tinyssh.org -- [warn] using experimental algorithm `- [info] available since OpenSSH 8.0 `- [info] the sntrup4591761 algorithm was withdrawn, as it may not provide strong post-quantum security