From e6291d1382827c5f725dc056babd75974c74253e Mon Sep 17 00:00:00 2001 From: Shulhan Date: Fri, 27 Mar 2026 06:10:04 +0700 Subject: ssh/knownhosts: implements DB interface There is an issue with current SSH client implementation. Given a single host public key in the known_hosts file, host ssh-ed25519 key... Calling ssh.Dial(`tcp`, "host", ...) will return an error knownhosts: key mismatch from [handshakeTransport.enterKeyExchange], because only key "mlkem768x25519-sha256" is checked on the client side. This changes add DB interface for knownhosts that have two methods: - HostKeyAlgorithms: return the host key that matches in known_hosts based on the "host" name or address for [ssh.ClientConfig.HostKeyAlgorithms]. - HostKeyCallback: return the ssh.HostKeyCallback for [ssh.ClientConfig.HostKeyCallback]. Author: Faye Salwin Reference: https://go-review.googlesource.com/c/crypto/+/154458 --- ssh/knownhosts/db.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 ssh/knownhosts/db.go diff --git a/ssh/knownhosts/db.go b/ssh/knownhosts/db.go new file mode 100644 index 0000000..9d81617 --- /dev/null +++ b/ssh/knownhosts/db.go @@ -0,0 +1,68 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package knownhosts + +import ( + "fmt" + "net" + "os" + + "golang.org/x/crypto/ssh" +) + +type DB interface { + // HostKeyAlgorithms takes an address and returns a list of matching key types. + HostKeyAlgorithms(address string) ([]string, error) + + // HostKeyCallback is knownhosts.New without the DB initialization. + HostKeyCallback() ssh.HostKeyCallback +} + +// NewDB creates a new known_hosts database from the files given and returns +// it. +func NewDB(files ...string) (DB, error) { + logp := `NewDB` + db := newHostKeyDB() + for _, fn := range files { + f, err := os.Open(fn) + if err != nil { + return nil, fmt.Errorf(`%s: %w`, logp, err) + } + defer f.Close() + err = db.Read(f, fn) + if err != nil { + return nil, fmt.Errorf(`%s: %w`, logp, err) + } + } + return db, nil +} + +// HostKeyAlgorithms returns a list of host key algorithms associated +// with the given address. +func (db *hostKeyDB) HostKeyAlgorithms(address string) (knownTypes []string, err error) { + logp := `HostKeyAlgorithms` + host, port, err := net.SplitHostPort(address) + if err != nil { + return nil, fmt.Errorf(`%s: %w`, logp, err) + } + + hostToCheck := addr{host, port} + for _, l := range db.lines { + if l.match(hostToCheck) { + knownTypes = append(knownTypes, l.knownKey.Key.Type()) + } + } + return knownTypes, nil +} + +// HostKeyCallback is the way to get the ssh.HostKeyCallback if you have used +// NewDB. +func (db *hostKeyDB) HostKeyCallback() ssh.HostKeyCallback { + var certChecker ssh.CertChecker + certChecker.IsHostAuthority = db.IsHostAuthority + certChecker.IsRevoked = db.IsRevoked + certChecker.HostKeyFallback = db.check + return certChecker.CheckHostKey +} -- cgit v1.3