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