net-ssh 3.3.0.beta1 → 4.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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.