net-ssh 3.3.0.beta1 → 4.0.0.alpha1

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 (49) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.travis.yml +2 -1
  5. data/CHANGES.txt +3 -16
  6. data/Gemfile +17 -0
  7. data/README.rdoc +1 -1
  8. data/Rakefile +16 -9
  9. data/lib/net/ssh.rb +1 -5
  10. data/lib/net/ssh/authentication/agent/java_pageant.rb +1 -1
  11. data/lib/net/ssh/authentication/agent/socket.rb +5 -5
  12. data/lib/net/ssh/authentication/ed25519.rb +140 -0
  13. data/lib/net/ssh/authentication/key_manager.rb +2 -2
  14. data/lib/net/ssh/authentication/pageant.rb +1 -1
  15. data/lib/net/ssh/buffer.rb +5 -23
  16. data/lib/net/ssh/connection/session.rb +3 -20
  17. data/lib/net/ssh/key_factory.rb +14 -4
  18. data/lib/net/ssh/proxy/http.rb +2 -2
  19. data/lib/net/ssh/service/forward.rb +1 -1
  20. data/lib/net/ssh/test/socket.rb +1 -1
  21. data/lib/net/ssh/transport/algorithms.rb +2 -16
  22. data/lib/net/ssh/transport/cipher_factory.rb +16 -22
  23. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +1 -1
  24. data/lib/net/ssh/transport/key_expander.rb +1 -0
  25. data/lib/net/ssh/transport/openssl.rb +1 -1
  26. data/lib/net/ssh/transport/session.rb +0 -1
  27. data/lib/net/ssh/version.rb +3 -3
  28. data/net-ssh.gemspec +28 -9
  29. data/test/authentication/test_agent.rb +1 -9
  30. data/test/authentication/test_ed25519.rb +77 -0
  31. data/test/common.rb +0 -16
  32. data/test/connection/test_channel.rb +3 -3
  33. data/test/connection/test_session.rb +0 -1
  34. data/test/integration/{README.txt → README.md} +2 -1
  35. data/test/integration/common.rb +8 -6
  36. data/test/integration/playbook.yml +8 -7
  37. data/test/integration/test_ed25519_pkeys.rb +70 -0
  38. data/test/integration/test_forward.rb +15 -120
  39. data/test/integration/test_id_rsa_keys.rb +11 -11
  40. data/test/integration/test_proxy.rb +2 -2
  41. data/test/test_buffer.rb +1 -29
  42. data/test/transport/kex/test_ecdh_sha2_nistp384.rb +1 -1
  43. data/test/transport/test_algorithms.rb +6 -6
  44. data/test/transport/test_cipher_factory.rb +0 -119
  45. data/test/transport/test_packet_stream.rb +0 -576
  46. data/test/transport/test_session.rb +1 -1
  47. metadata +79 -6
  48. metadata.gz.sig +0 -0
  49. data/test/integration/test_encoding.rb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b4ac524bacb278642fe098d34f8f85d1a4f0579f
4
- data.tar.gz: b6725236e2cae6de59f5994602cf3ead0c39bd8d
3
+ metadata.gz: 0844171c8c635cf6a3507e82ea80755ba3f62907
4
+ data.tar.gz: 695c5873e12985df1085af7de53c9953415dfd16
5
5
  SHA512:
6
- metadata.gz: d3e76a59cde72ff7dba71135482a0409b295e6b624a5b7c870bb8fd87d55af9c3cc446237dd8ffa3e53da453fbda52e3d96979e83d032c22177b7dfb62ebeca2
7
- data.tar.gz: adcd1157982585d306d5bed0fcf5b64e9c001674d511054b683a4fee1ac0a75aa023fb462ff02f335570717fc6d011dc14bd6bc7273d2b2cf80437909f7d88f4
6
+ metadata.gz: d12f8d882dc07599c08f5ca9824ecf631faee841a6bc53f6e4bd16141061208b560cc128a844652dbdd9beaf3720f986b05a5aa4eeed31ac3c75990c440225ab
7
+ data.tar.gz: c3307431b05c1107f98046b7347bd915d468da7ffa5c18f68b471153cb416e0003709b8ed4da418a4c2ce73e22124db1d3208848e25b1c05722adc2b63ed6b56
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -7,8 +7,9 @@ rvm:
7
7
  - 2.3.0
8
8
  - jruby-head
9
9
  - jruby-19mode
10
+ - rbx-2
10
11
 
11
- install: gem install test-unit mocha
12
+ install: bundle install
12
13
 
13
14
  script: rake test
14
15
 
@@ -1,20 +1,7 @@
1
- === 3.3.0 beta 1
1
+ === 4.0.0.alpha1
2
2
 
3
- * UTF-8 buffer building encoding fixes [Ethan J. Brown, #434]
4
-
5
- === 3.2.1 beta 1
6
-
7
- * Fix pageant/windows [Elconas, #385]
8
-
9
- === 3.2.0
10
-
11
- * Added agent_socket_factory option [Alon Goldboim]
12
- * Send KEXINIT asap don't wait for server [Miklos Fazekas]
13
- * Close channels in case server closed connection [Miklos Fazekas]
14
-
15
- === 3.1.1
16
-
17
- * added missing etc require
3
+ * ed25519 key support [Miklos Fazekas]
4
+ * removed camellia [Miklos Fazekas]
18
5
 
19
6
  === 3.1.0
20
7
  === 3.1.0.rc1
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Note: this is run at package time not install time so if you are
4
+ # running on jruby, you need to install jruby-pageant manually.
5
+ gem 'jruby-pageant', ">=1.1.1" if RUBY_PLATFORM == "java"
6
+
7
+ gem 'rbnacl-libsodium', ">=1.0.2"
8
+ gem 'rbnacl', ">=3.1.2"
9
+ gem 'bcrypt_pbkdf', '1.0.0.alpha1' unless RUBY_PLATFORM == "java"
10
+
11
+
12
+ group :development do
13
+ gem 'rake'
14
+ gem 'test-unit', ">= 0.8.5"
15
+ gem 'mocha'
16
+ gem 'jeweler'
17
+ end
@@ -1,4 +1,4 @@
1
- = Net::SSH 3.x
1
+ = Net::SSH 4.x
2
2
 
3
3
  <em><b>Please note: this project is in maintenance mode. It is not under active development but pull requests are very much welcome. Just be sure to include tests! -- delano</b></em>
4
4
 
data/Rakefile CHANGED
@@ -32,15 +32,7 @@ begin
32
32
  s.authors = ["Jamis Buck", "Delano Mandelbaum", "Miklós Fazekas"]
33
33
  s.required_ruby_version = '>= 2.0'
34
34
 
35
- # Note: this is run at package time not install time so if you are
36
- # running on jruby, you need to install jruby-pageant manually.
37
- if RUBY_PLATFORM == "java"
38
- s.add_dependency 'jruby-pageant', ">=1.1.1"
39
- end
40
-
41
- s.add_development_dependency 'test-unit'
42
- s.add_development_dependency 'mocha'
43
-
35
+ # dependencies defined in Gemfile
44
36
  s.license = "MIT"
45
37
 
46
38
  unless ENV['NET_SSH_NOKEY']
@@ -51,6 +43,21 @@ begin
51
43
  raise "No key found at #{signing_key} for signing, use rake <taskname> NET_SSH_NOKEY=1 to build without key" unless File.exist?(signing_key)
52
44
  end
53
45
  end
46
+ def s.to_ruby
47
+ # see https://github.com/technicalpickles/jeweler/issues/170
48
+ result = super
49
+ result = result.chomp("\n").split("\n").map do |line|
50
+ if line =~ /%q<bcrypt_pbkdf>/
51
+ line += ' unless RUBY_PLATFORM == "java"'
52
+ else
53
+ line
54
+ end
55
+ end.join("\n") << "\n"
56
+ fail "Unexpected gemspec:#{result.inspect}" unless result.chomp!("\nend\n")
57
+ result << "\n s.add_dependency('jruby-pageant', ['>= 1.1.1']) if RUBY_PLATFORM == 'jruby'"
58
+ result << "\nend\n"
59
+ result
60
+ end
54
61
  end
55
62
  Jeweler::RubygemsDotOrgTasks.new
56
63
  rescue LoadError
@@ -3,7 +3,6 @@
3
3
  ENV['HOME'] ||= ENV['HOMEPATH'] ? "#{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}" : Dir.pwd
4
4
 
5
5
  require 'logger'
6
- require 'etc'
7
6
 
8
7
  require 'net/ssh/config'
9
8
  require 'net/ssh/errors'
@@ -70,7 +69,7 @@ module Net
70
69
  :known_hosts, :global_known_hosts_file, :user_known_hosts_file, :host_key_alias,
71
70
  :host_name, :user, :properties, :passphrase, :keys_only, :max_pkt_size,
72
71
  :max_win_size, :send_env, :use_agent, :number_of_password_prompts,
73
- :append_supported_algorithms, :non_interactive, :agent_socket_factory
72
+ :append_supported_algorithms, :non_interactive
74
73
  ]
75
74
 
76
75
  # The standard means of starting a new SSH connection. When used with a
@@ -192,9 +191,6 @@ module Net
192
191
  # password auth method
193
192
  # * :non_interactive => non interactive applications should set it to true
194
193
  # to prefer failing a password/etc auth methods vs asking for password
195
- # * :agent_socket_factory => enables the user to pass a lambda/block that will serve as the socket factory
196
- # Net::SSH::start(user,host,agent_socket_factory: ->{ UNIXSocket.open('/foo/bar') })
197
- # example: ->{ UNIXSocket.open('/foo/bar')}
198
194
  #
199
195
  # If +user+ parameter is nil it defaults to USER from ssh_config, or
200
196
  # local username
@@ -19,7 +19,7 @@ module Net; module SSH; module Authentication
19
19
 
20
20
  # Instantiates a new agent object, connects to a running SSH agent,
21
21
  # negotiates the agent protocol version, and returns the agent object.
22
- def self.connect(logger=nil, agent_socket_factory)
22
+ def self.connect(logger=nil)
23
23
  agent = new(logger)
24
24
  agent.connect!
25
25
  agent
@@ -42,9 +42,9 @@ module Net; module SSH; module Authentication
42
42
 
43
43
  # Instantiates a new agent object, connects to a running SSH agent,
44
44
  # negotiates the agent protocol version, and returns the agent object.
45
- def self.connect(logger=nil, agent_socket_factory = nil)
45
+ def self.connect(logger=nil)
46
46
  agent = new(logger)
47
- agent.connect!(agent_socket_factory)
47
+ agent.connect!
48
48
  agent.negotiate!
49
49
  agent
50
50
  end
@@ -59,10 +59,10 @@ module Net; module SSH; module Authentication
59
59
  # given by the attribute writers. If the agent on the other end of the
60
60
  # socket reports that it is an SSH2-compatible agent, this will fail
61
61
  # (it only supports the ssh-agent distributed by OpenSSH).
62
- def connect!(agent_socket_factory = nil)
62
+ def connect!
63
63
  begin
64
64
  debug { "connecting to ssh-agent" }
65
- @socket = agent_socket_factory.nil? ? socket_class.open(ENV['SSH_AUTH_SOCK']) : agent_socket_factory.call
65
+ @socket = agent_socket_factory.open(ENV['SSH_AUTH_SOCK'])
66
66
  rescue
67
67
  error { "could not connect to ssh-agent" }
68
68
  raise AgentNotAvailable, $!.message
@@ -132,7 +132,7 @@ module Net; module SSH; module Authentication
132
132
  private
133
133
 
134
134
  # Returns the agent socket factory to use.
135
- def socket_class
135
+ def agent_socket_factory
136
136
  if Net::SSH::Authentication::PLATFORM == :win32
137
137
  Pageant::Socket
138
138
  else
@@ -0,0 +1,140 @@
1
+ gem 'rbnacl-libsodium'
2
+ gem 'rbnacl'
3
+
4
+ require 'rbnacl/libsodium'
5
+ require 'rbnacl'
6
+ require 'rbnacl/signatures/ed25519/verify_key'
7
+ require 'rbnacl/signatures/ed25519/signing_key'
8
+
9
+ require 'rbnacl/hash'
10
+
11
+ require 'base64'
12
+
13
+ require 'net/ssh/transport/cipher_factory'
14
+ require 'bcrypt_pbkdf' unless RUBY_PLATFORM == "java"
15
+
16
+ module RbNaCl
17
+ module Signatures
18
+ module Ed25519
19
+ class SigningKeyFromFile < SigningKey
20
+ def initialize(pk,sk)
21
+ @signing_key = sk
22
+ @verify_key = VerifyKey.new(pk)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ module ED25519
30
+ class PubKey
31
+ def initialize(data)
32
+ @verify_key = RbNaCl::Signatures::Ed25519::VerifyKey.new(data)
33
+ end
34
+
35
+ def self.read_keyblob(buffer)
36
+ PubKey.new(buffer.read_string)
37
+ end
38
+
39
+ def to_blob
40
+ Net::SSH::Buffer.from(:string,"ssh-ed25519",:string,@verify_key.to_bytes).to_s
41
+ end
42
+
43
+ def ssh_type
44
+ "ssh-ed25519"
45
+ end
46
+
47
+ def ssh_do_verify(sig,data)
48
+ @verify_key.verify(sig,data)
49
+ end
50
+
51
+ def to_pem
52
+ # TODO this is not pem
53
+ ssh_type + Base64.encode64(@verify_key.to_bytes)
54
+ end
55
+
56
+ def fingerprint
57
+ @fingerprint ||= OpenSSL::Digest::MD5.hexdigest(to_blob).scan(/../).join(":")
58
+ end
59
+ end
60
+
61
+ class PrivKey
62
+ CipherFactory = Net::SSH::Transport::CipherFactory
63
+
64
+ MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
65
+ MEND = "-----END OPENSSH PRIVATE KEY-----\n"
66
+ MAGIC = "openssh-key-v1"
67
+
68
+ def initialize(datafull,password)
69
+ raise ArgumentError.new("Expected #{MBEGIN} at start of private key") unless datafull.start_with?(MBEGIN)
70
+ raise ArgumentError.new("Expected #{MEND} at end of private key") unless datafull.end_with?(MEND)
71
+ datab64 = datafull[MBEGIN.size ... -MEND.size]
72
+ data = Base64.decode64(datab64)
73
+ raise ArgumentError.new("Expected #{MAGIC} at start of decoded private key") unless data.start_with?(MAGIC)
74
+ buffer = Net::SSH::Buffer.new(data[MAGIC.size+1 .. -1])
75
+
76
+ ciphername = buffer.read_string
77
+ raise ArgumentError.new("#{ciphername} in private key is not supported") unless
78
+ CipherFactory.supported?(ciphername)
79
+
80
+ kdfname = buffer.read_string
81
+ raise ArgumentError.new("Expected #{kdfname} to be or none or bcrypt") unless %w(none bcrypt).include?(kdfname)
82
+
83
+ kdfopts = Net::SSH::Buffer.new(buffer.read_string)
84
+ num_keys = buffer.read_long
85
+ raise ArgumentError.new("Only 1 key is supported in ssh keys #{num_keys} was in private key") unless num_keys == 1
86
+ _pubkey = buffer.read_string
87
+
88
+ len = buffer.read_long
89
+
90
+ keylen, blocksize, ivlen = CipherFactory.get_lengths(ciphername, iv_len: true)
91
+ raise ArgumentError.new("Private key len:#{len} is not a multiple of #{blocksize}") if
92
+ ((len < blocksize) || ((blocksize > 0) && (len % blocksize) != 0))
93
+
94
+ if kdfname == 'bcrypt'
95
+ salt = kdfopts.read_string
96
+ rounds = kdfopts.read_long
97
+
98
+ raise "BCryptPbkdf is not implemented for jruby" if RUBY_PLATFORM == "java"
99
+ key = BCryptPbkdf::key(password, salt, keylen + ivlen, rounds)
100
+ else
101
+ key = '\x00' * (keylen + ivlen)
102
+ end
103
+
104
+ cipher = CipherFactory.get(ciphername, key: key[0...keylen], iv:key[keylen...keylen+ivlen], decrypt: true)
105
+
106
+ decoded = cipher.update(buffer.remainder_as_buffer.to_s)
107
+ decoded << cipher.final
108
+
109
+ decoded = Net::SSH::Buffer.new(decoded)
110
+ check1 = decoded.read_long
111
+ check2 = decoded.read_long
112
+
113
+ raise ArgumentError, "Decrypt failed on private key" if (check1 != check2)
114
+
115
+ _type_name = decoded.read_string
116
+ pk = decoded.read_string
117
+ sk = decoded.read_string
118
+ _comment = decoded.read_string
119
+
120
+ @pk = pk
121
+ @sign_key = RbNaCl::Signatures::Ed25519::SigningKeyFromFile.new(pk,sk)
122
+ end
123
+
124
+ def public_key
125
+ PubKey.new(@pk)
126
+ end
127
+
128
+ def ssh_do_sign(data)
129
+ @sign_key.sign(data)
130
+ end
131
+
132
+ def self.read(data,password)
133
+ self.new(data,password)
134
+ end
135
+
136
+ def self.read_keyblob(buffer)
137
+ ED25519::PubKey.read_keyblob(buffer)
138
+ end
139
+ end
140
+ end
@@ -147,7 +147,7 @@ module Net
147
147
 
148
148
  if info[:key]
149
149
  return Net::SSH::Buffer.from(:string, identity.ssh_type,
150
- :mstring, info[:key].ssh_do_sign(data.to_s)).to_s
150
+ :string, info[:key].ssh_do_sign(data.to_s)).to_s
151
151
  end
152
152
 
153
153
  if info[:from] == :agent
@@ -176,7 +176,7 @@ module Net
176
176
  # or if the agent is otherwise not available.
177
177
  def agent
178
178
  return unless use_agent?
179
- @agent ||= Agent.connect(logger, options[:agent_socket_factory])
179
+ @agent ||= Agent.connect(logger)
180
180
  rescue AgentNotAvailable
181
181
  @use_agent = false
182
182
  nil
@@ -254,7 +254,7 @@ module Net; module SSH; module Authentication
254
254
  Win.GetTokenInformation(token_handle,
255
255
  token_information_class,
256
256
  Win::NULL, 0, preturn_length)
257
- ptoken_information = malloc_ptr(preturn_length.to_s(Win::SIZEOF_DWORD).unpack('L')[0])
257
+ ptoken_information = malloc_ptr(preturn_length.ptr.to_i)
258
258
 
259
259
  # This call is going to write the requested information to
260
260
  # the memory location referenced by token_information.
@@ -34,7 +34,6 @@ module Net; module SSH
34
34
  # * :long => write a 4-byte integer (#write_long)
35
35
  # * :byte => write a single byte (#write_byte)
36
36
  # * :string => write a 4-byte length followed by character data (#write_string)
37
- # * :mstring => same as string, but caller cannot resuse the string, avoids potential duplication (#write_moved)
38
37
  # * :bool => write a single byte, interpreted as a boolean (#write_bool)
39
38
  # * :bignum => write an SSH-encoded bignum (#write_bignum)
40
39
  # * :key => write an SSH-encoded key value (#write_key)
@@ -183,7 +182,7 @@ module Net; module SSH
183
182
  consume!
184
183
  data
185
184
  end
186
-
185
+
187
186
  # Return the next 8 bytes as a 64-bit integer (in network byte order).
188
187
  # Returns nil if there are less than 8 bytes remaining to be read in the
189
188
  # buffer.
@@ -256,6 +255,9 @@ module Net; module SSH
256
255
  key.e = read_bignum
257
256
  key.n = read_bignum
258
257
 
258
+ when /^ssh-ed25519$/
259
+ key = ED25519::PubKey.read_keyblob(self)
260
+
259
261
  when /^ecdsa\-sha2\-(\w*)$/
260
262
  unless defined?(OpenSSL::PKey::EC)
261
263
  raise NotImplementedError, "unsupported key type `#{type}'"
@@ -282,14 +284,7 @@ module Net; module SSH
282
284
  # Writes the given data literally into the string. Does not alter the
283
285
  # read position. Returns the buffer object.
284
286
  def write(*data)
285
- data.each { |datum| @content << datum.dup.force_encoding('BINARY') }
286
- self
287
- end
288
-
289
- # Optimized version of write where the caller gives up ownership of string
290
- # to the method. This way we can mutate the string.
291
- def write_moved(string)
292
- @content << string.force_encoding('BINARY')
287
+ data.each { |datum| @content << datum }
293
288
  self
294
289
  end
295
290
 
@@ -332,19 +327,6 @@ module Net; module SSH
332
327
  self
333
328
  end
334
329
 
335
- # Writes each argument to the buffer as an SSH2-encoded string. Each
336
- # string is prefixed by its length, encoded as a 4-byte long integer.
337
- # Does not alter the read position. Returns the buffer object.
338
- # Might alter arguments see write_moved
339
- def write_mstring(*text)
340
- text.each do |string|
341
- s = string.to_s
342
- write_long(s.bytesize)
343
- write_moved(s)
344
- end
345
- self
346
- end
347
-
348
330
  # Writes each argument to the buffer as a (C-style) boolean, with 1
349
331
  # meaning true, and 0 meaning false. Does not alter the read position.
350
332
  # Returns the buffer object.
@@ -210,9 +210,6 @@ module Net; module SSH; module Connection
210
210
  readers, writers, = Net::SSH::Compat.io_select(r, w, nil, io_select_wait(wait))
211
211
 
212
212
  postprocess(readers, writers)
213
- rescue
214
- force_channel_cleanup_on_close if closed?
215
- raise
216
213
  end
217
214
 
218
215
  # This is called internally as part of #process. It dispatches any
@@ -368,7 +365,6 @@ module Net; module SSH; module Connection
368
365
  channel.wait
369
366
 
370
367
  channel[:result] ||= "" unless block
371
- channel[:result] &&= channel[:result].force_encoding("UTF-8") unless block
372
368
 
373
369
  return channel[:result]
374
370
  end
@@ -460,9 +456,9 @@ module Net; module SSH; module Connection
460
456
  end
461
457
 
462
458
  def cleanup_channel(channel)
463
- if channel.local_closed? and channel.remote_closed?
464
- info { "#{host} delete channel #{channel.local_id} which closed locally and remotely" }
465
- channels.delete(channel.local_id)
459
+ if channel.local_closed? and channel.remote_closed?
460
+ info { "#{host} delete channel #{channel.local_id} which closed locally and remotely" }
461
+ channels.delete(channel.local_id)
466
462
  end
467
463
  end
468
464
 
@@ -479,9 +475,6 @@ module Net; module SSH; module Connection
479
475
 
480
476
  send(MAP[packet.type], packet)
481
477
  end
482
- rescue
483
- force_channel_cleanup_on_close if closed?
484
- raise
485
478
  end
486
479
 
487
480
  # Returns the next available channel id to be assigned, and increments
@@ -490,16 +483,6 @@ module Net; module SSH; module Connection
490
483
  @channel_id_counter += 1
491
484
  end
492
485
 
493
- def force_channel_cleanup_on_close
494
- channels.each do |id, channel|
495
- channel.remote_closed!
496
- channel.close
497
-
498
- cleanup_channel(channel)
499
- channel.do_close
500
- end
501
- end
502
-
503
486
  # Invoked when a global request is received. The registered global
504
487
  # request callback will be invoked, if one exists, and the necessary
505
488
  # reply returned.