net-ssh 7.2.3 → 8.0.4

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 (78) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.dockerignore +3 -0
  4. data/.github/pull_request_template.md +1 -0
  5. data/.github/workflows/changelog.yml +18 -0
  6. data/.github/workflows/ci-with-docker.yml +20 -9
  7. data/.github/workflows/ci.yml +10 -20
  8. data/.github/workflows/rubocop.yml +3 -1
  9. data/.rubocop.yml +5 -1
  10. data/.rubocop_todo.yml +4 -8
  11. data/CHANGES.txt +122 -0
  12. data/CONTRIBUTING.md +34 -0
  13. data/Dockerfile +4 -1
  14. data/Dockerfile.openssl3 +1 -1
  15. data/Dockerfile.zlib_ng +29 -0
  16. data/Gemfile +2 -0
  17. data/README.md +74 -57
  18. data/Rakefile +30 -12
  19. data/appveyor.yml +4 -16
  20. data/changelog.d/README.md +1 -0
  21. data/docker-compose.yml +1 -9
  22. data/lib/net/ssh/authentication/agent.rb +35 -11
  23. data/lib/net/ssh/authentication/ed25519.rb +140 -37
  24. data/lib/net/ssh/authentication/ed25519_loader.rb +14 -9
  25. data/lib/net/ssh/authentication/key_manager.rb +42 -16
  26. data/lib/net/ssh/authentication/methods/abstract.rb +2 -8
  27. data/lib/net/ssh/authentication/methods/publickey.rb +3 -1
  28. data/lib/net/ssh/authentication/security_key.rb +71 -0
  29. data/lib/net/ssh/authentication/session.rb +29 -17
  30. data/lib/net/ssh/buffer.rb +6 -1
  31. data/lib/net/ssh/buffered_io.rb +40 -3
  32. data/lib/net/ssh/config.rb +30 -13
  33. data/lib/net/ssh/connection/channel.rb +49 -2
  34. data/lib/net/ssh/connection/session.rb +151 -13
  35. data/lib/net/ssh/errors.rb +24 -6
  36. data/lib/net/ssh/key_factory.rb +11 -4
  37. data/lib/net/ssh/known_hosts.rb +62 -8
  38. data/lib/net/ssh/multiplex/client.rb +162 -0
  39. data/lib/net/ssh/multiplex/control_path.rb +120 -0
  40. data/lib/net/ssh/multiplex/proxy_transport.rb +218 -0
  41. data/lib/net/ssh/multiplex.rb +126 -0
  42. data/lib/net/ssh/prompt.rb +3 -3
  43. data/lib/net/ssh/proxy/command.rb +28 -3
  44. data/lib/net/ssh/proxy/jump.rb +71 -8
  45. data/lib/net/ssh/service/forward.rb +44 -27
  46. data/lib/net/ssh/test/extensions.rb +3 -2
  47. data/lib/net/ssh/test/socket.rb +1 -1
  48. data/lib/net/ssh/test.rb +2 -1
  49. data/lib/net/ssh/transport/aes128_gcm.rb +40 -0
  50. data/lib/net/ssh/transport/aes256_gcm.rb +40 -0
  51. data/lib/net/ssh/transport/algorithms.rb +96 -34
  52. data/lib/net/ssh/transport/chacha20_poly1305_cipher.rb +89 -16
  53. data/lib/net/ssh/transport/chacha20_poly1305_cipher_loader.rb +20 -1
  54. data/lib/net/ssh/transport/cipher_factory.rb +32 -8
  55. data/lib/net/ssh/transport/constants.rb +1 -0
  56. data/lib/net/ssh/transport/gcm_cipher.rb +228 -0
  57. data/lib/net/ssh/transport/hmac/abstract.rb +16 -0
  58. data/lib/net/ssh/transport/hmac/sha1_etm.rb +12 -0
  59. data/lib/net/ssh/transport/hmac.rb +2 -0
  60. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +41 -9
  61. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +14 -6
  62. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +8 -0
  63. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +45 -15
  64. data/lib/net/ssh/transport/openssl.rb +2 -1
  65. data/lib/net/ssh/transport/packet_stream.rb +19 -3
  66. data/lib/net/ssh/transport/server_version.rb +33 -13
  67. data/lib/net/ssh/transport/session.rb +61 -46
  68. data/lib/net/ssh/transport/state.rb +1 -1
  69. data/lib/net/ssh/verifiers/always.rb +43 -8
  70. data/lib/net/ssh/version.rb +3 -3
  71. data/lib/net/ssh.rb +148 -63
  72. data/net-ssh-public_cert.pem +7 -7
  73. data/net-ssh.gemspec +6 -8
  74. data.tar.gz.sig +0 -0
  75. metadata +64 -46
  76. metadata.gz.sig +0 -0
  77. data/Gemfile.noed25519 +0 -12
  78. data/Gemfile.norbnacl +0 -12
@@ -0,0 +1,126 @@
1
+ require 'socket'
2
+
3
+ require 'net/ssh/errors'
4
+ require 'net/ssh/connection/session'
5
+ require 'net/ssh/multiplex/client'
6
+ require 'net/ssh/multiplex/control_path'
7
+ require 'net/ssh/multiplex/proxy_transport'
8
+
9
+ module Net
10
+ module SSH
11
+ # Reuse of an existing OpenSSH ControlMaster connection, enabled by the
12
+ # :control_master option of Net::SSH.start. Net::SSH only ever connects to
13
+ # a master someone else started (e.g. with ssh -M or ControlMaster auto);
14
+ # it never becomes one.
15
+ module Multiplex
16
+ # Used for the mux handshake when the :timeout option isn't set.
17
+ DEFAULT_TIMEOUT = 5
18
+
19
+ # Options for setting up a connection, which the master already did
20
+ # with its own settings.
21
+ MASTER_OWNED_OPTIONS = %i[
22
+ agent_socket_factory append_all_supported_algorithms auth_methods
23
+ bind_address check_host_ip compression compression_level encryption
24
+ fingerprint_hash global_known_hosts_file hmac host_key identity_agent
25
+ keepalive keepalive_interval keepalive_maxcount kex key_data keycert_data
26
+ keycerts keys keys_only known_hosts languages minimum_dh_bits no_delay
27
+ non_interactive number_of_password_prompts passphrase password
28
+ password_prompt proxy pubkey_algorithms rekey_blocks_limit rekey_limit
29
+ rekey_packet_limit use_agent user_known_hosts_file verify_host_key
30
+ ].freeze
31
+
32
+ VALUES = [:auto, false, nil].freeze
33
+
34
+ def self.validate_options!(options)
35
+ return if VALUES.include?(options[:control_master])
36
+
37
+ raise ArgumentError, "control_master must be :auto or false, got #{options[:control_master].inspect}"
38
+ end
39
+
40
+ # Returns a Connection::Session running through the ControlMaster for
41
+ # this connection, or nil (after logging why) when there is no usable
42
+ # master, in which case the caller connects as usual. +explicit+ are
43
+ # the option names the caller passed to Net::SSH.start. Raises
44
+ # ArgumentError for an invalid ControlPath, as ssh does.
45
+ def self.connect(host_arg, host, user, options, explicit: [])
46
+ logger = options[:logger]
47
+ path = socket_path(host_arg, host, user, options)
48
+ return nil unless path
49
+
50
+ socket = open_proxy(path, options)
51
+ return nil unless socket
52
+
53
+ logger.info { "control_master: using ControlMaster at #{path}" }
54
+ log_master_owned(logger, explicit)
55
+ begin
56
+ transport = ProxyTransport.new(socket, host: host, control_path: path, options: options)
57
+ Connection::Session.new(transport, session_options(logger, options))
58
+ rescue StandardError
59
+ socket.close
60
+ raise
61
+ end
62
+ end
63
+
64
+ # ENOENT and ECONNREFUSED just mean no master is running, while the
65
+ # other errors mean a socket is there but can't be used.
66
+ def self.open_proxy(path, options)
67
+ Client.new(path, timeout: options[:timeout] || DEFAULT_TIMEOUT, logger: options[:logger]).connect_proxy
68
+ rescue Errno::ENOENT, Errno::ECONNREFUSED => e
69
+ options[:logger].info { "control_master: no ControlMaster at #{path}: #{e.message} (#{e.class})" }
70
+ nil
71
+ rescue Multiplex::Error, SystemCallError, IOError, SocketError => e
72
+ options[:logger].warn { "control_master: not using ControlMaster at #{path}: #{e.message} (#{e.class})" }
73
+ nil
74
+ end
75
+
76
+ def self.socket_path(host_arg, host, user, options)
77
+ if Gem.win_platform? || !defined?(UNIXSocket)
78
+ options[:logger].debug { "control_master: UNIX sockets are not available on this platform" }
79
+ return nil
80
+ end
81
+
82
+ control_path = options[:control_path]
83
+ if control_path.nil? || control_path.to_s == 'none'
84
+ options[:logger].debug { "control_master: no ControlPath set" }
85
+ return nil
86
+ end
87
+
88
+ expander = ControlPath.new(
89
+ host_arg: host_arg, host: host, port: options[:port] || ProxyTransport::DEFAULT_PORT, remote_user: user,
90
+ jump_host: jump_host(options[:proxy]), host_key_alias: options[:host_key_alias]
91
+ )
92
+ begin
93
+ path = expander.expand(control_path.to_s)
94
+ Socket.sockaddr_un(path)
95
+ rescue ArgumentError => e
96
+ raise ArgumentError, "invalid control_path #{control_path.to_s.inspect}: #{e.message}"
97
+ end
98
+ path
99
+ end
100
+
101
+ def self.jump_host(proxy)
102
+ return '' unless defined?(Net::SSH::Proxy::Jump) && proxy.is_a?(Net::SSH::Proxy::Jump)
103
+
104
+ ControlPath.jump_host(proxy.jump_proxies)
105
+ end
106
+
107
+ def self.log_master_owned(logger, explicit)
108
+ bypassed = explicit & MASTER_OWNED_OPTIONS
109
+ return if bypassed.empty?
110
+
111
+ logger.info { "control_master: ignoring #{bypassed.join(', ')}, the ControlMaster's own settings apply" }
112
+ end
113
+
114
+ # Keepalive is a global request, which the master doesn't allow; it
115
+ # watches its own connection anyway. The master serves agent requests
116
+ # from its own agent only if it has ForwardAgent enabled itself.
117
+ def self.session_options(logger, options)
118
+ logger.info { "control_master: keepalive is left to the ControlMaster" } if options[:keepalive]
119
+ logger.info { "control_master: ignoring ForwardAgent, the ControlMaster's own ForwardAgent setting applies" } if options[:forward_agent]
120
+ options.merge(keepalive: false, forward_agent: false)
121
+ end
122
+
123
+ private_class_method :open_proxy, :socket_path, :jump_host, :log_master_owned, :session_options
124
+ end
125
+ end
126
+ end
@@ -41,9 +41,9 @@ module Net
41
41
  def ask(prompt, echo = true)
42
42
  $stdout.print(prompt)
43
43
  $stdout.flush
44
- ret = $stdin.noecho(&:gets).chomp
45
- $stdout.print("\n")
46
- ret
44
+ ret = echo ? $stdin.gets : $stdin.noecho(&:gets)
45
+ $stdout.print("\n") unless echo
46
+ ret.chomp
47
47
  end
48
48
 
49
49
  # success method will be called when the password was accepted
@@ -16,6 +16,11 @@ module Net
16
16
  # ...
17
17
  # end
18
18
  class Command
19
+ # The command line is run by the shell, so like OpenSSH 9.6 (CVE-2023-51385) we refuse
20
+ # host and user names containing shell metacharacters rather than substituting them.
21
+ INVALID_HOST_NAME = /\A-|[[:space:][:cntrl:]'`"$\\;&<>|(){},]/.freeze
22
+ INVALID_USER_NAME = /\A-|[[:cntrl:]'`";&<>|(){}]|[[:space:]]-|\\\z/.freeze
23
+
19
24
  # The command line template
20
25
  attr_reader :command_line_template
21
26
 
@@ -41,13 +46,13 @@ module Net
41
46
  command_line = @command_line_template.gsub(/%(.)/) {
42
47
  case $1
43
48
  when 'h'
44
- host
49
+ validate_host_name!(host)
45
50
  when 'p'
46
- port.to_s
51
+ validate_port!(port)
47
52
  when 'r'
48
53
  remote_user = connection_options && connection_options[:remote_user]
49
54
  if remote_user
50
- remote_user
55
+ validate_user_name!(remote_user)
51
56
  else
52
57
  raise ArgumentError, "remote user name not available"
53
58
  end
@@ -117,6 +122,26 @@ module Net
117
122
  Process.kill('TERM', io.pid)
118
123
  Thread.new { io.close }
119
124
  end
125
+
126
+ private
127
+
128
+ def validate_host_name!(host)
129
+ raise ArgumentError, "host name contains invalid characters: #{host.inspect}" if host.to_s.match?(INVALID_HOST_NAME)
130
+
131
+ host
132
+ end
133
+
134
+ def validate_user_name!(user)
135
+ raise ArgumentError, "user name contains invalid characters: #{user.inspect}" if user.to_s.match?(INVALID_USER_NAME)
136
+
137
+ user
138
+ end
139
+
140
+ def validate_port!(port)
141
+ raise ArgumentError, "port is not a number: #{port.inspect}" unless port.to_s.match?(/\A\d+\z/)
142
+
143
+ port.to_s
144
+ end
120
145
  end
121
146
  end
122
147
  end
@@ -1,3 +1,4 @@
1
+ require 'shellwords'
1
2
  require 'uri'
2
3
  require 'net/ssh/proxy/command'
3
4
 
@@ -14,14 +15,33 @@ module Net
14
15
  # Net::SSH.start('host', 'user', :proxy => proxy) do |ssh|
15
16
  # ...
16
17
  # end
18
+ #
19
+ # ssh_config options for the jump host can be given as a Hash via
20
+ # +options:+; each is passed to the ssh command as +-o Key=value+:
21
+ #
22
+ # proxy = Net::SSH::Proxy::Jump.new('user@proxy', options: { 'StrictHostKeyChecking' => 'no' })
23
+ #
24
+ # Options that ssh_config allows more than once (+IdentityFile+,
25
+ # +CertificateFile+ and +SendEnv+) take an Array and are passed once per
26
+ # element; an empty Array passes nothing:
27
+ #
28
+ # proxy = Net::SSH::Proxy::Jump.new('user@proxy', options: { 'IdentityFile' => ['~/.ssh/jump', '~/.ssh/jump_old'] })
29
+ #
30
+ # A +nil+ value, alone or in an Array, raises ArgumentError, as it does
31
+ # for the options passed to Net::SSH.start; omit the option instead.
32
+ #
33
+ # The options apply to the first jump host only; any further hosts in
34
+ # the chain are connected to by that host's own ssh.
17
35
  class Jump < Command
18
36
  # The jump proxies
19
37
  attr_reader :jump_proxies
20
38
 
21
39
  # Create a new socket factory that tunnels via multiple jump proxes as
22
40
  # [user@]host[:port].
23
- def initialize(jump_proxies)
41
+ def initialize(jump_proxies, options: {})
24
42
  @jump_proxies = jump_proxies
43
+ @options = options
44
+ validate_options!
25
45
  end
26
46
 
27
47
  # Return a new socket connected to the given host and port via the jump
@@ -34,20 +54,63 @@ module Net
34
54
  # We cannot build the ProxyCommand template until we know if the :config
35
55
  # option was specified during `Net::SSH.start`.
36
56
  def build_proxy_command_equivalent(connection_options = nil)
37
- first_jump, extra_jumps = jump_proxies.split(",", 2)
57
+ raise ArgumentError, "no jump host given" if jump_proxies.empty?
58
+
38
59
  config = connection_options && connection_options[:config]
39
- uri = URI.parse("ssh://#{first_jump}")
60
+ uri, *extra_jumps = jump_proxies.split(",", -1).map { |jump| validate_jump!(jump) }
40
61
 
41
62
  template = "ssh".dup
42
- template << " -l #{uri.user}" if uri.user
43
- template << " -p #{uri.port}" if uri.port
44
- template << " -J #{extra_jumps}" if extra_jumps
45
- template << " -F #{config}" if config != true && config
63
+ template << " -l #{escape(uri.user)}" if uri.user
64
+ template << " -p #{uri.port}" if uri.port
65
+ template << " -J #{escape(extra_jumps.map { |jump| jump_spec(jump) }.join(','))}" unless extra_jumps.empty?
66
+ template << " -F #{escape(config)}" if config != true && config
67
+ @options.each do |key, values|
68
+ Array(values).each { |value| template << " -o #{escape(key)}=#{escape(value)}" }
69
+ end
46
70
  template << " -W %h:%p "
47
- template << uri.host
71
+ template << escape(uri.host)
48
72
 
49
73
  @command_line_template = template
50
74
  end
75
+
76
+ private
77
+
78
+ # Like Net::SSH.start, reject nil option values (alone or in an Array)
79
+ # rather than silently passing nothing or an empty value to ssh.
80
+ def validate_options!
81
+ nil_keys = @options.select { |_key, value| value.nil? || (value.is_a?(Array) && value.include?(nil)) }.keys
82
+ return if nil_keys.empty?
83
+
84
+ raise ArgumentError, "nil is not allowed for #{nil_keys.map(&:inspect).join(', ')} in Net::SSH::Proxy::Jump options; " \
85
+ "omit the option to use the default or ssh_config value"
86
+ end
87
+
88
+ # Escape for the shell, and double any % so it survives the %h/%p
89
+ # substitution done on the template in Command#open.
90
+ def escape(value)
91
+ Shellwords.escape(value).gsub("%", "%%")
92
+ end
93
+
94
+ # Only [user@]host[:port] is checked, so anything else URI accepts (a path,
95
+ # query, fragment or password) must be refused before it reaches the shell.
96
+ def validate_jump!(jump)
97
+ uri = URI.parse("ssh://#{jump}")
98
+ raise URI::InvalidURIError if uri.host.to_s.empty? || jump_spec(uri) != jump
99
+
100
+ validate_user_name!(uri.user) if uri.user
101
+ validate_host_name!(uri.host)
102
+ validate_port!(uri.port) if uri.port
103
+ uri
104
+ rescue URI::InvalidURIError
105
+ raise ArgumentError, "jump host is not [user@]host[:port]: #{jump.inspect}"
106
+ end
107
+
108
+ def jump_spec(uri)
109
+ spec = uri.host.dup
110
+ spec.prepend("#{uri.user}@") if uri.user
111
+ spec << ":#{uri.port}" if uri.port
112
+ spec
113
+ end
51
114
  end
52
115
  end
53
116
  end
@@ -48,9 +48,9 @@ module Net
48
48
  # If three arguments are given, it is as if the local bind address is
49
49
  # "127.0.0.1", and the rest are applied as above.
50
50
  #
51
- # To request an ephemeral port on the remote server, provide 0 (zero) for
52
- # the port number. In all cases, this method will return the port that
53
- # has been assigned.
51
+ # To request an ephemeral port on the local server, provide 0 (zero) for
52
+ # the local port number. In all cases, this method will return the port
53
+ # that has been assigned.
54
54
  #
55
55
  # ssh.forward.local(1234, "www.capify.org", 80)
56
56
  # assigned_port = ssh.forward.local("0.0.0.0", 0, "www.capify.org", 80)
@@ -320,6 +320,11 @@ module Net
320
320
  session.listen_to(client)
321
321
  channel[:socket] = client
322
322
 
323
+ # Grant window only for data the local end has accepted, so a slow
324
+ # reader stalls the remote sender instead of filling our memory.
325
+ channel.grant_window_on_consume!
326
+ client.on_sent { |bytes| channel.consumed(bytes) }
327
+
323
328
  channel.on_data do |ch, data|
324
329
  debug { "data:#{data.length} on #{type} forwarded channel" }
325
330
  ch[:socket].enqueue(data)
@@ -327,36 +332,40 @@ module Net
327
332
 
328
333
  channel.on_eof do |ch|
329
334
  debug { "eof #{type} on #{type} forwarded channel" }
330
- begin
331
- ch[:socket].send_pending
332
- ch[:socket].shutdown Socket::SHUT_WR
333
- rescue IOError => e
334
- if e.message =~ /closed/ then
335
- debug { "epipe in on_eof => shallowing exception:#{e}" }
336
- else
337
- raise
338
- end
339
- rescue Errno::EPIPE => e
340
- debug { "epipe in on_eof => shallowing exception:#{e}" }
341
- rescue Errno::ENOTCONN => e
342
- debug { "enotconn in on_eof => shallowing exception:#{e}" }
343
- end
335
+ ch[:socket].shutdown_write_when_drained
344
336
  end
345
337
 
346
338
  channel.on_close do |ch|
347
339
  debug { "closing #{type} forwarded channel" }
348
- ch[:socket].close if !client.closed?
349
- session.stop_listening_to(ch[:socket])
340
+ session.close_when_drained(ch[:socket])
350
341
  end
351
342
 
352
343
  channel.on_process do |ch|
353
- if ch[:socket].closed?
344
+ socket = ch[:socket]
345
+ if socket.closed?
354
346
  ch.info { "#{type} forwarded connection closed" }
347
+ session.stop_listening_to(socket)
355
348
  ch.close
356
- elsif ch[:socket].available > 0
357
- data = ch[:socket].read_available(8192)
358
- ch.debug { "read #{data.length} bytes from client, sending over #{type} forwarded connection" }
359
- ch.send_data(data)
349
+ elsif ch.eof?
350
+ # Like OpenSSH's chan_shutdown_read: data already read still goes out before CHANNEL_EOF, the rest is dropped.
351
+ ch.output.append(socket.read_available) if socket.available > 0
352
+ session.pause_reading(socket)
353
+ else
354
+ if socket.available > 0
355
+ data = socket.read_available(8192)
356
+ ch.debug { "read #{data.length} bytes from client, sending over #{type} forwarded connection" }
357
+ ch.send_data(data)
358
+ end
359
+
360
+ # Like OpenSSH's channel_pre_open, only read more once the remote
361
+ # window can take what is already queued; writes to the socket keep
362
+ # flowing. ch.output is still unsent here because Channel#process
363
+ # runs this block before enqueue_pending_output.
364
+ if ch.output.length + socket.available < ch.remote_window_size.to_i
365
+ session.resume_reading(socket)
366
+ else
367
+ session.pause_reading(socket)
368
+ end
360
369
  end
361
370
  end
362
371
  end
@@ -371,6 +380,8 @@ module Net
371
380
  end
372
381
 
373
382
  channel.on_process do |ch|
383
+ next if ch.eof?
384
+
374
385
  data = ch[:socket].read(8192)
375
386
  if data
376
387
  ch.debug { "read #{data.length} bytes from client, sending over #{type} forwarded connection" }
@@ -388,13 +399,13 @@ module Net
388
399
  originator_address = packet.read_string
389
400
  originator_port = packet.read_long
390
401
 
391
- puts "REMOTE 0: #{connected_port} #{connected_address} #{originator_address} #{originator_port}"
402
+ debug { "REMOTE 0: #{connected_port} #{connected_address} #{originator_address} #{originator_port}" }
392
403
  remote = @remote_forwarded_ports[[connected_port, connected_address]]
393
404
  if remote.nil?
394
405
  raise Net::SSH::ChannelOpenFailed.new(1, "unknown request from remote forwarded connection on #{connected_address}:#{connected_port}")
395
406
  end
396
407
 
397
- puts "REMOTE: #{remote.host} #{remote.port}"
408
+ debug { "REMOTE: #{remote.host} #{remote.port}" }
398
409
  client = TCPSocket.new(remote.host, remote.port)
399
410
  info { "connected #{connected_address}:#{connected_port} originator #{originator_address}:#{originator_port}" }
400
411
 
@@ -405,11 +416,17 @@ module Net
405
416
 
406
417
  # The callback used when an auth-agent channel is requested by the server.
407
418
  def auth_agent_channel(session, channel, packet)
419
+ # Like OpenSSH, only relay the local agent when forwarding was requested (GHSA-qpg5-fx6p-8cq3).
420
+ unless session.options[:forward_agent] || @agent_forwarded
421
+ error { "server tried agent forwarding, which was not requested; refusing" }
422
+ raise Net::SSH::ChannelOpenFailed.new(1, "agent forwarding was not requested")
423
+ end
424
+
408
425
  info { "opening auth-agent channel" }
409
426
  channel[:invisible] = true
410
427
 
411
428
  begin
412
- agent = Authentication::Agent.connect(logger, session.options[:agent_socket_factory])
429
+ agent = Authentication::Agent.connect(logger, session.options[:agent_socket_factory], session.options[:identity_agent])
413
430
  if (agent.socket.is_a? ::IO)
414
431
  prepare_client(agent.socket, channel, :agent)
415
432
  else
@@ -57,8 +57,9 @@ module Net
57
57
  return false unless script.next(:first)
58
58
 
59
59
  if script.next(:first).remote?
60
- self.string << script.next.to_s
61
- self.pos = pos
60
+ current_pos = pos
61
+ self.string = "#{self.string}#{script.next}"
62
+ self.pos = current_pos
62
63
  end
63
64
 
64
65
  return true
@@ -22,7 +22,7 @@ module Net
22
22
  # connection.
23
23
  def initialize
24
24
  extend(Net::SSH::Transport::PacketStream)
25
- super "SSH-2.0-Test\r\n"
25
+ super(+"SSH-2.0-Test\r\n")
26
26
 
27
27
  @script = Script.new
28
28
 
data/lib/net/ssh/test.rb CHANGED
@@ -73,7 +73,8 @@ module Net
73
73
  def transport(options = {})
74
74
  @transport ||= Net::SSH::Transport::Session.new(
75
75
  options[:host] || "localhost",
76
- options.merge(kex: "test", host_key: "ssh-rsa", append_all_supported_algorithms: true, verify_host_key: :never, proxy: socket(options))
76
+ options.merge(kex: "test", host_key: "ssh-rsa", encryption: "+none", hmac: "+none",
77
+ append_all_supported_algorithms: true, verify_host_key: :never, proxy: socket(options))
77
78
  )
78
79
  end
79
80
 
@@ -0,0 +1,40 @@
1
+ require 'net/ssh/transport/hmac/abstract'
2
+ require 'net/ssh/transport/gcm_cipher'
3
+
4
+ module Net::SSH::Transport
5
+ ## Implements the aes128-gcm@openssh cipher
6
+ class AES128_GCM
7
+ extend ::Net::SSH::Transport::GCMCipher
8
+
9
+ ## Implicit HMAC, do need to do anything
10
+ class ImplicitHMac < ::Net::SSH::Transport::HMAC::Abstract
11
+ def aead
12
+ true
13
+ end
14
+
15
+ def key_length
16
+ 16
17
+ end
18
+ end
19
+
20
+ def implicit_mac
21
+ ImplicitHMac.new
22
+ end
23
+
24
+ def algo_name
25
+ 'aes-128-gcm'
26
+ end
27
+
28
+ def name
29
+ 'aes128-gcm@openssh.com'
30
+ end
31
+
32
+ #
33
+ # --- RFC 5647 ---
34
+ # K_LEN AES key length 16 octets
35
+ #
36
+ def self.key_length
37
+ 16
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ require 'net/ssh/transport/hmac/abstract'
2
+ require 'net/ssh/transport/gcm_cipher'
3
+
4
+ module Net::SSH::Transport
5
+ ## Implements the aes256-gcm@openssh cipher
6
+ class AES256_GCM
7
+ extend ::Net::SSH::Transport::GCMCipher
8
+
9
+ ## Implicit HMAC, do need to do anything
10
+ class ImplicitHMac < ::Net::SSH::Transport::HMAC::Abstract
11
+ def aead
12
+ true
13
+ end
14
+
15
+ def key_length
16
+ 32
17
+ end
18
+ end
19
+
20
+ def implicit_mac
21
+ ImplicitHMac.new
22
+ end
23
+
24
+ def algo_name
25
+ 'aes-256-gcm'
26
+ end
27
+
28
+ def name
29
+ 'aes256-gcm@openssh.com'
30
+ end
31
+
32
+ #
33
+ # --- RFC 5647 ---
34
+ # K_LEN AES key length 32 octets
35
+ #
36
+ def self.key_length
37
+ 32
38
+ end
39
+ end
40
+ end