Copyright 2017 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 implements a parser for the OpenSSH known_hosts host key database, and provides utility functions for writing OpenSSH compliant known_hosts files.
package knownhosts

import (
	
	
	
	
	
	
	
	
	
	
	
	

	
)
See the sshd manpage (http://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT) for background.

type addr struct{ host, port string }

func ( *addr) () string {
	 := .host
	if strings.Contains(, ":") {
		 = "[" +  + "]"
	}
	return  + ":" + .port
}

type matcher interface {
	match(addr) bool
}

type hostPattern struct {
	negate bool
	addr   addr
}

func ( *hostPattern) () string {
	 := ""
	if .negate {
		 = "!"
	}

	return  + .addr.String()
}

type hostPatterns []hostPattern

func ( hostPatterns) ( addr) bool {
	 := false
	for ,  := range  {
		if !.match() {
			continue
		}
		if .negate {
			return false
		}
		 = true
	}
	return 
}
See https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/addrmatch.c The matching of * has no regard for separators, unlike filesystem globs
func ( []byte,  []byte) bool {
	for {
		if len() == 0 {
			return len() == 0
		}
		if len() == 0 {
			return false
		}

		if [0] == '*' {
			if len() == 1 {
				return true
			}

			for  := range  {
				if ([1:], [:]) {
					return true
				}
			}
			return false
		}

		if [0] == '?' || [0] == [0] {
			 = [1:]
			 = [1:]
		} else {
			return false
		}
	}
}

func ( *hostPattern) ( addr) bool {
	return wildcardMatch([]byte(.addr.host), []byte(.host)) && .addr.port == .port
}

type keyDBLine struct {
	cert     bool
	matcher  matcher
	knownKey KnownKey
}

func ( ssh.PublicKey) string {
	return .Type() + " " + base64.StdEncoding.EncodeToString(.Marshal())
}

func ( *keyDBLine) ( addr) bool {
	return .matcher.match()
}

Serialized version of revoked keys
	revoked map[string]*KnownKey
	lines   []keyDBLine
}

func () *hostKeyDB {
	 := &hostKeyDB{
		revoked: make(map[string]*KnownKey),
	}

	return 
}

func (,  ssh.PublicKey) bool {
	return bytes.Equal(.Marshal(), .Marshal())
}
IsAuthorityForHost can be used as a callback in ssh.CertChecker
func ( *hostKeyDB) ( ssh.PublicKey,  string) bool {
	, ,  := net.SplitHostPort()
	if  != nil {
		return false
	}
	 := addr{host: , port: }

	for ,  := range .lines {
		if .cert && keyEq(.knownKey.Key, ) && .match() {
			return true
		}
	}
	return false
}
IsRevoked can be used as a callback in ssh.CertChecker
func ( *hostKeyDB) ( *ssh.Certificate) bool {
	,  := .revoked[string(.Marshal())]
	return 
}

const markerCert = "@cert-authority"
const markerRevoked = "@revoked"

func ( []byte) (string, []byte) {
	 := bytes.IndexAny(, "\t ")
	if  == -1 {
		return string(), nil
	}

	return string([:]), bytes.TrimSpace([:])
}

func ( []byte) (,  string,  ssh.PublicKey,  error) {
	if ,  := nextWord();  == markerCert ||  == markerRevoked {
		 = 
		 = 
	}

	,  = nextWord()
	if len() == 0 {
		return "", "", nil, errors.New("knownhosts: missing host pattern")
	}
ignore the keytype as it's in the key blob anyway.
	_,  = nextWord()
	if len() == 0 {
		return "", "", nil, errors.New("knownhosts: missing key type pattern")
	}

	,  := nextWord()

	,  := base64.StdEncoding.DecodeString()
	if  != nil {
		return "", "", nil, 
	}
	,  = ssh.ParsePublicKey()
	if  != nil {
		return "", "", nil, 
	}

	return , , , nil
}

func ( *hostKeyDB) ( []byte,  string,  int) error {
	, , ,  := parseLine()
	if  != nil {
		return 
	}

	if  == markerRevoked {
		.revoked[string(.Marshal())] = &KnownKey{
			Key:      ,
			Filename: ,
			Line:     ,
		}

		return nil
	}

	 := keyDBLine{
		cert:  == markerCert,
		knownKey: KnownKey{
			Filename: ,
			Line:     ,
			Key:      ,
		},
	}

	if [0] == '|' {
		.matcher,  = newHashedHost()
	} else {
		.matcher,  = newHostnameMatcher()
	}

	if  != nil {
		return 
	}

	.lines = append(.lines, )
	return nil
}

func ( string) (matcher, error) {
	var  hostPatterns
	for ,  := range strings.Split(, ",") {
		if len() == 0 {
			continue
		}

		var  addr
		var  bool
		if [0] == '!' {
			 = true
			 = [1:]
		}

		if len() == 0 {
			return nil, errors.New("knownhosts: negation without following hostname")
		}

		var  error
		if [0] == '[' {
			.host, .port,  = net.SplitHostPort()
			if  != nil {
				return nil, 
			}
		} else {
			.host, .port,  = net.SplitHostPort()
			if  != nil {
				.host = 
				.port = "22"
			}
		}
		 = append(, hostPattern{
			negate: ,
			addr:   ,
		})
	}
	return , nil
}
KnownKey represents a key declared in a known_hosts file.
type KnownKey struct {
	Key      ssh.PublicKey
	Filename string
	Line     int
}

func ( *KnownKey) () string {
	return fmt.Sprintf("%s:%d: %s", .Filename, .Line, serialize(.Key))
}
KeyError is returned if we did not find the key in the host key database, or there was a mismatch. Typically, in batch applications, this should be interpreted as failure. Interactive applications can offer an interactive prompt to the user.
Want holds the accepted host keys. For each key algorithm, there can be one hostkey. If Want is empty, the host is unknown. If Want is non-empty, there was a mismatch, which can signify a MITM attack.
	Want []KnownKey
}

func ( *KeyError) () string {
	if len(.Want) == 0 {
		return "knownhosts: key is unknown"
	}
	return "knownhosts: key mismatch"
}
RevokedError is returned if we found a key that was revoked.
type RevokedError struct {
	Revoked KnownKey
}

func ( *RevokedError) () string {
	return "knownhosts: key is revoked"
}
check checks a key against the host database. This should not be used for verifying certificates.
func ( *hostKeyDB) ( string,  net.Addr,  ssh.PublicKey) error {
	if  := .revoked[string(.Marshal())];  != nil {
		return &RevokedError{Revoked: *}
	}

	, ,  := net.SplitHostPort(.String())
	if  != nil {
		return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", , )
	}

	 := addr{, }
Give preference to the hostname if available.
		, ,  := net.SplitHostPort()
		if  != nil {
			return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", , )
		}

		 = addr{, }
	}

	return .checkAddr(, )
}
checkAddr checks if we can find the given public key for the given address. If we only find an entry for the IP address, or only the hostname, then this still succeeds.
TODO(hanwen): are these the right semantics? What if there is just a key for the IP address, but not for the hostname?
Algorithm => key.
	 := map[string]KnownKey{}
	for ,  := range .lines {
		if .match() {
			 := .knownKey.Key.Type()
			if ,  := []; ! {
				[] = .knownKey
			}
		}
	}

	 := &KeyError{}
	for ,  := range  {
		.Want = append(.Want, )
	}
Unknown remote host.
	if len() == 0 {
		return 
	}
If the remote host starts using a different, unknown key type, we also interpret that as a mismatch.
	if ,  := [.Type()]; ! || !keyEq(.Key, ) {
		return 
	}

	return nil
}
The Read function parses file contents.
func ( *hostKeyDB) ( io.Reader,  string) error {
	 := bufio.NewScanner()

	 := 0
	for .Scan() {
		++
		 := .Bytes()
		 = bytes.TrimSpace()
		if len() == 0 || [0] == '#' {
			continue
		}

		if  := .parseLine(, , );  != nil {
			return fmt.Errorf("knownhosts: %s:%d: %v", , , )
		}
	}
	return .Err()
}
New creates a host key callback from the given OpenSSH host key files. The returned callback is for use in ssh.ClientConfig.HostKeyCallback. By preference, the key check operates on the hostname if available, i.e. if a server changes its IP address, the host key check will still succeed, even though a record of the new IP address is not available.
func ( ...string) (ssh.HostKeyCallback, error) {
	 := newHostKeyDB()
	for ,  := range  {
		,  := os.Open()
		if  != nil {
			return nil, 
		}
		defer .Close()
		if  := .Read(, );  != nil {
			return nil, 
		}
	}

	var  ssh.CertChecker
	.IsHostAuthority = .IsHostAuthority
	.IsRevoked = .IsRevoked
	.HostKeyFallback = .check

	return .CheckHostKey, nil
}
Normalize normalizes an address into the form used in known_hosts
func ( string) string {
	, ,  := net.SplitHostPort()
	if  != nil {
		 = 
		 = "22"
	}
	 := 
	if  != "22" {
		 = "[" +  + "]:" + 
	} else if strings.Contains(, ":") && !strings.HasPrefix(, "[") {
		 = "[" +  + "]"
	}
	return 
}
Line returns a line to add append to the known_hosts files.
func ( []string,  ssh.PublicKey) string {
	var  []string
	for ,  := range  {
		 = append(, Normalize())
	}

	return strings.Join(, ",") + " " + serialize()
}
HashHostname hashes the given hostname. The hostname is not normalized before hashing.
TODO(hanwen): check if we can safely normalize this always.
	 := make([]byte, sha1.Size)

	,  := rand.Read()
	if  != nil {
		panic(fmt.Sprintf("crypto/rand failure %v", ))
	}

	 := hashHost(, )
	return encodeHash(sha1HashType, , )
}

func ( string) ( string, ,  []byte,  error) {
	if len() == 0 || [0] != '|' {
		 = errors.New("knownhosts: hashed host must start with '|'")
		return
	}
	 := strings.Split(, "|")
	if len() != 4 {
		 = fmt.Errorf("knownhosts: got %d components, want 3", len())
		return
	}

	 = [1]
	if ,  = base64.StdEncoding.DecodeString([2]);  != nil {
		return
	}
	if ,  = base64.StdEncoding.DecodeString([3]);  != nil {
		return
	}
	return
}

func ( string,  []byte,  []byte) string {
	return strings.Join([]string{"",
		,
		base64.StdEncoding.EncodeToString(),
		base64.StdEncoding.EncodeToString(),
	}, "|")
}
See https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/hostfile.c#120
func ( string,  []byte) []byte {
	 := hmac.New(sha1.New, )
	.Write([]byte())
	return .Sum(nil)
}

type hashedHost struct {
	salt []byte
	hash []byte
}

const sha1HashType = "1"

func ( string) (*hashedHost, error) {
	, , ,  := decodeHash()
	if  != nil {
		return nil, 
	}
The type field seems for future algorithm agility, but it's actually hardcoded in openssh currently, see https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/hostfile.c#120
	if  != sha1HashType {
		return nil, fmt.Errorf("knownhosts: got hash type %s, must be '1'", )
	}

	return &hashedHost{salt: , hash: }, nil
}

func ( *hashedHost) ( addr) bool {
	return bytes.Equal(hashHost(Normalize(.String()), .salt), .hash)