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

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
44 changes: 34 additions & 10 deletions ssh/knownhosts/knownhosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
Expand All @@ -229,6 +232,7 @@ func (db *hostKeyDB) parseLine(line []byte, filename string, linenum int) error
Filename: filename,
Line: linenum,
Key: key,
Comments: comments,
},
}

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