summaryrefslogtreecommitdiff
path: root/lib/net/ssh/transport/kex/abstract.rb
blob: 3fd8c3cf588f0849f3f0447598f12e55ce1a5ec4 (plain)
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