You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

540 lines
12 KiB

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