1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
require 'base64'
module SSHKit
module Backend
class Netssh < Abstract
class KnownHostsKeys
include Mutex_m
def initialize(path)
super()
@path = File.expand_path(path)
@hosts_keys = nil
end
def keys_for(hostlist)
keys, hashes = hosts_keys, hosts_hashes
parse_file unless keys && hashes
keys, hashes = hosts_keys, hosts_hashes
host_names = hostlist.split(',')
keys_found = host_names.map { |h| keys[h] || [] }.compact.inject(:&)
return keys_found unless keys_found.empty?
host_names.each do |host|
hashes.each do |(hmac, salt), hash_keys|
if OpenSSL::HMAC.digest(sha1, salt, host) == hmac
return hash_keys
end
end
end
[]
end
private
attr_reader :path
attr_accessor :hosts_keys, :hosts_hashes
def sha1
@sha1 ||= OpenSSL::Digest.new('sha1')
end
def parse_file
synchronize do
return if hosts_keys && hosts_hashes
unless File.readable?(path)
self.hosts_keys = {}
self.hosts_hashes = []
return
end
new_keys = {}
new_hashes = []
File.open(path) do |file|
scanner = StringScanner.new("")
file.each_line do |line|
scanner.string = line
parse_line(scanner, new_keys, new_hashes)
end
end
self.hosts_keys = new_keys
self.hosts_hashes = new_hashes
end
end
def parse_line(scanner, hosts_keys, hosts_hashes)
return if empty_line?(scanner)
hostlist = parse_hostlist(scanner)
return unless supported_type?(scanner)
key = parse_key(scanner)
if hostlist.size == 1 && hostlist.first =~ /\A\|1(\|.+){2}\z/
hosts_hashes << [parse_host_hash(hostlist.first), key]
else
hostlist.each do |host|
(hosts_keys[host] ||= []) << key
end
end
end
def parse_host_hash(line)
_, _, salt, hmac = line.split('|')
[Base64.decode64(hmac), Base64.decode64(salt)]
end
def empty_line?(scanner)
scanner.skip(/\s*/)
scanner.match?(/$|#/)
end
def parse_hostlist(scanner)
scanner.skip(/\s*/)
scanner.scan(/\S+/).split(',')
end
def supported_type?(scanner)
scanner.skip(/\s*/)
Net::SSH::KnownHosts::SUPPORTED_TYPE.include?(scanner.scan(/\S+/))
end
def parse_key(scanner)
scanner.skip(/\s*/)
Net::SSH::Buffer.new(scanner.rest.unpack("m*").first).read_key
end
end
class KnownHosts
include Mutex_m
def initialize
super()
@files = {}
end
def search_for(host, options = {})
keys = ::Net::SSH::KnownHosts.hostfiles(options).map do |path|
known_hosts_file(path).keys_for(host)
end.flatten
::Net::SSH::HostKeys.new(keys, host, self, options)
end
def add(*args)
::Net::SSH::KnownHosts.add(*args)
synchronize { @files = {} }
end
private
def known_hosts_file(path)
@files[path] || synchronize { @files[path] ||= KnownHostsKeys.new(path) }
end
end
end
end
end
|