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
|
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
|