net-ssh 5.0.2 → 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 (76) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +8 -2
  6. data/.rubocop_todo.yml +392 -379
  7. data/.travis.yml +22 -22
  8. data/CHANGES.txt +63 -0
  9. data/Manifest +0 -1
  10. data/README.md +287 -0
  11. data/Rakefile +1 -2
  12. data/appveyor.yml +4 -2
  13. data/lib/net/ssh.rb +13 -4
  14. data/lib/net/ssh/authentication/agent.rb +9 -3
  15. data/lib/net/ssh/authentication/certificate.rb +10 -1
  16. data/lib/net/ssh/authentication/ed25519.rb +75 -46
  17. data/lib/net/ssh/authentication/ed25519_loader.rb +1 -1
  18. data/lib/net/ssh/authentication/key_manager.rb +35 -6
  19. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +3 -1
  20. data/lib/net/ssh/authentication/methods/publickey.rb +2 -0
  21. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +0 -1
  22. data/lib/net/ssh/authentication/session.rb +9 -6
  23. data/lib/net/ssh/buffer.rb +41 -10
  24. data/lib/net/ssh/buffered_io.rb +0 -1
  25. data/lib/net/ssh/config.rb +68 -35
  26. data/lib/net/ssh/connection/channel.rb +17 -5
  27. data/lib/net/ssh/connection/event_loop.rb +0 -1
  28. data/lib/net/ssh/connection/session.rb +7 -4
  29. data/lib/net/ssh/key_factory.rb +104 -17
  30. data/lib/net/ssh/known_hosts.rb +41 -26
  31. data/lib/net/ssh/loggable.rb +2 -2
  32. data/lib/net/ssh/proxy/command.rb +0 -1
  33. data/lib/net/ssh/proxy/socks5.rb +0 -1
  34. data/lib/net/ssh/service/forward.rb +2 -1
  35. data/lib/net/ssh/test.rb +8 -7
  36. data/lib/net/ssh/test/extensions.rb +2 -0
  37. data/lib/net/ssh/transport/algorithms.rb +161 -105
  38. data/lib/net/ssh/transport/cipher_factory.rb +11 -27
  39. data/lib/net/ssh/transport/constants.rb +10 -6
  40. data/lib/net/ssh/transport/ctr.rb +1 -7
  41. data/lib/net/ssh/transport/hmac.rb +15 -13
  42. data/lib/net/ssh/transport/hmac/abstract.rb +16 -0
  43. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  44. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  45. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  46. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  47. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  48. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  49. data/lib/net/ssh/transport/kex.rb +14 -11
  50. data/lib/net/ssh/transport/kex/abstract.rb +123 -0
  51. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  52. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +38 -0
  53. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  54. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +1 -15
  55. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +9 -118
  56. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +0 -6
  57. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  58. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +18 -79
  59. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +5 -4
  60. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +5 -4
  61. data/lib/net/ssh/transport/openssl.rb +106 -93
  62. data/lib/net/ssh/transport/packet_stream.rb +52 -20
  63. data/lib/net/ssh/transport/session.rb +26 -4
  64. data/lib/net/ssh/transport/state.rb +1 -1
  65. data/lib/net/ssh/verifiers/accept_new.rb +7 -0
  66. data/lib/net/ssh/verifiers/always.rb +4 -0
  67. data/lib/net/ssh/verifiers/never.rb +4 -0
  68. data/lib/net/ssh/version.rb +3 -3
  69. data/net-ssh-public_cert.pem +18 -19
  70. data/net-ssh.gemspec +9 -7
  71. metadata +56 -40
  72. metadata.gz.sig +0 -0
  73. data/Gemfile.noed25519.lock +0 -41
  74. data/README.rdoc +0 -169
  75. data/lib/net/ssh/ruby_compat.rb +0 -13
  76. data/support/arcfour_check.rb +0 -20
@@ -1,8 +1,9 @@
1
- module Net
2
- module SSH
3
- module Transport
4
- module Kex
1
+ require 'net/ssh/transport/kex/ecdh_sha2_nistp256'
5
2
 
3
+ module Net
4
+ module SSH
5
+ module Transport
6
+ module Kex
6
7
  # A key-exchange service implementing the "ecdh-sha2-nistp256"
7
8
  # key-exchange algorithm. (defined in RFC 5656)
8
9
  class EcdhSHA2NistP384 < EcdhSHA2NistP256
@@ -1,8 +1,9 @@
1
- module Net
2
- module SSH
3
- module Transport
4
- module Kex
1
+ require 'net/ssh/transport/kex/ecdh_sha2_nistp256'
5
2
 
3
+ module Net
4
+ module SSH
5
+ module Transport
6
+ module Kex
6
7
  # A key-exchange service implementing the "ecdh-sha2-nistp521"
7
8
  # key-exchange algorithm. (defined in RFC 5656)
8
9
  class EcdhSHA2NistP521 < EcdhSHA2NistP256
@@ -98,9 +98,9 @@ module OpenSSL
98
98
  sig_r = sig[0,20].unpack("H*")[0].to_i(16)
99
99
  sig_s = sig[20,20].unpack("H*")[0].to_i(16)
100
100
  a1sig = OpenSSL::ASN1::Sequence([
101
- OpenSSL::ASN1::Integer(sig_r),
102
- OpenSSL::ASN1::Integer(sig_s)
103
- ])
101
+ OpenSSL::ASN1::Integer(sig_r),
102
+ OpenSSL::ASN1::Integer(sig_s)
103
+ ])
104
104
  return verify(OpenSSL::Digest::SHA1.new, a1sig.to_der, data)
105
105
  end
106
106
 
@@ -121,115 +121,128 @@ module OpenSSL
121
121
  end
122
122
  end
123
123
 
124
- if defined?(OpenSSL::PKey::EC)
125
- # This class is originally defined in the OpenSSL module. As needed, methods
126
- # have been added to it by the Net::SSH module for convenience in dealing
127
- # with SSH functionality.
128
- class EC
129
- CurveNameAlias = {
130
- "nistp256" => "prime256v1",
131
- "nistp384" => "secp384r1",
132
- "nistp521" => "secp521r1"
133
- }
134
-
135
- CurveNameAliasInv = {
136
- "prime256v1" => "nistp256",
137
- "secp384r1" => "nistp384",
138
- "secp521r1" => "nistp521"
139
- }
140
-
141
- def self.read_keyblob(curve_name_in_type, buffer)
142
- curve_name_in_key = buffer.read_string
143
- raise Net::SSH::Exception, "curve name mismatched (`#{curve_name_in_key}' with `#{curve_name_in_type}')" unless curve_name_in_type == curve_name_in_key
144
- public_key_oct = buffer.read_string
145
- begin
146
- key = OpenSSL::PKey::EC.new(OpenSSL::PKey::EC::CurveNameAlias[curve_name_in_key])
147
- group = key.group
148
- point = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(public_key_oct, 2))
149
- key.public_key = point
150
-
151
- return key
152
- rescue OpenSSL::PKey::ECError
153
- raise NotImplementedError, "unsupported key type `#{type}'"
154
- end
124
+ # This class is originally defined in the OpenSSL module. As needed, methods
125
+ # have been added to it by the Net::SSH module for convenience in dealing
126
+ # with SSH functionality.
127
+ class EC
128
+ CurveNameAlias = {
129
+ 'nistp256' => 'prime256v1',
130
+ 'nistp384' => 'secp384r1',
131
+ 'nistp521' => 'secp521r1'
132
+ }.freeze
133
+
134
+ CurveNameAliasInv = {
135
+ 'prime256v1' => 'nistp256',
136
+ 'secp384r1' => 'nistp384',
137
+ 'secp521r1' => 'nistp521'
138
+ }.freeze
139
+
140
+ def self.read_keyblob(curve_name_in_type, buffer)
141
+ curve_name_in_key = buffer.read_string
142
+
143
+ unless curve_name_in_type == curve_name_in_key
144
+ raise Net::SSH::Exception, "curve name mismatched (`#{curve_name_in_key}' with `#{curve_name_in_type}')"
155
145
  end
156
146
 
157
- # Returns the description of this key type used by the
158
- # SSH2 protocol, like "ecdsa-sha2-nistp256"
159
- def ssh_type
160
- "ecdsa-sha2-#{CurveNameAliasInv[self.group.curve_name]}"
161
- end
147
+ public_key_oct = buffer.read_string
148
+ begin
149
+ key = OpenSSL::PKey::EC.new(OpenSSL::PKey::EC::CurveNameAlias[curve_name_in_key])
150
+ group = key.group
151
+ point = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(public_key_oct, 2))
152
+ key.public_key = point
162
153
 
163
- def ssh_signature_type
164
- ssh_type
154
+ return key
155
+ rescue OpenSSL::PKey::ECError
156
+ raise NotImplementedError, "unsupported key type `#{type}'"
165
157
  end
158
+ end
166
159
 
167
- def digester
168
- if self.group.curve_name =~ /^[a-z]+(\d+)\w*\z/
169
- curve_size = $1.to_i
170
- if curve_size <= 256
171
- OpenSSL::Digest::SHA256.new
172
- elsif curve_size <= 384
173
- OpenSSL::Digest::SHA384.new
174
- else
175
- OpenSSL::Digest::SHA512.new
176
- end
177
- else
160
+ # Returns the description of this key type used by the
161
+ # SSH2 protocol, like "ecdsa-sha2-nistp256"
162
+ def ssh_type
163
+ "ecdsa-sha2-#{CurveNameAliasInv[group.curve_name]}"
164
+ end
165
+
166
+ def ssh_signature_type
167
+ ssh_type
168
+ end
169
+
170
+ def digester
171
+ if group.curve_name =~ /^[a-z]+(\d+)\w*\z/
172
+ curve_size = Regexp.last_match(1).to_i
173
+ if curve_size <= 256
178
174
  OpenSSL::Digest::SHA256.new
175
+ elsif curve_size <= 384
176
+ OpenSSL::Digest::SHA384.new
177
+ else
178
+ OpenSSL::Digest::SHA512.new
179
179
  end
180
+ else
181
+ OpenSSL::Digest::SHA256.new
180
182
  end
181
- private :digester
183
+ end
184
+ private :digester
182
185
 
183
- # Converts the key to a blob, according to the SSH2 protocol.
184
- def to_blob
185
- @blob ||= Net::SSH::Buffer.from(:string, ssh_type,
186
- :string, CurveNameAliasInv[self.group.curve_name],
187
- :mstring, self.public_key.to_bn.to_s(2)).to_s
188
- @blob
189
- end
186
+ # Converts the key to a blob, according to the SSH2 protocol.
187
+ def to_blob
188
+ @blob ||= Net::SSH::Buffer.from(:string, ssh_type,
189
+ :string, CurveNameAliasInv[group.curve_name],
190
+ :mstring, public_key.to_bn.to_s(2)).to_s
191
+ @blob
192
+ end
190
193
 
191
- # Verifies the given signature matches the given data.
192
- def ssh_do_verify(sig, data)
193
- digest = digester.digest(data)
194
- a1sig = nil
194
+ # Verifies the given signature matches the given data.
195
+ def ssh_do_verify(sig, data)
196
+ digest = digester.digest(data)
197
+ a1sig = nil
195
198
 
196
- begin
197
- sig_r_len = sig[0,4].unpack("H*")[0].to_i(16)
198
- sig_l_len = sig[4 + sig_r_len,4].unpack("H*")[0].to_i(16)
199
+ begin
200
+ sig_r_len = sig[0, 4].unpack('H*')[0].to_i(16)
201
+ sig_l_len = sig[4 + sig_r_len, 4].unpack('H*')[0].to_i(16)
199
202
 
200
- sig_r = sig[4,sig_r_len].unpack("H*")[0]
201
- sig_s = sig[4 + sig_r_len + 4,sig_l_len].unpack("H*")[0]
203
+ sig_r = sig[4, sig_r_len].unpack('H*')[0]
204
+ sig_s = sig[4 + sig_r_len + 4, sig_l_len].unpack('H*')[0]
202
205
 
203
- a1sig = OpenSSL::ASN1::Sequence([
204
- OpenSSL::ASN1::Integer(sig_r.to_i(16)),
205
- OpenSSL::ASN1::Integer(sig_s.to_i(16))
206
- ])
207
- rescue StandardError
208
- end
206
+ a1sig = OpenSSL::ASN1::Sequence([
207
+ OpenSSL::ASN1::Integer(sig_r.to_i(16)),
208
+ OpenSSL::ASN1::Integer(sig_s.to_i(16))
209
+ ])
210
+ rescue StandardError
211
+ end
209
212
 
210
- if a1sig == nil
211
- return false
212
- else
213
- dsa_verify_asn1(digest, a1sig.to_der)
214
- end
213
+ if a1sig.nil?
214
+ return false
215
+ else
216
+ dsa_verify_asn1(digest, a1sig.to_der)
215
217
  end
218
+ end
219
+
220
+ # Returns the signature for the given data.
221
+ def ssh_do_sign(data)
222
+ digest = digester.digest(data)
223
+ sig = dsa_sign_asn1(digest)
224
+ a1sig = OpenSSL::ASN1.decode(sig)
216
225
 
217
- # Returns the signature for the given data.
218
- def ssh_do_sign(data)
219
- digest = digester.digest(data)
220
- sig = dsa_sign_asn1(digest)
221
- a1sig = OpenSSL::ASN1.decode(sig)
226
+ sig_r = a1sig.value[0].value
227
+ sig_s = a1sig.value[1].value
222
228
 
223
- sig_r = a1sig.value[0].value
224
- sig_s = a1sig.value[1].value
229
+ Net::SSH::Buffer.from(:bignum, sig_r, :bignum, sig_s).to_s
230
+ end
225
231
 
226
- return Net::SSH::Buffer.from(:bignum, sig_r, :bignum, sig_s).to_s
232
+ class Point
233
+ # Returns the description of this key type used by the
234
+ # SSH2 protocol, like "ecdsa-sha2-nistp256"
235
+ def ssh_type
236
+ "ecdsa-sha2-#{CurveNameAliasInv[group.curve_name]}"
237
+ end
238
+
239
+ # Converts the key to a blob, according to the SSH2 protocol.
240
+ def to_blob
241
+ @blob ||= Net::SSH::Buffer.from(:string, ssh_type,
242
+ :string, CurveNameAliasInv[group.curve_name],
243
+ :mstring, to_bn.to_s(2)).to_s
244
+ @blob
227
245
  end
228
- end
229
- else
230
- class OpenSSL::PKey::ECError < RuntimeError
231
- # for compatibility with interpreters
232
- # without EC support (i.e. JRuby)
233
246
  end
234
247
  end
235
248
  end
@@ -1,7 +1,6 @@
1
1
  require 'net/ssh/buffered_io'
2
2
  require 'net/ssh/errors'
3
3
  require 'net/ssh/packet'
4
- require 'net/ssh/ruby_compat'
5
4
  require 'net/ssh/transport/cipher_factory'
6
5
  require 'net/ssh/transport/hmac'
7
6
  require 'net/ssh/transport/state'
@@ -81,8 +80,8 @@ module Net
81
80
  # default), then this will return immediately, whether a packet is
82
81
  # available or not, and will return nil if there is no packet ready to be
83
82
  # returned. If the mode parameter is :block, then this method will block
84
- # until a packet is available.
85
- def next_packet(mode=:nonblock)
83
+ # until a packet is available or timeout seconds have passed.
84
+ def next_packet(mode=:nonblock, timeout=nil)
86
85
  case mode
87
86
  when :nonblock then
88
87
  packet = poll_next_packet
@@ -105,11 +104,8 @@ module Net
105
104
  packet = poll_next_packet
106
105
  return packet if packet
107
106
 
108
- loop do
109
- result = IO.select([self]) or next
110
- break if result.first.any?
111
- end
112
-
107
+ result = IO.select([self], nil, nil, timeout)
108
+ raise Net::SSH::ConnectionTimeout, "timeout waiting for next packet" unless result
113
109
  raise Net::SSH::Disconnect, "connection closed by remote host" if fill <= 0
114
110
  end
115
111
 
@@ -133,7 +129,7 @@ module Net
133
129
  payload = client.compress(payload)
134
130
 
135
131
  # the length of the packet, minus the padding
136
- actual_length = 4 + payload.bytesize + 1
132
+ actual_length = (client.hmac.etm ? 0 : 4) + payload.bytesize + 1
137
133
 
138
134
  # compute the padding length
139
135
  padding_length = client.block_size - (actual_length % client.block_size)
@@ -149,11 +145,32 @@ module Net
149
145
 
150
146
  padding = Array.new(padding_length) { rand(256) }.pack("C*")
151
147
 
152
- unencrypted_data = [packet_length, padding_length, payload, padding].pack("NCA*A*")
153
- mac = client.hmac.digest([client.sequence_number, unencrypted_data].pack("NA*"))
148
+ if client.hmac.etm
149
+ debug { "using encrypt-then-mac" }
150
+
151
+ # Encrypt padding_length, payload, and padding. Take MAC
152
+ # from the unencrypted packet_lenght and the encrypted
153
+ # data.
154
+ length_data = [packet_length].pack("N")
155
+
156
+ unencrypted_data = [padding_length, payload, padding].pack("CA*A*")
154
157
 
155
- encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
156
- message = encrypted_data + mac
158
+ encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
159
+
160
+ mac_data = length_data + encrypted_data
161
+
162
+ mac = client.hmac.digest([client.sequence_number, mac_data].pack("NA*"))
163
+
164
+ message = mac_data + mac
165
+ else
166
+ unencrypted_data = [packet_length, padding_length, payload, padding].pack("NCA*A*")
167
+
168
+ mac = client.hmac.digest([client.sequence_number, unencrypted_data].pack("NA*"))
169
+
170
+ encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
171
+
172
+ message = encrypted_data + mac
173
+ end
157
174
 
158
175
  debug { "queueing packet nr #{client.sequence_number} type #{payload.getbyte(0)} len #{packet_length}" }
159
176
  enqueue(message)
@@ -198,18 +215,27 @@ module Net
198
215
  # read, post-processed according to the cipher, hmac, and compression
199
216
  # algorithms specified in the server state object, and returned as a
200
217
  # new Packet object.
218
+ # rubocop:disable Metrics/AbcSize
201
219
  def poll_next_packet
220
+ aad_length = server.hmac.etm ? 4 : 0
221
+
202
222
  if @packet.nil?
203
223
  minimum = server.block_size < 4 ? 4 : server.block_size
204
- return nil if available < minimum
205
- data = read_available(minimum)
224
+ return nil if available < minimum + aad_length
225
+ data = read_available(minimum + aad_length)
206
226
 
207
227
  # decipher it
208
- @packet = Net::SSH::Buffer.new(server.update_cipher(data))
209
- @packet_length = @packet.read_long
228
+ if server.hmac.etm
229
+ @packet_length = data.unpack("N").first
230
+ @mac_data = data
231
+ @packet = Net::SSH::Buffer.new(server.update_cipher(data[aad_length..-1]))
232
+ else
233
+ @packet = Net::SSH::Buffer.new(server.update_cipher(data))
234
+ @packet_length = @packet.read_long
235
+ end
210
236
  end
211
237
 
212
- need = @packet_length + 4 - server.block_size
238
+ need = @packet_length + 4 - aad_length - server.block_size
213
239
  raise Net::SSH::Exception, "padding error, need #{need} block #{server.block_size}" if need % server.block_size != 0
214
240
 
215
241
  return nil if available < need + server.hmac.mac_length
@@ -217,6 +243,7 @@ module Net
217
243
  if need > 0
218
244
  # read the remainder of the packet and decrypt it.
219
245
  data = read_available(need)
246
+ @mac_data += data if server.hmac.etm
220
247
  @packet.append(server.update_cipher(data))
221
248
  end
222
249
 
@@ -229,8 +256,12 @@ module Net
229
256
 
230
257
  payload = @packet.read(@packet_length - padding_length - 1)
231
258
 
232
- my_computed_hmac = server.hmac.digest([server.sequence_number, @packet.content].pack("NA*"))
233
- raise Net::SSH::Exception, "corrupted hmac detected" if real_hmac != my_computed_hmac
259
+ my_computed_hmac = if server.hmac.etm
260
+ server.hmac.digest([server.sequence_number, @mac_data].pack("NA*"))
261
+ else
262
+ server.hmac.digest([server.sequence_number, @packet.content].pack("NA*"))
263
+ end
264
+ raise Net::SSH::Exception, "corrupted hmac detected #{server.hmac.class}" if real_hmac != my_computed_hmac
234
265
 
235
266
  # try to decompress the payload, in case compression is active
236
267
  payload = server.decompress(payload)
@@ -243,6 +274,7 @@ module Net
243
274
  return Packet.new(payload)
244
275
  end
245
276
  end
277
+ # rubocop:enable Metrics/AbcSize
246
278
 
247
279
  end
248
280
  end
@@ -190,7 +190,7 @@ module Net
190
190
  loop do
191
191
  return @queue.shift if consume_queue && @queue.any? && algorithms.allow?(@queue.first)
192
192
 
193
- packet = socket.next_packet(mode)
193
+ packet = socket.next_packet(mode, options[:timeout])
194
194
  return nil if packet.nil?
195
195
 
196
196
  case packet.type
@@ -274,6 +274,23 @@ module Net
274
274
 
275
275
  private
276
276
 
277
+ # Compatibility verifier which allows users to keep using
278
+ # custom verifier code without adding new :verify_signature
279
+ # method.
280
+ class CompatibleVerifier
281
+ def initialize(verifier)
282
+ @verifier = verifier
283
+ end
284
+
285
+ def verify(arguments)
286
+ @verifier.verify(arguments)
287
+ end
288
+
289
+ def verify_signature(&block)
290
+ yield
291
+ end
292
+ end
293
+
277
294
  # Instantiates a new host-key verification class, based on the value of
278
295
  # the parameter.
279
296
  #
@@ -285,8 +302,8 @@ module Net
285
302
  # - :accept_new (insecure)
286
303
  # - :always (secure)
287
304
  #
288
- # If the argument happens to respond to :verify, it is returned
289
- # directly. Otherwise, an exception is raised.
305
+ # If the argument happens to respond to :verify and :verify_signature,
306
+ # it is returned directly. Otherwise, an exception is raised.
290
307
  #
291
308
  # Values false, true, and :very were deprecated in
292
309
  # [#595](https://github.com/net-ssh/net-ssh/pull/595)
@@ -314,7 +331,12 @@ module Net
314
331
  Net::SSH::Verifiers::Always.new
315
332
  else
316
333
  if verifier.respond_to?(:verify)
317
- verifier
334
+ if verifier.respond_to?(:verify_signature)
335
+ verifier
336
+ else
337
+ Kernel.warn("Warning: verifier without :verify_signature is deprecated")
338
+ CompatibleVerifier.new(verifier)
339
+ end
318
340
  else
319
341
  raise(
320
342
  ArgumentError,