minmb-net-ssh 2.5.1

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 (137) hide show
  1. data/CHANGELOG.rdoc +291 -0
  2. data/Manifest +132 -0
  3. data/README.rdoc +184 -0
  4. data/Rakefile +86 -0
  5. data/Rudyfile +96 -0
  6. data/THANKS.rdoc +19 -0
  7. data/lib/net/ssh.rb +223 -0
  8. data/lib/net/ssh/authentication/agent.rb +23 -0
  9. data/lib/net/ssh/authentication/agent/java_pageant.rb +85 -0
  10. data/lib/net/ssh/authentication/agent/socket.rb +170 -0
  11. data/lib/net/ssh/authentication/constants.rb +18 -0
  12. data/lib/net/ssh/authentication/key_manager.rb +253 -0
  13. data/lib/net/ssh/authentication/methods/abstract.rb +60 -0
  14. data/lib/net/ssh/authentication/methods/hostbased.rb +75 -0
  15. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +70 -0
  16. data/lib/net/ssh/authentication/methods/password.rb +43 -0
  17. data/lib/net/ssh/authentication/methods/publickey.rb +96 -0
  18. data/lib/net/ssh/authentication/pageant.rb +301 -0
  19. data/lib/net/ssh/authentication/session.rb +154 -0
  20. data/lib/net/ssh/buffer.rb +350 -0
  21. data/lib/net/ssh/buffered_io.rb +207 -0
  22. data/lib/net/ssh/config.rb +207 -0
  23. data/lib/net/ssh/connection/channel.rb +630 -0
  24. data/lib/net/ssh/connection/constants.rb +33 -0
  25. data/lib/net/ssh/connection/session.rb +603 -0
  26. data/lib/net/ssh/connection/term.rb +178 -0
  27. data/lib/net/ssh/errors.rb +88 -0
  28. data/lib/net/ssh/key_factory.rb +107 -0
  29. data/lib/net/ssh/known_hosts.rb +141 -0
  30. data/lib/net/ssh/loggable.rb +61 -0
  31. data/lib/net/ssh/packet.rb +102 -0
  32. data/lib/net/ssh/prompt.rb +93 -0
  33. data/lib/net/ssh/proxy/command.rb +75 -0
  34. data/lib/net/ssh/proxy/errors.rb +14 -0
  35. data/lib/net/ssh/proxy/http.rb +94 -0
  36. data/lib/net/ssh/proxy/socks4.rb +70 -0
  37. data/lib/net/ssh/proxy/socks5.rb +142 -0
  38. data/lib/net/ssh/ruby_compat.rb +77 -0
  39. data/lib/net/ssh/service/forward.rb +327 -0
  40. data/lib/net/ssh/test.rb +89 -0
  41. data/lib/net/ssh/test/channel.rb +129 -0
  42. data/lib/net/ssh/test/extensions.rb +152 -0
  43. data/lib/net/ssh/test/kex.rb +44 -0
  44. data/lib/net/ssh/test/local_packet.rb +51 -0
  45. data/lib/net/ssh/test/packet.rb +81 -0
  46. data/lib/net/ssh/test/remote_packet.rb +38 -0
  47. data/lib/net/ssh/test/script.rb +157 -0
  48. data/lib/net/ssh/test/socket.rb +64 -0
  49. data/lib/net/ssh/transport/algorithms.rb +407 -0
  50. data/lib/net/ssh/transport/cipher_factory.rb +106 -0
  51. data/lib/net/ssh/transport/constants.rb +32 -0
  52. data/lib/net/ssh/transport/ctr.rb +95 -0
  53. data/lib/net/ssh/transport/hmac.rb +45 -0
  54. data/lib/net/ssh/transport/hmac/abstract.rb +79 -0
  55. data/lib/net/ssh/transport/hmac/md5.rb +12 -0
  56. data/lib/net/ssh/transport/hmac/md5_96.rb +11 -0
  57. data/lib/net/ssh/transport/hmac/none.rb +15 -0
  58. data/lib/net/ssh/transport/hmac/ripemd160.rb +13 -0
  59. data/lib/net/ssh/transport/hmac/sha1.rb +13 -0
  60. data/lib/net/ssh/transport/hmac/sha1_96.rb +11 -0
  61. data/lib/net/ssh/transport/hmac/sha2_256.rb +15 -0
  62. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +13 -0
  63. data/lib/net/ssh/transport/hmac/sha2_512.rb +14 -0
  64. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +13 -0
  65. data/lib/net/ssh/transport/identity_cipher.rb +55 -0
  66. data/lib/net/ssh/transport/kex.rb +28 -0
  67. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +44 -0
  68. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +216 -0
  69. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +80 -0
  70. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +15 -0
  71. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +93 -0
  72. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +13 -0
  73. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +13 -0
  74. data/lib/net/ssh/transport/key_expander.rb +26 -0
  75. data/lib/net/ssh/transport/openssl.rb +237 -0
  76. data/lib/net/ssh/transport/packet_stream.rb +235 -0
  77. data/lib/net/ssh/transport/server_version.rb +71 -0
  78. data/lib/net/ssh/transport/session.rb +278 -0
  79. data/lib/net/ssh/transport/state.rb +206 -0
  80. data/lib/net/ssh/verifiers/lenient.rb +30 -0
  81. data/lib/net/ssh/verifiers/null.rb +12 -0
  82. data/lib/net/ssh/verifiers/strict.rb +53 -0
  83. data/lib/net/ssh/version.rb +62 -0
  84. data/net-ssh.gemspec +164 -0
  85. data/setup.rb +1585 -0
  86. data/support/arcfour_check.rb +20 -0
  87. data/support/ssh_tunnel_bug.rb +65 -0
  88. data/test/authentication/methods/common.rb +28 -0
  89. data/test/authentication/methods/test_abstract.rb +51 -0
  90. data/test/authentication/methods/test_hostbased.rb +114 -0
  91. data/test/authentication/methods/test_keyboard_interactive.rb +100 -0
  92. data/test/authentication/methods/test_password.rb +52 -0
  93. data/test/authentication/methods/test_publickey.rb +148 -0
  94. data/test/authentication/test_agent.rb +205 -0
  95. data/test/authentication/test_key_manager.rb +218 -0
  96. data/test/authentication/test_session.rb +106 -0
  97. data/test/common.rb +107 -0
  98. data/test/configs/eqsign +3 -0
  99. data/test/configs/exact_match +8 -0
  100. data/test/configs/host_plus +10 -0
  101. data/test/configs/multihost +4 -0
  102. data/test/configs/wild_cards +14 -0
  103. data/test/connection/test_channel.rb +467 -0
  104. data/test/connection/test_session.rb +488 -0
  105. data/test/known_hosts/github +1 -0
  106. data/test/test_all.rb +9 -0
  107. data/test/test_buffer.rb +426 -0
  108. data/test/test_buffered_io.rb +63 -0
  109. data/test/test_config.rb +120 -0
  110. data/test/test_key_factory.rb +121 -0
  111. data/test/test_known_hosts.rb +13 -0
  112. data/test/transport/hmac/test_md5.rb +39 -0
  113. data/test/transport/hmac/test_md5_96.rb +25 -0
  114. data/test/transport/hmac/test_none.rb +34 -0
  115. data/test/transport/hmac/test_ripemd160.rb +34 -0
  116. data/test/transport/hmac/test_sha1.rb +34 -0
  117. data/test/transport/hmac/test_sha1_96.rb +25 -0
  118. data/test/transport/hmac/test_sha2_256.rb +35 -0
  119. data/test/transport/hmac/test_sha2_256_96.rb +25 -0
  120. data/test/transport/hmac/test_sha2_512.rb +35 -0
  121. data/test/transport/hmac/test_sha2_512_96.rb +25 -0
  122. data/test/transport/kex/test_diffie_hellman_group14_sha1.rb +13 -0
  123. data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +146 -0
  124. data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +92 -0
  125. data/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb +33 -0
  126. data/test/transport/kex/test_ecdh_sha2_nistp256.rb +161 -0
  127. data/test/transport/kex/test_ecdh_sha2_nistp384.rb +37 -0
  128. data/test/transport/kex/test_ecdh_sha2_nistp521.rb +37 -0
  129. data/test/transport/test_algorithms.rb +330 -0
  130. data/test/transport/test_cipher_factory.rb +441 -0
  131. data/test/transport/test_hmac.rb +34 -0
  132. data/test/transport/test_identity_cipher.rb +40 -0
  133. data/test/transport/test_packet_stream.rb +1745 -0
  134. data/test/transport/test_server_version.rb +78 -0
  135. data/test/transport/test_session.rb +315 -0
  136. data/test/transport/test_state.rb +179 -0
  137. metadata +208 -0
@@ -0,0 +1,85 @@
1
+ require 'jruby_pageant'
2
+
3
+ module Net; module SSH; module Authentication
4
+
5
+ # This class implements an agent for JRuby + Pageant.
6
+ #
7
+ # Written by Artūras Šlajus <arturas.slajus@gmail.com>
8
+ class Agent
9
+ include Loggable
10
+ include JRubyPageant
11
+
12
+ # A simple module for extending keys, to allow blobs and comments to be
13
+ # specified for them.
14
+ module Key
15
+ # :blob is used by OpenSSL::PKey::RSA#to_blob
16
+ attr_accessor :java_blob
17
+ attr_accessor :comment
18
+ end
19
+
20
+ # Instantiates a new agent object, connects to a running SSH agent,
21
+ # negotiates the agent protocol version, and returns the agent object.
22
+ def self.connect(logger=nil)
23
+ agent = new(logger)
24
+ agent.connect!
25
+ agent
26
+ end
27
+
28
+ # Creates a new Agent object, using the optional logger instance to
29
+ # report status.
30
+ def initialize(logger=nil)
31
+ self.logger = logger
32
+ end
33
+
34
+ # Connect to the agent process using the socket factory and socket name
35
+ # given by the attribute writers. If the agent on the other end of the
36
+ # socket reports that it is an SSH2-compatible agent, this will fail
37
+ # (it only supports the ssh-agent distributed by OpenSSH).
38
+ def connect!
39
+ debug { "connecting to Pageant ssh-agent (via java connector)" }
40
+ @agent_proxy = JRubyPageant.create
41
+ unless @agent_proxy.is_running
42
+ raise AgentNotAvailable, "Pageant is not running!"
43
+ end
44
+ debug { "connection to Pageant ssh-agent (via java connector) succeeded" }
45
+ rescue AgentProxyException => e
46
+ error { "could not connect to Pageant ssh-agent (via java connector)" }
47
+ raise AgentNotAvailable, e.message, e.backtrace
48
+ end
49
+
50
+ # Return an array of all identities (public keys) known to the agent.
51
+ # Each key returned is augmented with a +comment+ property which is set
52
+ # to the comment returned by the agent for that key.
53
+ def identities
54
+ debug { "getting identities from Pageant" }
55
+ @agent_proxy.get_identities.map do |identity|
56
+ blob = identity.get_blob
57
+ key = Buffer.new(String.from_java_bytes(blob)).read_key
58
+ key.extend(Key)
59
+ key.java_blob = blob
60
+ key.comment = String.from_java_bytes(identity.get_comment)
61
+ key
62
+ end
63
+ rescue AgentProxyException => e
64
+ raise AgentError, "Cannot get identities: #{e.message}", e.backtrace
65
+ end
66
+
67
+ # Simulate agent close. This agent reference is no longer able to
68
+ # query the agent.
69
+ def close
70
+ @agent_proxy = nil
71
+ end
72
+
73
+ # Using the agent and the given public key, sign the given data. The
74
+ # signature is returned in SSH2 format.
75
+ def sign(key, data)
76
+ signed = @agent_proxy.sign(key.java_blob, data.to_java_bytes)
77
+ String.from_java_bytes(signed)
78
+ rescue AgentProxyException => e
79
+ raise AgentError,
80
+ "agent could not sign data with requested identity: #{e.message}",
81
+ e.backtrace
82
+ end
83
+ end
84
+
85
+ end; end; end
@@ -0,0 +1,170 @@
1
+ require 'net/ssh/transport/server_version'
2
+
3
+ # Only load pageant on Windows
4
+ if Net::SSH::Authentication::PLATFORM == :win32
5
+ require 'net/ssh/authentication/pageant'
6
+ end
7
+
8
+ module Net; module SSH; module Authentication
9
+
10
+ # This class implements a simple client for the ssh-agent protocol. It
11
+ # does not implement any specific protocol, but instead copies the
12
+ # behavior of the ssh-agent functions in the OpenSSH library (3.8).
13
+ #
14
+ # This means that although it behaves like a SSH1 client, it also has
15
+ # some SSH2 functionality (like signing data).
16
+ class Agent
17
+ include Loggable
18
+
19
+ # A simple module for extending keys, to allow comments to be specified
20
+ # for them.
21
+ module Comment
22
+ attr_accessor :comment
23
+ end
24
+
25
+ SSH2_AGENT_REQUEST_VERSION = 1
26
+ SSH2_AGENT_REQUEST_IDENTITIES = 11
27
+ SSH2_AGENT_IDENTITIES_ANSWER = 12
28
+ SSH2_AGENT_SIGN_REQUEST = 13
29
+ SSH2_AGENT_SIGN_RESPONSE = 14
30
+ SSH2_AGENT_FAILURE = 30
31
+ SSH2_AGENT_VERSION_RESPONSE = 103
32
+
33
+ SSH_COM_AGENT2_FAILURE = 102
34
+
35
+ SSH_AGENT_REQUEST_RSA_IDENTITIES = 1
36
+ SSH_AGENT_RSA_IDENTITIES_ANSWER1 = 2
37
+ SSH_AGENT_RSA_IDENTITIES_ANSWER2 = 5
38
+ SSH_AGENT_FAILURE = 5
39
+
40
+ # The underlying socket being used to communicate with the SSH agent.
41
+ attr_reader :socket
42
+
43
+ # Instantiates a new agent object, connects to a running SSH agent,
44
+ # negotiates the agent protocol version, and returns the agent object.
45
+ def self.connect(logger=nil)
46
+ agent = new(logger)
47
+ agent.connect!
48
+ agent.negotiate!
49
+ agent
50
+ end
51
+
52
+ # Creates a new Agent object, using the optional logger instance to
53
+ # report status.
54
+ def initialize(logger=nil)
55
+ self.logger = logger
56
+ end
57
+
58
+ # Connect to the agent process using the socket factory and socket name
59
+ # given by the attribute writers. If the agent on the other end of the
60
+ # socket reports that it is an SSH2-compatible agent, this will fail
61
+ # (it only supports the ssh-agent distributed by OpenSSH).
62
+ def connect!
63
+ begin
64
+ debug { "connecting to ssh-agent" }
65
+ @socket = agent_socket_factory.open(ENV['SSH_AUTH_SOCK'])
66
+ rescue
67
+ error { "could not connect to ssh-agent" }
68
+ raise AgentNotAvailable, $!.message
69
+ end
70
+ end
71
+
72
+ # Attempts to negotiate the SSH agent protocol version. Raises an error
73
+ # if the version could not be negotiated successfully.
74
+ def negotiate!
75
+ # determine what type of agent we're communicating with
76
+ type, body = send_and_wait(SSH2_AGENT_REQUEST_VERSION, :string, Transport::ServerVersion::PROTO_VERSION)
77
+
78
+ if type == SSH2_AGENT_VERSION_RESPONSE
79
+ raise NotImplementedError, "SSH2 agents are not yet supported"
80
+ elsif type != SSH_AGENT_RSA_IDENTITIES_ANSWER1 && type != SSH_AGENT_RSA_IDENTITIES_ANSWER2
81
+ raise AgentError, "unknown response from agent: #{type}, #{body.to_s.inspect}"
82
+ end
83
+ end
84
+
85
+ # Return an array of all identities (public keys) known to the agent.
86
+ # Each key returned is augmented with a +comment+ property which is set
87
+ # to the comment returned by the agent for that key.
88
+ def identities
89
+ type, body = send_and_wait(SSH2_AGENT_REQUEST_IDENTITIES)
90
+ raise AgentError, "could not get identity count" if agent_failed(type)
91
+ raise AgentError, "bad authentication reply: #{type}" if type != SSH2_AGENT_IDENTITIES_ANSWER
92
+
93
+ identities = []
94
+ body.read_long.times do
95
+ key = Buffer.new(body.read_string).read_key
96
+ key.extend(Comment)
97
+ key.comment = body.read_string
98
+ identities.push key
99
+ end
100
+
101
+ return identities
102
+ end
103
+
104
+ # Closes this socket. This agent reference is no longer able to
105
+ # query the agent.
106
+ def close
107
+ @socket.close
108
+ end
109
+
110
+ # Using the agent and the given public key, sign the given data. The
111
+ # signature is returned in SSH2 format.
112
+ def sign(key, data)
113
+ type, reply = send_and_wait(SSH2_AGENT_SIGN_REQUEST, :string, Buffer.from(:key, key), :string, data, :long, 0)
114
+
115
+ if agent_failed(type)
116
+ raise AgentError, "agent could not sign data with requested identity"
117
+ elsif type != SSH2_AGENT_SIGN_RESPONSE
118
+ raise AgentError, "bad authentication response #{type}"
119
+ end
120
+
121
+ return reply.read_string
122
+ end
123
+
124
+ private
125
+
126
+ # Returns the agent socket factory to use.
127
+ def agent_socket_factory
128
+ if Net::SSH::Authentication::PLATFORM == :win32
129
+ Pageant::socket_factory
130
+ else
131
+ UNIXSocket
132
+ end
133
+ end
134
+
135
+ # Send a new packet of the given type, with the associated data.
136
+ def send_packet(type, *args)
137
+ buffer = Buffer.from(*args)
138
+ data = [buffer.length + 1, type.to_i, buffer.to_s].pack("NCA*")
139
+ debug { "sending agent request #{type} len #{buffer.length}" }
140
+ @socket.send data, 0
141
+ end
142
+
143
+ # Read the next packet from the agent. This will return a two-part
144
+ # tuple consisting of the packet type, and the packet's body (which
145
+ # is returned as a Net::SSH::Buffer).
146
+ def read_packet
147
+ buffer = Net::SSH::Buffer.new(@socket.read(4))
148
+ buffer.append(@socket.read(buffer.read_long))
149
+ type = buffer.read_byte
150
+ debug { "received agent packet #{type} len #{buffer.length-4}" }
151
+ return type, buffer
152
+ end
153
+
154
+ # Send the given packet and return the subsequent reply from the agent.
155
+ # (See #send_packet and #read_packet).
156
+ def send_and_wait(type, *args)
157
+ send_packet(type, *args)
158
+ read_packet
159
+ end
160
+
161
+ # Returns +true+ if the parameter indicates a "failure" response from
162
+ # the agent, and +false+ otherwise.
163
+ def agent_failed(type)
164
+ type == SSH_AGENT_FAILURE ||
165
+ type == SSH2_AGENT_FAILURE ||
166
+ type == SSH_COM_AGENT2_FAILURE
167
+ end
168
+ end
169
+
170
+ end; end; end
@@ -0,0 +1,18 @@
1
+ module Net; module SSH; module Authentication
2
+
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
11
+
12
+ USERAUTH_PASSWD_CHANGEREQ = 60
13
+ USERAUTH_PK_OK = 60
14
+
15
+ USERAUTH_METHOD_RANGE = 60..79
16
+ end
17
+
18
+ end; end; end
@@ -0,0 +1,253 @@
1
+ require 'net/ssh/errors'
2
+ require 'net/ssh/key_factory'
3
+ require 'net/ssh/loggable'
4
+ require 'net/ssh/authentication/agent'
5
+
6
+ module Net
7
+ module SSH
8
+ module Authentication
9
+
10
+ # A trivial exception class used to report errors in the key manager.
11
+ class KeyManagerError < Net::SSH::Exception; end
12
+
13
+ # This class encapsulates all operations done by clients on a user's
14
+ # private keys. In practice, the client should never need a reference
15
+ # to a private key; instead, they grab a list of "identities" (public
16
+ # keys) that are available from the KeyManager, and then use
17
+ # the KeyManager to do various private key operations using those
18
+ # identities.
19
+ #
20
+ # The KeyManager also uses the Agent class to encapsulate the
21
+ # ssh-agent. Thus, from a client's perspective it is completely
22
+ # hidden whether an identity comes from the ssh-agent or from a file
23
+ # on disk.
24
+ class KeyManager
25
+ include Loggable
26
+
27
+ # The list of user key files that will be examined
28
+ attr_reader :key_files
29
+
30
+ # The list of user key data that will be examined
31
+ attr_reader :key_data
32
+
33
+ # The map of loaded identities
34
+ attr_reader :known_identities
35
+
36
+ # The map of options that were passed to the key-manager
37
+ attr_reader :options
38
+
39
+ # Create a new KeyManager. By default, the manager will
40
+ # use the ssh-agent (if it is running).
41
+ def initialize(logger, options={})
42
+ self.logger = logger
43
+ @key_files = []
44
+ @key_data = []
45
+ @use_agent = true
46
+ @known_identities = {}
47
+ @agent = nil
48
+ @options = options
49
+ end
50
+
51
+ # Clear all knowledge of any loaded user keys. This also clears the list
52
+ # of default identity files that are to be loaded, thus making it
53
+ # appropriate to use if a client wishes to NOT use the default identity
54
+ # files.
55
+ def clear!
56
+ key_files.clear
57
+ key_data.clear
58
+ known_identities.clear
59
+ self
60
+ end
61
+
62
+ # Add the given key_file to the list of key files that will be used.
63
+ def add(key_file)
64
+ key_files.push(File.expand_path(key_file)).uniq!
65
+ self
66
+ end
67
+
68
+ # Add the given key_file to the list of keys that will be used.
69
+ def add_key_data(key_data_)
70
+ key_data.push(key_data_).uniq!
71
+ self
72
+ end
73
+
74
+ # This is used as a hint to the KeyManager indicating that the agent
75
+ # connection is no longer needed. Any other open resources may be closed
76
+ # at this time.
77
+ #
78
+ # Calling this does NOT indicate that the KeyManager will no longer
79
+ # be used. Identities may still be requested and operations done on
80
+ # loaded identities, in which case, the agent will be automatically
81
+ # reconnected. This method simply allows the client connection to be
82
+ # closed when it will not be used in the immediate future.
83
+ def finish
84
+ @agent.close if @agent
85
+ @agent = nil
86
+ end
87
+
88
+ # Iterates over all available identities (public keys) known to this
89
+ # manager. As it finds one, it will then yield it to the caller.
90
+ # The origin of the identities may be from files on disk or from an
91
+ # ssh-agent. Note that identities from an ssh-agent are always listed
92
+ # first in the array, with other identities coming after.
93
+ #
94
+ # If key manager was created with :keys_only option, any identity
95
+ # from ssh-agent will be ignored unless it present in key_files or
96
+ # key_data.
97
+ def each_identity
98
+ prepared_identities = prepare_identities_from_files + prepare_identities_from_data
99
+
100
+ user_identities = load_identities(prepared_identities, false)
101
+
102
+ if agent
103
+ agent.identities.each do |key|
104
+ corresponding_user_identity = user_identities.detect { |identity|
105
+ identity[:public_key] && identity[:public_key].to_pem == key.to_pem
106
+ }
107
+ user_identities.delete(corresponding_user_identity) if corresponding_user_identity
108
+
109
+ if !options[:keys_only] || corresponding_user_identity
110
+ known_identities[key] = { :from => :agent }
111
+ yield key
112
+ end
113
+ end
114
+ end
115
+
116
+ user_identities = load_identities(user_identities, true)
117
+
118
+ user_identities.each do |identity|
119
+ key = identity.delete(:public_key)
120
+ known_identities[key] = identity
121
+ yield key
122
+ end
123
+
124
+ self
125
+ end
126
+
127
+ # Sign the given data, using the corresponding private key of the given
128
+ # identity. If the identity was originally obtained from an ssh-agent,
129
+ # then the ssh-agent will be used to sign the data, otherwise the
130
+ # private key for the identity will be loaded from disk (if it hasn't
131
+ # been loaded already) and will then be used to sign the data.
132
+ #
133
+ # Regardless of the identity's origin or who does the signing, this
134
+ # will always return the signature in an SSH2-specified "signature
135
+ # blob" format.
136
+ def sign(identity, data)
137
+ info = known_identities[identity] or raise KeyManagerError, "the given identity is unknown to the key manager"
138
+
139
+ if info[:key].nil? && info[:from] == :file
140
+ begin
141
+ info[:key] = KeyFactory.load_private_key(info[:file], options[:passphrase], true)
142
+ rescue Exception, OpenSSL::OpenSSLError => e
143
+ raise KeyManagerError, "the given identity is known, but the private key could not be loaded: #{e.class} (#{e.message})"
144
+ end
145
+ end
146
+
147
+ if info[:key]
148
+ return Net::SSH::Buffer.from(:string, identity.ssh_type,
149
+ :string, info[:key].ssh_do_sign(data.to_s)).to_s
150
+ end
151
+
152
+ if info[:from] == :agent
153
+ raise KeyManagerError, "the agent is no longer available" unless agent
154
+ return agent.sign(identity, data.to_s)
155
+ end
156
+
157
+ raise KeyManagerError, "[BUG] can't determine identity origin (#{info.inspect})"
158
+ end
159
+
160
+ # Identifies whether the ssh-agent will be used or not.
161
+ def use_agent?
162
+ @use_agent
163
+ end
164
+
165
+ # Toggles whether the ssh-agent will be used or not. If true, an
166
+ # attempt will be made to use the ssh-agent. If false, any existing
167
+ # connection to an agent is closed and the agent will not be used.
168
+ def use_agent=(use_agent)
169
+ finish if !use_agent
170
+ @use_agent = use_agent
171
+ end
172
+
173
+ # Returns an Agent instance to use for communicating with an SSH
174
+ # agent process. Returns nil if use of an SSH agent has been disabled,
175
+ # or if the agent is otherwise not available.
176
+ def agent
177
+ return unless use_agent?
178
+ @agent ||= Agent.connect(logger)
179
+ rescue AgentNotAvailable
180
+ @use_agent = false
181
+ nil
182
+ end
183
+
184
+ private
185
+
186
+ # Prepares identities from user key_files for loading, preserving their order and sources.
187
+ def prepare_identities_from_files
188
+ key_files.map do |file|
189
+ public_key_file = file + ".pub"
190
+ if File.readable?(public_key_file)
191
+ { :load_from => :pubkey_file, :file => file }
192
+ elsif File.readable?(file)
193
+ { :load_from => :privkey_file, :file => file }
194
+ end
195
+ end.compact
196
+ end
197
+
198
+ # Prepared identities from user key_data, preserving their order and sources.
199
+ def prepare_identities_from_data
200
+ key_data.map do |data|
201
+ { :load_from => :data, :data => data }
202
+ end
203
+ end
204
+
205
+ # Load prepared identities. Private key decryption errors ignored if passphrase was not prompted.
206
+ def load_identities(identities, ask_passphrase)
207
+ identities.map do |identity|
208
+ begin
209
+ case identity[:load_from]
210
+ when :pubkey_file
211
+ key = KeyFactory.load_public_key(identity[:file] + ".pub")
212
+ { :public_key => key, :from => :file, :file => identity[:file] }
213
+ when :privkey_file
214
+ private_key = KeyFactory.load_private_key(identity[:file], options[:passphrase], ask_passphrase)
215
+ key = private_key.send(:public_key)
216
+ { :public_key => key, :from => :file, :file => identity[:file], :key => private_key }
217
+ when :data
218
+ private_key = KeyFactory.load_data_private_key(identity[:data], options[:passphrase], ask_passphrase)
219
+ key = private_key.send(:public_key)
220
+ { :public_key => key, :from => :key_data, :data => identity[:data], :key => private_key }
221
+ else
222
+ identity
223
+ end
224
+
225
+ rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError, OpenSSL::PKey::ECError => e
226
+ if ask_passphrase
227
+ process_identity_loading_error(identity, e)
228
+ nil
229
+ else
230
+ identity
231
+ end
232
+ rescue Exception => e
233
+ process_identity_loading_error(identity, e)
234
+ nil
235
+ end
236
+ end.compact
237
+ end
238
+
239
+ def process_identity_loading_error(identity, e)
240
+ case identity[:load_from]
241
+ when :pubkey_file
242
+ error { "could not load public key file `#{identity[:file]}': #{e.class} (#{e.message})" }
243
+ when :privkey_file
244
+ error { "could not load private key file `#{identity[:file]}': #{e.class} (#{e.message})" }
245
+ else
246
+ raise e
247
+ end
248
+ end
249
+
250
+ end
251
+ end
252
+ end
253
+ end