net-ssh 2.7.0 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/.dockerignore +6 -0
  4. data/.github/FUNDING.yml +1 -0
  5. data/.github/config/rubocop_linter_action.yml +4 -0
  6. data/.github/workflows/ci-with-docker.yml +44 -0
  7. data/.github/workflows/ci.yml +94 -0
  8. data/.github/workflows/rubocop.yml +16 -0
  9. data/.gitignore +15 -0
  10. data/.rubocop.yml +22 -0
  11. data/.rubocop_todo.yml +1081 -0
  12. data/CHANGES.txt +387 -0
  13. data/DEVELOPMENT.md +23 -0
  14. data/Dockerfile +29 -0
  15. data/Dockerfile.openssl3 +17 -0
  16. data/Gemfile +13 -0
  17. data/Gemfile.noed25519 +12 -0
  18. data/Gemfile.norbnacl +12 -0
  19. data/ISSUE_TEMPLATE.md +30 -0
  20. data/Manifest +4 -5
  21. data/README.md +303 -0
  22. data/Rakefile +174 -40
  23. data/SECURITY.md +4 -0
  24. data/THANKS.txt +25 -0
  25. data/appveyor.yml +58 -0
  26. data/docker-compose.yml +25 -0
  27. data/lib/net/ssh/authentication/agent.rb +279 -18
  28. data/lib/net/ssh/authentication/certificate.rb +183 -0
  29. data/lib/net/ssh/authentication/constants.rb +17 -15
  30. data/lib/net/ssh/authentication/ed25519.rb +184 -0
  31. data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
  32. data/lib/net/ssh/authentication/key_manager.rb +125 -54
  33. data/lib/net/ssh/authentication/methods/abstract.rb +67 -48
  34. data/lib/net/ssh/authentication/methods/hostbased.rb +34 -37
  35. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +19 -12
  36. data/lib/net/ssh/authentication/methods/none.rb +16 -19
  37. data/lib/net/ssh/authentication/methods/password.rb +56 -19
  38. data/lib/net/ssh/authentication/methods/publickey.rb +96 -55
  39. data/lib/net/ssh/authentication/pageant.rb +483 -246
  40. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  41. data/lib/net/ssh/authentication/session.rb +138 -120
  42. data/lib/net/ssh/buffer.rb +399 -300
  43. data/lib/net/ssh/buffered_io.rb +154 -150
  44. data/lib/net/ssh/config.rb +361 -166
  45. data/lib/net/ssh/connection/channel.rb +640 -596
  46. data/lib/net/ssh/connection/constants.rb +29 -29
  47. data/lib/net/ssh/connection/event_loop.rb +123 -0
  48. data/lib/net/ssh/connection/keepalive.rb +59 -0
  49. data/lib/net/ssh/connection/session.rb +628 -548
  50. data/lib/net/ssh/connection/term.rb +125 -123
  51. data/lib/net/ssh/errors.rb +101 -95
  52. data/lib/net/ssh/key_factory.rb +198 -100
  53. data/lib/net/ssh/known_hosts.rb +221 -98
  54. data/lib/net/ssh/loggable.rb +50 -49
  55. data/lib/net/ssh/packet.rb +83 -79
  56. data/lib/net/ssh/prompt.rb +50 -81
  57. data/lib/net/ssh/proxy/command.rb +108 -60
  58. data/lib/net/ssh/proxy/errors.rb +12 -10
  59. data/lib/net/ssh/proxy/http.rb +82 -78
  60. data/lib/net/ssh/proxy/https.rb +50 -0
  61. data/lib/net/ssh/proxy/jump.rb +54 -0
  62. data/lib/net/ssh/proxy/socks4.rb +5 -8
  63. data/lib/net/ssh/proxy/socks5.rb +18 -20
  64. data/lib/net/ssh/service/forward.rb +383 -255
  65. data/lib/net/ssh/test/channel.rb +145 -136
  66. data/lib/net/ssh/test/extensions.rb +131 -110
  67. data/lib/net/ssh/test/kex.rb +34 -32
  68. data/lib/net/ssh/test/local_packet.rb +46 -44
  69. data/lib/net/ssh/test/packet.rb +89 -70
  70. data/lib/net/ssh/test/remote_packet.rb +32 -30
  71. data/lib/net/ssh/test/script.rb +156 -142
  72. data/lib/net/ssh/test/socket.rb +49 -48
  73. data/lib/net/ssh/test.rb +82 -77
  74. data/lib/net/ssh/transport/aes128_gcm.rb +40 -0
  75. data/lib/net/ssh/transport/aes256_gcm.rb +40 -0
  76. data/lib/net/ssh/transport/algorithms.rb +472 -348
  77. data/lib/net/ssh/transport/chacha20_poly1305_cipher.rb +117 -0
  78. data/lib/net/ssh/transport/chacha20_poly1305_cipher_loader.rb +17 -0
  79. data/lib/net/ssh/transport/cipher_factory.rb +124 -100
  80. data/lib/net/ssh/transport/constants.rb +32 -24
  81. data/lib/net/ssh/transport/ctr.rb +42 -22
  82. data/lib/net/ssh/transport/gcm_cipher.rb +207 -0
  83. data/lib/net/ssh/transport/hmac/abstract.rb +97 -63
  84. data/lib/net/ssh/transport/hmac/md5.rb +0 -2
  85. data/lib/net/ssh/transport/hmac/md5_96.rb +0 -2
  86. data/lib/net/ssh/transport/hmac/none.rb +0 -2
  87. data/lib/net/ssh/transport/hmac/ripemd160.rb +0 -2
  88. data/lib/net/ssh/transport/hmac/sha1.rb +0 -2
  89. data/lib/net/ssh/transport/hmac/sha1_96.rb +0 -2
  90. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  91. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  92. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  93. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  94. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  95. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  96. data/lib/net/ssh/transport/hmac.rb +14 -12
  97. data/lib/net/ssh/transport/identity_cipher.rb +54 -44
  98. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  99. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  100. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  101. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  102. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
  103. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  104. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +119 -213
  105. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -61
  106. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  107. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
  108. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
  109. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
  110. data/lib/net/ssh/transport/kex.rb +15 -12
  111. data/lib/net/ssh/transport/key_expander.rb +24 -20
  112. data/lib/net/ssh/transport/openssl.rb +161 -124
  113. data/lib/net/ssh/transport/openssl_cipher_extensions.rb +8 -0
  114. data/lib/net/ssh/transport/packet_stream.rb +246 -183
  115. data/lib/net/ssh/transport/server_version.rb +57 -51
  116. data/lib/net/ssh/transport/session.rb +307 -235
  117. data/lib/net/ssh/transport/state.rb +178 -176
  118. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  119. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  120. data/lib/net/ssh/verifiers/always.rb +58 -0
  121. data/lib/net/ssh/verifiers/never.rb +19 -0
  122. data/lib/net/ssh/version.rb +57 -51
  123. data/lib/net/ssh.rb +140 -40
  124. data/net-ssh-public_cert.pem +21 -0
  125. data/net-ssh.gemspec +39 -184
  126. data/support/ssh_tunnel_bug.rb +5 -5
  127. data.tar.gz.sig +0 -0
  128. metadata +205 -99
  129. metadata.gz.sig +0 -0
  130. data/README.rdoc +0 -219
  131. data/Rudyfile +0 -96
  132. data/gem-public_cert.pem +0 -20
  133. data/lib/net/ssh/authentication/agent/java_pageant.rb +0 -85
  134. data/lib/net/ssh/authentication/agent/socket.rb +0 -170
  135. data/lib/net/ssh/ruby_compat.rb +0 -51
  136. data/lib/net/ssh/verifiers/lenient.rb +0 -30
  137. data/lib/net/ssh/verifiers/null.rb +0 -12
  138. data/lib/net/ssh/verifiers/secure.rb +0 -54
  139. data/lib/net/ssh/verifiers/strict.rb +0 -24
  140. data/setup.rb +0 -1585
  141. data/support/arcfour_check.rb +0 -20
  142. data/test/README.txt +0 -47
  143. data/test/authentication/methods/common.rb +0 -28
  144. data/test/authentication/methods/test_abstract.rb +0 -51
  145. data/test/authentication/methods/test_hostbased.rb +0 -114
  146. data/test/authentication/methods/test_keyboard_interactive.rb +0 -100
  147. data/test/authentication/methods/test_none.rb +0 -41
  148. data/test/authentication/methods/test_password.rb +0 -52
  149. data/test/authentication/methods/test_publickey.rb +0 -148
  150. data/test/authentication/test_agent.rb +0 -205
  151. data/test/authentication/test_key_manager.rb +0 -218
  152. data/test/authentication/test_session.rb +0 -108
  153. data/test/common.rb +0 -108
  154. data/test/configs/eqsign +0 -3
  155. data/test/configs/exact_match +0 -8
  156. data/test/configs/host_plus +0 -10
  157. data/test/configs/multihost +0 -4
  158. data/test/configs/nohost +0 -19
  159. data/test/configs/numeric_host +0 -4
  160. data/test/configs/send_env +0 -2
  161. data/test/configs/substitutes +0 -8
  162. data/test/configs/wild_cards +0 -14
  163. data/test/connection/test_channel.rb +0 -467
  164. data/test/connection/test_session.rb +0 -526
  165. data/test/known_hosts/github +0 -1
  166. data/test/manual/test_forward.rb +0 -223
  167. data/test/start/test_options.rb +0 -36
  168. data/test/start/test_transport.rb +0 -28
  169. data/test/test_all.rb +0 -11
  170. data/test/test_buffer.rb +0 -433
  171. data/test/test_buffered_io.rb +0 -63
  172. data/test/test_config.rb +0 -151
  173. data/test/test_key_factory.rb +0 -173
  174. data/test/test_known_hosts.rb +0 -13
  175. data/test/transport/hmac/test_md5.rb +0 -41
  176. data/test/transport/hmac/test_md5_96.rb +0 -27
  177. data/test/transport/hmac/test_none.rb +0 -34
  178. data/test/transport/hmac/test_ripemd160.rb +0 -36
  179. data/test/transport/hmac/test_sha1.rb +0 -36
  180. data/test/transport/hmac/test_sha1_96.rb +0 -27
  181. data/test/transport/hmac/test_sha2_256.rb +0 -37
  182. data/test/transport/hmac/test_sha2_256_96.rb +0 -27
  183. data/test/transport/hmac/test_sha2_512.rb +0 -37
  184. data/test/transport/hmac/test_sha2_512_96.rb +0 -27
  185. data/test/transport/kex/test_diffie_hellman_group14_sha1.rb +0 -13
  186. data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +0 -146
  187. data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +0 -92
  188. data/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb +0 -34
  189. data/test/transport/kex/test_ecdh_sha2_nistp256.rb +0 -161
  190. data/test/transport/kex/test_ecdh_sha2_nistp384.rb +0 -38
  191. data/test/transport/kex/test_ecdh_sha2_nistp521.rb +0 -38
  192. data/test/transport/test_algorithms.rb +0 -330
  193. data/test/transport/test_cipher_factory.rb +0 -443
  194. data/test/transport/test_hmac.rb +0 -34
  195. data/test/transport/test_identity_cipher.rb +0 -40
  196. data/test/transport/test_packet_stream.rb +0 -1755
  197. data/test/transport/test_server_version.rb +0 -78
  198. data/test/transport/test_session.rb +0 -319
  199. data/test/transport/test_state.rb +0 -181
@@ -2,22 +2,283 @@ require 'net/ssh/buffer'
2
2
  require 'net/ssh/errors'
3
3
  require 'net/ssh/loggable'
4
4
 
5
- module Net; module SSH; module Authentication
6
- PLATFORM = File::ALT_SEPARATOR \
7
- ? RUBY_PLATFORM =~ /java/ ? :java_win32 : :win32 \
8
- : RUBY_PLATFORM =~ /java/ ? :java : :unix
9
-
10
- # A trivial exception class for representing agent-specific errors.
11
- class AgentError < Net::SSH::Exception; end
12
-
13
- # An exception for indicating that the SSH agent is not available.
14
- class AgentNotAvailable < AgentError; end
15
- end; end; end
16
-
17
- case Net::SSH::Authentication::PLATFORM
18
- when :java_win32
19
- # Java pageant requires whole different agent.
20
- require 'net/ssh/authentication/agent/java_pageant'
21
- else
22
- require 'net/ssh/authentication/agent/socket'
5
+ require 'net/ssh/transport/server_version'
6
+ require 'socket'
7
+ require 'rubygems'
8
+
9
+ require 'net/ssh/authentication/pageant' if Gem.win_platform? && RUBY_PLATFORM != "java"
10
+
11
+ module Net
12
+ module SSH
13
+ module Authentication
14
+ # Class for representing agent-specific errors.
15
+ class AgentError < Net::SSH::Exception; end
16
+
17
+ # An exception for indicating that the SSH agent is not available.
18
+ class AgentNotAvailable < AgentError; end
19
+
20
+ # This class implements a simple client for the ssh-agent protocol. It
21
+ # does not implement any specific protocol, but instead copies the
22
+ # behavior of the ssh-agent functions in the OpenSSH library (3.8).
23
+ #
24
+ # This means that although it behaves like a SSH1 client, it also has
25
+ # some SSH2 functionality (like signing data).
26
+ class Agent
27
+ include Loggable
28
+
29
+ # A simple module for extending keys, to allow comments to be specified
30
+ # for them.
31
+ module Comment
32
+ attr_accessor :comment
33
+ end
34
+
35
+ SSH2_AGENT_REQUEST_VERSION = 1
36
+ SSH2_AGENT_REQUEST_IDENTITIES = 11
37
+ SSH2_AGENT_IDENTITIES_ANSWER = 12
38
+ SSH2_AGENT_SIGN_REQUEST = 13
39
+ SSH2_AGENT_SIGN_RESPONSE = 14
40
+ SSH2_AGENT_ADD_IDENTITY = 17
41
+ SSH2_AGENT_REMOVE_IDENTITY = 18
42
+ SSH2_AGENT_REMOVE_ALL_IDENTITIES = 19
43
+ SSH2_AGENT_LOCK = 22
44
+ SSH2_AGENT_UNLOCK = 23
45
+ SSH2_AGENT_ADD_ID_CONSTRAINED = 25
46
+ SSH2_AGENT_FAILURE = 30
47
+ SSH2_AGENT_VERSION_RESPONSE = 103
48
+
49
+ SSH_COM_AGENT2_FAILURE = 102
50
+
51
+ SSH_AGENT_REQUEST_RSA_IDENTITIES = 1
52
+ SSH_AGENT_RSA_IDENTITIES_ANSWER1 = 2
53
+ SSH_AGENT_RSA_IDENTITIES_ANSWER2 = 5
54
+ SSH_AGENT_FAILURE = 5
55
+ SSH_AGENT_SUCCESS = 6
56
+
57
+ SSH_AGENT_CONSTRAIN_LIFETIME = 1
58
+ SSH_AGENT_CONSTRAIN_CONFIRM = 2
59
+
60
+ SSH_AGENT_RSA_SHA2_256 = 0x02
61
+ SSH_AGENT_RSA_SHA2_512 = 0x04
62
+
63
+ # The underlying socket being used to communicate with the SSH agent.
64
+ attr_reader :socket
65
+
66
+ # Instantiates a new agent object, connects to a running SSH agent,
67
+ # negotiates the agent protocol version, and returns the agent object.
68
+ def self.connect(logger = nil, agent_socket_factory = nil, identity_agent = nil)
69
+ agent = new(logger)
70
+ agent.connect!(agent_socket_factory, identity_agent)
71
+ agent.negotiate!
72
+ agent
73
+ end
74
+
75
+ # Creates a new Agent object, using the optional logger instance to
76
+ # report status.
77
+ def initialize(logger = nil)
78
+ self.logger = logger
79
+ end
80
+
81
+ # Connect to the agent process using the socket factory and socket name
82
+ # given by the attribute writers. If the agent on the other end of the
83
+ # socket reports that it is an SSH2-compatible agent, this will fail
84
+ # (it only supports the ssh-agent distributed by OpenSSH).
85
+ def connect!(agent_socket_factory = nil, identity_agent = nil)
86
+ debug { "connecting to ssh-agent" }
87
+ @socket =
88
+ if agent_socket_factory
89
+ agent_socket_factory.call
90
+ elsif identity_agent
91
+ unix_socket_class.open(File.expand_path(identity_agent))
92
+ elsif ENV['SSH_AUTH_SOCK'] && unix_socket_class
93
+ unix_socket_class.open(File.expand_path(ENV['SSH_AUTH_SOCK']))
94
+ elsif Gem.win_platform? && RUBY_ENGINE != "jruby"
95
+ Pageant::Socket.open
96
+ else
97
+ raise AgentNotAvailable, "Agent not configured"
98
+ end
99
+ rescue StandardError => e
100
+ error { "could not connect to ssh-agent: #{e.message}" }
101
+ raise AgentNotAvailable, $!.message
102
+ end
103
+
104
+ # Attempts to negotiate the SSH agent protocol version. Raises an error
105
+ # if the version could not be negotiated successfully.
106
+ def negotiate!
107
+ # determine what type of agent we're communicating with
108
+ type, body = send_and_wait(SSH2_AGENT_REQUEST_VERSION, :string, Transport::ServerVersion::PROTO_VERSION)
109
+
110
+ raise AgentNotAvailable, "SSH2 agents are not yet supported" if type == SSH2_AGENT_VERSION_RESPONSE
111
+
112
+ if type == SSH2_AGENT_FAILURE
113
+ debug { "Unexpected response type==#{type}, this will be ignored" }
114
+ elsif type != SSH_AGENT_RSA_IDENTITIES_ANSWER1 && type != SSH_AGENT_RSA_IDENTITIES_ANSWER2
115
+ raise AgentNotAvailable, "unknown response from agent: #{type}, #{body.to_s.inspect}"
116
+ end
117
+ end
118
+
119
+ # Return an array of all identities (public keys) known to the agent.
120
+ # Each key returned is augmented with a +comment+ property which is set
121
+ # to the comment returned by the agent for that key.
122
+ def identities
123
+ type, body = send_and_wait(SSH2_AGENT_REQUEST_IDENTITIES)
124
+ raise AgentError, "could not get identity count" if agent_failed(type)
125
+ raise AgentError, "bad authentication reply: #{type}" if type != SSH2_AGENT_IDENTITIES_ANSWER
126
+
127
+ identities = []
128
+ body.read_long.times do
129
+ key_str = body.read_string
130
+ comment_str = body.read_string
131
+ begin
132
+ key = Buffer.new(key_str).read_key
133
+ if key.nil?
134
+ error { "ignoring invalid key: #{comment_str}" }
135
+ next
136
+ end
137
+ key.extend(Comment)
138
+ key.comment = comment_str
139
+ identities.push key
140
+ rescue NotImplementedError => e
141
+ error { "ignoring unimplemented key:#{e.message} #{comment_str}" }
142
+ end
143
+ end
144
+
145
+ return identities
146
+ end
147
+
148
+ # Closes this socket. This agent reference is no longer able to
149
+ # query the agent.
150
+ def close
151
+ @socket.close
152
+ end
153
+
154
+ # Using the agent and the given public key, sign the given data. The
155
+ # signature is returned in SSH2 format.
156
+ def sign(key, data, flags = 0)
157
+ type, reply = send_and_wait(SSH2_AGENT_SIGN_REQUEST, :string, Buffer.from(:key, key), :string, data, :long, flags)
158
+
159
+ raise AgentError, "agent could not sign data with requested identity" if agent_failed(type)
160
+ raise AgentError, "bad authentication response #{type}" if type != SSH2_AGENT_SIGN_RESPONSE
161
+
162
+ return reply.read_string
163
+ end
164
+
165
+ # Adds the private key with comment to the agent.
166
+ # If lifetime is given, the key will automatically be removed after lifetime
167
+ # seconds.
168
+ # If confirm is true, confirmation will be required for each agent signing
169
+ # operation.
170
+ def add_identity(priv_key, comment, lifetime: nil, confirm: false)
171
+ constraints = Buffer.new
172
+ if lifetime
173
+ constraints.write_byte(SSH_AGENT_CONSTRAIN_LIFETIME)
174
+ constraints.write_long(lifetime)
175
+ end
176
+ constraints.write_byte(SSH_AGENT_CONSTRAIN_CONFIRM) if confirm
177
+
178
+ req_type = constraints.empty? ? SSH2_AGENT_ADD_IDENTITY : SSH2_AGENT_ADD_ID_CONSTRAINED
179
+ type, = send_and_wait(req_type, :string, priv_key.ssh_type, :raw, blob_for_add(priv_key),
180
+ :string, comment, :raw, constraints)
181
+ raise AgentError, "could not add identity to agent" if type != SSH_AGENT_SUCCESS
182
+ end
183
+
184
+ # Removes key from the agent.
185
+ def remove_identity(key)
186
+ type, = send_and_wait(SSH2_AGENT_REMOVE_IDENTITY, :string, key.to_blob)
187
+ raise AgentError, "could not remove identity from agent" if type != SSH_AGENT_SUCCESS
188
+ end
189
+
190
+ # Removes all identities from the agent.
191
+ def remove_all_identities
192
+ type, = send_and_wait(SSH2_AGENT_REMOVE_ALL_IDENTITIES)
193
+ raise AgentError, "could not remove all identity from agent" if type != SSH_AGENT_SUCCESS
194
+ end
195
+
196
+ # lock the ssh agent with password
197
+ def lock(password)
198
+ type, = send_and_wait(SSH2_AGENT_LOCK, :string, password)
199
+ raise AgentError, "could not lock agent" if type != SSH_AGENT_SUCCESS
200
+ end
201
+
202
+ # unlock the ssh agent with password
203
+ def unlock(password)
204
+ type, = send_and_wait(SSH2_AGENT_UNLOCK, :string, password)
205
+ raise AgentError, "could not unlock agent" if type != SSH_AGENT_SUCCESS
206
+ end
207
+
208
+ private
209
+
210
+ def unix_socket_class
211
+ defined?(UNIXSocket) && UNIXSocket
212
+ end
213
+
214
+ # Send a new packet of the given type, with the associated data.
215
+ def send_packet(type, *args)
216
+ buffer = Buffer.from(*args)
217
+ data = [buffer.length + 1, type.to_i, buffer.to_s].pack("NCA*")
218
+ debug { "sending agent request #{type} len #{buffer.length}" }
219
+ @socket.send data, 0
220
+ end
221
+
222
+ # Read the next packet from the agent. This will return a two-part
223
+ # tuple consisting of the packet type, and the packet's body (which
224
+ # is returned as a Net::SSH::Buffer).
225
+ def read_packet
226
+ buffer = Net::SSH::Buffer.new(@socket.read(4))
227
+ buffer.append(@socket.read(buffer.read_long))
228
+ type = buffer.read_byte
229
+ debug { "received agent packet #{type} len #{buffer.length - 4}" }
230
+ return type, buffer
231
+ end
232
+
233
+ # Send the given packet and return the subsequent reply from the agent.
234
+ # (See #send_packet and #read_packet).
235
+ def send_and_wait(type, *args)
236
+ send_packet(type, *args)
237
+ read_packet
238
+ end
239
+
240
+ # Returns +true+ if the parameter indicates a "failure" response from
241
+ # the agent, and +false+ otherwise.
242
+ def agent_failed(type)
243
+ type == SSH_AGENT_FAILURE ||
244
+ type == SSH2_AGENT_FAILURE ||
245
+ type == SSH_COM_AGENT2_FAILURE
246
+ end
247
+
248
+ def blob_for_add(priv_key)
249
+ # Ideally we'd have something like `to_private_blob` on the various key types, but the
250
+ # nuances with encoding (e.g. `n` and `e` are reversed for RSA keys) make this impractical.
251
+ case priv_key.ssh_type
252
+ when /^ssh-dss$/
253
+ Net::SSH::Buffer.from(:bignum, priv_key.p, :bignum, priv_key.q, :bignum, priv_key.g,
254
+ :bignum, priv_key.pub_key, :bignum, priv_key.priv_key).to_s
255
+ when /^ssh-dss-cert-v01@openssh\.com$/
256
+ Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.priv_key).to_s
257
+ when /^ecdsa\-sha2\-(\w*)$/
258
+ curve_name = OpenSSL::PKey::EC::CurveNameAliasInv[priv_key.group.curve_name]
259
+ Net::SSH::Buffer.from(:string, curve_name, :mstring, priv_key.public_key.to_bn.to_s(2),
260
+ :bignum, priv_key.private_key).to_s
261
+ when /^ecdsa\-sha2\-(\w*)-cert-v01@openssh\.com$/
262
+ Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.private_key).to_s
263
+ when /^ssh-ed25519$/
264
+ Net::SSH::Buffer.from(:string, priv_key.public_key.verify_key.to_bytes,
265
+ :string, priv_key.sign_key.keypair).to_s
266
+ when /^ssh-ed25519-cert-v01@openssh\.com$/
267
+ # Unlike the other certificate types, the public key is included after the certifiate.
268
+ Net::SSH::Buffer.from(:string, priv_key.to_blob,
269
+ :string, priv_key.key.public_key.verify_key.to_bytes,
270
+ :string, priv_key.key.sign_key.keypair).to_s
271
+ when /^ssh-rsa$/
272
+ # `n` and `e` are reversed compared to the ordering in `OpenSSL::PKey::RSA#to_blob`.
273
+ Net::SSH::Buffer.from(:bignum, priv_key.n, :bignum, priv_key.e, :bignum, priv_key.d,
274
+ :bignum, priv_key.iqmp, :bignum, priv_key.p, :bignum, priv_key.q).to_s
275
+ when /^ssh-rsa-cert-v01@openssh\.com$/
276
+ Net::SSH::Buffer.from(:string, priv_key.to_blob, :bignum, priv_key.key.d,
277
+ :bignum, priv_key.key.iqmp, :bignum, priv_key.key.p,
278
+ :bignum, priv_key.key.q).to_s
279
+ end
280
+ end
281
+ end
282
+ end
283
+ end
23
284
  end
@@ -0,0 +1,183 @@
1
+ require 'securerandom'
2
+
3
+ module Net
4
+ module SSH
5
+ module Authentication
6
+ # Class for representing an SSH certificate.
7
+ #
8
+ # http://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/usr.bin/ssh/PROTOCOL.certkeys?rev=1.10&content-type=text/plain
9
+ class Certificate
10
+ attr_accessor :nonce
11
+ attr_accessor :key
12
+ attr_accessor :serial
13
+ attr_accessor :type
14
+ attr_accessor :key_id
15
+ attr_accessor :valid_principals
16
+ attr_accessor :valid_after
17
+ attr_accessor :valid_before
18
+ attr_accessor :critical_options
19
+ attr_accessor :extensions
20
+ attr_accessor :reserved
21
+ attr_accessor :signature_key
22
+ attr_accessor :signature
23
+
24
+ # Read a certificate blob associated with a key of the given type.
25
+ def self.read_certblob(buffer, type)
26
+ cert = Certificate.new
27
+ cert.nonce = buffer.read_string
28
+ cert.key = buffer.read_keyblob(type)
29
+ cert.serial = buffer.read_int64
30
+ cert.type = type_symbol(buffer.read_long)
31
+ cert.key_id = buffer.read_string
32
+ cert.valid_principals = buffer.read_buffer.read_all(&:read_string)
33
+ cert.valid_after = Time.at(buffer.read_int64)
34
+
35
+ cert.valid_before = if RUBY_PLATFORM == "java"
36
+ # 0x20c49ba5e353f7 = 0x7fffffffffffffff/1000, the largest value possible for JRuby
37
+ # JRuby Time.at multiplies the arg by 1000, and then stores it in a signed long.
38
+ # 0x20c49ba2d52500 = 292278993-01-01 00:00:00 +0000
39
+ # JRuby 9.1 does not accept the year 292278994 because of edge cases (https://github.com/JodaOrg/joda-time/issues/190)
40
+ Time.at([0x20c49ba2d52500, buffer.read_int64].min)
41
+ else
42
+ Time.at(buffer.read_int64)
43
+ end
44
+
45
+ cert.critical_options = read_options(buffer)
46
+ cert.extensions = read_options(buffer)
47
+ cert.reserved = buffer.read_string
48
+ cert.signature_key = buffer.read_buffer.read_key
49
+ cert.signature = buffer.read_string
50
+ cert
51
+ end
52
+
53
+ def ssh_type
54
+ key.ssh_type + "-cert-v01@openssh.com"
55
+ end
56
+
57
+ def ssh_signature_type
58
+ key.ssh_type
59
+ end
60
+
61
+ # Serializes the certificate (and key).
62
+ def to_blob
63
+ Buffer.from(
64
+ :raw, to_blob_without_signature,
65
+ :string, signature
66
+ ).to_s
67
+ end
68
+
69
+ def ssh_do_sign(data, sig_alg = nil)
70
+ key.ssh_do_sign(data, sig_alg)
71
+ end
72
+
73
+ def ssh_do_verify(sig, data, options = {})
74
+ key.ssh_do_verify(sig, data, options)
75
+ end
76
+
77
+ def to_pem
78
+ key.to_pem
79
+ end
80
+
81
+ def fingerprint
82
+ key.fingerprint
83
+ end
84
+
85
+ # Signs the certificate with key.
86
+ def sign!(key, sign_nonce = nil)
87
+ # ssh-keygen uses 32 bytes of nonce.
88
+ self.nonce = sign_nonce || SecureRandom.random_bytes(32)
89
+ self.signature_key = key
90
+ self.signature = Net::SSH::Buffer.from(
91
+ :string, key.ssh_signature_type,
92
+ :mstring, key.ssh_do_sign(to_blob_without_signature)
93
+ ).to_s
94
+ self
95
+ end
96
+
97
+ def sign(key, sign_nonce = nil)
98
+ cert = clone
99
+ cert.sign!(key, sign_nonce)
100
+ end
101
+
102
+ # Checks whether the certificate's signature was signed by signature key.
103
+ def signature_valid?
104
+ buffer = Buffer.new(signature)
105
+ sig_format = buffer.read_string
106
+ signature_key.ssh_do_verify(buffer.read_string, to_blob_without_signature, host_key: sig_format)
107
+ end
108
+
109
+ def self.read_options(buffer)
110
+ names = []
111
+ options = buffer.read_buffer.read_all do |b|
112
+ name = b.read_string
113
+ names << name
114
+ data = b.read_string
115
+ data = Buffer.new(data).read_string unless data.empty?
116
+ [name, data]
117
+ end
118
+
119
+ raise ArgumentError, "option/extension names must be in sorted order" if names.sort != names
120
+
121
+ Hash[options]
122
+ end
123
+ private_class_method :read_options
124
+
125
+ def self.type_symbol(type)
126
+ types = { 1 => :user, 2 => :host }
127
+ raise ArgumentError("unsupported type: #{type}") unless types.include?(type)
128
+
129
+ types.fetch(type)
130
+ end
131
+ private_class_method :type_symbol
132
+
133
+ private
134
+
135
+ def type_value(type)
136
+ types = { user: 1, host: 2 }
137
+ raise ArgumentError("unsupported type: #{type}") unless types.include?(type)
138
+
139
+ types.fetch(type)
140
+ end
141
+
142
+ def ssh_time(t)
143
+ # Times in certificates are represented as a uint64.
144
+ [[t.to_i, 0].max, 2 << 64 - 1].min
145
+ end
146
+
147
+ def to_blob_without_signature
148
+ Buffer.from(
149
+ :string, ssh_type,
150
+ :string, nonce,
151
+ :raw, key_without_type,
152
+ :int64, serial,
153
+ :long, type_value(type),
154
+ :string, key_id,
155
+ :string, valid_principals.inject(Buffer.new) { |acc, elem| acc.write_string(elem) }.to_s,
156
+ :int64, ssh_time(valid_after),
157
+ :int64, ssh_time(valid_before),
158
+ :string, options_to_blob(critical_options),
159
+ :string, options_to_blob(extensions),
160
+ :string, reserved,
161
+ :string, signature_key.to_blob
162
+ ).to_s
163
+ end
164
+
165
+ def key_without_type
166
+ # key.to_blob gives us e.g. "ssh-rsa,<key>" but we just want "<key>".
167
+ tmp = Buffer.new(key.to_blob)
168
+ tmp.read_string # skip the underlying key type
169
+ tmp.read
170
+ end
171
+
172
+ def options_to_blob(options)
173
+ options.keys.sort.inject(Buffer.new) do |b, name|
174
+ b.write_string(name)
175
+ data = options.fetch(name)
176
+ data = Buffer.from(:string, data).to_s unless data.empty?
177
+ b.write_string(data)
178
+ end.to_s
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -1,18 +1,20 @@
1
- module Net; module SSH; module Authentication
1
+ module Net
2
+ module SSH
3
+ module Authentication
4
+ # Describes the constants used by the Net::SSH::Authentication components
5
+ # of the Net::SSH library. Individual authentication method implemenations
6
+ # may define yet more constants that are specific to their implementation.
7
+ module Constants
8
+ USERAUTH_REQUEST = 50
9
+ USERAUTH_FAILURE = 51
10
+ USERAUTH_SUCCESS = 52
11
+ USERAUTH_BANNER = 53
2
12
 
3
- # Describes the constants used by the Net::SSH::Authentication components
4
- # of the Net::SSH library. Individual authentication method implemenations
5
- # may define yet more constants that are specific to their implementation.
6
- module Constants
7
- USERAUTH_REQUEST = 50
8
- USERAUTH_FAILURE = 51
9
- USERAUTH_SUCCESS = 52
10
- USERAUTH_BANNER = 53
13
+ USERAUTH_PASSWD_CHANGEREQ = 60
14
+ USERAUTH_PK_OK = 60
11
15
 
12
- USERAUTH_PASSWD_CHANGEREQ = 60
13
- USERAUTH_PK_OK = 60
14
-
15
- USERAUTH_METHOD_RANGE = 60..79
16
+ USERAUTH_METHOD_RANGE = 60..79
17
+ end
18
+ end
16
19
  end
17
-
18
- end; end; end
20
+ end