Skip to content

Commit 3f660a4

Browse files
committed
Add support for hashed host lists
1 parent 7b59efa commit 3f660a4

1 file changed

Lines changed: 60 additions & 20 deletions

File tree

lib/sshkit/backends/netssh.rb

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,56 +30,96 @@ def initialize(path)
3030
end
3131

3232
def keys_for(hostlist)
33-
keys = hosts_keys || parse_file
33+
keys, hashes = hosts_keys, hosts_hashes
34+
parse_file unless keys && hashes
35+
keys, hashes = hosts_keys, hosts_hashes
36+
3437
hostlist.split(',').each do |host|
35-
if key_list = keys[host]
36-
return key_list
38+
key_list = keys[host]
39+
return key_list if key_list
40+
41+
hashes.each do |(hmac, salt), key|
42+
if OpenSSL::HMAC.digest(sha1, salt, host) == hmac
43+
return key
44+
end
3745
end
3846
end
47+
3948
[]
4049
end
4150

4251
private
4352

4453
attr_reader :path
45-
attr_accessor :hosts_keys
54+
attr_accessor :hosts_keys, :hosts_hashes
55+
56+
def sha1
57+
@sha1 ||= OpenSSL::Digest.new('sha1')
58+
end
4659

4760
def parse_file
4861
synchronize do
49-
return keys if hosts_keys
62+
return if hosts_keys && hosts_hashes
5063

51-
return self.hosts_keys = {} unless File.readable?(path)
64+
unless File.readable?(path)
65+
self.hosts_keys = {}
66+
self.hosts_hashes = []
67+
return
68+
end
5269

5370
new_keys = {}
71+
new_hashes = []
5472
File.open(path) do |file|
5573
scanner = StringScanner.new("")
5674
file.each_line do |line|
5775
scanner.string = line
58-
hostlist, key = parse_line(scanner)
59-
next unless key
60-
61-
hostlist.each do |host|
62-
(new_keys[host] ||= []) << key
63-
end
76+
parse_line(scanner, new_keys, new_hashes)
6477
end
6578
end
66-
return self.hosts_keys = new_keys
79+
self.hosts_keys = new_keys
80+
self.hosts_hashes = new_hashes
6781
end
6882
end
6983

70-
def parse_line(scanner)
84+
def parse_line(scanner, hosts_keys, hosts_hashes)
85+
return if empty_line?(scanner)
86+
87+
hostlist = parse_hostlist(scanner)
88+
return unless supported_type?(scanner)
89+
key = parse_key(scanner)
90+
91+
if hostlist.size == 1 && hostlist.first =~ /\A\|1(\|.+){2}\z/
92+
hosts_hashes << [parse_host_hash(hostlist.first), key]
93+
else
94+
hostlist.each do |host|
95+
(hosts_keys[host] ||= []) << key
96+
end
97+
end
98+
end
99+
100+
def parse_host_hash(line)
101+
_, _, salt, hmac = line.split('|')
102+
[Base64.decode64(hmac), Base64.decode64(salt)]
103+
end
104+
105+
def empty_line?(scanner)
71106
scanner.skip(/\s*/)
72-
return if scanner.match?(/$|#/)
107+
scanner.match?(/$|#/)
108+
end
73109

74-
hostlist = scanner.scan(/\S+/).split(',')
110+
def parse_hostlist(scanner)
75111
scanner.skip(/\s*/)
76-
type = scanner.scan(/\S+/)
112+
scanner.scan(/\S+/).split(',')
113+
end
77114

78-
return unless Net::SSH::KnownHosts::SUPPORTED_TYPE.include?(type)
115+
def supported_type?(scanner)
116+
scanner.skip(/\s*/)
117+
Net::SSH::KnownHosts::SUPPORTED_TYPE.include?(scanner.scan(/\S+/))
118+
end
79119

120+
def parse_key(scanner)
80121
scanner.skip(/\s*/)
81-
blob = scanner.rest.unpack("m*").first
82-
return hostlist, Net::SSH::Buffer.new(blob).read_key
122+
Net::SSH::Buffer.new(scanner.rest.unpack("m*").first).read_key
83123
end
84124
end
85125

0 commit comments

Comments
 (0)