jayniz-net-ssh 2.0.15
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.
- data/CHANGELOG.rdoc +161 -0
- data/Manifest +107 -0
- data/README.rdoc +140 -0
- data/Rakefile +79 -0
- data/Rudyfile +110 -0
- data/THANKS.rdoc +16 -0
- data/lib/net/ssh/authentication/agent.rb +176 -0
- data/lib/net/ssh/authentication/constants.rb +18 -0
- data/lib/net/ssh/authentication/key_manager.rb +193 -0
- data/lib/net/ssh/authentication/methods/abstract.rb +60 -0
- data/lib/net/ssh/authentication/methods/hostbased.rb +71 -0
- data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +66 -0
- data/lib/net/ssh/authentication/methods/password.rb +39 -0
- data/lib/net/ssh/authentication/methods/publickey.rb +92 -0
- data/lib/net/ssh/authentication/pageant.rb +183 -0
- data/lib/net/ssh/authentication/session.rb +134 -0
- data/lib/net/ssh/buffer.rb +340 -0
- data/lib/net/ssh/buffered_io.rb +150 -0
- data/lib/net/ssh/config.rb +185 -0
- data/lib/net/ssh/connection/channel.rb +625 -0
- data/lib/net/ssh/connection/constants.rb +33 -0
- data/lib/net/ssh/connection/session.rb +597 -0
- data/lib/net/ssh/connection/term.rb +178 -0
- data/lib/net/ssh/errors.rb +85 -0
- data/lib/net/ssh/key_factory.rb +102 -0
- data/lib/net/ssh/known_hosts.rb +129 -0
- data/lib/net/ssh/loggable.rb +61 -0
- data/lib/net/ssh/packet.rb +102 -0
- data/lib/net/ssh/prompt.rb +93 -0
- data/lib/net/ssh/proxy/errors.rb +14 -0
- data/lib/net/ssh/proxy/http.rb +94 -0
- data/lib/net/ssh/proxy/socks4.rb +70 -0
- data/lib/net/ssh/proxy/socks5.rb +142 -0
- data/lib/net/ssh/ruby_compat.rb +43 -0
- data/lib/net/ssh/service/forward.rb +267 -0
- data/lib/net/ssh/test/channel.rb +129 -0
- data/lib/net/ssh/test/extensions.rb +152 -0
- data/lib/net/ssh/test/kex.rb +44 -0
- data/lib/net/ssh/test/local_packet.rb +51 -0
- data/lib/net/ssh/test/packet.rb +81 -0
- data/lib/net/ssh/test/remote_packet.rb +38 -0
- data/lib/net/ssh/test/script.rb +157 -0
- data/lib/net/ssh/test/socket.rb +59 -0
- data/lib/net/ssh/test.rb +89 -0
- data/lib/net/ssh/transport/algorithms.rb +384 -0
- data/lib/net/ssh/transport/cipher_factory.rb +97 -0
- data/lib/net/ssh/transport/constants.rb +30 -0
- data/lib/net/ssh/transport/hmac/abstract.rb +79 -0
- data/lib/net/ssh/transport/hmac/md5.rb +12 -0
- data/lib/net/ssh/transport/hmac/md5_96.rb +11 -0
- data/lib/net/ssh/transport/hmac/none.rb +15 -0
- data/lib/net/ssh/transport/hmac/sha1.rb +13 -0
- data/lib/net/ssh/transport/hmac/sha1_96.rb +11 -0
- data/lib/net/ssh/transport/hmac.rb +31 -0
- data/lib/net/ssh/transport/identity_cipher.rb +55 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +208 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +77 -0
- data/lib/net/ssh/transport/kex.rb +13 -0
- data/lib/net/ssh/transport/openssl.rb +128 -0
- data/lib/net/ssh/transport/packet_stream.rb +232 -0
- data/lib/net/ssh/transport/server_version.rb +70 -0
- data/lib/net/ssh/transport/session.rb +276 -0
- data/lib/net/ssh/transport/state.rb +206 -0
- data/lib/net/ssh/verifiers/lenient.rb +30 -0
- data/lib/net/ssh/verifiers/null.rb +12 -0
- data/lib/net/ssh/verifiers/strict.rb +53 -0
- data/lib/net/ssh/version.rb +62 -0
- data/lib/net/ssh.rb +215 -0
- data/net-ssh.gemspec +131 -0
- data/setup.rb +1585 -0
- data/support/arcfour_check.rb +20 -0
- data/test/authentication/methods/common.rb +28 -0
- data/test/authentication/methods/test_abstract.rb +51 -0
- data/test/authentication/methods/test_hostbased.rb +114 -0
- data/test/authentication/methods/test_keyboard_interactive.rb +98 -0
- data/test/authentication/methods/test_password.rb +50 -0
- data/test/authentication/methods/test_publickey.rb +127 -0
- data/test/authentication/test_agent.rb +205 -0
- data/test/authentication/test_key_manager.rb +105 -0
- data/test/authentication/test_session.rb +93 -0
- data/test/common.rb +107 -0
- data/test/configs/eqsign +3 -0
- data/test/configs/exact_match +8 -0
- data/test/configs/multihost +4 -0
- data/test/configs/wild_cards +14 -0
- data/test/connection/test_channel.rb +452 -0
- data/test/connection/test_session.rb +488 -0
- data/test/test_all.rb +8 -0
- data/test/test_buffer.rb +336 -0
- data/test/test_buffered_io.rb +63 -0
- data/test/test_config.rb +99 -0
- data/test/test_key_factory.rb +67 -0
- data/test/transport/hmac/test_md5.rb +39 -0
- data/test/transport/hmac/test_md5_96.rb +25 -0
- data/test/transport/hmac/test_none.rb +34 -0
- data/test/transport/hmac/test_sha1.rb +34 -0
- data/test/transport/hmac/test_sha1_96.rb +25 -0
- data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +146 -0
- data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +92 -0
- data/test/transport/test_algorithms.rb +302 -0
- data/test/transport/test_cipher_factory.rb +213 -0
- data/test/transport/test_hmac.rb +34 -0
- data/test/transport/test_identity_cipher.rb +40 -0
- data/test/transport/test_packet_stream.rb +441 -0
- data/test/transport/test_server_version.rb +68 -0
- data/test/transport/test_session.rb +315 -0
- data/test/transport/test_state.rb +173 -0
- metadata +168 -0
data/Rudyfile
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Rudyfile
|
|
2
|
+
#
|
|
3
|
+
# This configuration is used to test installing
|
|
4
|
+
# and running net-ssh on a clean machine.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
#
|
|
8
|
+
# $ rudy -vv startup
|
|
9
|
+
# $ rudy -vv testsuite
|
|
10
|
+
# $ rudy -vv shutdown
|
|
11
|
+
#
|
|
12
|
+
# Requires: Rudy 0.9 (http://code.google.com/p/rudy/)
|
|
13
|
+
#
|
|
14
|
+
|
|
15
|
+
defaults do
|
|
16
|
+
color true
|
|
17
|
+
environment :test
|
|
18
|
+
role :netssh
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
machines do
|
|
22
|
+
region :'us-east-1' do
|
|
23
|
+
ami 'ami-e348af8a' # Alestic Debian 5.0, 32-bit (US)
|
|
24
|
+
end
|
|
25
|
+
env :test do
|
|
26
|
+
role :netssh do
|
|
27
|
+
user :root
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
commands do
|
|
33
|
+
allow :apt_get, "apt-get", :y, :q
|
|
34
|
+
allow :gem_install, "/usr/bin/gem", "install", :n, '/usr/bin', :y, :V, "--no-rdoc", "--no-ri"
|
|
35
|
+
allow :gem_sources, "/usr/bin/gem", "sources"
|
|
36
|
+
allow :gem_uninstall, "/usr/bin/gem", "uninstall", :V
|
|
37
|
+
allow :update_rubygems
|
|
38
|
+
allow :rm
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
routines do
|
|
42
|
+
|
|
43
|
+
testsuite do
|
|
44
|
+
before :sysupdate, :installdeps, :install_gem
|
|
45
|
+
|
|
46
|
+
remote :root do
|
|
47
|
+
directory_upload 'test', '/tmp/'
|
|
48
|
+
cd '/tmp'
|
|
49
|
+
ruby :I, 'lib/', :I, 'test/', :r, 'rubygems', 'test/test_all.rb'
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
after :install_rubyforge, :install_github
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
install_rubyforge do
|
|
56
|
+
remote :root do
|
|
57
|
+
gem_install 'net-ssh', '--version', '2.0.7'
|
|
58
|
+
gem_install 'net-ssh'
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
install_github do
|
|
63
|
+
remote :root do
|
|
64
|
+
gem_sources :a, "http://gems.github.com"
|
|
65
|
+
gem_install 'net-ssh-net-ssh'
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
install_gem do
|
|
70
|
+
before :package_gem
|
|
71
|
+
remote :root do
|
|
72
|
+
disable_safe_mode
|
|
73
|
+
file_upload "pkg/net-ssh-*.gem", "/tmp/"
|
|
74
|
+
gem_install "/tmp/net-ssh-*.gem"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
package_gem do
|
|
79
|
+
local do
|
|
80
|
+
rm :r, :f, 'pkg'
|
|
81
|
+
rake 'package'
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
remove do
|
|
86
|
+
remote :root do
|
|
87
|
+
gem_uninstall 'net-ssh'
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
installdeps do
|
|
92
|
+
remote :root do
|
|
93
|
+
gem_install "rye", "test-unit", "mocha"
|
|
94
|
+
rye 'authorize-local'
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
sysupdate do
|
|
99
|
+
remote :root do
|
|
100
|
+
apt_get "update"
|
|
101
|
+
apt_get "install", "build-essential", "git-core"
|
|
102
|
+
apt_get "install", "ruby1.8-dev", "rdoc", "libzlib-ruby", "rubygems"
|
|
103
|
+
mkdir :p, "/var/lib/gems/1.8/bin" # Doesn't get created, but causes Rubygems to fail
|
|
104
|
+
gem_install "builder", "session"
|
|
105
|
+
gem_install 'rubygems-update', "-v=1.3.4" # circular issue with 1.3.5 and hoe
|
|
106
|
+
update_rubygems
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
data/THANKS.rdoc
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Net::SSH was originally written by Jamis Buck <jamis@37signals.com>. In
|
|
2
|
+
addition, the following individuals are gratefully acknowledged for their
|
|
3
|
+
contributions:
|
|
4
|
+
|
|
5
|
+
GOTOU Yuuzou <gotoyuzo@notwork.org>
|
|
6
|
+
* help and code related to OpenSSL
|
|
7
|
+
|
|
8
|
+
Guillaume Mar�ais <guillaume.marcais@free.fr>
|
|
9
|
+
* support for communicating with the the PuTTY "pageant" process
|
|
10
|
+
|
|
11
|
+
Daniel Berger <djberg96@yahoo.com>
|
|
12
|
+
* help getting unit tests in earlier Net::SSH versions to pass in Windows
|
|
13
|
+
* initial version of Net::SSH::Config provided inspiration and encouragement
|
|
14
|
+
|
|
15
|
+
Chris Andrews <chris@nodnol.org> and Lee Jensen <lee@outerim.com>
|
|
16
|
+
* support for ssh agent forwarding
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
require 'net/ssh/buffer'
|
|
2
|
+
require 'net/ssh/errors'
|
|
3
|
+
require 'net/ssh/loggable'
|
|
4
|
+
require 'net/ssh/transport/server_version'
|
|
5
|
+
|
|
6
|
+
require 'net/ssh/authentication/pageant' if File::ALT_SEPARATOR && !(RUBY_PLATFORM =~ /java/)
|
|
7
|
+
|
|
8
|
+
module Net; module SSH; module Authentication
|
|
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
|
+
|
|
16
|
+
# This class implements a simple client for the ssh-agent protocol. It
|
|
17
|
+
# does not implement any specific protocol, but instead copies the
|
|
18
|
+
# behavior of the ssh-agent functions in the OpenSSH library (3.8).
|
|
19
|
+
#
|
|
20
|
+
# This means that although it behaves like a SSH1 client, it also has
|
|
21
|
+
# some SSH2 functionality (like signing data).
|
|
22
|
+
class Agent
|
|
23
|
+
include Loggable
|
|
24
|
+
|
|
25
|
+
# A simple module for extending keys, to allow comments to be specified
|
|
26
|
+
# for them.
|
|
27
|
+
module Comment
|
|
28
|
+
attr_accessor :comment
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
SSH2_AGENT_REQUEST_VERSION = 1
|
|
32
|
+
SSH2_AGENT_REQUEST_IDENTITIES = 11
|
|
33
|
+
SSH2_AGENT_IDENTITIES_ANSWER = 12
|
|
34
|
+
SSH2_AGENT_SIGN_REQUEST = 13
|
|
35
|
+
SSH2_AGENT_SIGN_RESPONSE = 14
|
|
36
|
+
SSH2_AGENT_FAILURE = 30
|
|
37
|
+
SSH2_AGENT_VERSION_RESPONSE = 103
|
|
38
|
+
|
|
39
|
+
SSH_COM_AGENT2_FAILURE = 102
|
|
40
|
+
|
|
41
|
+
SSH_AGENT_REQUEST_RSA_IDENTITIES = 1
|
|
42
|
+
SSH_AGENT_RSA_IDENTITIES_ANSWER1 = 2
|
|
43
|
+
SSH_AGENT_RSA_IDENTITIES_ANSWER2 = 5
|
|
44
|
+
SSH_AGENT_FAILURE = 5
|
|
45
|
+
|
|
46
|
+
# The underlying socket being used to communicate with the SSH agent.
|
|
47
|
+
attr_reader :socket
|
|
48
|
+
|
|
49
|
+
# Instantiates a new agent object, connects to a running SSH agent,
|
|
50
|
+
# negotiates the agent protocol version, and returns the agent object.
|
|
51
|
+
def self.connect(logger=nil)
|
|
52
|
+
agent = new(logger)
|
|
53
|
+
agent.connect!
|
|
54
|
+
agent.negotiate!
|
|
55
|
+
agent
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Creates a new Agent object, using the optional logger instance to
|
|
59
|
+
# report status.
|
|
60
|
+
def initialize(logger=nil)
|
|
61
|
+
self.logger = logger
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Connect to the agent process using the socket factory and socket name
|
|
65
|
+
# given by the attribute writers. If the agent on the other end of the
|
|
66
|
+
# socket reports that it is an SSH2-compatible agent, this will fail
|
|
67
|
+
# (it only supports the ssh-agent distributed by OpenSSH).
|
|
68
|
+
def connect!
|
|
69
|
+
begin
|
|
70
|
+
debug { "connecting to ssh-agent" }
|
|
71
|
+
@socket = agent_socket_factory.open(ENV['SSH_AUTH_SOCK'])
|
|
72
|
+
rescue
|
|
73
|
+
error { "could not connect to ssh-agent" }
|
|
74
|
+
raise AgentNotAvailable, $!.message
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Attempts to negotiate the SSH agent protocol version. Raises an error
|
|
79
|
+
# if the version could not be negotiated successfully.
|
|
80
|
+
def negotiate!
|
|
81
|
+
# determine what type of agent we're communicating with
|
|
82
|
+
type, body = send_and_wait(SSH2_AGENT_REQUEST_VERSION, :string, Transport::ServerVersion::PROTO_VERSION)
|
|
83
|
+
|
|
84
|
+
if type == SSH2_AGENT_VERSION_RESPONSE
|
|
85
|
+
raise NotImplementedError, "SSH2 agents are not yet supported"
|
|
86
|
+
elsif type != SSH_AGENT_RSA_IDENTITIES_ANSWER1 && type != SSH_AGENT_RSA_IDENTITIES_ANSWER2
|
|
87
|
+
raise AgentError, "unknown response from agent: #{type}, #{body.to_s.inspect}"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Return an array of all identities (public keys) known to the agent.
|
|
92
|
+
# Each key returned is augmented with a +comment+ property which is set
|
|
93
|
+
# to the comment returned by the agent for that key.
|
|
94
|
+
def identities
|
|
95
|
+
type, body = send_and_wait(SSH2_AGENT_REQUEST_IDENTITIES)
|
|
96
|
+
raise AgentError, "could not get identity count" if agent_failed(type)
|
|
97
|
+
raise AgentError, "bad authentication reply: #{type}" if type != SSH2_AGENT_IDENTITIES_ANSWER
|
|
98
|
+
|
|
99
|
+
identities = []
|
|
100
|
+
body.read_long.times do
|
|
101
|
+
key = Buffer.new(body.read_string).read_key
|
|
102
|
+
key.extend(Comment)
|
|
103
|
+
key.comment = body.read_string
|
|
104
|
+
identities.push key
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
return identities
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Closes this socket. This agent reference is no longer able to
|
|
111
|
+
# query the agent.
|
|
112
|
+
def close
|
|
113
|
+
@socket.close
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Using the agent and the given public key, sign the given data. The
|
|
117
|
+
# signature is returned in SSH2 format.
|
|
118
|
+
def sign(key, data)
|
|
119
|
+
type, reply = send_and_wait(SSH2_AGENT_SIGN_REQUEST, :string, Buffer.from(:key, key), :string, data, :long, 0)
|
|
120
|
+
|
|
121
|
+
if agent_failed(type)
|
|
122
|
+
raise AgentError, "agent could not sign data with requested identity"
|
|
123
|
+
elsif type != SSH2_AGENT_SIGN_RESPONSE
|
|
124
|
+
raise AgentError, "bad authentication response #{type}"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
return reply.read_string
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
# Returns the agent socket factory to use.
|
|
133
|
+
def agent_socket_factory
|
|
134
|
+
if File::ALT_SEPARATOR
|
|
135
|
+
Pageant::Socket
|
|
136
|
+
else
|
|
137
|
+
UNIXSocket
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Send a new packet of the given type, with the associated data.
|
|
142
|
+
def send_packet(type, *args)
|
|
143
|
+
buffer = Buffer.from(*args)
|
|
144
|
+
data = [buffer.length + 1, type.to_i, buffer.to_s].pack("NCA*")
|
|
145
|
+
debug { "sending agent request #{type} len #{buffer.length}" }
|
|
146
|
+
@socket.send data, 0
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Read the next packet from the agent. This will return a two-part
|
|
150
|
+
# tuple consisting of the packet type, and the packet's body (which
|
|
151
|
+
# is returned as a Net::SSH::Buffer).
|
|
152
|
+
def read_packet
|
|
153
|
+
buffer = Net::SSH::Buffer.new(@socket.read(4))
|
|
154
|
+
buffer.append(@socket.read(buffer.read_long))
|
|
155
|
+
type = buffer.read_byte
|
|
156
|
+
debug { "received agent packet #{type} len #{buffer.length-4}" }
|
|
157
|
+
return type, buffer
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Send the given packet and return the subsequent reply from the agent.
|
|
161
|
+
# (See #send_packet and #read_packet).
|
|
162
|
+
def send_and_wait(type, *args)
|
|
163
|
+
send_packet(type, *args)
|
|
164
|
+
read_packet
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Returns +true+ if the parameter indicates a "failure" response from
|
|
168
|
+
# the agent, and +false+ otherwise.
|
|
169
|
+
def agent_failed(type)
|
|
170
|
+
type == SSH_AGENT_FAILURE ||
|
|
171
|
+
type == SSH2_AGENT_FAILURE ||
|
|
172
|
+
type == SSH_COM_AGENT2_FAILURE
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
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,193 @@
|
|
|
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
|
+
def each_identity
|
|
94
|
+
if agent
|
|
95
|
+
agent.identities.each do |key|
|
|
96
|
+
known_identities[key] = { :from => :agent }
|
|
97
|
+
yield key
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
key_files.each do |file|
|
|
102
|
+
public_key_file = file + ".pub"
|
|
103
|
+
if File.readable?(public_key_file)
|
|
104
|
+
begin
|
|
105
|
+
key = KeyFactory.load_public_key(public_key_file)
|
|
106
|
+
known_identities[key] = { :from => :file, :file => file }
|
|
107
|
+
yield key
|
|
108
|
+
rescue Exception => e
|
|
109
|
+
error { "could not load public key file `#{public_key_file}': #{e.class} (#{e.message})" }
|
|
110
|
+
end
|
|
111
|
+
elsif File.readable?(file)
|
|
112
|
+
begin
|
|
113
|
+
private_key = KeyFactory.load_private_key(file, options[:passphrase])
|
|
114
|
+
key = private_key.send(:public_key)
|
|
115
|
+
known_identities[key] = { :from => :file, :file => file, :key => private_key }
|
|
116
|
+
yield key
|
|
117
|
+
rescue Exception => e
|
|
118
|
+
error { "could not load private key file `#{file}': #{e.class} (#{e.message})" }
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
key_data.each do |data|
|
|
124
|
+
private_key = KeyFactory.load_data_private_key(data)
|
|
125
|
+
key = private_key.send(:public_key)
|
|
126
|
+
known_identities[key] = { :from => :key_data, :data => data, :key => private_key }
|
|
127
|
+
yield key
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
self
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Sign the given data, using the corresponding private key of the given
|
|
134
|
+
# identity. If the identity was originally obtained from an ssh-agent,
|
|
135
|
+
# then the ssh-agent will be used to sign the data, otherwise the
|
|
136
|
+
# private key for the identity will be loaded from disk (if it hasn't
|
|
137
|
+
# been loaded already) and will then be used to sign the data.
|
|
138
|
+
#
|
|
139
|
+
# Regardless of the identity's origin or who does the signing, this
|
|
140
|
+
# will always return the signature in an SSH2-specified "signature
|
|
141
|
+
# blob" format.
|
|
142
|
+
def sign(identity, data)
|
|
143
|
+
info = known_identities[identity] or raise KeyManagerError, "the given identity is unknown to the key manager"
|
|
144
|
+
|
|
145
|
+
if info[:key].nil? && info[:from] == :file
|
|
146
|
+
begin
|
|
147
|
+
info[:key] = KeyFactory.load_private_key(info[:file], options[:passphrase])
|
|
148
|
+
rescue Exception => e
|
|
149
|
+
raise KeyManagerError, "the given identity is known, but the private key could not be loaded: #{e.class} (#{e.message})"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
if info[:key]
|
|
154
|
+
return Net::SSH::Buffer.from(:string, identity.ssh_type,
|
|
155
|
+
:string, info[:key].ssh_do_sign(data.to_s)).to_s
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
if info[:from] == :agent
|
|
159
|
+
raise KeyManagerError, "the agent is no longer available" unless agent
|
|
160
|
+
return agent.sign(identity, data.to_s)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
raise KeyManagerError, "[BUG] can't determine identity origin (#{info.inspect})"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Identifies whether the ssh-agent will be used or not.
|
|
167
|
+
def use_agent?
|
|
168
|
+
@use_agent
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Toggles whether the ssh-agent will be used or not. If true, an
|
|
172
|
+
# attempt will be made to use the ssh-agent. If false, any existing
|
|
173
|
+
# connection to an agent is closed and the agent will not be used.
|
|
174
|
+
def use_agent=(use_agent)
|
|
175
|
+
finish if !use_agent
|
|
176
|
+
@use_agent = use_agent
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Returns an Agent instance to use for communicating with an SSH
|
|
180
|
+
# agent process. Returns nil if use of an SSH agent has been disabled,
|
|
181
|
+
# or if the agent is otherwise not available.
|
|
182
|
+
def agent
|
|
183
|
+
return unless use_agent?
|
|
184
|
+
@agent ||= Agent.connect(logger)
|
|
185
|
+
rescue AgentNotAvailable
|
|
186
|
+
@use_agent = false
|
|
187
|
+
nil
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|