ssh/knownhosts: verify declared key type matches decoded key

The parseLine function previously ignored the key type field (e.g.,
"ssh-rsa") in known_hosts entries, relying solely on the type
information embedded within the base64-encoded key blob.

OpenSSH's sshkey_read implementation explicitly verifies that the key
type declared in the text matches the type of the parsed key, returning
SSH_ERR_KEY_TYPE_MISMATCH if they differ.

This change adds a check to ensure the declared key type matches
key.Type(), returning an error for malformed lines where they diverge.

This issue was found during a security audit by NCC Group Cryptography
Services, sponsored by Teleport, and was assessed and is being fixed as
a non-security bug.

Change-Id: Id4f35c74055f5691088273630b50cdd02c81bfe9
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/782427
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
diff --git a/ssh/knownhosts/knownhosts.go b/ssh/knownhosts/knownhosts.go
index 1ef1c35..c7eda05 100644
--- a/ssh/knownhosts/knownhosts.go
+++ b/ssh/knownhosts/knownhosts.go
@@ -192,8 +192,7 @@
 		return "", "", nil, errors.New("knownhosts: missing host pattern")
 	}
 
-	// ignore the keytype as it's in the key blob anyway.
-	_, line = nextWord(line)
+	wantType, line := nextWord(line)
 	if len(line) == 0 {
 		return "", "", nil, errors.New("knownhosts: missing key type pattern")
 	}
@@ -209,6 +208,10 @@
 		return "", "", nil, err
 	}
 
+	if key.Type() != wantType {
+		return "", "", nil, fmt.Errorf("knownhosts: key type mismatch: found %q, want %q", key.Type(), wantType)
+	}
+
 	return marker, host, key, nil
 }
 
diff --git a/ssh/knownhosts/knownhosts_test.go b/ssh/knownhosts/knownhosts_test.go
index 8e1fba2..f3af2da 100644
--- a/ssh/knownhosts/knownhosts_test.go
+++ b/ssh/knownhosts/knownhosts_test.go
@@ -12,6 +12,7 @@
 	"fmt"
 	"net"
 	"reflect"
+	"strings"
 	"testing"
 
 	"golang.org/x/crypto/ssh"
@@ -446,3 +447,18 @@
 		t.Errorf("Did not match dirty host, implying parsing issue")
 	}
 }
+
+func TestKeyTypeMismatch(t *testing.T) {
+	line := fmt.Sprintf("server.org ssh-rsa %s", base64.StdEncoding.EncodeToString(edKey.Marshal()))
+
+	db := newHostKeyDB()
+	err := db.Read(bytes.NewBufferString(line), "testdb")
+	if err == nil {
+		t.Fatal("Read succeeded on line with key type mismatch, expected error")
+	}
+
+	expectedErr := `knownhosts: key type mismatch: found "ssh-ed25519", want "ssh-rsa"`
+	if !strings.Contains(err.Error(), expectedErr) {
+		t.Fatalf("got error %q, want to contain %q", err.Error(), expectedErr)
+	}
+}