Skip to content

ssh/knownhosts: add support for parsing known hosts key metadata #303

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
44 changes: 34 additions & 10 deletions ssh/knownhosts/knownhosts.go
Original file line number Diff line number Diff line change
@@ -176,39 +176,41 @@ 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
}

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)
5 changes: 4 additions & 1 deletion ssh/knownhosts/knownhosts_test.go
Original file line number Diff line number Diff line change
@@ -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")