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
|