OSDN Git Service

new repo
[bytom/vapor.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
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 // IsAuthorityForHost can be used as a callback in ssh.CertChecker
148 func (db *hostKeyDB) IsHostAuthority(remote ssh.PublicKey, address string) bool {
149         h, p, err := net.SplitHostPort(address)
150         if err != nil {
151                 return false
152         }
153         a := addr{host: h, port: p}
154
155         for _, l := range db.lines {
156                 if l.cert && keyEq(l.knownKey.Key, remote) && l.match([]addr{a}) {
157                         return true
158                 }
159         }
160         return false
161 }
162
163 // IsRevoked can be used as a callback in ssh.CertChecker
164 func (db *hostKeyDB) IsRevoked(key *ssh.Certificate) bool {
165         _, ok := db.revoked[string(key.Marshal())]
166         return ok
167 }
168
169 const markerCert = "@cert-authority"
170 const markerRevoked = "@revoked"
171
172 func nextWord(line []byte) (string, []byte) {
173         i := bytes.IndexAny(line, "\t ")
174         if i == -1 {
175                 return string(line), nil
176         }
177
178         return string(line[:i]), bytes.TrimSpace(line[i:])
179 }
180
181 func parseLine(line []byte) (marker, host string, key ssh.PublicKey, err error) {
182         if w, next := nextWord(line); w == markerCert || w == markerRevoked {
183                 marker = w
184                 line = next
185         }
186
187         host, line = nextWord(line)
188         if len(line) == 0 {
189                 return "", "", nil, errors.New("knownhosts: missing host pattern")
190         }
191
192         // ignore the keytype as it's in the key blob anyway.
193         _, line = nextWord(line)
194         if len(line) == 0 {
195                 return "", "", nil, errors.New("knownhosts: missing key type pattern")
196         }
197
198         keyBlob, _ := nextWord(line)
199
200         keyBytes, err := base64.StdEncoding.DecodeString(keyBlob)
201         if err != nil {
202                 return "", "", nil, err
203         }
204         key, err = ssh.ParsePublicKey(keyBytes)
205         if err != nil {
206                 return "", "", nil, err
207         }
208
209         return marker, host, key, nil
210 }
211
212 func (db *hostKeyDB) parseLine(line []byte, filename string, linenum int) error {
213         marker, pattern, key, err := parseLine(line)
214         if err != nil {
215                 return err
216         }
217
218         if marker == markerRevoked {
219                 db.revoked[string(key.Marshal())] = &KnownKey{
220                         Key:      key,
221                         Filename: filename,
222                         Line:     linenum,
223                 }
224
225                 return nil
226         }
227
228         entry := keyDBLine{
229                 cert: marker == markerCert,
230                 knownKey: KnownKey{
231                         Filename: filename,
232                         Line:     linenum,
233                         Key:      key,
234                 },
235         }
236
237         if pattern[0] == '|' {
238                 entry.matcher, err = newHashedHost(pattern)
239         } else {
240                 entry.matcher, err = newHostnameMatcher(pattern)
241         }
242
243         if err != nil {
244                 return err
245         }
246
247         db.lines = append(db.lines, entry)
248         return nil
249 }
250
251 func newHostnameMatcher(pattern string) (matcher, error) {
252         var hps hostPatterns
253         for _, p := range strings.Split(pattern, ",") {
254                 if len(p) == 0 {
255                         continue
256                 }
257
258                 var a addr
259                 var negate bool
260                 if p[0] == '!' {
261                         negate = true
262                         p = p[1:]
263                 }
264
265                 if len(p) == 0 {
266                         return nil, errors.New("knownhosts: negation without following hostname")
267                 }
268
269                 var err error
270                 if p[0] == '[' {
271                         a.host, a.port, err = net.SplitHostPort(p)
272                         if err != nil {
273                                 return nil, err
274                         }
275                 } else {
276                         a.host, a.port, err = net.SplitHostPort(p)
277                         if err != nil {
278                                 a.host = p
279                                 a.port = "22"
280                         }
281                 }
282                 hps = append(hps, hostPattern{
283                         negate: negate,
284                         addr:   a,
285                 })
286         }
287         return hps, nil
288 }
289
290 // KnownKey represents a key declared in a known_hosts file.
291 type KnownKey struct {
292         Key      ssh.PublicKey
293         Filename string
294         Line     int
295 }
296
297 func (k *KnownKey) String() string {
298         return fmt.Sprintf("%s:%d: %s", k.Filename, k.Line, serialize(k.Key))
299 }
300
301 // KeyError is returned if we did not find the key in the host key
302 // database, or there was a mismatch.  Typically, in batch
303 // applications, this should be interpreted as failure. Interactive
304 // applications can offer an interactive prompt to the user.
305 type KeyError struct {
306         // Want holds the accepted host keys. For each key algorithm,
307         // there can be one hostkey.  If Want is empty, the host is
308         // unknown. If Want is non-empty, there was a mismatch, which
309         // can signify a MITM attack.
310         Want []KnownKey
311 }
312
313 func (u *KeyError) Error() string {
314         if len(u.Want) == 0 {
315                 return "knownhosts: key is unknown"
316         }
317         return "knownhosts: key mismatch"
318 }
319
320 // RevokedError is returned if we found a key that was revoked.
321 type RevokedError struct {
322         Revoked KnownKey
323 }
324
325 func (r *RevokedError) Error() string {
326         return "knownhosts: key is revoked"
327 }
328
329 // check checks a key against the host database. This should not be
330 // used for verifying certificates.
331 func (db *hostKeyDB) check(address string, remote net.Addr, remoteKey ssh.PublicKey) error {
332         if revoked := db.revoked[string(remoteKey.Marshal())]; revoked != nil {
333                 return &RevokedError{Revoked: *revoked}
334         }
335
336         host, port, err := net.SplitHostPort(remote.String())
337         if err != nil {
338                 return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", remote, err)
339         }
340
341         addrs := []addr{
342                 {host, port},
343         }
344
345         if address != "" {
346                 host, port, err := net.SplitHostPort(address)
347                 if err != nil {
348                         return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", address, err)
349                 }
350
351                 addrs = append(addrs, addr{host, port})
352         }
353
354         return db.checkAddrs(addrs, remoteKey)
355 }
356
357 // checkAddrs checks if we can find the given public key for any of
358 // the given addresses.  If we only find an entry for the IP address,
359 // or only the hostname, then this still succeeds.
360 func (db *hostKeyDB) checkAddrs(addrs []addr, remoteKey ssh.PublicKey) error {
361         // TODO(hanwen): are these the right semantics? What if there
362         // is just a key for the IP address, but not for the
363         // hostname?
364
365         // Algorithm => key.
366         knownKeys := map[string]KnownKey{}
367         for _, l := range db.lines {
368                 if l.match(addrs) {
369                         typ := l.knownKey.Key.Type()
370                         if _, ok := knownKeys[typ]; !ok {
371                                 knownKeys[typ] = l.knownKey
372                         }
373                 }
374         }
375
376         keyErr := &KeyError{}
377         for _, v := range knownKeys {
378                 keyErr.Want = append(keyErr.Want, v)
379         }
380
381         // Unknown remote host.
382         if len(knownKeys) == 0 {
383                 return keyErr
384         }
385
386         // If the remote host starts using a different, unknown key type, we
387         // also interpret that as a mismatch.
388         if known, ok := knownKeys[remoteKey.Type()]; !ok || !keyEq(known.Key, remoteKey) {
389                 return keyErr
390         }
391
392         return nil
393 }
394
395 // The Read function parses file contents.
396 func (db *hostKeyDB) Read(r io.Reader, filename string) error {
397         scanner := bufio.NewScanner(r)
398
399         lineNum := 0
400         for scanner.Scan() {
401                 lineNum++
402                 line := scanner.Bytes()
403                 line = bytes.TrimSpace(line)
404                 if len(line) == 0 || line[0] == '#' {
405                         continue
406                 }
407
408                 if err := db.parseLine(line, filename, lineNum); err != nil {
409                         return fmt.Errorf("knownhosts: %s:%d: %v", filename, lineNum, err)
410                 }
411         }
412         return scanner.Err()
413 }
414
415 // New creates a host key callback from the given OpenSSH host key
416 // files. The returned callback is for use in
417 // ssh.ClientConfig.HostKeyCallback. Hashed hostnames are not supported.
418 func New(files ...string) (ssh.HostKeyCallback, error) {
419         db := newHostKeyDB()
420         for _, fn := range files {
421                 f, err := os.Open(fn)
422                 if err != nil {
423                         return nil, err
424                 }
425                 defer f.Close()
426                 if err := db.Read(f, fn); err != nil {
427                         return nil, err
428                 }
429         }
430
431         var certChecker ssh.CertChecker
432         certChecker.IsHostAuthority = db.IsHostAuthority
433         certChecker.IsRevoked = db.IsRevoked
434         certChecker.HostKeyFallback = db.check
435
436         return certChecker.CheckHostKey, nil
437 }
438
439 // Normalize normalizes an address into the form used in known_hosts
440 func Normalize(address string) string {
441         host, port, err := net.SplitHostPort(address)
442         if err != nil {
443                 host = address
444                 port = "22"
445         }
446         entry := host
447         if port != "22" {
448                 entry = "[" + entry + "]:" + port
449         } else if strings.Contains(host, ":") && !strings.HasPrefix(host, "[") {
450                 entry = "[" + entry + "]"
451         }
452         return entry
453 }
454
455 // Line returns a line to add append to the known_hosts files.
456 func Line(addresses []string, key ssh.PublicKey) string {
457         var trimmed []string
458         for _, a := range addresses {
459                 trimmed = append(trimmed, Normalize(a))
460         }
461
462         return strings.Join(trimmed, ",") + " " + serialize(key)
463 }
464
465 // HashHostname hashes the given hostname. The hostname is not
466 // normalized before hashing.
467 func HashHostname(hostname string) string {
468         // TODO(hanwen): check if we can safely normalize this always.
469         salt := make([]byte, sha1.Size)
470
471         _, err := rand.Read(salt)
472         if err != nil {
473                 panic(fmt.Sprintf("crypto/rand failure %v", err))
474         }
475
476         hash := hashHost(hostname, salt)
477         return encodeHash(sha1HashType, salt, hash)
478 }
479
480 func decodeHash(encoded string) (hashType string, salt, hash []byte, err error) {
481         if len(encoded) == 0 || encoded[0] != '|' {
482                 err = errors.New("knownhosts: hashed host must start with '|'")
483                 return
484         }
485         components := strings.Split(encoded, "|")
486         if len(components) != 4 {
487                 err = fmt.Errorf("knownhosts: got %d components, want 3", len(components))
488                 return
489         }
490
491         hashType = components[1]
492         if salt, err = base64.StdEncoding.DecodeString(components[2]); err != nil {
493                 return
494         }
495         if hash, err = base64.StdEncoding.DecodeString(components[3]); err != nil {
496                 return
497         }
498         return
499 }
500
501 func encodeHash(typ string, salt []byte, hash []byte) string {
502         return strings.Join([]string{"",
503                 typ,
504                 base64.StdEncoding.EncodeToString(salt),
505                 base64.StdEncoding.EncodeToString(hash),
506         }, "|")
507 }
508
509 // See https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/hostfile.c#120
510 func hashHost(hostname string, salt []byte) []byte {
511         mac := hmac.New(sha1.New, salt)
512         mac.Write([]byte(hostname))
513         return mac.Sum(nil)
514 }
515
516 type hashedHost struct {
517         salt []byte
518         hash []byte
519 }
520
521 const sha1HashType = "1"
522
523 func newHashedHost(encoded string) (*hashedHost, error) {
524         typ, salt, hash, err := decodeHash(encoded)
525         if err != nil {
526                 return nil, err
527         }
528
529         // The type field seems for future algorithm agility, but it's
530         // actually hardcoded in openssh currently, see
531         // https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/hostfile.c#120
532         if typ != sha1HashType {
533                 return nil, fmt.Errorf("knownhosts: got hash type %s, must be '1'", typ)
534         }
535
536         return &hashedHost{salt: salt, hash: hash}, nil
537 }
538
539 func (h *hashedHost) match(addrs []addr) bool {
540         for _, a := range addrs {
541                 if bytes.Equal(hashHost(Normalize(a.String()), h.salt), h.hash) {
542                         return true
543                 }
544         }
545         return false
546 }