require 'strscan' require 'openssl' require 'base64' require 'net/ssh/buffer' require 'net/ssh/authentication/ed25519_loader' module Net module SSH # Represents the result of a search in known hosts # see search_for class HostKeys include Enumerable attr_reader :host def initialize(host_keys, host, known_hosts, options = {}) @host_keys = host_keys @host = host @known_hosts = known_hosts @options = options end def add_host_key(key) @known_hosts.add(@host, key, @options) @host_keys.push(key) end def each(&block) @host_keys.each(&block) end def empty? @host_keys.empty? end end # Searches an OpenSSH-style known-host file for a given host, and returns all # matching keys. This is used to implement host-key verification, as well as # to determine what key a user prefers to use for a given host. # # This is used internally by Net::SSH, and will never need to be used directly # by consumers of the library. class KnownHosts if defined?(OpenSSL::PKey::EC) SUPPORTED_TYPE = %w[ssh-rsa ssh-dss ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521] else SUPPORTED_TYPE = %w[ssh-rsa ssh-dss] end SUPPORTED_TYPE.push('ssh-ed25519') if Net::SSH::Authentication::ED25519Loader::LOADED class < 1 && hostlist.size > 1 next unless found scanner.skip(/\s*/) type = scanner.scan(/\S+/) next unless SUPPORTED_TYPE.include?(type) scanner.skip(/\s*/) blob = scanner.rest.unpack("m*").first keys << Net::SSH::Buffer.new(blob).read_key end end keys end def match(host, pattern) # see man 8 sshd for pattern details pattern_regexp = pattern.split('*').map do |x| x.split('?').map do |y| Regexp.escape(y) end.join('.') end.join('[^.]*') host =~ Regexp.new("\\A#{pattern_regexp}\\z") end # Indicates whether one of the entries matches an hostname that has been # stored as a HMAC-SHA1 hash in the known hosts. def known_host_hash?(hostlist, entries) if hostlist.size == 1 && hostlist.first =~ /\A\|1(\|.+){2}\z/ chunks = hostlist.first.split(/\|/) salt = Base64.decode64(chunks[2]) digest = OpenSSL::Digest.new('sha1') entries.each do |entry| hmac = OpenSSL::HMAC.digest(digest, salt, entry) return true if Base64.encode64(hmac).chomp == chunks[3] end end false end # Tries to append an entry to the current source file for the given host # and key. If it is unable to (because the file is not writable, for # instance), an exception will be raised. def add(host, key) File.open(source, "a") do |file| blob = [Net::SSH::Buffer.from(:key, key).to_s].pack("m*").gsub(/\s/, "") file.puts "#{host} #{key.ssh_type} #{blob}" end end end end end