chore: make deps, go mod vendor

This commit is contained in:
2024-12-02 01:45:06 +01:00
parent f664599836
commit 31fa9b1a7a
598 changed files with 37898 additions and 18309 deletions

36
vendor/github.com/skeema/knownhosts/CONTRIBUTING.md generated vendored Normal file
View File

@ -0,0 +1,36 @@
# Contributing to skeema/knownhosts
Thank you for your interest in contributing! This document provides guidelines for submitting pull requests.
### Link to an issue
Before starting the pull request process, initial discussion should take place on a GitHub issue first. For bug reports, the issue should track the open bug and confirm it is reproducible. For feature requests, the issue should cover why the feature is necessary.
In the issue comments, discuss your suggested approach for a fix/implementation, and please wait to get feedback before opening a pull request.
### Test coverage
In general, please provide reasonably thorough test coverage. Whenever possible, your PR should aim to match or improve the overall test coverage percentage of the package. You can run tests and check coverage locally using `go test -cover`. We also have CI automation in GitHub Actions which will comment on each pull request with a coverage percentage.
That said, it is fine to submit an initial draft / work-in-progress PR without coverage, if you are waiting on implementation feedback before writing the tests.
We intentionally avoid hard-coding SSH keys or known_hosts files into the test logic. Instead, the tests generate new keys and then use them to generate a known_hosts file, which is then cached/reused for that overall test run, in order to keep performance reasonable.
### Documentation
Exported types require doc comments. The linter CI step will catch this if missing.
### Backwards compatibility
Because this package is imported by [nearly 7000 repos on GitHub](https://github.com/skeema/knownhosts/network/dependents), we must be very strict about backwards compatibility of exported symbols and function signatures.
Backwards compatibility can be very tricky in some situations. In this case, a maintainer may need to add additional commits to your branch to adjust the approach. Please do not take offense if this occurs; it is sometimes simply faster to implement a refactor on our end directly. When the PR/branch is merged, a merge commit will be used, to ensure your commits appear as-is in the repo history and are still properly credited to you.
### Avoid rewriting core x/crypto/ssh/knownhosts logic
skeema/knownhosts is intended to be a relatively thin *wrapper* around x/crypto/ssh/knownhosts, without duplicating or re-implementing the core known_hosts file parsing and host key handling logic. Importers of this package should be confident that it can be used as a nearly-drop-in replacement for x/crypto/ssh/knownhosts without introducing substantial risk, security flaws, parser differentials, or unexpected behavior changes.
To solve shortcomings in x/crypto/ssh/knownhosts, we try to come up with workarounds that still utilize x/crypto/ssh/knownhosts functionality whenever possible.
Some bugs in x/crypto/ssh/knownhosts do require re-reading the known_hosts file here to solve, but we make that *optional* by offering separate constructors/types with and without that behavior.

View File

@ -1,31 +1,33 @@
# knownhosts: enhanced Golang SSH known_hosts management
[![build status](https://img.shields.io/github/actions/workflow/status/skeema/knownhosts/tests.yml?branch=main)](https://github.com/skeema/knownhosts/actions)
[![code coverage](https://img.shields.io/coveralls/skeema/knownhosts.svg)](https://coveralls.io/r/skeema/knownhosts)
[![godoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://pkg.go.dev/github.com/skeema/knownhosts)
> This repo is brought to you by [Skeema](https://github.com/skeema/skeema), a
> declarative pure-SQL schema management system for MySQL and MariaDB. Our
> premium products include extensive [SSH tunnel](https://www.skeema.io/docs/options/#ssh)
> premium products include extensive [SSH tunnel](https://www.skeema.io/docs/features/ssh/)
> functionality, which internally makes use of this package.
Go provides excellent functionality for OpenSSH known_hosts files in its
external package [golang.org/x/crypto/ssh/knownhosts](https://pkg.go.dev/golang.org/x/crypto/ssh/knownhosts).
However, that package is somewhat low-level, making it difficult to implement full known_hosts management similar to command-line `ssh`'s behavior for `StrictHostKeyChecking=no` configuration.
However, that package is somewhat low-level, making it difficult to implement full known_hosts management similar to OpenSSH's command-line behavior. Additionally, [golang.org/x/crypto/ssh/knownhosts](https://pkg.go.dev/golang.org/x/crypto/ssh/knownhosts) has several known issues in edge cases, some of which have remained open for multiple years.
This repo ([github.com/skeema/knownhosts](https://github.com/skeema/knownhosts)) is a thin wrapper package around [golang.org/x/crypto/ssh/knownhosts](https://pkg.go.dev/golang.org/x/crypto/ssh/knownhosts), adding the following functionality:
Package [github.com/skeema/knownhosts](https://github.com/skeema/knownhosts) provides a *thin wrapper* around [golang.org/x/crypto/ssh/knownhosts](https://pkg.go.dev/golang.org/x/crypto/ssh/knownhosts), adding the following improvements and fixes without duplicating its core logic:
* Look up known_hosts public keys for any given host
* Auto-populate ssh.ClientConfig.HostKeyAlgorithms easily based on known_hosts, providing a solution for [golang/go#29286](https://github.com/golang/go/issues/29286)
* Auto-populate ssh.ClientConfig.HostKeyAlgorithms easily based on known_hosts, providing a solution for [golang/go#29286](https://github.com/golang/go/issues/29286). (This also properly handles cert algorithms for hosts using CA keys when [using the NewDB constructor](#enhancements-requiring-extra-parsing) added in skeema/knownhosts v1.3.0.)
* Properly match wildcard hostname known_hosts entries regardless of port number, providing a solution for [golang/go#52056](https://github.com/golang/go/issues/52056). (Added in v1.3.0; requires [using the NewDB constructor](#enhancements-requiring-extra-parsing))
* Write new known_hosts entries to an io.Writer
* Properly format/normalize new known_hosts entries containing ipv6 addresses, providing a solution for [golang/go#53463](https://github.com/golang/go/issues/53463)
* Determine if an ssh.HostKeyCallback's error corresponds to a host whose key has changed (indicating potential MitM attack) vs a host that just isn't known yet
* Easily determine if an ssh.HostKeyCallback's error corresponds to a host whose key has changed (indicating potential MitM attack) vs a host that just isn't known yet
## How host key lookup works
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
@ -42,20 +44,33 @@ 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
}
```
## Enhancements requiring extra parsing
Originally, this package did not re-read/re-parse the known_hosts files at all, relying entirely on [golang.org/x/crypto/ssh/knownhosts](https://pkg.go.dev/golang.org/x/crypto/ssh/knownhosts) for all known_hosts file reading and processing. This package only offered a constructor called `New`, returning a host key callback, identical to the call pattern of [golang.org/x/crypto/ssh/knownhosts](https://pkg.go.dev/golang.org/x/crypto/ssh/knownhosts) but with extra methods available on the callback type.
However, a couple shortcomings in [golang.org/x/crypto/ssh/knownhosts](https://pkg.go.dev/golang.org/x/crypto/ssh/knownhosts) cannot possibly be solved without re-reading the known_hosts file. Therefore, as of v1.3.0 of this package, we now offer an alternative constructor `NewDB`, which does an additional read of the known_hosts file (after the one from [golang.org/x/crypto/ssh/knownhosts](https://pkg.go.dev/golang.org/x/crypto/ssh/knownhosts)), in order to detect:
* @cert-authority lines, so that we can correctly return cert key algorithms instead of normal host key algorithms when appropriate
* host pattern wildcards, so that we can match OpenSSH's behavior for non-standard port numbers, unlike how [golang.org/x/crypto/ssh/knownhosts](https://pkg.go.dev/golang.org/x/crypto/ssh/knownhosts) normally treats them
Aside from *detecting* these special cases, this package otherwise still directly uses [golang.org/x/crypto/ssh/knownhosts](https://pkg.go.dev/golang.org/x/crypto/ssh/knownhosts) for host lookups and all other known_hosts file processing. We do **not** fork or re-implement those core behaviors of [golang.org/x/crypto/ssh/knownhosts](https://pkg.go.dev/golang.org/x/crypto/ssh/knownhosts).
The performance impact of this extra known_hosts read should be minimal, as the file should typically be in the filesystem cache already from the original read by [golang.org/x/crypto/ssh/knownhosts](https://pkg.go.dev/golang.org/x/crypto/ssh/knownhosts). That said, users who wish to avoid the extra read can stay with the `New` constructor, which intentionally retains its pre-v1.3.0 behavior as-is. However, the extra fixes for @cert-authority and host pattern wildcards will not be enabled in that case.
## Writing new known_hosts entries
If you wish to mimic the behavior of OpenSSH's `StrictHostKeyChecking=no` or `StrictHostKeyChecking=ask`, this package provides a few functions to simplify this task. For example:
@ -63,7 +78,7 @@ 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)
}
@ -71,7 +86,8 @@ if err != nil {
// 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) {

View File

@ -3,11 +3,14 @@
package knownhosts
import (
"bufio"
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"net"
"os"
"sort"
"strings"
@ -15,23 +18,133 @@ import (
xknownhosts "golang.org/x/crypto/ssh/knownhosts"
)
// HostKeyCallback wraps ssh.HostKeyCallback with an additional method to
// perform host key algorithm lookups from the known_hosts entries.
type HostKeyCallback ssh.HostKeyCallback
// New creates a host key callback from the given OpenSSH host key files. The
// returned value may be used in ssh.ClientConfig.HostKeyCallback by casting it
// to ssh.HostKeyCallback, or using its HostKeyCallback method. Otherwise, it
// operates the same as the New function in golang.org/x/crypto/ssh/knownhosts.
func New(files ...string) (HostKeyCallback, error) {
cb, err := xknownhosts.New(files...)
return HostKeyCallback(cb), err
// HostKeyDB wraps logic in golang.org/x/crypto/ssh/knownhosts with additional
// behaviors, such as the ability to perform host key/algorithm lookups from
// known_hosts entries.
type HostKeyDB struct {
callback ssh.HostKeyCallback
isCert map[string]bool // keyed by "filename:line"
isWildcard map[string]bool // keyed by "filename:line"
}
// HostKeyCallback simply casts the receiver back to ssh.HostKeyCallback, for
// use in ssh.ClientConfig.HostKeyCallback.
func (hkcb HostKeyCallback) HostKeyCallback() ssh.HostKeyCallback {
return ssh.HostKeyCallback(hkcb)
// NewDB creates a HostKeyDB from the given OpenSSH known_hosts file(s). It
// reads and parses the provided files one additional time (beyond logic in
// golang.org/x/crypto/ssh/knownhosts) in order to:
//
// - Handle CA lines properly and return ssh.CertAlgo* values when calling the
// HostKeyAlgorithms method, for use in ssh.ClientConfig.HostKeyAlgorithms
// - Allow * wildcards in hostnames to match on non-standard ports, providing
// a workaround for https://github.com/golang/go/issues/52056 in order to
// align with OpenSSH's wildcard behavior
//
// When supplying multiple files, their order does not matter.
func NewDB(files ...string) (*HostKeyDB, error) {
cb, err := xknownhosts.New(files...)
if err != nil {
return nil, err
}
hkdb := &HostKeyDB{
callback: cb,
isCert: make(map[string]bool),
isWildcard: make(map[string]bool),
}
// Re-read each file a single time, looking for @cert-authority lines. The
// logic for reading the file is designed to mimic hostKeyDB.Read from
// golang.org/x/crypto/ssh/knownhosts
for _, filename := range files {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
scanner := bufio.NewScanner(f)
lineNum := 0
for scanner.Scan() {
lineNum++
line := scanner.Bytes()
line = bytes.TrimSpace(line)
// Does the line start with "@cert-authority" followed by whitespace?
if len(line) > 15 && bytes.HasPrefix(line, []byte("@cert-authority")) && (line[15] == ' ' || line[15] == '\t') {
mapKey := fmt.Sprintf("%s:%d", filename, lineNum)
hkdb.isCert[mapKey] = true
line = bytes.TrimSpace(line[16:])
}
// truncate line to just the host pattern field
if i := bytes.IndexAny(line, "\t "); i >= 0 {
line = line[:i]
}
// Does the host pattern contain a * wildcard and no specific port?
if i := bytes.IndexRune(line, '*'); i >= 0 && !bytes.Contains(line[i:], []byte("]:")) {
mapKey := fmt.Sprintf("%s:%d", filename, lineNum)
hkdb.isWildcard[mapKey] = true
}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("knownhosts: %s:%d: %w", filename, lineNum, err)
}
}
return hkdb, nil
}
// HostKeyCallback returns an ssh.HostKeyCallback. This can be used directly in
// ssh.ClientConfig.HostKeyCallback, as shown in the example for NewDB.
// Alternatively, you can wrap it with an outer callback to potentially handle
// appending a new entry to the known_hosts file; see example in WriteKnownHost.
func (hkdb *HostKeyDB) HostKeyCallback() ssh.HostKeyCallback {
// Either NewDB found no wildcard host patterns, or hkdb was created from
// HostKeyCallback.ToDB in which case we didn't scan known_hosts for them:
// return the callback (which came from x/crypto/ssh/knownhosts) as-is
if len(hkdb.isWildcard) == 0 {
return hkdb.callback
}
// If we scanned for wildcards and found at least one, return a wrapped
// callback with extra behavior: if the host lookup found no matches, and the
// host arg had a non-standard port, re-do the lookup on standard port 22. If
// that second call returns a *xknownhosts.KeyError, filter down any resulting
// Want keys to known wildcard entries.
f := func(hostname string, remote net.Addr, key ssh.PublicKey) error {
callbackErr := hkdb.callback(hostname, remote, key)
if callbackErr == nil || IsHostKeyChanged(callbackErr) { // hostname has known_host entries as-is
return callbackErr
}
justHost, port, splitErr := net.SplitHostPort(hostname)
if splitErr != nil || port == "" || port == "22" { // hostname already using standard port
return callbackErr
}
// If we reach here, the port was non-standard and no known_host entries
// were found for the non-standard port. Try again with standard port.
if tcpAddr, ok := remote.(*net.TCPAddr); ok && tcpAddr.Port != 22 {
remote = &net.TCPAddr{
IP: tcpAddr.IP,
Port: 22,
Zone: tcpAddr.Zone,
}
}
callbackErr = hkdb.callback(justHost+":22", remote, key)
var keyErr *xknownhosts.KeyError
if errors.As(callbackErr, &keyErr) && len(keyErr.Want) > 0 {
wildcardKeys := make([]xknownhosts.KnownKey, 0, len(keyErr.Want))
for _, wantKey := range keyErr.Want {
if hkdb.isWildcard[fmt.Sprintf("%s:%d", wantKey.Filename, wantKey.Line)] {
wildcardKeys = append(wildcardKeys, wantKey)
}
}
callbackErr = &xknownhosts.KeyError{
Want: wildcardKeys,
}
}
return callbackErr
}
return ssh.HostKeyCallback(f)
}
// PublicKey wraps ssh.PublicKey with an additional field, to identify
// whether the key corresponds to a certificate authority.
type PublicKey struct {
ssh.PublicKey
Cert bool
}
// HostKeys returns a slice of known host public keys for the supplied host:port
@ -39,12 +152,16 @@ func (hkcb HostKeyCallback) HostKeyCallback() ssh.HostKeyCallback {
// already known. For hosts that have multiple known_hosts entries (for
// different key types), the result will be sorted by known_hosts filename and
// line number.
func (hkcb HostKeyCallback) HostKeys(hostWithPort string) (keys []ssh.PublicKey) {
// If hkdb was originally created by calling NewDB, the Cert boolean field of
// each result entry reports whether the key corresponded to a @cert-authority
// line. If hkdb was NOT obtained from NewDB, then Cert will always be false.
func (hkdb *HostKeyDB) HostKeys(hostWithPort string) (keys []PublicKey) {
var keyErr *xknownhosts.KeyError
placeholderAddr := &net.TCPAddr{IP: []byte{0, 0, 0, 0}}
placeholderPubKey := &fakePublicKey{}
var kkeys []xknownhosts.KnownKey
if hkcbErr := hkcb(hostWithPort, placeholderAddr, placeholderPubKey); errors.As(hkcbErr, &keyErr) {
callback := hkdb.HostKeyCallback()
if hkcbErr := callback(hostWithPort, placeholderAddr, placeholderPubKey); errors.As(hkcbErr, &keyErr) {
kkeys = append(kkeys, keyErr.Want...)
knownKeyLess := func(i, j int) bool {
if kkeys[i].Filename < kkeys[j].Filename {
@ -53,9 +170,14 @@ func (hkcb HostKeyCallback) HostKeys(hostWithPort string) (keys []ssh.PublicKey)
return (kkeys[i].Filename == kkeys[j].Filename && kkeys[i].Line < kkeys[j].Line)
}
sort.Slice(kkeys, knownKeyLess)
keys = make([]ssh.PublicKey, len(kkeys))
keys = make([]PublicKey, len(kkeys))
for n := range kkeys {
keys[n] = kkeys[n].Key
keys[n] = PublicKey{
PublicKey: kkeys[n].Key,
}
if len(hkdb.isCert) > 0 {
keys[n].Cert = hkdb.isCert[fmt.Sprintf("%s:%d", kkeys[n].Filename, kkeys[n].Line)]
}
}
}
return keys
@ -66,17 +188,23 @@ func (hkcb HostKeyCallback) HostKeys(hostWithPort string) (keys []ssh.PublicKey)
// is not already known. The result may be used in ssh.ClientConfig's
// HostKeyAlgorithms field, either as-is or after filtering (if you wish to
// ignore or prefer particular algorithms). For hosts that have multiple
// known_hosts entries (for different key types), the result will be sorted by
// known_hosts entries (of different key types), the result will be sorted by
// known_hosts filename and line number.
func (hkcb HostKeyCallback) HostKeyAlgorithms(hostWithPort string) (algos []string) {
// If hkdb was originally created by calling NewDB, any @cert-authority lines
// in the known_hosts file will properly be converted to the corresponding
// ssh.CertAlgo* values.
func (hkdb *HostKeyDB) HostKeyAlgorithms(hostWithPort string) (algos []string) {
// We ensure that algos never contains duplicates. This is done for robustness
// even though currently golang.org/x/crypto/ssh/knownhosts never exposes
// multiple keys of the same type. This way our behavior here is unaffected
// even if https://github.com/golang/go/issues/28870 is implemented, for
// example by https://github.com/golang/crypto/pull/254.
hostKeys := hkcb.HostKeys(hostWithPort)
hostKeys := hkdb.HostKeys(hostWithPort)
seen := make(map[string]struct{}, len(hostKeys))
addAlgo := func(typ string) {
addAlgo := func(typ string, cert bool) {
if cert {
typ = keyTypeToCertAlgo(typ)
}
if _, already := seen[typ]; !already {
algos = append(algos, typ)
seen[typ] = struct{}{}
@ -88,25 +216,143 @@ func (hkcb HostKeyCallback) HostKeyAlgorithms(hostWithPort string) (algos []stri
// KeyAlgoRSASHA256 and KeyAlgoRSASHA512 are only public key algorithms,
// not public key formats, so they can't appear as a PublicKey.Type.
// The corresponding PublicKey.Type is KeyAlgoRSA. See RFC 8332, Section 2.
addAlgo(ssh.KeyAlgoRSASHA512)
addAlgo(ssh.KeyAlgoRSASHA256)
addAlgo(ssh.KeyAlgoRSASHA512, key.Cert)
addAlgo(ssh.KeyAlgoRSASHA256, key.Cert)
}
addAlgo(typ)
addAlgo(typ, key.Cert)
}
return algos
}
func keyTypeToCertAlgo(keyType string) string {
switch keyType {
case ssh.KeyAlgoRSA:
return ssh.CertAlgoRSAv01
case ssh.KeyAlgoRSASHA256:
return ssh.CertAlgoRSASHA256v01
case ssh.KeyAlgoRSASHA512:
return ssh.CertAlgoRSASHA512v01
case ssh.KeyAlgoDSA:
return ssh.CertAlgoDSAv01
case ssh.KeyAlgoECDSA256:
return ssh.CertAlgoECDSA256v01
case ssh.KeyAlgoSKECDSA256:
return ssh.CertAlgoSKECDSA256v01
case ssh.KeyAlgoECDSA384:
return ssh.CertAlgoECDSA384v01
case ssh.KeyAlgoECDSA521:
return ssh.CertAlgoECDSA521v01
case ssh.KeyAlgoED25519:
return ssh.CertAlgoED25519v01
case ssh.KeyAlgoSKED25519:
return ssh.CertAlgoSKED25519v01
}
return ""
}
// HostKeyCallback wraps ssh.HostKeyCallback with additional methods to
// perform host key and algorithm lookups from the known_hosts entries. It is
// otherwise identical to ssh.HostKeyCallback, and does not introduce any file-
// parsing behavior beyond what is in golang.org/x/crypto/ssh/knownhosts.
//
// In most situations, use HostKeyDB and its constructor NewDB instead of using
// the HostKeyCallback type. The HostKeyCallback type is only provided for
// backwards compatibility with older versions of this package, as well as for
// very strict situations where any extra known_hosts file-parsing is
// undesirable.
//
// Methods of HostKeyCallback do not provide any special treatment for
// @cert-authority lines, which will (incorrectly) look like normal non-CA host
// keys. Additionally, HostKeyCallback lacks the fix for applying * wildcard
// known_host entries to all ports, like OpenSSH's behavior.
type HostKeyCallback ssh.HostKeyCallback
// New creates a HostKeyCallback from the given OpenSSH known_hosts file(s). The
// returned value may be used in ssh.ClientConfig.HostKeyCallback by casting it
// to ssh.HostKeyCallback, or using its HostKeyCallback method. Otherwise, it
// operates the same as the New function in golang.org/x/crypto/ssh/knownhosts.
// When supplying multiple files, their order does not matter.
//
// In most situations, you should avoid this function, as the returned value
// lacks several enhanced behaviors. See doc comment for HostKeyCallback for
// more information. Instead, most callers should use NewDB to create a
// HostKeyDB, which includes these enhancements.
func New(files ...string) (HostKeyCallback, error) {
cb, err := xknownhosts.New(files...)
return HostKeyCallback(cb), err
}
// HostKeyCallback simply casts the receiver back to ssh.HostKeyCallback, for
// use in ssh.ClientConfig.HostKeyCallback.
func (hkcb HostKeyCallback) HostKeyCallback() ssh.HostKeyCallback {
return ssh.HostKeyCallback(hkcb)
}
// ToDB converts the receiver into a HostKeyDB. However, the returned HostKeyDB
// lacks the enhanced behaviors described in the doc comment for NewDB: proper
// CA support, and wildcard matching on nonstandard ports.
//
// It is generally preferable to create a HostKeyDB by using NewDB. The ToDB
// method is only provided for situations in which the calling code needs to
// make the extra NewDB behaviors optional / user-configurable, perhaps for
// reasons of performance or code trust (since NewDB reads the known_host file
// an extra time, which may be undesirable in some strict situations). This way,
// callers can conditionally create a non-enhanced HostKeyDB by using New and
// ToDB. See code example.
func (hkcb HostKeyCallback) ToDB() *HostKeyDB {
// This intentionally leaves the isCert and isWildcard map fields as nil, as
// there is no way to retroactively populate them from just a HostKeyCallback.
// Methods of HostKeyDB will skip any related enhanced behaviors accordingly.
return &HostKeyDB{callback: ssh.HostKeyCallback(hkcb)}
}
// HostKeys returns a slice of known host public keys for the supplied host:port
// found in the known_hosts file(s), or an empty slice if the host is not
// already known. For hosts that have multiple known_hosts entries (for
// different key types), the result will be sorted by known_hosts filename and
// line number.
// In the returned values, there is no way to distinguish between CA keys
// (known_hosts lines beginning with @cert-authority) and regular keys. To do
// so, see NewDB and HostKeyDB.HostKeys instead.
func (hkcb HostKeyCallback) HostKeys(hostWithPort string) []ssh.PublicKey {
annotatedKeys := hkcb.ToDB().HostKeys(hostWithPort)
rawKeys := make([]ssh.PublicKey, len(annotatedKeys))
for n, ak := range annotatedKeys {
rawKeys[n] = ak.PublicKey
}
return rawKeys
}
// HostKeyAlgorithms returns a slice of host key algorithms for the supplied
// host:port found in the known_hosts file(s), or an empty slice if the host
// is not already known. The result may be used in ssh.ClientConfig's
// HostKeyAlgorithms field, either as-is or after filtering (if you wish to
// ignore or prefer particular algorithms). For hosts that have multiple
// known_hosts entries (for different key types), the result will be sorted by
// known_hosts filename and line number.
// The returned values will not include ssh.CertAlgo* values. If any
// known_hosts lines had @cert-authority prefixes, their original key algo will
// be returned instead. For proper CA support, see NewDB and
// HostKeyDB.HostKeyAlgorithms instead.
func (hkcb HostKeyCallback) HostKeyAlgorithms(hostWithPort string) (algos []string) {
return hkcb.ToDB().HostKeyAlgorithms(hostWithPort)
}
// HostKeyAlgorithms is a convenience function for performing host key algorithm
// lookups on an ssh.HostKeyCallback directly. It is intended for use in code
// paths that stay with the New method of golang.org/x/crypto/ssh/knownhosts
// rather than this package's New method.
// rather than this package's New or NewDB methods.
// The returned values will not include ssh.CertAlgo* values. If any
// known_hosts lines had @cert-authority prefixes, their original key algo will
// be returned instead. For proper CA support, see NewDB and
// HostKeyDB.HostKeyAlgorithms instead.
func HostKeyAlgorithms(cb ssh.HostKeyCallback, hostWithPort string) []string {
return HostKeyCallback(cb).HostKeyAlgorithms(hostWithPort)
}
// IsHostKeyChanged returns a boolean indicating whether the error indicates
// the host key has changed. It is intended to be called on the error returned
// from invoking a HostKeyCallback to check whether an SSH host is known.
// from invoking a host key callback, to check whether an SSH host is known.
func IsHostKeyChanged(err error) bool {
var keyErr *xknownhosts.KeyError
return errors.As(err, &keyErr) && len(keyErr.Want) > 0
@ -114,7 +360,7 @@ func IsHostKeyChanged(err error) bool {
// IsHostUnknown returns a boolean indicating whether the error represents an
// unknown host. It is intended to be called on the error returned from invoking
// a HostKeyCallback to check whether an SSH host is known.
// a host key callback to check whether an SSH host is known.
func IsHostUnknown(err error) bool {
var keyErr *xknownhosts.KeyError
return errors.As(err, &keyErr) && len(keyErr.Want) == 0
@ -154,11 +400,12 @@ func Line(addresses []string, key ssh.PublicKey) string {
}, " ")
}
// WriteKnownHost writes a known_hosts line to writer for the supplied hostname,
// WriteKnownHost writes a known_hosts line to w for the supplied hostname,
// remote, and key. This is useful when writing a custom hostkey callback which
// wraps a callback obtained from knownhosts.New to provide additional
// known_hosts management functionality. The hostname, remote, and key typically
// correspond to the callback's args.
// wraps a callback obtained from this package to provide additional known_hosts
// management functionality. The hostname, remote, and key typically correspond
// to the callback's args. This function does not support writing
// @cert-authority lines.
func WriteKnownHost(w io.Writer, hostname string, remote net.Addr, key ssh.PublicKey) error {
// Always include hostname; only also include remote if it isn't a zero value
// and doesn't normalize to the same string as hostname.
@ -177,6 +424,14 @@ func WriteKnownHost(w io.Writer, hostname string, remote net.Addr, key ssh.Publi
return err
}
// WriteKnownHostCA writes a @cert-authority line to w for the supplied host
// name/pattern and key.
func WriteKnownHostCA(w io.Writer, hostPattern string, key ssh.PublicKey) error {
encodedKey := base64.StdEncoding.EncodeToString(key.Marshal())
_, err := fmt.Fprintf(w, "@cert-authority %s %s %s\n", hostPattern, key.Type(), encodedKey)
return err
}
// fakePublicKey is used as part of the work-around for
// https://github.com/golang/go/issues/29286
type fakePublicKey struct{}