ssh-audit icon indicating copy to clipboard operation
ssh-audit copied to clipboard

builtin hardened policy vs ED25519 CA

Open zomfg opened this issue 4 years ago • 2 comments

  • OpenSSH 8.4p1 / Debian 11
  • Hardened OpenSSH Server v8.4 (version 1) policy

server host keys signed with ED25519 CA

$ ./ssh-audit.py debian11 -P "Hardened OpenSSH Server v8.4 (version 1)"
Host: debian11
Policy: Hardened OpenSSH Server v8.4 (version 1)
Result: ❌ Failed!

Errors:
  * RSA CA key ([email protected]) sizes did not match.
    - Expected: 4096
    - Actual:   0

  * RSA CA key ([email protected]) sizes did not match.
    - Expected: 4096
    - Actual:   0
$ ./ssh-audit.py debian11
# general
(gen) banner: SSH-2.0-OpenSSH_8.4p1
(gen) software: OpenSSH 8.4p1
(gen) compatibility: OpenSSH 7.8+, Dropbear SSH 2018.76+
(gen) compression: enabled ([email protected])

# key exchange algorithms
(kex) curve25519-sha256                     -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76
(kex) [email protected]          -- [info] available since OpenSSH 6.5, Dropbear SSH 2013.62
(kex) diffie-hellman-group16-sha512         -- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73
(kex) diffie-hellman-group18-sha512         -- [info] available since OpenSSH 7.3
(kex) diffie-hellman-group-exchange-sha256 (2048-bit) -- [info] available since OpenSSH 4.4

# host-key algorithms
(key) rsa-sha2-512 (4096-bit)               -- [info] available since OpenSSH 7.2
(key) rsa-sha2-256 (4096-bit)               -- [info] available since OpenSSH 7.2
(key) [email protected] (4096-bit) -- [info] available since OpenSSH 7.8
(key) [email protected] (4096-bit) -- [info] available since OpenSSH 7.8
(key) ssh-ed25519                           -- [info] available since OpenSSH 6.5
(key) [email protected] (4096-bit) -- [info] available since OpenSSH 6.5

# encryption algorithms (ciphers)
(enc) [email protected]         -- [info] available since OpenSSH 6.5
                                            `- [info] default cipher since OpenSSH 6.9.
(enc) [email protected]                -- [info] available since OpenSSH 6.2
(enc) [email protected]                -- [info] available since OpenSSH 6.2
(enc) aes256-ctr                            -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52
(enc) aes192-ctr                            -- [info] available since OpenSSH 3.7
(enc) aes128-ctr                            -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52

# message authentication code algorithms
(mac) [email protected]         -- [info] available since OpenSSH 6.2
(mac) [email protected]         -- [info] available since OpenSSH 6.2
(mac) [email protected]              -- [info] available since OpenSSH 6.2

# fingerprints
(fin) ssh-ed25519: SHA256:czmBs9YLHz3r9Y4ETVrSpkZwaTcCmZ+1ET7q8oipveo
(fin) ssh-rsa: SHA256:AmB9U/EExKoewKgfX5rGtVsY75Z4rt3y0+tmWOqzjyc

# algorithm recommendations (for OpenSSH 8.4)
(rec) +diffie-hellman-group14-sha256        -- kex algorithm to append

signed with RSA CA

$ ./ssh-audit.py debian11 -P "Hardened OpenSSH Server v8.4 (version 1)"
Host:   debian11
Policy: Hardened OpenSSH Server v8.4 (version 1)
Result: ✔ Passed
$ ./ssh-audit.py debian11
# general
(gen) banner: SSH-2.0-OpenSSH_8.4p1
(gen) software: OpenSSH 8.4p1
(gen) compatibility: OpenSSH 7.8+, Dropbear SSH 2018.76+
(gen) compression: enabled ([email protected])

# key exchange algorithms
(kex) curve25519-sha256                     -- [info] available since OpenSSH 7.4, Dropbear SSH 2018.76
(kex) [email protected]          -- [info] available since OpenSSH 6.5, Dropbear SSH 2013.62
(kex) diffie-hellman-group16-sha512         -- [info] available since OpenSSH 7.3, Dropbear SSH 2016.73
(kex) diffie-hellman-group18-sha512         -- [info] available since OpenSSH 7.3
(kex) diffie-hellman-group-exchange-sha256 (2048-bit) -- [info] available since OpenSSH 4.4

# host-key algorithms
(key) rsa-sha2-512 (4096-bit)               -- [info] available since OpenSSH 7.2
(key) rsa-sha2-256 (4096-bit)               -- [info] available since OpenSSH 7.2
(key) [email protected] (4096-bit cert/4096-bit CA) -- [info] available since OpenSSH 7.8
(key) [email protected] (4096-bit cert/4096-bit CA) -- [info] available since OpenSSH 7.8
(key) ssh-ed25519                           -- [info] available since OpenSSH 6.5
(key) [email protected] (4096-bit cert/4096-bit CA) -- [info] available since OpenSSH 6.5

# encryption algorithms (ciphers)
(enc) [email protected]         -- [info] available since OpenSSH 6.5
                                            `- [info] default cipher since OpenSSH 6.9.
(enc) [email protected]                -- [info] available since OpenSSH 6.2
(enc) [email protected]                -- [info] available since OpenSSH 6.2
(enc) aes256-ctr                            -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52
(enc) aes192-ctr                            -- [info] available since OpenSSH 3.7
(enc) aes128-ctr                            -- [info] available since OpenSSH 3.7, Dropbear SSH 0.52

# message authentication code algorithms
(mac) [email protected]         -- [info] available since OpenSSH 6.2
(mac) [email protected]         -- [info] available since OpenSSH 6.2
(mac) [email protected]              -- [info] available since OpenSSH 6.2

# fingerprints
(fin) ssh-ed25519: SHA256:czmBs9YLHz3r9Y4ETVrSpkZwaTcCmZ+1ET7q8oipveo
(fin) ssh-rsa: SHA256:AmB9U/EExKoewKgfX5rGtVsY75Z4rt3y0+tmWOqzjyc

# algorithm recommendations (for OpenSSH 8.4)
(rec) +diffie-hellman-group14-sha256        -- kex algorithm to append

it's not clear if it's a detection bug/difficulty or a case of "ed25519 ca = bad" the fact that it's not showing something similar to (4096-bit cert/4096-bit CA) for ed25519 CA makes me think that it's not detecting, but then is it a policy issue or elsewhere? Thank you

zomfg avatar Sep 11 '21 10:09 zomfg

I took a look at this and found that the code isn't correctly handling the case of mixed ED25519 CAs with RSA host keys. I'll be taking a closer look at this and fixing it shortly.

it's not clear if it's a detection bug/difficulty or a case of "ed25519 ca = bad"

ED25519 CAs are good, actually.

Thanks for reporting this!

-- Joseph S. Testa II Founder & Principal Security Consultant Positron Security

jtesta avatar Sep 13 '21 14:09 jtesta

awesome! thank you

zomfg avatar Sep 13 '21 14:09 zomfg

@zomfg: At long last, I've finally checked in a fix for this issue! (See https://github.com/jtesta/ssh-audit/commit/263267c5ad013d314574fcd9bc6a744699e226c5.) If you have an opportunity to give it a try, please let me know how it works for you.

Thanks for reporting!!

jtesta avatar Apr 25 '23 13:04 jtesta

Hi! I get this

Traceback (most recent call last):
  File ./ssh-audit.py", line 16, in <module>
    exit_code = main()
  File "src/ssh_audit/ssh_audit.py", line 1380, in main
    ret = audit(out, aconf)
  File "src/ssh_audit/ssh_audit.py", line 1104, in audit
    program_retval = exitcodes.GOOD if evaluate_policy(out, aconf, banner, s.client_host, kex=kex) else exitcodes.FAILURE
  File "src/ssh_audit/ssh_audit.py", line 566, in evaluate_policy
    passed, error_struct, error_str = aconf.policy.evaluate(banner, kex)
  File "src/ssh_audit/policy.py", line 400, in evaluate
    expected_hostkey_size = self._hostkey_sizes[hostkey_type]['hostkey_size']
TypeError: 'int' object is not subscriptable

and indeed, at that point self._hostkey_sizes seems to be

{'rsa-sha2-256': 4096, 'rsa-sha2-512': 4096}

looks like format mismatch

zomfg avatar Apr 25 '23 17:04 zomfg

Is the target host available on the public Internet? If so, I'd like to test against it. Could I get the host/IP? You could e-mail it to me at jtesta at-sign positronsecurity dot com, if you'd rather not list it here.

I suppose the policy file you used would make it easier to debug as well.

jtesta avatar Apr 25 '23 17:04 jtesta

it's an internal work thing so wont be much help ok so this is what happens same test as originally

$ ./ssh-audit.py debian11 -P "Hardened OpenSSH Server v8.4 (version 1)"

I get the crash above. Looking here https://github.com/jtesta/ssh-audit/blob/0bfb5d6979420c295bcc50c6c4213c2dd3a8e2f2/src/ssh_audit/policy.py#L399-L400 self._hostkey_sizes and server_host_keys are like this

{'rsa-sha2-256': 4096, 'rsa-sha2-512': 4096}
{
  'rsa-sha2-256': {'raw_hostkey_bytes': b'\x00\x00\x00\x07ssh-rsa...', 'hostkey_size': 4096, 'ca_key_type': '', 'ca_key_size': 0},
  'ssh-rsa': {'raw_hostkey_bytes': b'\x00\x00\x00\x07ssh-rsa...', 'hostkey_size': 4096, 'ca_key_type': '', 'ca_key_size': 0},
  'rsa-sha2-512': {'raw_hostkey_bytes': b'\x00\x00\x00\x07ssh-rsa...', 'hostkey_size': 4096, 'ca_key_type': '', 'ca_key_size': 0},
  '[email protected]': {'raw_hostkey_bytes': b'\x00\x00\x00\[email protected]...', 'hostkey_size': 4096, 'ca_key_type': 'ssh-ed25519', 'ca_key_size': 256},
  '[email protected]': {'raw_hostkey_bytes': b'\x00\x00\x00\[email protected]...', 'hostkey_size': 4096, 'ca_key_type': 'ssh-ed25519', 'ca_key_size': 256},
  'ssh-ed25519': {'raw_hostkey_bytes': b'\x00\x00\x00\x0bssh-ed25519...', 'hostkey_size': 256, 'ca_key_type': '', 'ca_key_size': 0},
  '[email protected]': {'raw_hostkey_bytes': b'\x00\x00\x00 [email protected]...', 'hostkey_size': 256, 'ca_key_type': 'ssh-ed25519', 'ca_key_size': 256}
}

However it works with a generated certpolicy.txt

$ ./ssh-audit.py -M certpolicy.txt debian11
$ ./ssh-audit.py -P certpolicy.txt debian11
Host:   debian11
Policy: Custom Policy (based on debian11 on 2023/04/25) (version 1)
Result: ✔ Passed

and self._hostkey_sizes looks like

{
  'rsa-sha2-256': {'hostkey_size': 4096, 'ca_key_type': '', 'ca_key_size': 0, 'raw_hostkey_bytes': b''},
  'ssh-rsa': {'hostkey_size': 4096, 'ca_key_type': '', 'ca_key_size': 0, 'raw_hostkey_bytes': b''},
  'rsa-sha2-512': {'hostkey_size': 4096, 'ca_key_type': '', 'ca_key_size': 0, 'raw_hostkey_bytes': b''},
  '[email protected]': {'hostkey_size': 4096, 'ca_key_type': 'ssh-ed25519', 'ca_key_size': 256, 'raw_hostkey_bytes': b''},
  '[email protected]': {'hostkey_size': 4096, 'ca_key_type': 'ssh-ed25519', 'ca_key_size': 256, 'raw_hostkey_bytes': b''},
  'ssh-ed25519': {'hostkey_size': 256, 'ca_key_type': '', 'ca_key_size': 0, 'raw_hostkey_bytes': b''},
  '[email protected]': {'hostkey_size': 256, 'ca_key_type': 'ssh-ed25519', 'ca_key_size': 256, 'raw_hostkey_bytes': b''}
}
#
# Custom policy based on debian11 (created on 2023/04/25)
#

# The name of this policy (displayed in the output during scans).  Must be in quotes.
name = "Custom Policy (based on debian11 on 2023/04/25)"

# The version of this policy (displayed in the output during scans).  Not parsed, and may be any value, including strings.
version = 1

# The banner that must match exactly.  Commented out to ignore banners, since minor variability in the banner is sometimes normal.
# banner = "SSH-2.0-OpenSSH_8.4p1 Debian-5+deb11u1"

# The compression options that must match exactly (order matters).  Commented out to ignore by default.
# compressions = none, [email protected]

# Dictionary containing all host key and size information.  Optionally contains the certificate authority's signature algorithm ('ca_key_type') and signature length ('ca_key_size'), if any.
host_key_sizes = {"rsa-sha2-256": {"hostkey_size": 4096}, "ssh-rsa": {"hostkey_size": 4096}, "rsa-sha2-512": {"hostkey_size": 4096}, "[email protected]": {"hostkey_size": 4096, "ca_key_type": "ssh-ed25519", "ca_key_size": 256}, "[email protected]": {"hostkey_size": 4096, "ca_key_type": "ssh-ed25519", "ca_key_size": 256}, "ssh-ed25519": {"hostkey_size": 256}, "[email protected]": {"hostkey_size": 256, "ca_key_type": "ssh-ed25519", "ca_key_size": 256}}

# Group exchange DH modulus sizes.
dh_modulus_sizes = {"diffie-hellman-group-exchange-sha256": 2048}

# The host key types that must match exactly (order matters).
host keys = rsa-sha2-512, rsa-sha2-256, [email protected], [email protected], ssh-ed25519, [email protected]

# Host key types that may optionally appear.
#optional host keys = [email protected],[email protected],[email protected],[email protected],[email protected]

# The key exchange algorithms that must match exactly (order matters).
key exchanges = curve25519-sha256, [email protected], diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256

# The ciphers that must match exactly (order matters).
ciphers = [email protected], [email protected], [email protected], aes256-ctr, aes192-ctr, aes128-ctr

# The MACs that must match exactly (order matters).
macs = [email protected], [email protected], [email protected]

Now if I alter the built-in Hardened OpenSSH Server v8.4 (version 1) policy as follows

'hostkey_sizes': {'rsa-sha2-256': {'hostkey_size': 4096, 'ca_key_type': '', 'ca_key_size': 0, 'raw_hostkey_bytes': b''}, 'rsa-sha2-512': {'hostkey_size': 4096, 'ca_key_type': '', 'ca_key_size': 0, 'raw_hostkey_bytes': b''}}

it doesn't crash

$ ./ssh-audit.py -P "Hardened OpenSSH Server v8.4 (version 1)" debian11
Host:   debian11
Policy: Hardened OpenSSH Server v8.4 (version 1)
Result: ✔ Passed

zomfg avatar Apr 25 '23 19:04 zomfg

@zomfg : Thanks for the quick response! I updated the master branch with a fix for the built-in policies. Might you be able to test it again?

jtesta avatar Apr 26 '23 20:04 jtesta

We are back more or less to the original issue

Host:   debian11
Policy: Hardened OpenSSH Server v8.4 (version 2)
Result: ❌ Failed!

Errors:
  * CA signature type did not match.
    - Expected: ssh-rsa
    - Actual:   ssh-ed25519

  * CA signature type did not match.
    - Expected: ssh-rsa
    - Actual:   ssh-ed25519

builtin policy assumes same type for hostkey and CA so mixing types doesn't work

{'[email protected]': {'hostkey_size': 4096, 'ca_key_type': 'ssh-rsa', 'ca_key_size': 4096, 'raw_hostkey_bytes': b''}}

while the custom policy dump saves mixed types

{'[email protected]': {'hostkey_size': 4096, 'ca_key_type': 'ssh-ed25519', 'ca_key_size': 256, 'raw_hostkey_bytes': b''}}

so maybe CA types/sizes could be something like

{
  '[email protected]': {
    'hostkey_size': 4096,
    'ca': [('ssh-rsa', 4096), ('ssh-ed25519', 256)],
    'raw_hostkey_bytes': b''
  }
}

and then

if (server_ca_key_type, server_ca_key_size) in self._hostkey_sizes[hostkey_type]['ca']

zomfg avatar Apr 26 '23 21:04 zomfg

Supporting multiple CA types per individual host key for built-in policies wasn't part of the plan here. The primary goal was to support custom policies with mixed host key/CA key types (i.e.: an RSA host key signed by an ED25519 CA). Does the recent commit fix this part for you?

If you'd like the built-in policies to support multiple CAs per host key, that would be a valid feature request for a future release. Feel free to open a new issue!

jtesta avatar Apr 26 '23 21:04 jtesta

I just realized that the original issue (and title) do talk about the built-in policies. I suppose at some point along the way, I re-framed it in my mind in terms of getting basic support for mixed host key/CA key support (which I think I succeeded in).

Still... this is an improvement over what we had before. And we can still track the desired functionality for a future release.

jtesta avatar Apr 26 '23 23:04 jtesta

oh yeah this is an improvement for sure and yeah if you want to close this one and deal with further improvements later is fine by me oh and thank you for adding policies for recent versions!

zomfg avatar Apr 28 '23 13:04 zomfg

Sure thing! And thanks for reporting this, and thanks for helping test!

jtesta avatar Apr 28 '23 13:04 jtesta