Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backwards-compatible support for @cert-authority #9

Merged
merged 3 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ This repo ([github.com/skeema/knownhosts](https://github.com/skeema/knownhosts))

Although [golang.org/x/crypto/ssh/knownhosts](https://pkg.go.dev/golang.org/x/crypto/ssh/knownhosts) doesn't directly expose a way to query its known_host map, we use a subtle trick to do so: invoke the HostKeyCallback with a valid host but a bogus key. The resulting KeyError allows us to determine which public keys are actually present for that host.

By using this technique, [github.com/skeema/knownhosts](https://github.com/skeema/knownhosts) doesn't need to duplicate or re-implement any of the actual known_hosts management from [golang.org/x/crypto/ssh/knownhosts](https://pkg.go.dev/golang.org/x/crypto/ssh/knownhosts).
By using this technique, [github.com/skeema/knownhosts](https://github.com/skeema/knownhosts) doesn't need to duplicate any of the core known_hosts host-lookup logic from [golang.org/x/crypto/ssh/knownhosts](https://pkg.go.dev/golang.org/x/crypto/ssh/knownhosts).

## Populating ssh.ClientConfig.HostKeyAlgorithms based on known_hosts

Expand All @@ -43,14 +43,14 @@ import (
)

func sshConfigForHost(hostWithPort string) (*ssh.ClientConfig, error) {
kh, err := knownhosts.New("/home/myuser/.ssh/known_hosts")
kh, err := knownhosts.NewDB("/home/myuser/.ssh/known_hosts")
if err != nil {
return nil, err
}
config := &ssh.ClientConfig{
User: "myuser",
Auth: []ssh.AuthMethod{ /* ... */ },
HostKeyCallback: kh.HostKeyCallback(), // or, equivalently, use ssh.HostKeyCallback(kh)
HostKeyCallback: kh.HostKeyCallback(),
HostKeyAlgorithms: kh.HostKeyAlgorithms(hostWithPort),
}
return config, nil
Expand All @@ -64,15 +64,16 @@ If you wish to mimic the behavior of OpenSSH's `StrictHostKeyChecking=no` or `St
```golang
sshHost := "yourserver.com:22"
khPath := "/home/myuser/.ssh/known_hosts"
kh, err := knownhosts.New(khPath)
kh, err := knownhosts.NewDB(khPath)
if err != nil {
log.Fatal("Failed to read known_hosts: ", err)
}

// Create a custom permissive hostkey callback which still errors on hosts
// with changed keys, but allows unknown hosts and adds them to known_hosts
cb := ssh.HostKeyCallback(func(hostname string, remote net.Addr, key ssh.PublicKey) error {
err := kh(hostname, remote, key)
innerCallback := kh.HostKeyCallback()
err := innerCallback(hostname, remote, key)
if knownhosts.IsHostKeyChanged(err) {
return fmt.Errorf("REMOTE HOST IDENTIFICATION HAS CHANGED for host %s! This may indicate a MitM attack.", hostname)
} else if knownhosts.IsHostUnknown(err) {
Expand Down
27 changes: 23 additions & 4 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,26 @@ func ExampleNew() {
config := &ssh.ClientConfig{
User: "myuser",
Auth: []ssh.AuthMethod{ /* ... */ },
HostKeyCallback: kh.HostKeyCallback(), // or, equivalently, use ssh.HostKeyCallback(kh)
HostKeyCallback: kh.HostKeyCallback(),
HostKeyAlgorithms: kh.HostKeyAlgorithms(sshHost),
}
client, err := ssh.Dial("tcp", sshHost, config)
if err != nil {
log.Fatal("Failed to dial: ", err)
}
defer client.Close()
}

func ExampleNewDB() {
sshHost := "yourserver.com:22"
kh, err := knownhosts.NewDB("/home/myuser/.ssh/known_hosts")
if err != nil {
log.Fatal("Failed to read known_hosts: ", err)
}
config := &ssh.ClientConfig{
User: "myuser",
Auth: []ssh.AuthMethod{ /* ... */ },
HostKeyCallback: kh.HostKeyCallback(),
HostKeyAlgorithms: kh.HostKeyAlgorithms(sshHost),
}
client, err := ssh.Dial("tcp", sshHost, config)
Expand All @@ -32,15 +51,16 @@ func ExampleNew() {
func ExampleWriteKnownHost() {
sshHost := "yourserver.com:22"
khPath := "/home/myuser/.ssh/known_hosts"
kh, err := knownhosts.New(khPath)
kh, err := knownhosts.NewDB(khPath)
if err != nil {
log.Fatal("Failed to read known_hosts: ", err)
}

// Create a custom permissive hostkey callback which still errors on hosts
// with changed keys, but allows unknown hosts and adds them to known_hosts
cb := ssh.HostKeyCallback(func(hostname string, remote net.Addr, key ssh.PublicKey) error {
err := kh(hostname, remote, key)
innerCallback := kh.HostKeyCallback()
err := innerCallback(hostname, remote, key)
if knownhosts.IsHostKeyChanged(err) {
return fmt.Errorf("REMOTE HOST IDENTIFICATION HAS CHANGED for host %s! This may indicate a MitM attack.", hostname)
} else if knownhosts.IsHostUnknown(err) {
Expand Down Expand Up @@ -70,5 +90,4 @@ func ExampleWriteKnownHost() {
log.Fatal("Failed to dial: ", err)
}
defer client.Close()

}
Loading
Loading