From 896300907b660115f1eb3f3144c0bbb7214f8e06 Mon Sep 17 00:00:00 2001 From: Emmett Hitz Date: Mon, 21 Oct 2024 02:21:47 -0400 Subject: [PATCH] add support for parsing known hosts key metadata --- ssh/knownhosts/knownhosts.go | 44 ++++++++++++++++++++++++------- ssh/knownhosts/knownhosts_test.go | 5 +++- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/ssh/knownhosts/knownhosts.go b/ssh/knownhosts/knownhosts.go index 7376a8dff2..38eba1f23f 100644 --- a/ssh/knownhosts/knownhosts.go +++ b/ssh/knownhosts/knownhosts.go @@ -176,7 +176,7 @@ func nextWord(line []byte) (string, []byte) { return string(line[:i]), bytes.TrimSpace(line[i:]) } -func parseLine(line []byte) (marker, host string, key ssh.PublicKey, err error) { +func parseLine(line []byte) (marker, host, comments string, key ssh.PublicKey, err error) { if w, next := nextWord(line); w == markerCert || w == markerRevoked { marker = w line = next @@ -184,31 +184,33 @@ func parseLine(line []byte) (marker, host string, key ssh.PublicKey, err error) host, line = nextWord(line) if len(line) == 0 { - return "", "", nil, errors.New("knownhosts: missing host pattern") + return "", "", "", nil, errors.New("knownhosts: missing host pattern") } // ignore the keytype as it's in the key blob anyway. _, line = nextWord(line) if len(line) == 0 { - return "", "", nil, errors.New("knownhosts: missing key type pattern") + return "", "", "", nil, errors.New("knownhosts: missing key type pattern") } - keyBlob, _ := nextWord(line) + keyBlob, line := nextWord(line) keyBytes, err := base64.StdEncoding.DecodeString(keyBlob) if err != nil { - return "", "", nil, err + return "", "", "", nil, err } key, err = ssh.ParsePublicKey(keyBytes) if err != nil { - return "", "", nil, err + return "", "", "", nil, err } + // the rest of the line is the comment, and may include whitespace. + restOfLine := string(bytes.TrimSpace(line)) - return marker, host, key, nil + return marker, host, restOfLine, key, nil } func (db *hostKeyDB) parseLine(line []byte, filename string, linenum int) error { - marker, pattern, key, err := parseLine(line) + marker, pattern, comments, key, err := parseLine(line) if err != nil { return err } @@ -218,6 +220,7 @@ func (db *hostKeyDB) parseLine(line []byte, filename string, linenum int) error Key: key, Filename: filename, Line: linenum, + Comments: comments, } return nil @@ -229,6 +232,7 @@ func (db *hostKeyDB) parseLine(line []byte, filename string, linenum int) error Filename: filename, Line: linenum, Key: key, + Comments: comments, }, } @@ -241,7 +245,6 @@ func (db *hostKeyDB) parseLine(line []byte, filename string, linenum int) error if err != nil { return err } - db.lines = append(db.lines, entry) return nil } @@ -290,10 +293,11 @@ type KnownKey struct { Key ssh.PublicKey Filename string Line int + Comments string } func (k *KnownKey) String() string { - return fmt.Sprintf("%s:%d: %s", k.Filename, k.Line, serialize(k.Key)) + return fmt.Sprintf("%s:%d: %s %s", k.Filename, k.Line, serialize(k.Key), k.Comments) } // KeyError is returned if we did not find the key in the host key @@ -435,6 +439,26 @@ func New(files ...string) (ssh.HostKeyCallback, error) { return certChecker.CheckHostKey, nil } +func NewKnownKeys(files ...string) ([]KnownKey, error) { + db := newHostKeyDB() + for _, fn := range files { + f, err := os.Open(fn) + if err != nil { + return nil, err + } + defer f.Close() + if err := db.Read(f, fn); err != nil { + return nil, err + } + } + + keys := make([]KnownKey, 0, len(db.lines)) + for _, l := range db.lines { + keys = append(keys, l.knownKey) + } + return keys, nil +} + // Normalize normalizes an address into the form used in known_hosts func Normalize(address string) string { host, port, err := net.SplitHostPort(address) diff --git a/ssh/knownhosts/knownhosts_test.go b/ssh/knownhosts/knownhosts_test.go index 464dd59249..ff6f2c821c 100644 --- a/ssh/knownhosts/knownhosts_test.go +++ b/ssh/knownhosts/knownhosts_test.go @@ -15,8 +15,10 @@ import ( ) const edKeyStr = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGBAarftlLeoyf+v+nVchEZII/vna2PCV8FaX4vsF5BX" +const edKeyComments = "comments are ignored" const alternateEdKeyStr = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIXffBYeYL+WVzVru8npl5JHt2cjlr4ornFTWzoij9sx" const ecKeyStr = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNLCu01+wpXe3xB5olXCN4SqU2rQu0qjSRKJO4Bg+JRCPU+ENcgdA5srTU8xYDz/GEa4dzK5ldPw4J/gZgSXCMs=" +const ecKeyComments = "and may include whitespace" var ecKey, alternateEdKey, edKey ssh.PublicKey var testAddr = &net.TCPAddr{ @@ -163,7 +165,7 @@ func TestIPv6Address(t *testing.T) { } func TestBasic(t *testing.T) { - str := fmt.Sprintf("#comment\n\nserver.org,%s %s\notherhost %s", testAddr, edKeyStr, ecKeyStr) + str := fmt.Sprintf("#comment\n\nserver.org,%s %s %s\notherhost %s %s", testAddr, edKeyStr, edKeyComments, ecKeyStr, ecKeyComments) db := testDB(t, str) if err := db.check("server.org:22", testAddr, edKey); err != nil { t.Errorf("got error %v, want none", err) @@ -173,6 +175,7 @@ func TestBasic(t *testing.T) { Key: edKey, Filename: "testdb", Line: 3, + Comments: edKeyComments, } if err := db.check("server.org:22", testAddr, ecKey); err == nil { t.Errorf("succeeded, want KeyError")