github.com/devops-filetransfer/sshego@v7.0.4+incompatible/_vendor/golang.org/x/crypto/ssh/knownhosts/knownhosts.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package knownhosts implements a parser for the OpenSSH
     6  // known_hosts host key database.
     7  package knownhosts
     8  
     9  import (
    10  	"bufio"
    11  	"bytes"
    12  	"crypto/hmac"
    13  	"crypto/rand"
    14  	"crypto/sha1"
    15  	"encoding/base64"
    16  	"errors"
    17  	"fmt"
    18  	"io"
    19  	"net"
    20  	"os"
    21  	"strings"
    22  
    23  	"golang.org/x/crypto/ssh"
    24  )
    25  
    26  // See the sshd manpage
    27  // (http://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT) for
    28  // background.
    29  
    30  type addr struct{ host, port string }
    31  
    32  func (a *addr) String() string {
    33  	h := a.host
    34  	if strings.Contains(h, ":") {
    35  		h = "[" + h + "]"
    36  	}
    37  	return h + ":" + a.port
    38  }
    39  
    40  type matcher interface {
    41  	match([]addr) bool
    42  }
    43  
    44  type hostPattern struct {
    45  	negate bool
    46  	addr   addr
    47  }
    48  
    49  func (p *hostPattern) String() string {
    50  	n := ""
    51  	if p.negate {
    52  		n = "!"
    53  	}
    54  
    55  	return n + p.addr.String()
    56  }
    57  
    58  type hostPatterns []hostPattern
    59  
    60  func (ps hostPatterns) match(addrs []addr) bool {
    61  	matched := false
    62  	for _, p := range ps {
    63  		for _, a := range addrs {
    64  			m := p.match(a)
    65  			if !m {
    66  				continue
    67  			}
    68  			if p.negate {
    69  				return false
    70  			}
    71  			matched = true
    72  		}
    73  	}
    74  	return matched
    75  }
    76  
    77  // See
    78  // https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/addrmatch.c
    79  // The matching of * has no regard for separators, unlike filesystem globs
    80  func wildcardMatch(pat []byte, str []byte) bool {
    81  	for {
    82  		if len(pat) == 0 {
    83  			return len(str) == 0
    84  		}
    85  		if len(str) == 0 {
    86  			return false
    87  		}
    88  
    89  		if pat[0] == '*' {
    90  			if len(pat) == 1 {
    91  				return true
    92  			}
    93  
    94  			for j := range str {
    95  				if wildcardMatch(pat[1:], str[j:]) {
    96  					return true
    97  				}
    98  			}
    99  			return false
   100  		}
   101  
   102  		if pat[0] == '?' || pat[0] == str[0] {
   103  			pat = pat[1:]
   104  			str = str[1:]
   105  		} else {
   106  			return false
   107  		}
   108  	}
   109  }
   110  
   111  func (l *hostPattern) match(a addr) bool {
   112  	return wildcardMatch([]byte(l.addr.host), []byte(a.host)) && l.addr.port == a.port
   113  }
   114  
   115  type keyDBLine struct {
   116  	cert     bool
   117  	matcher  matcher
   118  	knownKey KnownKey
   119  }
   120  
   121  func serialize(k ssh.PublicKey) string {
   122  	return k.Type() + " " + base64.StdEncoding.EncodeToString(k.Marshal())
   123  }
   124  
   125  func (l *keyDBLine) match(addrs []addr) bool {
   126  	return l.matcher.match(addrs)
   127  }
   128  
   129  type hostKeyDB struct {
   130  	// Serialized version of revoked keys
   131  	revoked map[string]*KnownKey
   132  	lines   []keyDBLine
   133  }
   134  
   135  func newHostKeyDB() *hostKeyDB {
   136  	db := &hostKeyDB{
   137  		revoked: make(map[string]*KnownKey),
   138  	}
   139  
   140  	return db
   141  }
   142  
   143  func keyEq(a, b ssh.PublicKey) bool {
   144  	return bytes.Equal(a.Marshal(), b.Marshal())
   145  }
   146  
   147  // IsAuthority can be used as a callback in ssh.CertChecker
   148  func (db *hostKeyDB) IsAuthority(remote ssh.PublicKey) bool {
   149  	for _, l := range db.lines {
   150  		// TODO(hanwen): should we check the hostname against host pattern?
   151  		if l.cert && keyEq(l.knownKey.Key, remote) {
   152  			return true
   153  		}
   154  	}
   155  	return false
   156  }
   157  
   158  // IsRevoked can be used as a callback in ssh.CertChecker
   159  func (db *hostKeyDB) IsRevoked(key *ssh.Certificate) bool {
   160  	_, ok := db.revoked[string(key.Marshal())]
   161  	return ok
   162  }
   163  
   164  const markerCert = "@cert-authority"
   165  const markerRevoked = "@revoked"
   166  
   167  func nextWord(line []byte) (string, []byte) {
   168  	i := bytes.IndexAny(line, "\t ")
   169  	if i == -1 {
   170  		return string(line), nil
   171  	}
   172  
   173  	return string(line[:i]), bytes.TrimSpace(line[i:])
   174  }
   175  
   176  func parseLine(line []byte) (marker, host string, key ssh.PublicKey, err error) {
   177  	if w, next := nextWord(line); w == markerCert || w == markerRevoked {
   178  		marker = w
   179  		line = next
   180  	}
   181  
   182  	host, line = nextWord(line)
   183  	if len(line) == 0 {
   184  		return "", "", nil, errors.New("knownhosts: missing host pattern")
   185  	}
   186  
   187  	// ignore the keytype as it's in the key blob anyway.
   188  	_, line = nextWord(line)
   189  	if len(line) == 0 {
   190  		return "", "", nil, errors.New("knownhosts: missing key type pattern")
   191  	}
   192  
   193  	keyBlob, _ := nextWord(line)
   194  
   195  	keyBytes, err := base64.StdEncoding.DecodeString(keyBlob)
   196  	if err != nil {
   197  		return "", "", nil, err
   198  	}
   199  	key, err = ssh.ParsePublicKey(keyBytes)
   200  	if err != nil {
   201  		return "", "", nil, err
   202  	}
   203  
   204  	return marker, host, key, nil
   205  }
   206  
   207  func (db *hostKeyDB) parseLine(line []byte, filename string, linenum int) error {
   208  	marker, pattern, key, err := parseLine(line)
   209  	if err != nil {
   210  		return err
   211  	}
   212  
   213  	if marker == markerRevoked {
   214  		db.revoked[string(key.Marshal())] = &KnownKey{
   215  			Key:      key,
   216  			Filename: filename,
   217  			Line:     linenum,
   218  		}
   219  
   220  		return nil
   221  	}
   222  
   223  	entry := keyDBLine{
   224  		cert: marker == markerCert,
   225  		knownKey: KnownKey{
   226  			Filename: filename,
   227  			Line:     linenum,
   228  			Key:      key,
   229  		},
   230  	}
   231  
   232  	if pattern[0] == '|' {
   233  		entry.matcher, err = newHashedHost(pattern)
   234  	} else {
   235  		entry.matcher, err = newHostnameMatcher(pattern)
   236  	}
   237  
   238  	if err != nil {
   239  		return err
   240  	}
   241  
   242  	db.lines = append(db.lines, entry)
   243  	return nil
   244  }
   245  
   246  func newHostnameMatcher(pattern string) (matcher, error) {
   247  	var hps hostPatterns
   248  	for _, p := range strings.Split(pattern, ",") {
   249  		if len(p) == 0 {
   250  			continue
   251  		}
   252  
   253  		var a addr
   254  		var negate bool
   255  		if p[0] == '!' {
   256  			negate = true
   257  			p = p[1:]
   258  		}
   259  
   260  		if len(p) == 0 {
   261  			return nil, errors.New("knownhosts: negation without following hostname")
   262  		}
   263  
   264  		var err error
   265  		if p[0] == '[' {
   266  			a.host, a.port, err = net.SplitHostPort(p)
   267  			if err != nil {
   268  				return nil, err
   269  			}
   270  		} else {
   271  			a.host, a.port, err = net.SplitHostPort(p)
   272  			if err != nil {
   273  				a.host = p
   274  				a.port = "22"
   275  			}
   276  		}
   277  		hps = append(hps, hostPattern{
   278  			negate: negate,
   279  			addr:   a,
   280  		})
   281  	}
   282  	return hps, nil
   283  }
   284  
   285  // KnownKey represents a key declared in a known_hosts file.
   286  type KnownKey struct {
   287  	Key      ssh.PublicKey
   288  	Filename string
   289  	Line     int
   290  }
   291  
   292  func (k *KnownKey) String() string {
   293  	return fmt.Sprintf("%s:%d: %s", k.Filename, k.Line, serialize(k.Key))
   294  }
   295  
   296  // KeyError is returned if we did not find the key in the host key
   297  // database, or there was a mismatch.  Typically, in batch
   298  // applications, this should be interpreted as failure. Interactive
   299  // applications can offer an interactive prompt to the user.
   300  type KeyError struct {
   301  	// Want holds the accepted host keys. For each key algorithm,
   302  	// there can be one hostkey.  If Want is empty, the host is
   303  	// unknown. If Want is non-empty, there was a mismatch, which
   304  	// can signify a MITM attack.
   305  	Want []KnownKey
   306  }
   307  
   308  func (u *KeyError) Error() string {
   309  	if len(u.Want) == 0 {
   310  		return "knownhosts: key is unknown"
   311  	}
   312  	return "knownhosts: key mismatch"
   313  }
   314  
   315  // RevokedError is returned if we found a key that was revoked.
   316  type RevokedError struct {
   317  	Revoked KnownKey
   318  }
   319  
   320  func (r *RevokedError) Error() string {
   321  	return "knownhosts: key is revoked"
   322  }
   323  
   324  // check checks a key against the host database. This should not be
   325  // used for verifying certificates.
   326  func (db *hostKeyDB) check(address string, remote net.Addr, remoteKey ssh.PublicKey) error {
   327  	if revoked := db.revoked[string(remoteKey.Marshal())]; revoked != nil {
   328  		return &RevokedError{Revoked: *revoked}
   329  	}
   330  
   331  	host, port, err := net.SplitHostPort(remote.String())
   332  	if err != nil {
   333  		return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", remote, err)
   334  	}
   335  
   336  	addrs := []addr{
   337  		{host, port},
   338  	}
   339  
   340  	if address != "" {
   341  		host, port, err := net.SplitHostPort(address)
   342  		if err != nil {
   343  			return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", address, err)
   344  		}
   345  
   346  		addrs = append(addrs, addr{host, port})
   347  	}
   348  
   349  	return db.checkAddrs(addrs, remoteKey)
   350  }
   351  
   352  // checkAddrs checks if we can find the given public key for any of
   353  // the given addresses.  If we only find an entry for the IP address,
   354  // or only the hostname, then this still succeeds.
   355  func (db *hostKeyDB) checkAddrs(addrs []addr, remoteKey ssh.PublicKey) error {
   356  	// TODO(hanwen): are these the right semantics? What if there
   357  	// is just a key for the IP address, but not for the
   358  	// hostname?
   359  
   360  	// Algorithm => key.
   361  	knownKeys := map[string]KnownKey{}
   362  	for _, l := range db.lines {
   363  		if l.match(addrs) {
   364  			typ := l.knownKey.Key.Type()
   365  			if _, ok := knownKeys[typ]; !ok {
   366  				knownKeys[typ] = l.knownKey
   367  			}
   368  		}
   369  	}
   370  
   371  	keyErr := &KeyError{}
   372  	for _, v := range knownKeys {
   373  		keyErr.Want = append(keyErr.Want, v)
   374  	}
   375  
   376  	// Unknown remote host.
   377  	if len(knownKeys) == 0 {
   378  		return keyErr
   379  	}
   380  
   381  	// If the remote host starts using a different, unknown key type, we
   382  	// also interpret that as a mismatch.
   383  	if known, ok := knownKeys[remoteKey.Type()]; !ok || !keyEq(known.Key, remoteKey) {
   384  		return keyErr
   385  	}
   386  
   387  	return nil
   388  }
   389  
   390  // The Read function parses file contents.
   391  func (db *hostKeyDB) Read(r io.Reader, filename string) error {
   392  	scanner := bufio.NewScanner(r)
   393  
   394  	lineNum := 0
   395  	for scanner.Scan() {
   396  		lineNum++
   397  		line := scanner.Bytes()
   398  		line = bytes.TrimSpace(line)
   399  		if len(line) == 0 || line[0] == '#' {
   400  			continue
   401  		}
   402  
   403  		if err := db.parseLine(line, filename, lineNum); err != nil {
   404  			return fmt.Errorf("knownhosts: %s:%d: %v", filename, lineNum, err)
   405  		}
   406  	}
   407  	return scanner.Err()
   408  }
   409  
   410  // New creates a host key callback from the given OpenSSH host key
   411  // files. The returned callback is for use in
   412  // ssh.ClientConfig.HostKeyCallback. Hostnames are ignored for
   413  // certificates, ie. any certificate authority is assumed to be valid
   414  // for all remote hosts.  Hashed hostnames are not supported.
   415  func New(files ...string) (ssh.HostKeyCallback, error) {
   416  	db := newHostKeyDB()
   417  	for _, fn := range files {
   418  		f, err := os.Open(fn)
   419  		if err != nil {
   420  			return nil, err
   421  		}
   422  		defer f.Close()
   423  		if err := db.Read(f, fn); err != nil {
   424  			return nil, err
   425  		}
   426  	}
   427  
   428  	// TODO(hanwen): properly supporting certificates requires an
   429  	// API change in the SSH library: IsAuthority should provide
   430  	// the address too?
   431  
   432  	var certChecker ssh.CertChecker
   433  	certChecker.IsAuthority = db.IsAuthority
   434  	certChecker.IsRevoked = db.IsRevoked
   435  	certChecker.HostKeyFallback = db.check
   436  
   437  	return certChecker.CheckHostKey, nil
   438  }
   439  
   440  // Normalize normalizes an address into the form used in known_hosts
   441  func Normalize(address string) string {
   442  	host, port, err := net.SplitHostPort(address)
   443  	if err != nil {
   444  		host = address
   445  		port = "22"
   446  	}
   447  	entry := host
   448  	if port != "22" {
   449  		entry = "[" + entry + "]:" + port
   450  	} else if strings.Contains(host, ":") && !strings.HasPrefix(host, "[") {
   451  		entry = "[" + entry + "]"
   452  	}
   453  	return entry
   454  }
   455  
   456  // Line returns a line to add append to the known_hosts files.
   457  func Line(addresses []string, key ssh.PublicKey) string {
   458  	var trimmed []string
   459  	for _, a := range addresses {
   460  		trimmed = append(trimmed, Normalize(a))
   461  	}
   462  
   463  	return strings.Join(trimmed, ",") + " " + serialize(key)
   464  }
   465  
   466  // HashHostname hashes the given hostname. The hostname is not
   467  // normalized before hashing.
   468  func HashHostname(hostname string) string {
   469  	// TODO(hanwen): check if we can safely normalize this always.
   470  	salt := make([]byte, sha1.Size)
   471  
   472  	_, err := rand.Read(salt)
   473  	if err != nil {
   474  		panic(fmt.Sprintf("crypto/rand failure %v", err))
   475  	}
   476  
   477  	hash := hashHost(hostname, salt)
   478  	return encodeHash(sha1HashType, salt, hash)
   479  }
   480  
   481  func decodeHash(encoded string) (hashType string, salt, hash []byte, err error) {
   482  	if len(encoded) == 0 || encoded[0] != '|' {
   483  		err = errors.New("knownhosts: hashed host must start with '|'")
   484  		return
   485  	}
   486  	components := strings.Split(encoded, "|")
   487  	if len(components) != 4 {
   488  		err = fmt.Errorf("knownhosts: got %d components, want 3", len(components))
   489  		return
   490  	}
   491  
   492  	hashType = components[1]
   493  	if salt, err = base64.StdEncoding.DecodeString(components[2]); err != nil {
   494  		return
   495  	}
   496  	if hash, err = base64.StdEncoding.DecodeString(components[3]); err != nil {
   497  		return
   498  	}
   499  	return
   500  }
   501  
   502  func encodeHash(typ string, salt []byte, hash []byte) string {
   503  	return strings.Join([]string{"",
   504  		typ,
   505  		base64.StdEncoding.EncodeToString(salt),
   506  		base64.StdEncoding.EncodeToString(hash),
   507  	}, "|")
   508  }
   509  
   510  // See https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/hostfile.c#120
   511  func hashHost(hostname string, salt []byte) []byte {
   512  	mac := hmac.New(sha1.New, salt)
   513  	mac.Write([]byte(hostname))
   514  	return mac.Sum(nil)
   515  }
   516  
   517  type hashedHost struct {
   518  	salt []byte
   519  	hash []byte
   520  }
   521  
   522  const sha1HashType = "1"
   523  
   524  func newHashedHost(encoded string) (*hashedHost, error) {
   525  	typ, salt, hash, err := decodeHash(encoded)
   526  	if err != nil {
   527  		return nil, err
   528  	}
   529  
   530  	// The type field seems for future algorithm agility, but it's
   531  	// actually hardcoded in openssh currently, see
   532  	// https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/hostfile.c#120
   533  	if typ != sha1HashType {
   534  		return nil, fmt.Errorf("knownhosts: got hash type %s, must be '1'", typ)
   535  	}
   536  
   537  	return &hashedHost{salt: salt, hash: hash}, nil
   538  }
   539  
   540  func (h *hashedHost) match(addrs []addr) bool {
   541  	for _, a := range addrs {
   542  		if bytes.Equal(hashHost(Normalize(a.String()), h.salt), h.hash) {
   543  			return true
   544  		}
   545  	}
   546  	return false
   547  }