lib/net/ssh/transport/kex/abstract.rb
require 'net/ssh/buffer' require 'net/ssh/errors' require 'net/ssh/loggable' require 'net/ssh/transport/openssl' require 'net/ssh/transport/constants' module Net module SSH module Transport module Kex # Abstract class that implement Diffie-Hellman Key Exchange # See https://tools.ietf.org/html/rfc4253#page-21 class Abstract include Loggable include Constants attr_reader :algorithms attr_reader :connection attr_reader :data attr_reader :dh # Create a new instance of the Diffie-Hellman Key Exchange algorithm. # The Diffie-Hellman (DH) key exchange provides a shared secret that # cannot be determined by either party alone. The key exchange is # combined with a signature with the host key to provide host # authentication. def initialize(algorithms, connection, data) @algorithms = algorithms @connection = connection @data = data.dup @dh = generate_key @logger = @data.delete(:logger) end # Perform the key-exchange for the given session, with the given # data. This method will return a hash consisting of the # following keys: # # * :session_id # * :server_key # * :shared_secret # * :hashing_algorithm # # The caller is expected to be able to understand how to use these # deliverables. def exchange_keys result = send_kexinit verify_server_key(result[:server_key]) session_id = verify_signature(result) confirm_newkeys { session_id: session_id, server_key: result[:server_key], shared_secret: result[:shared_secret], hashing_algorithm: digester } end def digester raise NotImplementedError, 'abstract class: digester not implemented' end private def matching?(key_ssh_type, host_key_alg) return true if key_ssh_type == host_key_alg return true if key_ssh_type == 'ssh-rsa' && ['rsa-sha2-512', 'rsa-sha2-256'].include?(host_key_alg) end # Verify that the given key is of the expected type, and that it # really is the key for the session's host. Raise Net::SSH::Exception # if it is not. def verify_server_key(key) # :nodoc: unless matching?(key.ssh_type, algorithms.host_key) raise Net::SSH::Exception, "host key algorithm mismatch '#{key.ssh_type}' != '#{algorithms.host_key}'" end blob, fingerprint = generate_key_fingerprint(key) unless connection.host_key_verifier.verify(key: key, key_blob: blob, fingerprint: fingerprint, session: connection) raise Net::SSH::Exception, 'host key verification failed' end end def generate_key_fingerprint(key) blob = Net::SSH::Buffer.from(:key, key).to_s fingerprint = Net::SSH::Authentication::PubKeyFingerprint.fingerprint(blob, @connection.options[:fingerprint_hash] || 'SHA256') [blob, fingerprint] rescue StandardError => e [nil, "(could not generate fingerprint: #{e.message})"] end # Verify the signature that was received. Raise Net::SSH::Exception # if the signature could not be verified. Otherwise, return the new # session-id. def verify_signature(result) # :nodoc: response = build_signature_buffer(result) hash = digester.digest(response.to_s) server_key = result[:server_key] server_sig = result[:server_sig] unless connection.host_key_verifier.verify_signature { server_key.ssh_do_verify(server_sig, hash, host_key: algorithms.host_key) } raise Net::SSH::Exception, 'could not verify server signature' end hash end # Send the NEWKEYS message, and expect the NEWKEYS message in # reply. def confirm_newkeys # :nodoc: # send own NEWKEYS message first (the wodSSHServer won't send first) response = Net::SSH::Buffer.new response.write_byte(NEWKEYS) connection.send_message(response) # wait for the server's NEWKEYS message buffer = connection.next_message raise Net::SSH::Exception, 'expected NEWKEYS' unless buffer.type == NEWKEYS end end end end end end