net-ssh 4.1.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.gitignore +5 -0
  5. data/.rubocop.yml +8 -2
  6. data/.rubocop_todo.yml +405 -552
  7. data/.travis.yml +23 -22
  8. data/CHANGES.txt +112 -1
  9. data/Gemfile +1 -7
  10. data/{Gemfile.norbnacl → Gemfile.noed25519} +1 -1
  11. data/Manifest +4 -5
  12. data/README.md +287 -0
  13. data/Rakefile +40 -29
  14. data/appveyor.yml +12 -6
  15. data/lib/net/ssh.rb +68 -32
  16. data/lib/net/ssh/authentication/agent.rb +234 -222
  17. data/lib/net/ssh/authentication/certificate.rb +175 -164
  18. data/lib/net/ssh/authentication/constants.rb +17 -14
  19. data/lib/net/ssh/authentication/ed25519.rb +162 -141
  20. data/lib/net/ssh/authentication/ed25519_loader.rb +32 -29
  21. data/lib/net/ssh/authentication/key_manager.rb +40 -9
  22. data/lib/net/ssh/authentication/methods/abstract.rb +53 -47
  23. data/lib/net/ssh/authentication/methods/hostbased.rb +32 -33
  24. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +1 -1
  25. data/lib/net/ssh/authentication/methods/none.rb +10 -10
  26. data/lib/net/ssh/authentication/methods/password.rb +13 -13
  27. data/lib/net/ssh/authentication/methods/publickey.rb +56 -55
  28. data/lib/net/ssh/authentication/pageant.rb +468 -465
  29. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  30. data/lib/net/ssh/authentication/session.rb +130 -122
  31. data/lib/net/ssh/buffer.rb +345 -312
  32. data/lib/net/ssh/buffered_io.rb +163 -163
  33. data/lib/net/ssh/config.rb +316 -238
  34. data/lib/net/ssh/connection/channel.rb +670 -650
  35. data/lib/net/ssh/connection/constants.rb +30 -26
  36. data/lib/net/ssh/connection/event_loop.rb +108 -105
  37. data/lib/net/ssh/connection/keepalive.rb +54 -50
  38. data/lib/net/ssh/connection/session.rb +682 -671
  39. data/lib/net/ssh/connection/term.rb +180 -176
  40. data/lib/net/ssh/errors.rb +101 -99
  41. data/lib/net/ssh/key_factory.rb +195 -108
  42. data/lib/net/ssh/known_hosts.rb +161 -152
  43. data/lib/net/ssh/loggable.rb +57 -55
  44. data/lib/net/ssh/packet.rb +82 -78
  45. data/lib/net/ssh/prompt.rb +55 -53
  46. data/lib/net/ssh/proxy/command.rb +104 -89
  47. data/lib/net/ssh/proxy/errors.rb +12 -8
  48. data/lib/net/ssh/proxy/http.rb +93 -91
  49. data/lib/net/ssh/proxy/https.rb +42 -39
  50. data/lib/net/ssh/proxy/jump.rb +50 -47
  51. data/lib/net/ssh/proxy/socks4.rb +0 -2
  52. data/lib/net/ssh/proxy/socks5.rb +11 -12
  53. data/lib/net/ssh/service/forward.rb +370 -317
  54. data/lib/net/ssh/test.rb +83 -77
  55. data/lib/net/ssh/test/channel.rb +146 -142
  56. data/lib/net/ssh/test/extensions.rb +150 -146
  57. data/lib/net/ssh/test/kex.rb +35 -31
  58. data/lib/net/ssh/test/local_packet.rb +48 -44
  59. data/lib/net/ssh/test/packet.rb +87 -84
  60. data/lib/net/ssh/test/remote_packet.rb +35 -31
  61. data/lib/net/ssh/test/script.rb +173 -171
  62. data/lib/net/ssh/test/socket.rb +59 -55
  63. data/lib/net/ssh/transport/algorithms.rb +430 -364
  64. data/lib/net/ssh/transport/cipher_factory.rb +95 -91
  65. data/lib/net/ssh/transport/constants.rb +33 -25
  66. data/lib/net/ssh/transport/ctr.rb +33 -11
  67. data/lib/net/ssh/transport/hmac.rb +15 -13
  68. data/lib/net/ssh/transport/hmac/abstract.rb +82 -63
  69. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  70. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  71. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  72. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  73. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  74. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  75. data/lib/net/ssh/transport/identity_cipher.rb +55 -51
  76. data/lib/net/ssh/transport/kex.rb +14 -13
  77. data/lib/net/ssh/transport/kex/abstract.rb +123 -0
  78. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  79. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +38 -0
  80. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  81. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
  82. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +112 -217
  83. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -62
  84. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  85. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
  86. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
  87. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
  88. data/lib/net/ssh/transport/key_expander.rb +29 -25
  89. data/lib/net/ssh/transport/openssl.rb +116 -116
  90. data/lib/net/ssh/transport/packet_stream.rb +223 -190
  91. data/lib/net/ssh/transport/server_version.rb +64 -66
  92. data/lib/net/ssh/transport/session.rb +306 -257
  93. data/lib/net/ssh/transport/state.rb +198 -196
  94. data/lib/net/ssh/verifiers/accept_new.rb +35 -0
  95. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +34 -0
  96. data/lib/net/ssh/verifiers/always.rb +56 -0
  97. data/lib/net/ssh/verifiers/never.rb +21 -0
  98. data/lib/net/ssh/version.rb +55 -53
  99. data/net-ssh-public_cert.pem +18 -19
  100. data/net-ssh.gemspec +12 -11
  101. data/support/ssh_tunnel_bug.rb +2 -2
  102. metadata +86 -75
  103. metadata.gz.sig +0 -0
  104. data/Gemfile.norbnacl.lock +0 -41
  105. data/README.rdoc +0 -169
  106. data/lib/net/ssh/ruby_compat.rb +0 -24
  107. data/lib/net/ssh/verifiers/lenient.rb +0 -30
  108. data/lib/net/ssh/verifiers/null.rb +0 -12
  109. data/lib/net/ssh/verifiers/secure.rb +0 -52
  110. data/lib/net/ssh/verifiers/strict.rb +0 -24
  111. data/support/arcfour_check.rb +0 -20
@@ -0,0 +1,43 @@
1
+ require 'openssl'
2
+
3
+ module Net
4
+ module SSH
5
+ module Authentication
6
+ # Public key fingerprinting utility module - internal not part of API.
7
+ # This is included in pubkey classes and called from there. All RSA, DSA, and ECC keys
8
+ # are supported.
9
+ #
10
+ # require 'net/ssh'
11
+ # my_pubkey_text = File.read('/path/to/id_ed25519.pub')
12
+ # #=> "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDB2NBh4GJPPUN1kXPMu8b633Xcv55WoKC3OkBjFAbzJ alice@example.com"
13
+ # my_pubkey = Net::SSH::KeyFactory.load_data_public_key(my_pubkey_text)
14
+ # #=> #<Net::SSH::Authentication::ED25519::PubKey:0x00007fc8e91819b0
15
+ # my_pubkey.fingerprint
16
+ # #=> "2f:7f:97:21:76:a4:0f:38:c4:fe:d8:b4:6a:39:72:30"
17
+ # my_pubkey.fingerprint('SHA256')
18
+ # #=> "SHA256:u6mXnY8P1b0FODGp8mckqOB33u8+jvkSCtJbD5Q9klg"
19
+ module PubKeyFingerprint # :nodoc:
20
+ # Return the key's fingerprint. Algorithm may be either +MD5+ (default),
21
+ # or +SHA256+. For +SHA256+, fingerprints are in the same format
22
+ # returned by OpenSSH's <tt>`ssh-add -l -E SHA256`</tt>, i.e.,
23
+ # trailing base64 padding '=' characters are stripped and the
24
+ # literal string +SHA256:+ is prepended.
25
+ def fingerprint(algorithm='MD5')
26
+ @fingerprint ||= {}
27
+ @fingerprint[algorithm] ||= PubKeyFingerprint.fingerprint(to_blob, algorithm)
28
+ end
29
+
30
+ def self.fingerprint(blob, algorithm='MD5')
31
+ case algorithm.to_s.upcase
32
+ when 'MD5'
33
+ OpenSSL::Digest.hexdigest(algorithm, blob).scan(/../).join(":")
34
+ when 'SHA256'
35
+ "SHA256:#{Base64.encode64(OpenSSL::Digest.digest(algorithm, blob)).chomp.gsub(/=+\z/, '')}"
36
+ else
37
+ raise OpenSSL::Digest::DigestError, "unsupported ssh key digest #{algorithm}"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -8,149 +8,157 @@ require 'net/ssh/authentication/methods/hostbased'
8
8
  require 'net/ssh/authentication/methods/password'
9
9
  require 'net/ssh/authentication/methods/keyboard_interactive'
10
10
 
11
- module Net; module SSH; module Authentication
11
+ module Net
12
+ module SSH
13
+ module Authentication
12
14
 
13
- # Raised if the current authentication method is not allowed
14
- class DisallowedMethod < Net::SSH::Exception
15
- end
15
+ # Raised if the current authentication method is not allowed
16
+ class DisallowedMethod < Net::SSH::Exception
17
+ end
16
18
 
17
- # Represents an authentication session. It manages the authentication of
18
- # a user over an established connection (the "transport" object, see
19
- # Net::SSH::Transport::Session).
20
- #
21
- # The use of an authentication session to manage user authentication is
22
- # internal to Net::SSH (specifically Net::SSH.start). Consumers of the
23
- # Net::SSH library will never need to access this class directly.
24
- class Session
25
- include Transport::Constants, Constants, Loggable
19
+ # Represents an authentication session. It manages the authentication of
20
+ # a user over an established connection (the "transport" object, see
21
+ # Net::SSH::Transport::Session).
22
+ #
23
+ # The use of an authentication session to manage user authentication is
24
+ # internal to Net::SSH (specifically Net::SSH.start). Consumers of the
25
+ # Net::SSH library will never need to access this class directly.
26
+ class Session
27
+ include Loggable
28
+ include Constants
29
+ include Transport::Constants
26
30
 
27
- # transport layer abstraction
28
- attr_reader :transport
31
+ # transport layer abstraction
32
+ attr_reader :transport
29
33
 
30
- # the list of authentication methods to try
31
- attr_reader :auth_methods
34
+ # the list of authentication methods to try
35
+ attr_reader :auth_methods
32
36
 
33
- # the list of authentication methods that are allowed
34
- attr_reader :allowed_auth_methods
37
+ # the list of authentication methods that are allowed
38
+ attr_reader :allowed_auth_methods
35
39
 
36
- # a hash of options, given at construction time
37
- attr_reader :options
40
+ # a hash of options, given at construction time
41
+ attr_reader :options
38
42
 
39
- # Instantiates a new Authentication::Session object over the given
40
- # transport layer abstraction.
41
- def initialize(transport, options={})
42
- self.logger = transport.logger
43
- @transport = transport
43
+ # Instantiates a new Authentication::Session object over the given
44
+ # transport layer abstraction.
45
+ def initialize(transport, options={})
46
+ self.logger = transport.logger
47
+ @transport = transport
44
48
 
45
- @auth_methods = options[:auth_methods] || Net::SSH::Config.default_auth_methods
46
- @options = options
49
+ @auth_methods = options[:auth_methods] || Net::SSH::Config.default_auth_methods
50
+ @options = options
47
51
 
48
- @allowed_auth_methods = @auth_methods
49
- end
52
+ @allowed_auth_methods = @auth_methods
53
+ end
50
54
 
51
- # Attempts to authenticate the given user, in preparation for the next
52
- # service request. Returns true if an authentication method succeeds in
53
- # authenticating the user, and false otherwise.
54
- def authenticate(next_service, username, password=nil)
55
- debug { "beginning authentication of `#{username}'" }
56
-
57
- transport.send_message(transport.service_request("ssh-userauth"))
58
- expect_message(SERVICE_ACCEPT)
59
-
60
- key_manager = KeyManager.new(logger, options)
61
- keys.each { |key| key_manager.add(key) } unless keys.empty?
62
- key_data.each { |key2| key_manager.add_key_data(key2) } unless key_data.empty?
63
-
64
- attempted = []
65
-
66
- @auth_methods.each do |name|
67
- begin
68
- next unless @allowed_auth_methods.include?(name)
69
- attempted << name
70
-
71
- debug { "trying #{name}" }
72
- begin
73
- auth_class = Methods.const_get(name.split(/\W+/).map { |p| p.capitalize }.join)
74
- method = auth_class.new(self, key_manager: key_manager, password_prompt: options[:password_prompt])
75
- rescue NameError
76
- debug{"Mechanism #{name} was requested, but isn't a known type. Ignoring it."}
77
- next
55
+ # Attempts to authenticate the given user, in preparation for the next
56
+ # service request. Returns true if an authentication method succeeds in
57
+ # authenticating the user, and false otherwise.
58
+ def authenticate(next_service, username, password=nil)
59
+ debug { "beginning authentication of `#{username}'" }
60
+
61
+ transport.send_message(transport.service_request("ssh-userauth"))
62
+ expect_message(SERVICE_ACCEPT)
63
+
64
+ key_manager = KeyManager.new(logger, options)
65
+ keys.each { |key| key_manager.add(key) } unless keys.empty?
66
+ keycerts.each { |keycert| key_manager.add_keycert(keycert) } unless keycerts.empty?
67
+ key_data.each { |key2| key_manager.add_key_data(key2) } unless key_data.empty?
68
+ default_keys.each { |key| key_manager.add(key) } unless options.key?(:keys) || options.key?(:key_data)
69
+
70
+ attempted = []
71
+
72
+ @auth_methods.each do |name|
73
+ begin
74
+ next unless @allowed_auth_methods.include?(name)
75
+ attempted << name
76
+
77
+ debug { "trying #{name}" }
78
+ begin
79
+ auth_class = Methods.const_get(name.split(/\W+/).map { |p| p.capitalize }.join)
80
+ method = auth_class.new(self, key_manager: key_manager, password_prompt: options[:password_prompt])
81
+ rescue NameError
82
+ debug {"Mechanism #{name} was requested, but isn't a known type. Ignoring it."}
83
+ next
84
+ end
85
+
86
+ return true if method.authenticate(next_service, username, password)
87
+ rescue Net::SSH::Authentication::DisallowedMethod
88
+ end
78
89
  end
79
90
 
80
- return true if method.authenticate(next_service, username, password)
81
- rescue Net::SSH::Authentication::DisallowedMethod
91
+ error { "all authorization methods failed (tried #{attempted.join(', ')})" }
92
+ return false
93
+ ensure
94
+ key_manager.finish if key_manager
82
95
  end
83
- end
84
-
85
- error { "all authorization methods failed (tried #{attempted.join(', ')})" }
86
- return false
87
- ensure
88
- key_manager.finish if key_manager
89
- end
90
-
91
- # Blocks until a packet is received. It silently handles USERAUTH_BANNER
92
- # packets, and will raise an error if any packet is received that is not
93
- # valid during user authentication.
94
- def next_message
95
- loop do
96
- packet = transport.next_message
97
-
98
- case packet.type
99
- when USERAUTH_BANNER
100
- info { packet[:message] }
101
- # TODO add a hook for people to retrieve the banner when it is sent
102
96
 
103
- when USERAUTH_FAILURE
104
- @allowed_auth_methods = packet[:authentications].split(/,/)
105
- debug { "allowed methods: #{packet[:authentications]}" }
106
- return packet
107
-
108
- when USERAUTH_METHOD_RANGE, SERVICE_ACCEPT
109
- return packet
110
-
111
- when USERAUTH_SUCCESS
112
- transport.hint :authenticated
113
- return packet
97
+ # Blocks until a packet is received. It silently handles USERAUTH_BANNER
98
+ # packets, and will raise an error if any packet is received that is not
99
+ # valid during user authentication.
100
+ def next_message
101
+ loop do
102
+ packet = transport.next_message
103
+
104
+ case packet.type
105
+ when USERAUTH_BANNER
106
+ info { packet[:message] }
107
+ # TODO add a hook for people to retrieve the banner when it is sent
108
+
109
+ when USERAUTH_FAILURE
110
+ @allowed_auth_methods = packet[:authentications].split(/,/)
111
+ debug { "allowed methods: #{packet[:authentications]}" }
112
+ return packet
113
+
114
+ when USERAUTH_METHOD_RANGE, SERVICE_ACCEPT
115
+ return packet
116
+
117
+ when USERAUTH_SUCCESS
118
+ transport.hint :authenticated
119
+ return packet
120
+
121
+ else
122
+ raise Net::SSH::Exception, "unexpected message #{packet.type} (#{packet})"
123
+ end
124
+ end
125
+ end
114
126
 
115
- else
116
- raise Net::SSH::Exception, "unexpected message #{packet.type} (#{packet})"
127
+ # Blocks until a packet is received, and returns it if it is of the given
128
+ # type. If it is not, an exception is raised.
129
+ def expect_message(type)
130
+ message = next_message
131
+ raise Net::SSH::Exception, "expected #{type}, got #{message.type} (#{message})" unless message.type == type
132
+ message
117
133
  end
118
- end
119
- end
120
134
 
121
- # Blocks until a packet is received, and returns it if it is of the given
122
- # type. If it is not, an exception is raised.
123
- def expect_message(type)
124
- message = next_message
125
- unless message.type == type
126
- raise Net::SSH::Exception, "expected #{type}, got #{message.type} (#{message})"
127
- end
128
- message
129
- end
135
+ private
130
136
 
131
- private
137
+ # Returns an array of paths to the key files usually defined
138
+ # by system default.
139
+ def default_keys
140
+ %w[~/.ssh/id_ed25519 ~/.ssh/id_rsa ~/.ssh/id_dsa ~/.ssh/id_ecdsa
141
+ ~/.ssh2/id_ed25519 ~/.ssh2/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_ecdsa]
142
+ end
132
143
 
133
- # Returns an array of paths to the key files usually defined
134
- # by system default.
135
- def default_keys
136
- if defined?(OpenSSL::PKey::EC)
137
- %w(~/.ssh/id_ed25519 ~/.ssh/id_rsa ~/.ssh/id_dsa ~/.ssh/id_ecdsa
138
- ~/.ssh2/id_ed25519 ~/.ssh2/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_ecdsa)
139
- else
140
- %w(~/.ssh/id_dsa ~/.ssh/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_rsa)
144
+ # Returns an array of paths to the key files that should be used when
145
+ # attempting any key-based authentication mechanism.
146
+ def keys
147
+ Array(options[:keys])
141
148
  end
142
- end
143
149
 
144
- # Returns an array of paths to the key files that should be used when
145
- # attempting any key-based authentication mechanism.
146
- def keys
147
- Array(options[:keys] || default_keys)
148
- end
150
+ # Returns an array of paths to the keycert files that should be used when
151
+ # attempting any key-based authentication mechanism.
152
+ def keycerts
153
+ Array(options[:keycerts])
154
+ end
149
155
 
150
- # Returns an array of the key data that should be used when
151
- # attempting any key-based authentication mechanism.
152
- def key_data
153
- Array(options[:key_data])
156
+ # Returns an array of the key data that should be used when
157
+ # attempting any key-based authentication mechanism.
158
+ def key_data
159
+ Array(options[:key_data])
160
+ end
154
161
  end
162
+ end
155
163
  end
156
- end; end; end
164
+ end
@@ -1,257 +1,297 @@
1
- require 'net/ssh/ruby_compat'
2
1
  require 'net/ssh/transport/openssl'
3
2
 
4
3
  require 'net/ssh/authentication/certificate'
5
4
  require 'net/ssh/authentication/ed25519_loader'
6
5
 
7
- module Net; module SSH
8
-
9
- # Net::SSH::Buffer is a flexible class for building and parsing binary
10
- # data packets. It provides a stream-like interface for sequentially
11
- # reading data items from the buffer, as well as a useful helper method
12
- # for building binary packets given a signature.
13
- #
14
- # Writing to a buffer always appends to the end, regardless of where the
15
- # read cursor is. Reading, on the other hand, always begins at the first
16
- # byte of the buffer and increments the read cursor, with subsequent reads
17
- # taking up where the last left off.
18
- #
19
- # As a consumer of the Net::SSH library, you will rarely come into contact
20
- # with these buffer objects directly, but it could happen. Also, if you
21
- # are ever implementing a protocol on top of SSH (e.g. SFTP), this buffer
22
- # class can be quite handy.
23
- class Buffer
24
- # This is a convenience method for creating and populating a new buffer
25
- # from a single command. The arguments must be even in length, with the
26
- # first of each pair of arguments being a symbol naming the type of the
27
- # data that follows. If the type is :raw, the value is written directly
28
- # to the hash.
29
- #
30
- # b = Buffer.from(:byte, 1, :string, "hello", :raw, "\1\2\3\4")
31
- # #-> "\1\0\0\0\5hello\1\2\3\4"
32
- #
33
- # The supported data types are:
6
+ module Net
7
+ module SSH
8
+
9
+ # Net::SSH::Buffer is a flexible class for building and parsing binary
10
+ # data packets. It provides a stream-like interface for sequentially
11
+ # reading data items from the buffer, as well as a useful helper method
12
+ # for building binary packets given a signature.
34
13
  #
35
- # * :raw => write the next value verbatim (#write)
36
- # * :int64 => write an 8-byte integer (#write_int64)
37
- # * :long => write a 4-byte integer (#write_long)
38
- # * :byte => write a single byte (#write_byte)
39
- # * :string => write a 4-byte length followed by character data (#write_string)
40
- # * :mstring => same as string, but caller cannot resuse the string, avoids potential duplication (#write_moved)
41
- # * :bool => write a single byte, interpreted as a boolean (#write_bool)
42
- # * :bignum => write an SSH-encoded bignum (#write_bignum)
43
- # * :key => write an SSH-encoded key value (#write_key)
14
+ # Writing to a buffer always appends to the end, regardless of where the
15
+ # read cursor is. Reading, on the other hand, always begins at the first
16
+ # byte of the buffer and increments the read cursor, with subsequent reads
17
+ # taking up where the last left off.
44
18
  #
45
- # Any of these, except for :raw, accepts an Array argument, to make it
46
- # easier to write multiple values of the same type in a briefer manner.
47
- def self.from(*args)
48
- raise ArgumentError, "odd number of arguments given" unless args.length % 2 == 0
49
-
50
- buffer = new
51
- 0.step(args.length-1, 2) do |index|
52
- type = args[index]
53
- value = args[index+1]
54
- if type == :raw
55
- buffer.append(value.to_s)
56
- elsif Array === value
57
- buffer.send("write_#{type}", *value)
58
- else
59
- buffer.send("write_#{type}", value)
19
+ # As a consumer of the Net::SSH library, you will rarely come into contact
20
+ # with these buffer objects directly, but it could happen. Also, if you
21
+ # are ever implementing a protocol on top of SSH (e.g. SFTP), this buffer
22
+ # class can be quite handy.
23
+ class Buffer
24
+ # This is a convenience method for creating and populating a new buffer
25
+ # from a single command. The arguments must be even in length, with the
26
+ # first of each pair of arguments being a symbol naming the type of the
27
+ # data that follows. If the type is :raw, the value is written directly
28
+ # to the hash.
29
+ #
30
+ # b = Buffer.from(:byte, 1, :string, "hello", :raw, "\1\2\3\4")
31
+ # #-> "\1\0\0\0\5hello\1\2\3\4"
32
+ #
33
+ # The supported data types are:
34
+ #
35
+ # * :raw => write the next value verbatim (#write)
36
+ # * :int64 => write an 8-byte integer (#write_int64)
37
+ # * :long => write a 4-byte integer (#write_long)
38
+ # * :byte => write a single byte (#write_byte)
39
+ # * :string => write a 4-byte length followed by character data (#write_string)
40
+ # * :mstring => same as string, but caller cannot resuse the string, avoids potential duplication (#write_moved)
41
+ # * :bool => write a single byte, interpreted as a boolean (#write_bool)
42
+ # * :bignum => write an SSH-encoded bignum (#write_bignum)
43
+ # * :key => write an SSH-encoded key value (#write_key)
44
+ #
45
+ # Any of these, except for :raw, accepts an Array argument, to make it
46
+ # easier to write multiple values of the same type in a briefer manner.
47
+ def self.from(*args)
48
+ raise ArgumentError, "odd number of arguments given" unless args.length % 2 == 0
49
+
50
+ buffer = new
51
+ 0.step(args.length - 1, 2) do |index|
52
+ type = args[index]
53
+ value = args[index + 1]
54
+ if type == :raw
55
+ buffer.append(value.to_s)
56
+ elsif Array === value
57
+ buffer.send("write_#{type}", *value)
58
+ else
59
+ buffer.send("write_#{type}", value)
60
+ end
60
61
  end
62
+
63
+ buffer
61
64
  end
62
65
 
63
- buffer
64
- end
66
+ # exposes the raw content of the buffer
67
+ attr_reader :content
65
68
 
66
- # exposes the raw content of the buffer
67
- attr_reader :content
69
+ # the current position of the pointer in the buffer
70
+ attr_accessor :position
68
71
 
69
- # the current position of the pointer in the buffer
70
- attr_accessor :position
72
+ # Creates a new buffer, initialized to the given content. The position
73
+ # is initialized to the beginning of the buffer.
74
+ def initialize(content="")
75
+ @content = content.to_s
76
+ @position = 0
77
+ end
71
78
 
72
- # Creates a new buffer, initialized to the given content. The position
73
- # is initialized to the beginning of the buffer.
74
- def initialize(content="")
75
- @content = content.to_s
76
- @position = 0
77
- end
79
+ # Returns the length of the buffer's content.
80
+ def length
81
+ @content.length
82
+ end
78
83
 
79
- # Returns the length of the buffer's content.
80
- def length
81
- @content.length
82
- end
84
+ # Returns the number of bytes available to be read (e.g., how many bytes
85
+ # remain between the current position and the end of the buffer).
86
+ def available
87
+ length - position
88
+ end
83
89
 
84
- # Returns the number of bytes available to be read (e.g., how many bytes
85
- # remain between the current position and the end of the buffer).
86
- def available
87
- length - position
88
- end
90
+ # Returns a copy of the buffer's content.
91
+ def to_s
92
+ (@content || "").dup
93
+ end
89
94
 
90
- # Returns a copy of the buffer's content.
91
- def to_s
92
- (@content || "").dup
93
- end
95
+ # Compares the contents of the two buffers, returning +true+ only if they
96
+ # are identical in size and content.
97
+ def ==(buffer)
98
+ to_s == buffer.to_s
99
+ end
94
100
 
95
- # Compares the contents of the two buffers, returning +true+ only if they
96
- # are identical in size and content.
97
- def ==(buffer)
98
- to_s == buffer.to_s
99
- end
101
+ # Returns +true+ if the buffer contains no data (e.g., it is of zero length).
102
+ def empty?
103
+ @content.empty?
104
+ end
100
105
 
101
- # Returns +true+ if the buffer contains no data (e.g., it is of zero length).
102
- def empty?
103
- @content.empty?
104
- end
106
+ # Resets the pointer to the start of the buffer. Subsequent reads will
107
+ # begin at position 0.
108
+ def reset!
109
+ @position = 0
110
+ end
105
111
 
106
- # Resets the pointer to the start of the buffer. Subsequent reads will
107
- # begin at position 0.
108
- def reset!
109
- @position = 0
110
- end
112
+ # Returns true if the pointer is at the end of the buffer. Subsequent
113
+ # reads will return nil, in this case.
114
+ def eof?
115
+ @position >= length
116
+ end
111
117
 
112
- # Returns true if the pointer is at the end of the buffer. Subsequent
113
- # reads will return nil, in this case.
114
- def eof?
115
- @position >= length
116
- end
118
+ # Resets the buffer, making it empty. Also, resets the read position to
119
+ # 0.
120
+ def clear!
121
+ @content = ""
122
+ @position = 0
123
+ end
117
124
 
118
- # Resets the buffer, making it empty. Also, resets the read position to
119
- # 0.
120
- def clear!
121
- @content = ""
122
- @position = 0
123
- end
125
+ # Consumes n bytes from the buffer, where n is the current position
126
+ # unless otherwise specified. This is useful for removing data from the
127
+ # buffer that has previously been read, when you are expecting more data
128
+ # to be appended. It helps to keep the size of buffers down when they
129
+ # would otherwise tend to grow without bound.
130
+ #
131
+ # Returns the buffer object itself.
132
+ def consume!(n=position)
133
+ if n >= length
134
+ # optimize for a fairly common case
135
+ clear!
136
+ elsif n > 0
137
+ @content = @content[n..-1] || ""
138
+ @position -= n
139
+ @position = 0 if @position < 0
140
+ end
141
+ self
142
+ end
124
143
 
125
- # Consumes n bytes from the buffer, where n is the current position
126
- # unless otherwise specified. This is useful for removing data from the
127
- # buffer that has previously been read, when you are expecting more data
128
- # to be appended. It helps to keep the size of buffers down when they
129
- # would otherwise tend to grow without bound.
130
- #
131
- # Returns the buffer object itself.
132
- def consume!(n=position)
133
- if n >= length
134
- # optimize for a fairly common case
135
- clear!
136
- elsif n > 0
137
- @content = @content[n..-1] || ""
138
- @position -= n
139
- @position = 0 if @position < 0
140
- end
141
- self
142
- end
144
+ # Appends the given text to the end of the buffer. Does not alter the
145
+ # read position. Returns the buffer object itself.
146
+ def append(text)
147
+ @content << text
148
+ self
149
+ end
143
150
 
144
- # Appends the given text to the end of the buffer. Does not alter the
145
- # read position. Returns the buffer object itself.
146
- def append(text)
147
- @content << text
148
- self
149
- end
151
+ # Returns all text from the current pointer to the end of the buffer as
152
+ # a new Net::SSH::Buffer object.
153
+ def remainder_as_buffer
154
+ Buffer.new(@content[@position..-1])
155
+ end
150
156
 
151
- # Returns all text from the current pointer to the end of the buffer as
152
- # a new Net::SSH::Buffer object.
153
- def remainder_as_buffer
154
- Buffer.new(@content[@position..-1])
155
- end
157
+ # Reads all data up to and including the given pattern, which may be a
158
+ # String, Fixnum, or Regexp and is interpreted exactly as String#index
159
+ # does. Returns nil if nothing matches. Increments the position to point
160
+ # immediately after the pattern, if it does match. Returns all data up to
161
+ # and including the text that matched the pattern.
162
+ def read_to(pattern)
163
+ index = @content.index(pattern, @position) or return nil
164
+ length = case pattern
165
+ when String then pattern.length
166
+ when Integer then 1
167
+ when Regexp then $&.length
168
+ end
169
+ index && read(index + length)
170
+ end
156
171
 
157
- # Reads all data up to and including the given pattern, which may be a
158
- # String, Fixnum, or Regexp and is interpreted exactly as String#index
159
- # does. Returns nil if nothing matches. Increments the position to point
160
- # immediately after the pattern, if it does match. Returns all data up to
161
- # and including the text that matched the pattern.
162
- def read_to(pattern)
163
- index = @content.index(pattern, @position) or return nil
164
- length = case pattern
165
- when String then pattern.length
166
- when Integer then 1
167
- when Regexp then $&.length
168
- end
169
- index && read(index+length)
170
- end
172
+ # Reads and returns the next +count+ bytes from the buffer, starting from
173
+ # the read position. If +count+ is +nil+, this will return all remaining
174
+ # text in the buffer. This method will increment the pointer.
175
+ def read(count=nil)
176
+ count ||= length
177
+ count = length - @position if @position + count > length
178
+ @position += count
179
+ @content[@position - count, count]
180
+ end
171
181
 
172
- # Reads and returns the next +count+ bytes from the buffer, starting from
173
- # the read position. If +count+ is +nil+, this will return all remaining
174
- # text in the buffer. This method will increment the pointer.
175
- def read(count=nil)
176
- count ||= length
177
- count = length - @position if @position + count > length
178
- @position += count
179
- @content[@position-count, count]
180
- end
182
+ # Reads (as #read) and returns the given number of bytes from the buffer,
183
+ # and then consumes (as #consume!) all data up to the new read position.
184
+ def read!(count=nil)
185
+ data = read(count)
186
+ consume!
187
+ data
188
+ end
181
189
 
182
- # Reads (as #read) and returns the given number of bytes from the buffer,
183
- # and then consumes (as #consume!) all data up to the new read position.
184
- def read!(count=nil)
185
- data = read(count)
186
- consume!
187
- data
188
- end
190
+ # Calls block(self) until the buffer is empty, and returns all results.
191
+ def read_all(&block)
192
+ Enumerator.new { |e| e << yield(self) until eof? }.to_a
193
+ end
189
194
 
190
- # Calls block(self) until the buffer is empty, and returns all results.
191
- def read_all(&block)
192
- Enumerator.new { |e| e << yield(self) until eof? }.to_a
193
- end
195
+ # Return the next 8 bytes as a 64-bit integer (in network byte order).
196
+ # Returns nil if there are less than 8 bytes remaining to be read in the
197
+ # buffer.
198
+ def read_int64
199
+ hi = read_long or return nil
200
+ lo = read_long or return nil
201
+ return (hi << 32) + lo
202
+ end
194
203
 
195
- # Return the next 8 bytes as a 64-bit integer (in network byte order).
196
- # Returns nil if there are less than 8 bytes remaining to be read in the
197
- # buffer.
198
- def read_int64
199
- hi = read_long or return nil
200
- lo = read_long or return nil
201
- return (hi << 32) + lo
202
- end
204
+ # Return the next four bytes as a long integer (in network byte order).
205
+ # Returns nil if there are less than 4 bytes remaining to be read in the
206
+ # buffer.
207
+ def read_long
208
+ b = read(4) or return nil
209
+ b.unpack("N").first
210
+ end
203
211
 
204
- # Return the next four bytes as a long integer (in network byte order).
205
- # Returns nil if there are less than 4 bytes remaining to be read in the
206
- # buffer.
207
- def read_long
208
- b = read(4) or return nil
209
- b.unpack("N").first
210
- end
212
+ # Read and return the next byte in the buffer. Returns nil if called at
213
+ # the end of the buffer.
214
+ def read_byte
215
+ b = read(1) or return nil
216
+ b.getbyte(0)
217
+ end
211
218
 
212
- # Read and return the next byte in the buffer. Returns nil if called at
213
- # the end of the buffer.
214
- def read_byte
215
- b = read(1) or return nil
216
- b.getbyte(0)
217
- end
219
+ # Read and return an SSH2-encoded string. The string starts with a long
220
+ # integer that describes the number of bytes remaining in the string.
221
+ # Returns nil if there are not enough bytes to satisfy the request.
222
+ def read_string
223
+ length = read_long or return nil
224
+ read(length)
225
+ end
218
226
 
219
- # Read and return an SSH2-encoded string. The string starts with a long
220
- # integer that describes the number of bytes remaining in the string.
221
- # Returns nil if there are not enough bytes to satisfy the request.
222
- def read_string
223
- length = read_long or return nil
224
- read(length)
225
- end
227
+ # Read a single byte and convert it into a boolean, using 'C' rules
228
+ # (i.e., zero is false, non-zero is true).
229
+ def read_bool
230
+ b = read_byte or return nil
231
+ b != 0
232
+ end
226
233
 
227
- # Read a single byte and convert it into a boolean, using 'C' rules
228
- # (i.e., zero is false, non-zero is true).
229
- def read_bool
230
- b = read_byte or return nil
231
- b != 0
232
- end
234
+ # Read a bignum (OpenSSL::BN) from the buffer, in SSH2 format. It is
235
+ # essentially just a string, which is reinterpreted to be a bignum in
236
+ # binary format.
237
+ def read_bignum
238
+ data = read_string
239
+ return unless data
240
+ OpenSSL::BN.new(data, 2)
241
+ end
233
242
 
234
- # Read a bignum (OpenSSL::BN) from the buffer, in SSH2 format. It is
235
- # essentially just a string, which is reinterpreted to be a bignum in
236
- # binary format.
237
- def read_bignum
238
- data = read_string
239
- return unless data
240
- OpenSSL::BN.new(data, 2)
241
- end
243
+ # Read a key from the buffer. The key will start with a string
244
+ # describing its type. The remainder of the key is defined by the
245
+ # type that was read.
246
+ def read_key
247
+ type = read_string
248
+ return (type ? read_keyblob(type) : nil)
249
+ end
242
250
 
243
- # Read a key from the buffer. The key will start with a string
244
- # describing its type. The remainder of the key is defined by the
245
- # type that was read.
246
- def read_key
247
- type = read_string
248
- return (type ? read_keyblob(type) : nil)
249
- end
251
+ def read_private_keyblob(type)
252
+ case type
253
+ when /^ssh-rsa$/
254
+ key = OpenSSL::PKey::RSA.new
255
+ n = read_bignum
256
+ e = read_bignum
257
+ d = read_bignum
258
+ iqmp = read_bignum
259
+ p = read_bignum
260
+ q = read_bignum
261
+ _unkown1 = read_bignum
262
+ _unkown2 = read_bignum
263
+ dmp1 = d % (p - 1)
264
+ dmq1 = d % (q - 1)
265
+ if key.respond_to?(:set_key)
266
+ key.set_key(n, e, d)
267
+ else
268
+ key.e = e
269
+ key.n = n
270
+ key.d = d
271
+ end
272
+ if key.respond_to?(:set_factors)
273
+ key.set_factors(p, q)
274
+ else
275
+ key.p = p
276
+ key.q = q
277
+ end
278
+ if key.respond_to?(:set_crt_params)
279
+ key.set_crt_params(dmp1, dmq1, iqmp)
280
+ else
281
+ key.dmp1 = dmp1
282
+ key.dmq1 = dmq1
283
+ key.iqmp = iqmp
284
+ end
285
+ key
286
+ else
287
+ raise Exception, "Cannot decode private key of type #{type}"
288
+ end
289
+ end
250
290
 
251
- # Read a keyblob of the given type from the buffer, and return it as
252
- # a key. Only RSA, DSA, and ECDSA keys are supported.
253
- def read_keyblob(type)
254
- case type
291
+ # Read a keyblob of the given type from the buffer, and return it as
292
+ # a key. Only RSA, DSA, and ECDSA keys are supported.
293
+ def read_keyblob(type)
294
+ case type
255
295
  when /^(.*)-cert-v01@openssh\.com$/
256
296
  key = Net::SSH::Authentication::Certificate.read_certblob(self, $1)
257
297
  when /^ssh-dss$/
@@ -282,115 +322,108 @@ module Net; module SSH
282
322
  Net::SSH::Authentication::ED25519Loader.raiseUnlessLoaded("unsupported key type `#{type}'")
283
323
  key = Net::SSH::Authentication::ED25519::PubKey.read_keyblob(self)
284
324
  when /^ecdsa\-sha2\-(\w*)$/
285
- unless defined?(OpenSSL::PKey::EC)
286
- raise NotImplementedError, "unsupported key type `#{type}'"
287
- else
288
- begin
289
- key = OpenSSL::PKey::EC.read_keyblob($1, self)
290
- rescue OpenSSL::PKey::ECError
291
- raise NotImplementedError, "unsupported key type `#{type}'"
292
- end
293
- end
325
+ key = OpenSSL::PKey::EC.read_keyblob($1, self)
294
326
  else
295
327
  raise NotImplementedError, "unsupported key type `#{type}'"
296
- end
328
+ end
297
329
 
298
- return key
299
- end
330
+ return key
331
+ end
300
332
 
301
- # Reads the next string from the buffer, and returns a new Buffer
302
- # object that wraps it.
303
- def read_buffer
304
- Buffer.new(read_string)
305
- end
333
+ # Reads the next string from the buffer, and returns a new Buffer
334
+ # object that wraps it.
335
+ def read_buffer
336
+ Buffer.new(read_string)
337
+ end
306
338
 
307
- # Writes the given data literally into the string. Does not alter the
308
- # read position. Returns the buffer object.
309
- def write(*data)
310
- data.each { |datum| @content << datum.dup.force_encoding('BINARY') }
311
- self
312
- end
339
+ # Writes the given data literally into the string. Does not alter the
340
+ # read position. Returns the buffer object.
341
+ def write(*data)
342
+ data.each { |datum| @content << datum.dup.force_encoding('BINARY') }
343
+ self
344
+ end
313
345
 
314
- # Optimized version of write where the caller gives up ownership of string
315
- # to the method. This way we can mutate the string.
316
- def write_moved(string)
317
- @content << string.force_encoding('BINARY')
318
- self
319
- end
346
+ # Optimized version of write where the caller gives up ownership of string
347
+ # to the method. This way we can mutate the string.
348
+ def write_moved(string)
349
+ @content << string.force_encoding('BINARY')
350
+ self
351
+ end
320
352
 
321
- # Writes each argument to the buffer as a network-byte-order-encoded
322
- # 64-bit integer (8 bytes). Does not alter the read position. Returns the
323
- # buffer object.
324
- def write_int64(*n)
325
- n.each do |i|
326
- hi = (i >> 32) & 0xFFFFFFFF
327
- lo = i & 0xFFFFFFFF
328
- @content << [hi, lo].pack("N2")
353
+ # Writes each argument to the buffer as a network-byte-order-encoded
354
+ # 64-bit integer (8 bytes). Does not alter the read position. Returns the
355
+ # buffer object.
356
+ def write_int64(*n)
357
+ n.each do |i|
358
+ hi = (i >> 32) & 0xFFFFFFFF
359
+ lo = i & 0xFFFFFFFF
360
+ @content << [hi, lo].pack("N2")
361
+ end
362
+ self
329
363
  end
330
- self
331
- end
332
364
 
333
- # Writes each argument to the buffer as a network-byte-order-encoded
334
- # long (4-byte) integer. Does not alter the read position. Returns the
335
- # buffer object.
336
- def write_long(*n)
337
- @content << n.pack("N*")
338
- self
339
- end
365
+ # Writes each argument to the buffer as a network-byte-order-encoded
366
+ # long (4-byte) integer. Does not alter the read position. Returns the
367
+ # buffer object.
368
+ def write_long(*n)
369
+ @content << n.pack("N*")
370
+ self
371
+ end
340
372
 
341
- # Writes each argument to the buffer as a byte. Does not alter the read
342
- # position. Returns the buffer object.
343
- def write_byte(*n)
344
- n.each { |b| @content << b.chr }
345
- self
346
- end
373
+ # Writes each argument to the buffer as a byte. Does not alter the read
374
+ # position. Returns the buffer object.
375
+ def write_byte(*n)
376
+ n.each { |b| @content << b.chr }
377
+ self
378
+ end
347
379
 
348
- # Writes each argument to the buffer as an SSH2-encoded string. Each
349
- # string is prefixed by its length, encoded as a 4-byte long integer.
350
- # Does not alter the read position. Returns the buffer object.
351
- def write_string(*text)
352
- text.each do |string|
353
- s = string.to_s
354
- write_long(s.bytesize)
355
- write(s)
380
+ # Writes each argument to the buffer as an SSH2-encoded string. Each
381
+ # string is prefixed by its length, encoded as a 4-byte long integer.
382
+ # Does not alter the read position. Returns the buffer object.
383
+ def write_string(*text)
384
+ text.each do |string|
385
+ s = string.to_s
386
+ write_long(s.bytesize)
387
+ write(s)
388
+ end
389
+ self
356
390
  end
357
- self
358
- end
359
391
 
360
- # Writes each argument to the buffer as an SSH2-encoded string. Each
361
- # string is prefixed by its length, encoded as a 4-byte long integer.
362
- # Does not alter the read position. Returns the buffer object.
363
- # Might alter arguments see write_moved
364
- def write_mstring(*text)
365
- text.each do |string|
366
- s = string.to_s
367
- write_long(s.bytesize)
368
- write_moved(s)
369
- end
370
- self
371
- end
392
+ # Writes each argument to the buffer as an SSH2-encoded string. Each
393
+ # string is prefixed by its length, encoded as a 4-byte long integer.
394
+ # Does not alter the read position. Returns the buffer object.
395
+ # Might alter arguments see write_moved
396
+ def write_mstring(*text)
397
+ text.each do |string|
398
+ s = string.to_s
399
+ write_long(s.bytesize)
400
+ write_moved(s)
401
+ end
402
+ self
403
+ end
372
404
 
373
- # Writes each argument to the buffer as a (C-style) boolean, with 1
374
- # meaning true, and 0 meaning false. Does not alter the read position.
375
- # Returns the buffer object.
376
- def write_bool(*b)
377
- b.each { |v| @content << (v ? "\1" : "\0") }
378
- self
379
- end
405
+ # Writes each argument to the buffer as a (C-style) boolean, with 1
406
+ # meaning true, and 0 meaning false. Does not alter the read position.
407
+ # Returns the buffer object.
408
+ def write_bool(*b)
409
+ b.each { |v| @content << (v ? "\1" : "\0") }
410
+ self
411
+ end
380
412
 
381
- # Writes each argument to the buffer as a bignum (SSH2-style). No
382
- # checking is done to ensure that the arguments are, in fact, bignums.
383
- # Does not alter the read position. Returns the buffer object.
384
- def write_bignum(*n)
385
- @content << n.map { |b| b.to_ssh }.join
386
- self
387
- end
413
+ # Writes each argument to the buffer as a bignum (SSH2-style). No
414
+ # checking is done to ensure that the arguments are, in fact, bignums.
415
+ # Does not alter the read position. Returns the buffer object.
416
+ def write_bignum(*n)
417
+ @content << n.map { |b| b.to_ssh }.join
418
+ self
419
+ end
388
420
 
389
- # Writes the given arguments to the buffer as SSH2-encoded keys. Does not
390
- # alter the read position. Returns the buffer object.
391
- def write_key(*key)
392
- key.each { |k| append(k.to_blob) }
393
- self
421
+ # Writes the given arguments to the buffer as SSH2-encoded keys. Does not
422
+ # alter the read position. Returns the buffer object.
423
+ def write_key(*key)
424
+ key.each { |k| append(k.to_blob) }
425
+ self
426
+ end
394
427
  end
395
428
  end
396
- end; end;
429
+ end;