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