skeema / knownhosts

Go SSH known_hosts wrapper with host key lookup
Apache License 2.0
32 stars 4 forks source link

OpenSSH vs golang with @revoked #11

Closed abakum closed 4 months ago

abakum commented 4 months ago

OpenSSH and golang understand known_hosts differently if it has marker @revoked known_hosts:

* ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDIXl2FNkUIHkxjfhb5oaonzI1zsXL6rUW9mrFXcJ5xP
@revoked 10.161.115.143 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDIXl2FNkUIHkxjfhb5oaonzI1zsXL6rUW9mrFXcJ5xP

config:

Host 12
 HostName 10.161.115.143
 Port 12

OpenSSH ssh 12 :

debug1: Host '[10.161.115.143]:12' is known and matches the ED25519 host key.

golang dssh 12:

main.go:229: sshStart failed: new conn [10.161.115.143:12] failed: ssh: handshake failed: knownhosts: key is revoked

If known_hosts:

* ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDIXl2FNkUIHkxjfhb5oaonzI1zsXL6rUW9mrFXcJ5xP
@revoked [10.161.115.143]:12 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDIXl2FNkUIHkxjfhb5oaonzI1zsXL6rUW9mrFXcJ5xP

then OpenSSH and golang react the same way.

evanelias commented 4 months ago

Thank you for the report, but more concrete information on the nature of the issue is needed:

abakum commented 4 months ago

Does this only come up with non-standard ports, or can it be reproduced with standard ports?

No, but OpenSSH acts predictably, unlike golang known_hosts:

* ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBARrpBl177hs/ykMnXHfkjmyKTbsax/vtSl+rInZvJoF8LfJaWCZSrai0uD5qRuYhy4QnJs563NBTmCgSBhm/MA=
@revoked [10.161.115.189]:2222 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBARrpBl177hs/ykMnXHfkjmyKTbsax/vtSl+rInZvJoF8LfJaWCZSrai0uD5qRuYhy4QnJs563NBTmCgSBhm/MA=

ssh user_@10.161.115.189 - connect dssh user_@10.161.115.189 - not connect

* ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDIXl2FNkUIHkxjfhb5oaonzI1zsXL6rUW9mrFXcJ5xP
@revoked 10.161.115.143 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDIXl2FNkUIHkxjfhb5oaonzI1zsXL6rUW9mrFXcJ5xP

ssh -p 12 koka@10.161.115.143 - connect dssh -p 12 koka@10.161.115.143 - not connect

Does this only come up if the same key is both used as @revoked as well as a normal non-revoked knownhosts line? (That situation seems incredibly rare in the real world I'd think?)

I agree. I just tried to find possible problems from a different implementation of wildcards

Does this reproduce when using x/crypto/ssh/knownhosts instead of skeema/knownhosts?

Yes. This is the behavior of x/crypto/ssh/knownhosts - skeema/knownhosts just inherited it. Maybe you can fix it?

When you say "then OpenSSH and golang react the same way" at the end, what specifically do they do?

Both not connected

* ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDIXl2FNkUIHkxjfhb5oaonzI1zsXL6rUW9mrFXcJ5xP
@revoked [10.161.115.143]:12 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDIXl2FNkUIHkxjfhb5oaonzI1zsXL6rUW9mrFXcJ5xP

ssh -p 12 koka@10.161.115.143 - not connect dssh -p 12 koka@10.161.115.143 - not connect

evanelias commented 4 months ago

It looks like x/crypto/ssh/knownhosts treats @revoked keys as applying to all hosts. The host pattern on the same line is ignored. It just tracks a map of revoked keys keyed only by a serialized string version of the host public key:

        db.revoked[string(key.Marshal())] = &KnownKey{
            Key:      key,
            Filename: filename,
            Line:     linenum,
        }

and then during the callback check, it first sees if the key is known to be revoked, without even looking at the host at all:

    if revoked := db.revoked[string(remoteKey.Marshal())]; revoked != nil {
        return &RevokedError{Revoked: *revoked}
    }

In contrast I assume OpenSSH is actually looking at the host pattern, and only applying @revoked to that specific pattern.

Honestly, x/crypto/ssh/knownhosts's behavior here makes more sense to me than OpenSSH's. The purpose of @revoked is to mark a host public key as unusable because the corresponding private key has been leaked/compromised. In that situation, you would presumably want to ban the key in general, regardless of what host is using it.

In any case, sorry but I don't think there is any way to adjust this behavior here in skeema/knownhosts. We rely on x/crypto/ssh/knownhosts for the core logic, and x/crypto/ssh/knownhosts will always return a RevokedError for any revoked key regardless of the host field. We could intercept that error in a wrapped callback, but then there's no way to re-do the key check in a way that then ignores a particular @revoked line.