net-ssh 4.1.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.gitignore +5 -0
  5. data/.rubocop.yml +8 -2
  6. data/.rubocop_todo.yml +405 -552
  7. data/.travis.yml +23 -22
  8. data/CHANGES.txt +112 -1
  9. data/Gemfile +1 -7
  10. data/{Gemfile.norbnacl → Gemfile.noed25519} +1 -1
  11. data/Manifest +4 -5
  12. data/README.md +287 -0
  13. data/Rakefile +40 -29
  14. data/appveyor.yml +12 -6
  15. data/lib/net/ssh.rb +68 -32
  16. data/lib/net/ssh/authentication/agent.rb +234 -222
  17. data/lib/net/ssh/authentication/certificate.rb +175 -164
  18. data/lib/net/ssh/authentication/constants.rb +17 -14
  19. data/lib/net/ssh/authentication/ed25519.rb +162 -141
  20. data/lib/net/ssh/authentication/ed25519_loader.rb +32 -29
  21. data/lib/net/ssh/authentication/key_manager.rb +40 -9
  22. data/lib/net/ssh/authentication/methods/abstract.rb +53 -47
  23. data/lib/net/ssh/authentication/methods/hostbased.rb +32 -33
  24. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +1 -1
  25. data/lib/net/ssh/authentication/methods/none.rb +10 -10
  26. data/lib/net/ssh/authentication/methods/password.rb +13 -13
  27. data/lib/net/ssh/authentication/methods/publickey.rb +56 -55
  28. data/lib/net/ssh/authentication/pageant.rb +468 -465
  29. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  30. data/lib/net/ssh/authentication/session.rb +130 -122
  31. data/lib/net/ssh/buffer.rb +345 -312
  32. data/lib/net/ssh/buffered_io.rb +163 -163
  33. data/lib/net/ssh/config.rb +316 -238
  34. data/lib/net/ssh/connection/channel.rb +670 -650
  35. data/lib/net/ssh/connection/constants.rb +30 -26
  36. data/lib/net/ssh/connection/event_loop.rb +108 -105
  37. data/lib/net/ssh/connection/keepalive.rb +54 -50
  38. data/lib/net/ssh/connection/session.rb +682 -671
  39. data/lib/net/ssh/connection/term.rb +180 -176
  40. data/lib/net/ssh/errors.rb +101 -99
  41. data/lib/net/ssh/key_factory.rb +195 -108
  42. data/lib/net/ssh/known_hosts.rb +161 -152
  43. data/lib/net/ssh/loggable.rb +57 -55
  44. data/lib/net/ssh/packet.rb +82 -78
  45. data/lib/net/ssh/prompt.rb +55 -53
  46. data/lib/net/ssh/proxy/command.rb +104 -89
  47. data/lib/net/ssh/proxy/errors.rb +12 -8
  48. data/lib/net/ssh/proxy/http.rb +93 -91
  49. data/lib/net/ssh/proxy/https.rb +42 -39
  50. data/lib/net/ssh/proxy/jump.rb +50 -47
  51. data/lib/net/ssh/proxy/socks4.rb +0 -2
  52. data/lib/net/ssh/proxy/socks5.rb +11 -12
  53. data/lib/net/ssh/service/forward.rb +370 -317
  54. data/lib/net/ssh/test.rb +83 -77
  55. data/lib/net/ssh/test/channel.rb +146 -142
  56. data/lib/net/ssh/test/extensions.rb +150 -146
  57. data/lib/net/ssh/test/kex.rb +35 -31
  58. data/lib/net/ssh/test/local_packet.rb +48 -44
  59. data/lib/net/ssh/test/packet.rb +87 -84
  60. data/lib/net/ssh/test/remote_packet.rb +35 -31
  61. data/lib/net/ssh/test/script.rb +173 -171
  62. data/lib/net/ssh/test/socket.rb +59 -55
  63. data/lib/net/ssh/transport/algorithms.rb +430 -364
  64. data/lib/net/ssh/transport/cipher_factory.rb +95 -91
  65. data/lib/net/ssh/transport/constants.rb +33 -25
  66. data/lib/net/ssh/transport/ctr.rb +33 -11
  67. data/lib/net/ssh/transport/hmac.rb +15 -13
  68. data/lib/net/ssh/transport/hmac/abstract.rb +82 -63
  69. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  70. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  71. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  72. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  73. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  74. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  75. data/lib/net/ssh/transport/identity_cipher.rb +55 -51
  76. data/lib/net/ssh/transport/kex.rb +14 -13
  77. data/lib/net/ssh/transport/kex/abstract.rb +123 -0
  78. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  79. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +38 -0
  80. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  81. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
  82. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +112 -217
  83. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -62
  84. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  85. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
  86. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
  87. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
  88. data/lib/net/ssh/transport/key_expander.rb +29 -25
  89. data/lib/net/ssh/transport/openssl.rb +116 -116
  90. data/lib/net/ssh/transport/packet_stream.rb +223 -190
  91. data/lib/net/ssh/transport/server_version.rb +64 -66
  92. data/lib/net/ssh/transport/session.rb +306 -257
  93. data/lib/net/ssh/transport/state.rb +198 -196
  94. data/lib/net/ssh/verifiers/accept_new.rb +35 -0
  95. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +34 -0
  96. data/lib/net/ssh/verifiers/always.rb +56 -0
  97. data/lib/net/ssh/verifiers/never.rb +21 -0
  98. data/lib/net/ssh/version.rb +55 -53
  99. data/net-ssh-public_cert.pem +18 -19
  100. data/net-ssh.gemspec +12 -11
  101. data/support/ssh_tunnel_bug.rb +2 -2
  102. metadata +86 -75
  103. metadata.gz.sig +0 -0
  104. data/Gemfile.norbnacl.lock +0 -41
  105. data/README.rdoc +0 -169
  106. data/lib/net/ssh/ruby_compat.rb +0 -24
  107. data/lib/net/ssh/verifiers/lenient.rb +0 -30
  108. data/lib/net/ssh/verifiers/null.rb +0 -12
  109. data/lib/net/ssh/verifiers/secure.rb +0 -52
  110. data/lib/net/ssh/verifiers/strict.rb +0 -24
  111. data/support/arcfour_check.rb +0 -20
@@ -3,62 +3,66 @@ require 'stringio'
3
3
  require 'net/ssh/test/extensions'
4
4
  require 'net/ssh/test/script'
5
5
 
6
- module Net; module SSH; module Test
6
+ module Net
7
+ module SSH
8
+ module Test
7
9
 
8
- # A mock socket implementation for use in testing. It implements the minimum
9
- # necessary interface for interacting with the rest of the Net::SSH::Test
10
- # system.
11
- class Socket < StringIO
12
- attr_reader :host, :port
13
-
14
- # The Net::SSH::Test::Script object in use by this socket. This is the
15
- # canonical script instance that should be used for any test depending on
16
- # this socket instance.
17
- attr_reader :script
18
-
19
- # Create a new test socket. This will also instantiate a new Net::SSH::Test::Script
20
- # and seed it with the necessary events to power the initialization of the
21
- # connection.
22
- def initialize
23
- extend(Net::SSH::Transport::PacketStream)
24
- super "SSH-2.0-Test\r\n"
25
-
26
- @script = Script.new
27
-
28
- script.sends(:kexinit)
29
- script.gets(:kexinit, 1, 2, 3, 4, "test", "ssh-rsa", "none", "none", "none", "none", "none", "none", "", "", false)
30
- script.sends(:newkeys)
31
- script.gets(:newkeys)
32
- end
33
-
34
- # This doesn't actually do anything, since we don't really care what gets
35
- # written.
36
- def write(data)
37
- # black hole, because we don't actually care about what gets written
38
- end
39
-
40
- # Allows the socket to also mimic a socket factory, simply returning
41
- # +self+.
42
- def open(host, port, options={})
43
- @host, @port = host, port
44
- self
45
- end
46
-
47
- # Returns a sockaddr struct for the port and host that were used when the
48
- # socket was instantiated.
49
- def getpeername
50
- ::Socket.sockaddr_in(port, host)
51
- end
52
-
53
- # Alias to #read, but never returns nil (returns an empty string instead).
54
- def recv(n)
55
- read(n) || ""
56
- end
10
+ # A mock socket implementation for use in testing. It implements the minimum
11
+ # necessary interface for interacting with the rest of the Net::SSH::Test
12
+ # system.
13
+ class Socket < StringIO
14
+ attr_reader :host, :port
15
+
16
+ # The Net::SSH::Test::Script object in use by this socket. This is the
17
+ # canonical script instance that should be used for any test depending on
18
+ # this socket instance.
19
+ attr_reader :script
20
+
21
+ # Create a new test socket. This will also instantiate a new Net::SSH::Test::Script
22
+ # and seed it with the necessary events to power the initialization of the
23
+ # connection.
24
+ def initialize
25
+ extend(Net::SSH::Transport::PacketStream)
26
+ super "SSH-2.0-Test\r\n"
27
+
28
+ @script = Script.new
29
+
30
+ script.sends(:kexinit)
31
+ script.gets(:kexinit, 1, 2, 3, 4, "test", "ssh-rsa", "none", "none", "none", "none", "none", "none", "", "", false)
32
+ script.sends(:newkeys)
33
+ script.gets(:newkeys)
34
+ end
35
+
36
+ # This doesn't actually do anything, since we don't really care what gets
37
+ # written.
38
+ def write(data)
39
+ # black hole, because we don't actually care about what gets written
40
+ end
41
+
42
+ # Allows the socket to also mimic a socket factory, simply returning
43
+ # +self+.
44
+ def open(host, port, options={})
45
+ @host, @port = host, port
46
+ self
47
+ end
48
+
49
+ # Returns a sockaddr struct for the port and host that were used when the
50
+ # socket was instantiated.
51
+ def getpeername
52
+ ::Socket.sockaddr_in(port, host)
53
+ end
54
+
55
+ # Alias to #read, but never returns nil (returns an empty string instead).
56
+ def recv(n)
57
+ read(n) || ""
58
+ end
59
+
60
+ def readpartial(n)
61
+ recv(n)
62
+ end
63
+
64
+ end
57
65
 
58
- def readpartial(n)
59
- recv(n)
60
66
  end
61
-
62
67
  end
63
-
64
- end; end; end
68
+ end
@@ -5,429 +5,495 @@ require 'net/ssh/transport/cipher_factory'
5
5
  require 'net/ssh/transport/constants'
6
6
  require 'net/ssh/transport/hmac'
7
7
  require 'net/ssh/transport/kex'
8
+ require 'net/ssh/transport/kex/curve25519_sha256_loader'
8
9
  require 'net/ssh/transport/server_version'
9
10
  require 'net/ssh/authentication/ed25519_loader'
10
11
 
11
- module Net; module SSH; module Transport
12
-
13
- # Implements the higher-level logic behind an SSH key-exchange. It handles
14
- # both the initial exchange, as well as subsequent re-exchanges (as needed).
15
- # It also encapsulates the negotiation of the algorithms, and provides a
16
- # single point of access to the negotiated algorithms.
17
- #
18
- # You will never instantiate or reference this directly. It is used
19
- # internally by the transport layer.
20
- class Algorithms
21
- include Constants, Loggable
22
-
23
- # Define the default algorithms, in order of preference, supported by
24
- # Net::SSH.
25
- ALGORITHMS = {
26
- host_key: %w(ssh-rsa ssh-dss
27
- ssh-rsa-cert-v01@openssh.com
28
- ssh-rsa-cert-v00@openssh.com),
29
- kex: %w(diffie-hellman-group-exchange-sha1
30
- diffie-hellman-group1-sha1
31
- diffie-hellman-group14-sha1
32
- diffie-hellman-group-exchange-sha256),
33
- encryption: %w(aes128-cbc 3des-cbc blowfish-cbc cast128-cbc
34
- aes192-cbc aes256-cbc rijndael-cbc@lysator.liu.se
35
- idea-cbc none arcfour128 arcfour256 arcfour
36
- aes128-ctr aes192-ctr aes256-ctr
37
- cast128-ctr blowfish-ctr 3des-ctr),
38
-
39
- hmac: %w(hmac-sha1 hmac-md5 hmac-sha1-96 hmac-md5-96
40
- hmac-ripemd160 hmac-ripemd160@openssh.com
41
- hmac-sha2-256 hmac-sha2-512 hmac-sha2-256-96
42
- hmac-sha2-512-96 none),
43
-
44
- compression: %w(none zlib@openssh.com zlib),
45
- language: %w()
46
- }
47
- if defined?(OpenSSL::PKey::EC)
48
- ALGORITHMS[:host_key] += %w(ecdsa-sha2-nistp256
49
- ecdsa-sha2-nistp384
50
- ecdsa-sha2-nistp521)
51
- if Net::SSH::Authentication::ED25519Loader::LOADED
52
- ALGORITHMS[:host_key] += %w(ssh-ed25519)
53
- end
54
- ALGORITHMS[:kex] += %w(ecdh-sha2-nistp256
55
- ecdh-sha2-nistp384
56
- ecdh-sha2-nistp521)
57
- end
12
+ module Net
13
+ module SSH
14
+ module Transport
15
+ # Implements the higher-level logic behind an SSH key-exchange. It handles
16
+ # both the initial exchange, as well as subsequent re-exchanges (as needed).
17
+ # It also encapsulates the negotiation of the algorithms, and provides a
18
+ # single point of access to the negotiated algorithms.
19
+ #
20
+ # You will never instantiate or reference this directly. It is used
21
+ # internally by the transport layer.
22
+ class Algorithms
23
+ include Loggable
24
+ include Constants
25
+
26
+ # Define the default algorithms, in order of preference, supported by Net::SSH.
27
+ DEFAULT_ALGORITHMS = {
28
+ host_key: %w[ecdsa-sha2-nistp521-cert-v01@openssh.com
29
+ ecdsa-sha2-nistp384-cert-v01@openssh.com
30
+ ecdsa-sha2-nistp256-cert-v01@openssh.com
31
+ ecdsa-sha2-nistp521
32
+ ecdsa-sha2-nistp384
33
+ ecdsa-sha2-nistp256
34
+ ssh-rsa-cert-v01@openssh.com
35
+ ssh-rsa-cert-v00@openssh.com
36
+ ssh-rsa],
37
+
38
+ kex: %w[ecdh-sha2-nistp521
39
+ ecdh-sha2-nistp384
40
+ ecdh-sha2-nistp256
41
+ diffie-hellman-group-exchange-sha256
42
+ diffie-hellman-group14-sha1],
43
+
44
+ encryption: %w[aes256-ctr aes192-ctr aes128-ctr],
45
+
46
+ hmac: %w[hmac-sha2-512-etm@openssh.com hmac-sha2-256-etm@openssh.com
47
+ hmac-sha2-512 hmac-sha2-256
48
+ hmac-sha1]
49
+ }.freeze
50
+
51
+ if Net::SSH::Authentication::ED25519Loader::LOADED
52
+ DEFAULT_ALGORITHMS[:host_key].unshift(
53
+ 'ssh-ed25519-cert-v01@openssh.com',
54
+ 'ssh-ed25519'
55
+ )
56
+ end
58
57
 
59
- # The underlying transport layer session that supports this object
60
- attr_reader :session
58
+ if Net::SSH::Transport::Kex::Curve25519Sha256Loader::LOADED
59
+ DEFAULT_ALGORITHMS[:kex].unshift(
60
+ 'curve25519-sha256',
61
+ 'curve25519-sha256@libssh.org'
62
+ )
63
+ end
61
64
 
62
- # The hash of options used to initialize this object
63
- attr_reader :options
65
+ # Define all algorithms, with the deprecated, supported by Net::SSH.
66
+ ALGORITHMS = {
67
+ host_key: DEFAULT_ALGORITHMS[:host_key] + %w[ssh-dss],
64
68
 
65
- # The kex algorithm to use settled on between the client and server.
66
- attr_reader :kex
69
+ kex: DEFAULT_ALGORITHMS[:kex] +
70
+ %w[diffie-hellman-group-exchange-sha1
71
+ diffie-hellman-group1-sha1],
67
72
 
68
- # The type of host key that will be used for this session.
69
- attr_reader :host_key
73
+ encryption: DEFAULT_ALGORITHMS[:encryption] +
74
+ %w[aes256-cbc aes192-cbc aes128-cbc
75
+ rijndael-cbc@lysator.liu.se
76
+ blowfish-ctr blowfish-cbc
77
+ cast128-ctr cast128-cbc
78
+ 3des-ctr 3des-cbc
79
+ idea-cbc
80
+ none],
70
81
 
71
- # The type of the cipher to use to encrypt packets sent from the client to
72
- # the server.
73
- attr_reader :encryption_client
82
+ hmac: DEFAULT_ALGORITHMS[:hmac] +
83
+ %w[hmac-sha2-512-96 hmac-sha2-256-96
84
+ hmac-sha1-96
85
+ hmac-ripemd160 hmac-ripemd160@openssh.com
86
+ hmac-md5 hmac-md5-96
87
+ none],
74
88
 
75
- # The type of the cipher to use to decrypt packets arriving from the server.
76
- attr_reader :encryption_server
89
+ compression: %w[none zlib@openssh.com zlib],
90
+ language: %w[]
91
+ }.freeze
77
92
 
78
- # The type of HMAC to use to sign packets sent by the client.
79
- attr_reader :hmac_client
93
+ # The underlying transport layer session that supports this object
94
+ attr_reader :session
80
95
 
81
- # The type of HMAC to use to validate packets arriving from the server.
82
- attr_reader :hmac_server
96
+ # The hash of options used to initialize this object
97
+ attr_reader :options
83
98
 
84
- # The type of compression to use to compress packets being sent by the client.
85
- attr_reader :compression_client
99
+ # The kex algorithm to use settled on between the client and server.
100
+ attr_reader :kex
86
101
 
87
- # The type of compression to use to decompress packets arriving from the server.
88
- attr_reader :compression_server
102
+ # The type of host key that will be used for this session.
103
+ attr_reader :host_key
89
104
 
90
- # The language that will be used in messages sent by the client.
91
- attr_reader :language_client
105
+ # The type of the cipher to use to encrypt packets sent from the client to
106
+ # the server.
107
+ attr_reader :encryption_client
92
108
 
93
- # The language that will be used in messages sent from the server.
94
- attr_reader :language_server
109
+ # The type of the cipher to use to decrypt packets arriving from the server.
110
+ attr_reader :encryption_server
95
111
 
96
- # The hash of algorithms preferred by the client, which will be told to
97
- # the server during algorithm negotiation.
98
- attr_reader :algorithms
112
+ # The type of HMAC to use to sign packets sent by the client.
113
+ attr_reader :hmac_client
99
114
 
100
- # The session-id for this session, as decided during the initial key exchange.
101
- attr_reader :session_id
115
+ # The type of HMAC to use to validate packets arriving from the server.
116
+ attr_reader :hmac_server
102
117
 
103
- # Returns true if the given packet can be processed during a key-exchange.
104
- def self.allowed_packet?(packet)
105
- ( 1.. 4).include?(packet.type) ||
106
- ( 6..19).include?(packet.type) ||
107
- (21..49).include?(packet.type)
108
- end
118
+ # The type of compression to use to compress packets being sent by the client.
119
+ attr_reader :compression_client
109
120
 
110
- # Instantiates a new Algorithms object, and prepares the hash of preferred
111
- # algorithms based on the options parameter and the ALGORITHMS constant.
112
- def initialize(session, options={})
113
- @session = session
114
- @logger = session.logger
115
- @options = options
116
- @algorithms = {}
117
- @pending = @initialized = false
118
- @client_packet = @server_packet = nil
119
- prepare_preferred_algorithms!
120
- end
121
+ # The type of compression to use to decompress packets arriving from the server.
122
+ attr_reader :compression_server
121
123
 
122
- # Start the algorithm negotation
123
- def start
124
- raise ArgumentError, "Cannot call start if it's negotiation started or done" if @pending || @initialized
125
- send_kexinit
126
- end
124
+ # The language that will be used in messages sent by the client.
125
+ attr_reader :language_client
127
126
 
128
- # Request a rekey operation. This will return immediately, and does not
129
- # actually perform the rekey operation. It does cause the session to change
130
- # state, however--until the key exchange finishes, no new packets will be
131
- # processed.
132
- def rekey!
133
- @client_packet = @server_packet = nil
134
- @initialized = false
135
- send_kexinit
136
- end
127
+ # The language that will be used in messages sent from the server.
128
+ attr_reader :language_server
137
129
 
138
- # Called by the transport layer when a KEXINIT packet is received, indicating
139
- # that the server wants to exchange keys. This can be spontaneous, or it
140
- # can be in response to a client-initiated rekey request (see #rekey!). Either
141
- # way, this will block until the key exchange completes.
142
- def accept_kexinit(packet)
143
- info { "got KEXINIT from server" }
144
- @server_data = parse_server_algorithm_packet(packet)
145
- @server_packet = @server_data[:raw]
146
- if !pending?
147
- send_kexinit
148
- else
149
- proceed!
150
- end
151
- end
130
+ # The hash of algorithms preferred by the client, which will be told to
131
+ # the server during algorithm negotiation.
132
+ attr_reader :algorithms
152
133
 
153
- # A convenience method for accessing the list of preferred types for a
154
- # specific algorithm (see #algorithms).
155
- def [](key)
156
- algorithms[key]
157
- end
134
+ # The session-id for this session, as decided during the initial key exchange.
135
+ attr_reader :session_id
158
136
 
159
- # Returns +true+ if a key-exchange is pending. This will be true from the
160
- # moment either the client or server requests the key exchange, until the
161
- # exchange completes. While an exchange is pending, only a limited number
162
- # of packets are allowed, so event processing essentially stops during this
163
- # period.
164
- def pending?
165
- @pending
166
- end
137
+ # Returns true if the given packet can be processed during a key-exchange.
138
+ def self.allowed_packet?(packet)
139
+ (1..4).include?(packet.type) ||
140
+ (6..19).include?(packet.type) ||
141
+ (21..49).include?(packet.type)
142
+ end
167
143
 
168
- # Returns true if no exchange is pending, and otherwise returns true or
169
- # false depending on whether the given packet is of a type that is allowed
170
- # during a key exchange.
171
- def allow?(packet)
172
- !pending? || Algorithms.allowed_packet?(packet)
173
- end
144
+ # Instantiates a new Algorithms object, and prepares the hash of preferred
145
+ # algorithms based on the options parameter and the ALGORITHMS constant.
146
+ def initialize(session, options={})
147
+ @session = session
148
+ @logger = session.logger
149
+ @options = options
150
+ @algorithms = {}
151
+ @pending = @initialized = false
152
+ @client_packet = @server_packet = nil
153
+ prepare_preferred_algorithms!
154
+ end
174
155
 
175
- # Returns true if the algorithms have been negotiated at all.
176
- def initialized?
177
- @initialized
178
- end
156
+ # Start the algorithm negotation
157
+ def start
158
+ raise ArgumentError, "Cannot call start if it's negotiation started or done" if @pending || @initialized
179
159
 
180
- private
181
-
182
- # Sends a KEXINIT packet to the server. If a server KEXINIT has already
183
- # been received, this will then invoke #proceed! to proceed with the key
184
- # exchange, otherwise it returns immediately (but sets the object to the
185
- # pending state).
186
- def send_kexinit
187
- info { "sending KEXINIT" }
188
- @pending = true
189
- packet = build_client_algorithm_packet
190
- @client_packet = packet.to_s
191
- session.send_message(packet)
192
- proceed! if @server_packet
193
- end
160
+ send_kexinit
161
+ end
194
162
 
195
- # After both client and server have sent their KEXINIT packets, this
196
- # will do the algorithm negotiation and key exchange. Once both finish,
197
- # the object leaves the pending state and the method returns.
198
- def proceed!
199
- info { "negotiating algorithms" }
200
- negotiate_algorithms
201
- exchange_keys
202
- @pending = false
203
- end
163
+ # Request a rekey operation. This will return immediately, and does not
164
+ # actually perform the rekey operation. It does cause the session to change
165
+ # state, however--until the key exchange finishes, no new packets will be
166
+ # processed.
167
+ def rekey!
168
+ @client_packet = @server_packet = nil
169
+ @initialized = false
170
+ send_kexinit
171
+ end
204
172
 
205
- # Prepares the list of preferred algorithms, based on the options hash
206
- # that was given when the object was constructed, and the ALGORITHMS
207
- # constant. Also, when determining the host_key type to use, the known
208
- # hosts files are examined to see if the host has ever sent a host_key
209
- # before, and if so, that key type is used as the preferred type for
210
- # communicating with this server.
211
- def prepare_preferred_algorithms!
212
- options[:compression] = %w(zlib@openssh.com zlib) if options[:compression] == true
213
-
214
- ALGORITHMS.each do |algorithm, supported|
215
- algorithms[algorithm] = compose_algorithm_list(supported, options[algorithm], options[:append_all_supported_algorithms])
173
+ # Called by the transport layer when a KEXINIT packet is received, indicating
174
+ # that the server wants to exchange keys. This can be spontaneous, or it
175
+ # can be in response to a client-initiated rekey request (see #rekey!). Either
176
+ # way, this will block until the key exchange completes.
177
+ def accept_kexinit(packet)
178
+ info { "got KEXINIT from server" }
179
+ @server_data = parse_server_algorithm_packet(packet)
180
+ @server_packet = @server_data[:raw]
181
+ if !pending?
182
+ send_kexinit
183
+ else
184
+ proceed!
185
+ end
216
186
  end
217
187
 
218
- # for convention, make sure our list has the same keys as the server
219
- # list
188
+ # A convenience method for accessing the list of preferred types for a
189
+ # specific algorithm (see #algorithms).
190
+ def [](key)
191
+ algorithms[key]
192
+ end
220
193
 
221
- algorithms[:encryption_client ] = algorithms[:encryption_server ] = algorithms[:encryption]
222
- algorithms[:hmac_client ] = algorithms[:hmac_server ] = algorithms[:hmac]
223
- algorithms[:compression_client] = algorithms[:compression_server] = algorithms[:compression]
224
- algorithms[:language_client ] = algorithms[:language_server ] = algorithms[:language]
194
+ # Returns +true+ if a key-exchange is pending. This will be true from the
195
+ # moment either the client or server requests the key exchange, until the
196
+ # exchange completes. While an exchange is pending, only a limited number
197
+ # of packets are allowed, so event processing essentially stops during this
198
+ # period.
199
+ def pending?
200
+ @pending
201
+ end
225
202
 
226
- if !options.key?(:host_key)
227
- # make sure the host keys are specified in preference order, where any
228
- # existing known key for the host has preference.
203
+ # Returns true if no exchange is pending, and otherwise returns true or
204
+ # false depending on whether the given packet is of a type that is allowed
205
+ # during a key exchange.
206
+ def allow?(packet)
207
+ !pending? || Algorithms.allowed_packet?(packet)
208
+ end
209
+
210
+ # Returns true if the algorithms have been negotiated at all.
211
+ def initialized?
212
+ @initialized
213
+ end
229
214
 
230
- existing_keys = session.host_keys
231
- host_keys = existing_keys.map { |key| key.ssh_type }.uniq
232
- algorithms[:host_key].each do |name|
233
- host_keys << name unless host_keys.include?(name)
215
+ def host_key_format
216
+ case host_key
217
+ when /^([a-z0-9-]+)-cert-v\d{2}@openssh.com$/
218
+ Regexp.last_match[1]
219
+ else
220
+ host_key
234
221
  end
235
- algorithms[:host_key] = host_keys
236
222
  end
237
- end
238
223
 
239
- # Composes the list of algorithms by taking supported algorithms and matching with supplied options.
240
- def compose_algorithm_list(supported, option, append_all_supported_algorithms = false)
241
- return supported.dup unless option
224
+ private
225
+
226
+ # Sends a KEXINIT packet to the server. If a server KEXINIT has already
227
+ # been received, this will then invoke #proceed! to proceed with the key
228
+ # exchange, otherwise it returns immediately (but sets the object to the
229
+ # pending state).
230
+ def send_kexinit
231
+ info { "sending KEXINIT" }
232
+ @pending = true
233
+ packet = build_client_algorithm_packet
234
+ @client_packet = packet.to_s
235
+ session.send_message(packet)
236
+ proceed! if @server_packet
237
+ end
238
+
239
+ # After both client and server have sent their KEXINIT packets, this
240
+ # will do the algorithm negotiation and key exchange. Once both finish,
241
+ # the object leaves the pending state and the method returns.
242
+ def proceed!
243
+ info { "negotiating algorithms" }
244
+ negotiate_algorithms
245
+ exchange_keys
246
+ @pending = false
247
+ end
248
+
249
+ # Prepares the list of preferred algorithms, based on the options hash
250
+ # that was given when the object was constructed, and the ALGORITHMS
251
+ # constant. Also, when determining the host_key type to use, the known
252
+ # hosts files are examined to see if the host has ever sent a host_key
253
+ # before, and if so, that key type is used as the preferred type for
254
+ # communicating with this server.
255
+ def prepare_preferred_algorithms!
256
+ options[:compression] = %w[zlib@openssh.com zlib] if options[:compression] == true
257
+
258
+ ALGORITHMS.each do |algorithm, supported|
259
+ algorithms[algorithm] = compose_algorithm_list(
260
+ supported, options[algorithm] || DEFAULT_ALGORITHMS[algorithm],
261
+ options[:append_all_supported_algorithms]
262
+ )
263
+ end
264
+
265
+ # for convention, make sure our list has the same keys as the server
266
+ # list
242
267
 
243
- list = []
244
- option = Array(option).compact.uniq
268
+ algorithms[:encryption_client ] = algorithms[:encryption_server ] = algorithms[:encryption]
269
+ algorithms[:hmac_client ] = algorithms[:hmac_server ] = algorithms[:hmac]
270
+ algorithms[:compression_client] = algorithms[:compression_server] = algorithms[:compression]
271
+ algorithms[:language_client ] = algorithms[:language_server ] = algorithms[:language]
245
272
 
246
- if option.first && option.first.start_with?('+')
247
- list = supported.dup
248
- list << option.first[1..-1]
249
- list.concat(option[1..-1])
250
- list.uniq!
251
- else
252
- list = option
273
+ if !options.key?(:host_key)
274
+ # make sure the host keys are specified in preference order, where any
275
+ # existing known key for the host has preference.
253
276
 
254
- if append_all_supported_algorithms
255
- supported.each { |name| list << name unless list.include?(name) }
277
+ existing_keys = session.host_keys
278
+ host_keys = existing_keys.map { |key| key.ssh_type }.uniq
279
+ algorithms[:host_key].each do |name|
280
+ host_keys << name unless host_keys.include?(name)
281
+ end
282
+ algorithms[:host_key] = host_keys
256
283
  end
257
284
  end
258
285
 
259
- unsupported = []
260
- list.select! do |name|
261
- is_supported = supported.include?(name)
262
- unsupported << name unless is_supported
263
- is_supported
264
- end
286
+ # Composes the list of algorithms by taking supported algorithms and matching with supplied options.
287
+ def compose_algorithm_list(supported, option, append_all_supported_algorithms = false)
288
+ return supported.dup unless option
265
289
 
266
- lwarn { %(unsupported algorithm: `#{unsupported}') } unless unsupported.empty?
290
+ list = []
291
+ option = Array(option).compact.uniq
267
292
 
268
- list
269
- end
293
+ if option.first && option.first.start_with?('+', '-')
294
+ list = supported.dup
270
295
 
271
- # Parses a KEXINIT packet from the server.
272
- def parse_server_algorithm_packet(packet)
273
- data = { raw: packet.content }
274
-
275
- packet.read(16) # skip the cookie value
276
-
277
- data[:kex] = packet.read_string.split(/,/)
278
- data[:host_key] = packet.read_string.split(/,/)
279
- data[:encryption_client] = packet.read_string.split(/,/)
280
- data[:encryption_server] = packet.read_string.split(/,/)
281
- data[:hmac_client] = packet.read_string.split(/,/)
282
- data[:hmac_server] = packet.read_string.split(/,/)
283
- data[:compression_client] = packet.read_string.split(/,/)
284
- data[:compression_server] = packet.read_string.split(/,/)
285
- data[:language_client] = packet.read_string.split(/,/)
286
- data[:language_server] = packet.read_string.split(/,/)
287
-
288
- # TODO: if first_kex_packet_follows, we need to try to skip the
289
- # actual kexinit stuff and try to guess what the server is doing...
290
- # need to read more about this scenario.
291
- # first_kex_packet_follows = packet.read_bool
292
-
293
- return data
294
- end
296
+ appends = option.select { |opt| opt.start_with?('+') }.map { |opt| opt[1..-1] }
297
+ deletions = option.select { |opt| opt.start_with?('-') }.map { |opt| opt[1..-1] }
295
298
 
296
- # Given the #algorithms map of preferred algorithm types, this constructs
297
- # a KEXINIT packet to send to the server. It does not actually send it,
298
- # it simply builds the packet and returns it.
299
- def build_client_algorithm_packet
300
- kex = algorithms[:kex ].join(",")
301
- host_key = algorithms[:host_key ].join(",")
302
- encryption = algorithms[:encryption ].join(",")
303
- hmac = algorithms[:hmac ].join(",")
304
- compression = algorithms[:compression].join(",")
305
- language = algorithms[:language ].join(",")
306
-
307
- Net::SSH::Buffer.from(:byte, KEXINIT,
308
- :long, [rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF)],
309
- :mstring, [kex, host_key, encryption, encryption, hmac, hmac],
310
- :mstring, [compression, compression, language, language],
311
- :bool, false, :long, 0)
312
- end
299
+ list.concat(appends)
300
+
301
+ deletions.each do |opt|
302
+ if opt.include?('*')
303
+ opt_escaped = Regexp.escape(opt)
304
+ algo_re = /\A#{opt_escaped.gsub('\*', '[A-Za-z\d\-@\.]*')}\z/
305
+ list.delete_if { |existing_opt| algo_re.match(existing_opt) }
306
+ else
307
+ list.delete(opt)
308
+ end
309
+ end
313
310
 
314
- # Given the parsed server KEX packet, and the client's preferred algorithm
315
- # lists in #algorithms, determine which preferred algorithms each has
316
- # in common and set those as the selected algorithms. If, for any algorithm,
317
- # no type can be settled on, an exception is raised.
318
- def negotiate_algorithms
319
- @kex = negotiate(:kex)
320
- @host_key = negotiate(:host_key)
321
- @encryption_client = negotiate(:encryption_client)
322
- @encryption_server = negotiate(:encryption_server)
323
- @hmac_client = negotiate(:hmac_client)
324
- @hmac_server = negotiate(:hmac_server)
325
- @compression_client = negotiate(:compression_client)
326
- @compression_server = negotiate(:compression_server)
327
- @language_client = negotiate(:language_client) rescue ""
328
- @language_server = negotiate(:language_server) rescue ""
329
-
330
- debug do
331
- "negotiated:\n" +
332
- [:kex, :host_key, :encryption_server, :encryption_client, :hmac_client, :hmac_server, :compression_client, :compression_server, :language_client, :language_server].map do |key|
333
- "* #{key}: #{instance_variable_get("@#{key}")}"
334
- end.join("\n")
311
+ list.uniq!
312
+ else
313
+ list = option
314
+
315
+ if append_all_supported_algorithms
316
+ supported.each { |name| list << name unless list.include?(name) }
317
+ end
318
+ end
319
+
320
+ unsupported = []
321
+ list.select! do |name|
322
+ is_supported = supported.include?(name)
323
+ unsupported << name unless is_supported
324
+ is_supported
325
+ end
326
+
327
+ lwarn { %(unsupported algorithm: `#{unsupported}') } unless unsupported.empty?
328
+
329
+ list
335
330
  end
336
- end
337
331
 
338
- # Negotiates a single algorithm based on the preferences reported by the
339
- # server and those set by the client. This is called by
340
- # #negotiate_algorithms.
341
- def negotiate(algorithm)
342
- match = self[algorithm].find { |item| @server_data[algorithm].include?(item) }
332
+ # Parses a KEXINIT packet from the server.
333
+ def parse_server_algorithm_packet(packet)
334
+ data = { raw: packet.content }
335
+
336
+ packet.read(16) # skip the cookie value
337
+
338
+ data[:kex] = packet.read_string.split(/,/)
339
+ data[:host_key] = packet.read_string.split(/,/)
340
+ data[:encryption_client] = packet.read_string.split(/,/)
341
+ data[:encryption_server] = packet.read_string.split(/,/)
342
+ data[:hmac_client] = packet.read_string.split(/,/)
343
+ data[:hmac_server] = packet.read_string.split(/,/)
344
+ data[:compression_client] = packet.read_string.split(/,/)
345
+ data[:compression_server] = packet.read_string.split(/,/)
346
+ data[:language_client] = packet.read_string.split(/,/)
347
+ data[:language_server] = packet.read_string.split(/,/)
348
+
349
+ # TODO: if first_kex_packet_follows, we need to try to skip the
350
+ # actual kexinit stuff and try to guess what the server is doing...
351
+ # need to read more about this scenario.
352
+ # first_kex_packet_follows = packet.read_bool
353
+
354
+ return data
355
+ end
343
356
 
344
- if match.nil?
345
- raise Net::SSH::Exception, "could not settle on #{algorithm} algorithm"
357
+ # Given the #algorithms map of preferred algorithm types, this constructs
358
+ # a KEXINIT packet to send to the server. It does not actually send it,
359
+ # it simply builds the packet and returns it.
360
+ def build_client_algorithm_packet
361
+ kex = algorithms[:kex].join(",")
362
+ host_key = algorithms[:host_key].join(",")
363
+ encryption = algorithms[:encryption].join(",")
364
+ hmac = algorithms[:hmac].join(",")
365
+ compression = algorithms[:compression].join(",")
366
+ language = algorithms[:language].join(",")
367
+
368
+ Net::SSH::Buffer.from(:byte, KEXINIT,
369
+ :long, [rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF)],
370
+ :mstring, [kex, host_key, encryption, encryption, hmac, hmac],
371
+ :mstring, [compression, compression, language, language],
372
+ :bool, false, :long, 0)
346
373
  end
347
374
 
348
- return match
349
- end
375
+ # Given the parsed server KEX packet, and the client's preferred algorithm
376
+ # lists in #algorithms, determine which preferred algorithms each has
377
+ # in common and set those as the selected algorithms. If, for any algorithm,
378
+ # no type can be settled on, an exception is raised.
379
+ def negotiate_algorithms
380
+ @kex = negotiate(:kex)
381
+ @host_key = negotiate(:host_key)
382
+ @encryption_client = negotiate(:encryption_client)
383
+ @encryption_server = negotiate(:encryption_server)
384
+ @hmac_client = negotiate(:hmac_client)
385
+ @hmac_server = negotiate(:hmac_server)
386
+ @compression_client = negotiate(:compression_client)
387
+ @compression_server = negotiate(:compression_server)
388
+ @language_client = negotiate(:language_client) rescue ""
389
+ @language_server = negotiate(:language_server) rescue ""
390
+
391
+ debug do
392
+ "negotiated:\n" +
393
+ %i[kex host_key encryption_server encryption_client hmac_client hmac_server
394
+ compression_client compression_server language_client language_server].map do |key|
395
+ "* #{key}: #{instance_variable_get("@#{key}")}"
396
+ end.join("\n")
397
+ end
398
+ end
350
399
 
351
- # Considers the sizes of the keys and block-sizes for the selected ciphers,
352
- # and the lengths of the hmacs, and returns the largest as the byte requirement
353
- # for the key-exchange algorithm.
354
- def kex_byte_requirement
355
- sizes = [8] # require at least 8 bytes
400
+ # Negotiates a single algorithm based on the preferences reported by the
401
+ # server and those set by the client. This is called by
402
+ # #negotiate_algorithms.
403
+ def negotiate(algorithm)
404
+ match = self[algorithm].find { |item| @server_data[algorithm].include?(item) }
356
405
 
357
- sizes.concat(CipherFactory.get_lengths(encryption_client))
358
- sizes.concat(CipherFactory.get_lengths(encryption_server))
406
+ if match.nil?
407
+ raise Net::SSH::Exception, "could not settle on #{algorithm} algorithm\n"\
408
+ "Server #{algorithm} preferences: #{@server_data[algorithm].join(',')}\n"\
409
+ "Client #{algorithm} preferences: #{self[algorithm].join(',')}"
410
+ end
359
411
 
360
- sizes << HMAC.key_length(hmac_client)
361
- sizes << HMAC.key_length(hmac_server)
412
+ return match
413
+ end
362
414
 
363
- sizes.max
364
- end
415
+ # Considers the sizes of the keys and block-sizes for the selected ciphers,
416
+ # and the lengths of the hmacs, and returns the largest as the byte requirement
417
+ # for the key-exchange algorithm.
418
+ def kex_byte_requirement
419
+ sizes = [8] # require at least 8 bytes
365
420
 
366
- # Instantiates one of the Transport::Kex classes (based on the negotiated
367
- # kex algorithm), and uses it to exchange keys. Then, the ciphers and
368
- # HMACs are initialized and fed to the transport layer, to be used in
369
- # further communication with the server.
370
- def exchange_keys
371
- debug { "exchanging keys" }
372
-
373
- algorithm = Kex::MAP[kex].new(self, session,
374
- client_version_string: Net::SSH::Transport::ServerVersion::PROTO_VERSION,
375
- server_version_string: session.server_version.version,
376
- server_algorithm_packet: @server_packet,
377
- client_algorithm_packet: @client_packet,
378
- need_bytes: kex_byte_requirement,
379
- minimum_dh_bits: options[:minimum_dh_bits],
380
- logger: logger)
381
- result = algorithm.exchange_keys
382
-
383
- secret = result[:shared_secret].to_ssh
384
- hash = result[:session_id]
385
- digester = result[:hashing_algorithm]
386
-
387
- @session_id ||= hash
388
-
389
- key = Proc.new { |salt| digester.digest(secret + hash + salt + @session_id) }
390
-
391
- iv_client = key["A"]
392
- iv_server = key["B"]
393
- key_client = key["C"]
394
- key_server = key["D"]
395
- mac_key_client = key["E"]
396
- mac_key_server = key["F"]
397
-
398
- parameters = { shared: secret, hash: hash, digester: digester }
399
-
400
- cipher_client = CipherFactory.get(encryption_client, parameters.merge(iv: iv_client, key: key_client, encrypt: true))
401
- cipher_server = CipherFactory.get(encryption_server, parameters.merge(iv: iv_server, key: key_server, decrypt: true))
402
-
403
- mac_client = HMAC.get(hmac_client, mac_key_client, parameters)
404
- mac_server = HMAC.get(hmac_server, mac_key_server, parameters)
405
-
406
- session.configure_client cipher: cipher_client, hmac: mac_client,
407
- compression: normalize_compression_name(compression_client),
408
- compression_level: options[:compression_level],
409
- rekey_limit: options[:rekey_limit],
410
- max_packets: options[:rekey_packet_limit],
411
- max_blocks: options[:rekey_blocks_limit]
412
-
413
- session.configure_server cipher: cipher_server, hmac: mac_server,
414
- compression: normalize_compression_name(compression_server),
415
- rekey_limit: options[:rekey_limit],
416
- max_packets: options[:rekey_packet_limit],
417
- max_blocks: options[:rekey_blocks_limit]
418
-
419
- @initialized = true
420
- end
421
+ sizes.concat(CipherFactory.get_lengths(encryption_client))
422
+ sizes.concat(CipherFactory.get_lengths(encryption_server))
421
423
 
422
- # Given the SSH name for some compression algorithm, return a normalized
423
- # name as a symbol.
424
- def normalize_compression_name(name)
425
- case name
426
- when "none" then false
427
- when "zlib" then :standard
428
- when "zlib@openssh.com" then :delayed
429
- else raise ArgumentError, "unknown compression type `#{name}'"
424
+ sizes << HMAC.key_length(hmac_client)
425
+ sizes << HMAC.key_length(hmac_server)
426
+
427
+ sizes.max
428
+ end
429
+
430
+ # Instantiates one of the Transport::Kex classes (based on the negotiated
431
+ # kex algorithm), and uses it to exchange keys. Then, the ciphers and
432
+ # HMACs are initialized and fed to the transport layer, to be used in
433
+ # further communication with the server.
434
+ def exchange_keys
435
+ debug { "exchanging keys" }
436
+
437
+ algorithm = Kex::MAP[kex].new(self, session,
438
+ client_version_string: Net::SSH::Transport::ServerVersion::PROTO_VERSION,
439
+ server_version_string: session.server_version.version,
440
+ server_algorithm_packet: @server_packet,
441
+ client_algorithm_packet: @client_packet,
442
+ need_bytes: kex_byte_requirement,
443
+ minimum_dh_bits: options[:minimum_dh_bits],
444
+ logger: logger)
445
+ result = algorithm.exchange_keys
446
+
447
+ secret = result[:shared_secret].to_ssh
448
+ hash = result[:session_id]
449
+ digester = result[:hashing_algorithm]
450
+
451
+ @session_id ||= hash
452
+
453
+ key = Proc.new { |salt| digester.digest(secret + hash + salt + @session_id) }
454
+
455
+ iv_client = key["A"]
456
+ iv_server = key["B"]
457
+ key_client = key["C"]
458
+ key_server = key["D"]
459
+ mac_key_client = key["E"]
460
+ mac_key_server = key["F"]
461
+
462
+ parameters = { shared: secret, hash: hash, digester: digester }
463
+
464
+ cipher_client = CipherFactory.get(encryption_client, parameters.merge(iv: iv_client, key: key_client, encrypt: true))
465
+ cipher_server = CipherFactory.get(encryption_server, parameters.merge(iv: iv_server, key: key_server, decrypt: true))
466
+
467
+ mac_client = HMAC.get(hmac_client, mac_key_client, parameters)
468
+ mac_server = HMAC.get(hmac_server, mac_key_server, parameters)
469
+
470
+ session.configure_client cipher: cipher_client, hmac: mac_client,
471
+ compression: normalize_compression_name(compression_client),
472
+ compression_level: options[:compression_level],
473
+ rekey_limit: options[:rekey_limit],
474
+ max_packets: options[:rekey_packet_limit],
475
+ max_blocks: options[:rekey_blocks_limit]
476
+
477
+ session.configure_server cipher: cipher_server, hmac: mac_server,
478
+ compression: normalize_compression_name(compression_server),
479
+ rekey_limit: options[:rekey_limit],
480
+ max_packets: options[:rekey_packet_limit],
481
+ max_blocks: options[:rekey_blocks_limit]
482
+
483
+ @initialized = true
484
+ end
485
+
486
+ # Given the SSH name for some compression algorithm, return a normalized
487
+ # name as a symbol.
488
+ def normalize_compression_name(name)
489
+ case name
490
+ when "none" then false
491
+ when "zlib" then :standard
492
+ when "zlib@openssh.com" then :delayed
493
+ else raise ArgumentError, "unknown compression type `#{name}'"
494
+ end
430
495
  end
431
496
  end
497
+ end
432
498
  end
433
- end; end; end
499
+ end