mcollective-client 1.3.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of mcollective-client might be problematic. Click here for more details.
- data/bin/mc-call-agent +54 -0
- data/bin/mco +27 -0
- data/lib/mcollective.rb +70 -0
- data/lib/mcollective/agents.rb +160 -0
- data/lib/mcollective/application.rb +354 -0
- data/lib/mcollective/applications.rb +145 -0
- data/lib/mcollective/client.rb +292 -0
- data/lib/mcollective/config.rb +202 -0
- data/lib/mcollective/connector.rb +18 -0
- data/lib/mcollective/connector/base.rb +24 -0
- data/lib/mcollective/facts.rb +39 -0
- data/lib/mcollective/facts/base.rb +86 -0
- data/lib/mcollective/log.rb +103 -0
- data/lib/mcollective/logger.rb +5 -0
- data/lib/mcollective/logger/base.rb +73 -0
- data/lib/mcollective/logger/console_logger.rb +61 -0
- data/lib/mcollective/logger/file_logger.rb +46 -0
- data/lib/mcollective/logger/syslog_logger.rb +53 -0
- data/lib/mcollective/matcher.rb +16 -0
- data/lib/mcollective/matcher/parser.rb +93 -0
- data/lib/mcollective/matcher/scanner.rb +123 -0
- data/lib/mcollective/message.rb +201 -0
- data/lib/mcollective/monkey_patches.rb +104 -0
- data/lib/mcollective/optionparser.rb +164 -0
- data/lib/mcollective/pluginmanager.rb +180 -0
- data/lib/mcollective/pluginpackager.rb +26 -0
- data/lib/mcollective/pluginpackager/agent_definition.rb +79 -0
- data/lib/mcollective/pluginpackager/standard_definition.rb +59 -0
- data/lib/mcollective/registration.rb +16 -0
- data/lib/mcollective/registration/base.rb +75 -0
- data/lib/mcollective/rpc.rb +188 -0
- data/lib/mcollective/rpc/actionrunner.rb +142 -0
- data/lib/mcollective/rpc/agent.rb +441 -0
- data/lib/mcollective/rpc/audit.rb +38 -0
- data/lib/mcollective/rpc/client.rb +793 -0
- data/lib/mcollective/rpc/ddl.rb +258 -0
- data/lib/mcollective/rpc/helpers.rb +339 -0
- data/lib/mcollective/rpc/progress.rb +63 -0
- data/lib/mcollective/rpc/reply.rb +61 -0
- data/lib/mcollective/rpc/request.rb +51 -0
- data/lib/mcollective/rpc/result.rb +41 -0
- data/lib/mcollective/rpc/stats.rb +185 -0
- data/lib/mcollective/runnerstats.rb +90 -0
- data/lib/mcollective/security.rb +26 -0
- data/lib/mcollective/security/base.rb +237 -0
- data/lib/mcollective/shell.rb +87 -0
- data/lib/mcollective/ssl.rb +246 -0
- data/lib/mcollective/unix_daemon.rb +37 -0
- data/lib/mcollective/util.rb +274 -0
- data/lib/mcollective/vendor.rb +41 -0
- data/lib/mcollective/vendor/require_vendored.rb +2 -0
- data/lib/mcollective/windows_daemon.rb +25 -0
- data/spec/Rakefile +16 -0
- data/spec/fixtures/application/test.rb +7 -0
- data/spec/fixtures/test-cert.pem +15 -0
- data/spec/fixtures/test-private.pem +15 -0
- data/spec/fixtures/test-public.pem +6 -0
- data/spec/monkey_patches/instance_variable_defined.rb +7 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/unit/agents_spec.rb +280 -0
- data/spec/unit/application_spec.rb +636 -0
- data/spec/unit/applications_spec.rb +155 -0
- data/spec/unit/array.rb +30 -0
- data/spec/unit/config_spec.rb +148 -0
- data/spec/unit/facts/base_spec.rb +118 -0
- data/spec/unit/facts_spec.rb +39 -0
- data/spec/unit/log_spec.rb +71 -0
- data/spec/unit/logger/base_spec.rb +110 -0
- data/spec/unit/logger/syslog_logger_spec.rb +86 -0
- data/spec/unit/matcher/parser_spec.rb +106 -0
- data/spec/unit/matcher/scanner_spec.rb +71 -0
- data/spec/unit/message_spec.rb +401 -0
- data/spec/unit/optionparser_spec.rb +113 -0
- data/spec/unit/pluginmanager_spec.rb +173 -0
- data/spec/unit/pluginpackager/agent_definition_spec.rb +130 -0
- data/spec/unit/pluginpackager/standard_definition_spec.rb +75 -0
- data/spec/unit/plugins/mcollective/connector/activemq_spec.rb +533 -0
- data/spec/unit/plugins/mcollective/connector/stomp/eventlogger_spec.rb +34 -0
- data/spec/unit/plugins/mcollective/connector/stomp_spec.rb +417 -0
- data/spec/unit/plugins/mcollective/packagers/ospackage_spec.rb +229 -0
- data/spec/unit/plugins/mcollective/security/psk_spec.rb +156 -0
- data/spec/unit/registration/base_spec.rb +77 -0
- data/spec/unit/rpc/actionrunner_spec.rb +213 -0
- data/spec/unit/rpc/agent_spec.rb +155 -0
- data/spec/unit/rpc/client_spec.rb +523 -0
- data/spec/unit/rpc/ddl_spec.rb +388 -0
- data/spec/unit/rpc/helpers_spec.rb +55 -0
- data/spec/unit/rpc/reply_spec.rb +143 -0
- data/spec/unit/rpc/request_spec.rb +115 -0
- data/spec/unit/rpc/result_spec.rb +66 -0
- data/spec/unit/rpc/stats_spec.rb +288 -0
- data/spec/unit/runnerstats_spec.rb +40 -0
- data/spec/unit/security/base_spec.rb +279 -0
- data/spec/unit/shell_spec.rb +144 -0
- data/spec/unit/ssl_spec.rb +244 -0
- data/spec/unit/symbol.rb +11 -0
- data/spec/unit/unix_daemon.rb +41 -0
- data/spec/unit/util_spec.rb +342 -0
- data/spec/unit/vendor_spec.rb +34 -0
- data/spec/unit/windows_daemon.rb +43 -0
- data/spec/windows_spec.opts +1 -0
- metadata +242 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
module MCollective
|
2
|
+
# Security is implimented using a module structure and installations
|
3
|
+
# can configure which module they want to use.
|
4
|
+
#
|
5
|
+
# Security modules deal with various aspects of authentication and authorization:
|
6
|
+
#
|
7
|
+
# - Determines if a filter excludes this host from dealing with a request
|
8
|
+
# - Serialization and Deserialization of messages
|
9
|
+
# - Validation of messages against keys, certificates or whatever the class choose to impliment
|
10
|
+
# - Encoding and Decoding of messages
|
11
|
+
#
|
12
|
+
# To impliment a new security class using SSL for example you would inherit from the base
|
13
|
+
# class and only impliment:
|
14
|
+
#
|
15
|
+
# - decodemsg
|
16
|
+
# - encodereply
|
17
|
+
# - encoderequest
|
18
|
+
# - validrequest?
|
19
|
+
#
|
20
|
+
# Each of these methods should increment various stats counters, see the default MCollective::Security::Psk module for examples of this
|
21
|
+
#
|
22
|
+
# Filtering can be extended by providing a new validate_filter? method.
|
23
|
+
module Security
|
24
|
+
autoload :Base, "mcollective/security/base"
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,237 @@
|
|
1
|
+
module MCollective
|
2
|
+
module Security
|
3
|
+
# This is a base class the other security modules should inherit from
|
4
|
+
# it handles statistics and validation of messages that should in most
|
5
|
+
# cases apply to all security models.
|
6
|
+
#
|
7
|
+
# To create your own security plugin you should provide a plugin that inherits
|
8
|
+
# from this and provides the following methods:
|
9
|
+
#
|
10
|
+
# decodemsg - Decodes a message that was received from the middleware
|
11
|
+
# encodereply - Encodes a reply message to a previous request message
|
12
|
+
# encoderequest - Encodes a new request message
|
13
|
+
# validrequest? - Validates a request received from the middleware
|
14
|
+
#
|
15
|
+
# Optionally if you are identifying users by some other means like certificate name
|
16
|
+
# you can provide your own callerid method that can provide the rest of the system
|
17
|
+
# with an id, and you would see this id being usable in SimpleRPC authorization methods
|
18
|
+
#
|
19
|
+
# The @initiated_by variable will be set to either :client or :node depending on
|
20
|
+
# who is using this plugin. This is to help security providers that operate in an
|
21
|
+
# asymetric mode like public/private key based systems.
|
22
|
+
#
|
23
|
+
# Specifics of each of these are a bit fluid and the interfaces for this is not
|
24
|
+
# set in stone yet, specifically the encode methods will be provided with a helper
|
25
|
+
# that takes care of encoding the core requirements. The best place to see how security
|
26
|
+
# works is by looking at the provided MCollective::Security::PSK plugin.
|
27
|
+
class Base
|
28
|
+
attr_reader :stats
|
29
|
+
attr_accessor :initiated_by
|
30
|
+
|
31
|
+
# Register plugins that inherits base
|
32
|
+
def self.inherited(klass)
|
33
|
+
PluginManager << {:type => "security_plugin", :class => klass.to_s}
|
34
|
+
end
|
35
|
+
|
36
|
+
# Initializes configuration and logging as well as prepare a zero'd hash of stats
|
37
|
+
# various security methods and filter validators should increment stats, see MCollective::Security::Psk for a sample
|
38
|
+
def initialize
|
39
|
+
@config = Config.instance
|
40
|
+
@log = Log
|
41
|
+
@stats = PluginManager["global_stats"]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Takes a Hash with a filter in it and validates it against host information.
|
45
|
+
#
|
46
|
+
# At present this supports filter matches against the following criteria:
|
47
|
+
#
|
48
|
+
# - puppet_class|cf_class - Presence of a configuration management class in
|
49
|
+
# the file configured with classesfile
|
50
|
+
# - agent - Presence of a MCollective agent with a supplied name
|
51
|
+
# - fact - The value of a fact avout this system
|
52
|
+
# - identity - the configured identity of the system
|
53
|
+
#
|
54
|
+
# TODO: Support REGEX and/or multiple filter keys to be AND'd
|
55
|
+
def validate_filter?(filter)
|
56
|
+
failed = 0
|
57
|
+
passed = 0
|
58
|
+
|
59
|
+
passed = 1 if Util.empty_filter?(filter)
|
60
|
+
|
61
|
+
filter.keys.each do |key|
|
62
|
+
case key
|
63
|
+
when /puppet_class|cf_class/
|
64
|
+
filter[key].each do |f|
|
65
|
+
Log.debug("Checking for class #{f}")
|
66
|
+
if Util.has_cf_class?(f) then
|
67
|
+
Log.debug("Passing based on configuration management class #{f}")
|
68
|
+
passed += 1
|
69
|
+
else
|
70
|
+
Log.debug("Failing based on configuration management class #{f}")
|
71
|
+
failed += 1
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
when "compound"
|
76
|
+
filter[key].each do |compound|
|
77
|
+
result = []
|
78
|
+
|
79
|
+
compound.each do |expression|
|
80
|
+
case expression.keys.first
|
81
|
+
when "statement"
|
82
|
+
result << Util.eval_compound_statement(expression).to_s
|
83
|
+
when "and"
|
84
|
+
result << "&&"
|
85
|
+
when "or"
|
86
|
+
result << "||"
|
87
|
+
when "("
|
88
|
+
result << "("
|
89
|
+
when ")"
|
90
|
+
result << ")"
|
91
|
+
when "not"
|
92
|
+
result << "!"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
result = eval(result.join(" "))
|
97
|
+
|
98
|
+
if result
|
99
|
+
Log.debug("Passing based on class and fact composition")
|
100
|
+
passed +=1
|
101
|
+
else
|
102
|
+
Log.debug("Failing based on class and fact composition")
|
103
|
+
failed +=1
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
when "agent"
|
108
|
+
filter[key].each do |f|
|
109
|
+
if Util.has_agent?(f) || f == "mcollective"
|
110
|
+
Log.debug("Passing based on agent #{f}")
|
111
|
+
passed += 1
|
112
|
+
else
|
113
|
+
Log.debug("Failing based on agent #{f}")
|
114
|
+
failed += 1
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
when "fact"
|
119
|
+
filter[key].each do |f|
|
120
|
+
if Util.has_fact?(f[:fact], f[:value], f[:operator])
|
121
|
+
Log.debug("Passing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}")
|
122
|
+
passed += 1
|
123
|
+
else
|
124
|
+
Log.debug("Failing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}")
|
125
|
+
failed += 1
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
when "identity"
|
130
|
+
unless filter[key].empty?
|
131
|
+
# Identity filters should not be 'and' but 'or' as each node can only have one identity
|
132
|
+
matched = filter[key].select{|f| Util.has_identity?(f)}.size
|
133
|
+
|
134
|
+
if matched == 1
|
135
|
+
Log.debug("Passing based on identity")
|
136
|
+
passed += 1
|
137
|
+
else
|
138
|
+
Log.debug("Failed based on identity")
|
139
|
+
failed += 1
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
if failed == 0 && passed > 0
|
146
|
+
Log.debug("Message passed the filter checks")
|
147
|
+
|
148
|
+
@stats.passed
|
149
|
+
|
150
|
+
return true
|
151
|
+
else
|
152
|
+
Log.debug("Message failed the filter checks")
|
153
|
+
|
154
|
+
@stats.filtered
|
155
|
+
|
156
|
+
return false
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def create_reply(reqid, agent, body)
|
161
|
+
Log.debug("Encoded a message for request #{reqid}")
|
162
|
+
|
163
|
+
{:senderid => @config.identity,
|
164
|
+
:requestid => reqid,
|
165
|
+
:senderagent => agent,
|
166
|
+
:msgtime => Time.now.utc.to_i,
|
167
|
+
:body => body}
|
168
|
+
end
|
169
|
+
|
170
|
+
def create_request(reqid, filter, msg, initiated_by, target_agent, target_collective, ttl=60)
|
171
|
+
Log.debug("Encoding a request for agent '#{target_agent}' in collective #{target_collective} with request id #{reqid}")
|
172
|
+
|
173
|
+
{:body => msg,
|
174
|
+
:senderid => @config.identity,
|
175
|
+
:requestid => reqid,
|
176
|
+
:filter => filter,
|
177
|
+
:collective => target_collective,
|
178
|
+
:agent => target_agent,
|
179
|
+
:callerid => callerid,
|
180
|
+
:ttl => ttl,
|
181
|
+
:msgtime => Time.now.utc.to_i}
|
182
|
+
end
|
183
|
+
|
184
|
+
# Give a MC::Message instance and a message id this will figure out if you the incoming
|
185
|
+
# message id matches the one the Message object is expecting and raise if its not
|
186
|
+
#
|
187
|
+
# Mostly used by security plugins to figure out if they should do the hard work of decrypting
|
188
|
+
# etc messages that would only later on be ignored
|
189
|
+
def should_process_msg?(msg, msgid)
|
190
|
+
if msg.expected_msgid
|
191
|
+
unless msg.expected_msgid == msgid
|
192
|
+
msgtext = "Got a message with id %s but was expecting %s, ignoring message" % [msgid, msg.expected_msgid]
|
193
|
+
Log.debug msgtext
|
194
|
+
raise MsgDoesNotMatchRequestID, msgtext
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
true
|
199
|
+
end
|
200
|
+
|
201
|
+
# Validates a callerid. We do not want to allow things like \ and / in
|
202
|
+
# callerids since other plugins make assumptions that these are safe strings.
|
203
|
+
#
|
204
|
+
# callerids are generally in the form uid=123 or cert=foo etc so we do that
|
205
|
+
# here but security plugins could override this for some complex uses
|
206
|
+
def valid_callerid?(id)
|
207
|
+
!!id.match(/^[\w]+=[\w\.\-]+$/)
|
208
|
+
end
|
209
|
+
|
210
|
+
# Returns a unique id for the caller, by default we just use the unix
|
211
|
+
# user id, security plugins can provide their own means of doing ids.
|
212
|
+
def callerid
|
213
|
+
"uid=#{Process.uid}"
|
214
|
+
end
|
215
|
+
|
216
|
+
# Security providers should provide this, see MCollective::Security::Psk
|
217
|
+
def validrequest?(req)
|
218
|
+
Log.error("validrequest? is not implimented in #{self.class}")
|
219
|
+
end
|
220
|
+
|
221
|
+
# Security providers should provide this, see MCollective::Security::Psk
|
222
|
+
def encoderequest(sender, msg, filter={})
|
223
|
+
Log.error("encoderequest is not implimented in #{self.class}")
|
224
|
+
end
|
225
|
+
|
226
|
+
# Security providers should provide this, see MCollective::Security::Psk
|
227
|
+
def encodereply(sender, msg, requestcallerid=nil)
|
228
|
+
Log.error("encodereply is not implimented in #{self.class}")
|
229
|
+
end
|
230
|
+
|
231
|
+
# Security providers should provide this, see MCollective::Security::Psk
|
232
|
+
def decodemsg(msg)
|
233
|
+
Log.error("decodemsg is not implimented in #{self.class}")
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module MCollective
|
2
|
+
# Wrapper around systemu that handles executing of system commands
|
3
|
+
# in a way that makes stdout, stderr and status available. Supports
|
4
|
+
# timeouts and sets a default sane environment.
|
5
|
+
#
|
6
|
+
# s = Shell.new("date", opts)
|
7
|
+
# s.runcommand
|
8
|
+
# puts s.stdout
|
9
|
+
# puts s.stderr
|
10
|
+
# puts s.status.exitcode
|
11
|
+
#
|
12
|
+
# Options hash can have:
|
13
|
+
#
|
14
|
+
# cwd - the working directory the command will be run from
|
15
|
+
# stdin - a string that will be sent to stdin of the program
|
16
|
+
# stdout - a variable that will receive stdout, must support <<
|
17
|
+
# stderr - a variable that will receive stdin, must support <<
|
18
|
+
# environment - the shell environment, defaults to include LC_ALL=C
|
19
|
+
# set to nil to clear the environment even of LC_ALL
|
20
|
+
#
|
21
|
+
class Shell
|
22
|
+
attr_reader :environment, :command, :status, :stdout, :stderr, :stdin, :cwd
|
23
|
+
|
24
|
+
def initialize(command, options={})
|
25
|
+
@environment = {"LC_ALL" => "C"}
|
26
|
+
@command = command
|
27
|
+
@status = nil
|
28
|
+
@stdout = ""
|
29
|
+
@stderr = ""
|
30
|
+
@stdin = nil
|
31
|
+
@cwd = Dir.tmpdir
|
32
|
+
|
33
|
+
options.each do |opt, val|
|
34
|
+
case opt.to_s
|
35
|
+
when "stdout"
|
36
|
+
raise "stdout should support <<" unless val.respond_to?("<<")
|
37
|
+
@stdout = val
|
38
|
+
|
39
|
+
when "stderr"
|
40
|
+
raise "stderr should support <<" unless val.respond_to?("<<")
|
41
|
+
@stderr = val
|
42
|
+
|
43
|
+
when "stdin"
|
44
|
+
raise "stdin should be a String" unless val.is_a?(String)
|
45
|
+
@stdin = val
|
46
|
+
|
47
|
+
when "cwd"
|
48
|
+
raise "Directory #{val} does not exist" unless File.directory?(val)
|
49
|
+
@cwd = val
|
50
|
+
|
51
|
+
when "environment"
|
52
|
+
if val.nil?
|
53
|
+
@environment = {}
|
54
|
+
else
|
55
|
+
@environment.merge!(val.dup)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Actually does the systemu call passing in the correct environment, stdout and stderr
|
62
|
+
def runcommand
|
63
|
+
opts = {"env" => @environment,
|
64
|
+
"stdout" => @stdout,
|
65
|
+
"stderr" => @stderr,
|
66
|
+
"cwd" => @cwd}
|
67
|
+
|
68
|
+
opts["stdin"] = @stdin if @stdin
|
69
|
+
|
70
|
+
# Running waitpid on the cid here will start a thread
|
71
|
+
# with the waitpid in it, this way even if the thread
|
72
|
+
# that started this process gets killed due to agent
|
73
|
+
# timeout or such there will still be a waitpid waiting
|
74
|
+
# for the child to exit and not leave zombies.
|
75
|
+
@status = systemu(@command, opts) do |cid|
|
76
|
+
begin
|
77
|
+
sleep 1
|
78
|
+
Process::waitpid(cid)
|
79
|
+
rescue SystemExit
|
80
|
+
rescue Errno::ECHILD
|
81
|
+
rescue Exception => e
|
82
|
+
Log.info("Unexpected exception received while waiting for child process: #{e.class}: #{e}")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
require 'digest/sha1'
|
4
|
+
|
5
|
+
module MCollective
|
6
|
+
# A class that assists in encrypting and decrypting data using a
|
7
|
+
# combination of RSA and AES
|
8
|
+
#
|
9
|
+
# Data will be AES encrypted for speed, the Key used in # the AES
|
10
|
+
# stage will be encrypted using RSA
|
11
|
+
#
|
12
|
+
# ssl = SSL.new(public_key, private_key, passphrase)
|
13
|
+
#
|
14
|
+
# data = File.read("largefile.dat")
|
15
|
+
#
|
16
|
+
# crypted_data = ssl.encrypt_with_private(data)
|
17
|
+
#
|
18
|
+
# pp crypted_data
|
19
|
+
#
|
20
|
+
# This will result in a hash of data like:
|
21
|
+
#
|
22
|
+
# crypted = {:key => "crd4NHvG....=",
|
23
|
+
# :data => "XWXlqN+i...=="}
|
24
|
+
#
|
25
|
+
# The key and data will all be base 64 encoded already by default
|
26
|
+
# you can pass a 2nd parameter as false to encrypt_with_private and
|
27
|
+
# counterparts that will prevent the base 64 encoding
|
28
|
+
#
|
29
|
+
# You can pass the data hash into ssl.decrypt_with_public which
|
30
|
+
# should return your original data
|
31
|
+
#
|
32
|
+
# There are matching methods for using a public key to encrypt
|
33
|
+
# data to be decrypted using a private key
|
34
|
+
class SSL
|
35
|
+
attr_reader :public_key_file, :private_key_file, :ssl_cipher
|
36
|
+
|
37
|
+
def initialize(pubkey=nil, privkey=nil, passphrase=nil, cipher=nil)
|
38
|
+
@public_key_file = pubkey
|
39
|
+
@private_key_file = privkey
|
40
|
+
|
41
|
+
@public_key = read_key(:public, pubkey)
|
42
|
+
@private_key = read_key(:private, privkey, passphrase)
|
43
|
+
|
44
|
+
@ssl_cipher = "aes-256-cbc"
|
45
|
+
@ssl_cipher = Config.instance.ssl_cipher if Config.instance.ssl_cipher
|
46
|
+
@ssl_cipher = cipher if cipher
|
47
|
+
|
48
|
+
raise "The supplied cipher '#{@ssl_cipher}' is not supported" unless OpenSSL::Cipher.ciphers.include?(@ssl_cipher)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Encrypts supplied data using AES and then encrypts using RSA
|
52
|
+
# the key and IV
|
53
|
+
#
|
54
|
+
# Return a hash with everything optionally base 64 encoded
|
55
|
+
def encrypt_with_public(plain_text, base64=true)
|
56
|
+
crypted = aes_encrypt(plain_text)
|
57
|
+
|
58
|
+
if base64
|
59
|
+
key = base64_encode(rsa_encrypt_with_public(crypted[:key]))
|
60
|
+
data = base64_encode(crypted[:data])
|
61
|
+
else
|
62
|
+
key = rsa_encrypt_with_public(crypted[:key])
|
63
|
+
data = crypted[:data]
|
64
|
+
end
|
65
|
+
|
66
|
+
{:key => key, :data => data}
|
67
|
+
end
|
68
|
+
|
69
|
+
# Encrypts supplied data using AES and then encrypts using RSA
|
70
|
+
# the key and IV
|
71
|
+
#
|
72
|
+
# Return a hash with everything optionally base 64 encoded
|
73
|
+
def encrypt_with_private(plain_text, base64=true)
|
74
|
+
crypted = aes_encrypt(plain_text)
|
75
|
+
|
76
|
+
if base64
|
77
|
+
key = base64_encode(rsa_encrypt_with_private(crypted[:key]))
|
78
|
+
data = base64_encode(crypted[:data])
|
79
|
+
else
|
80
|
+
key = rsa_encrypt_with_private(crypted[:key])
|
81
|
+
data = crypted[:data]
|
82
|
+
end
|
83
|
+
|
84
|
+
{:key => key, :data => data}
|
85
|
+
end
|
86
|
+
|
87
|
+
# Decrypts data, expects a hash as create with crypt_with_public
|
88
|
+
def decrypt_with_private(crypted, base64=true)
|
89
|
+
raise "Crypted data should include a key" unless crypted.include?(:key)
|
90
|
+
raise "Crypted data should include data" unless crypted.include?(:data)
|
91
|
+
|
92
|
+
if base64
|
93
|
+
key = rsa_decrypt_with_private(base64_decode(crypted[:key]))
|
94
|
+
aes_decrypt(key, base64_decode(crypted[:data]))
|
95
|
+
else
|
96
|
+
key = rsa_decrypt_with_private(crypted[:key])
|
97
|
+
aes_decrypt(key, crypted[:data])
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Decrypts data, expects a hash as create with crypt_with_private
|
102
|
+
def decrypt_with_public(crypted, base64=true)
|
103
|
+
raise "Crypted data should include a key" unless crypted.include?(:key)
|
104
|
+
raise "Crypted data should include data" unless crypted.include?(:data)
|
105
|
+
|
106
|
+
if base64
|
107
|
+
key = rsa_decrypt_with_public(base64_decode(crypted[:key]))
|
108
|
+
aes_decrypt(key, base64_decode(crypted[:data]))
|
109
|
+
else
|
110
|
+
key = rsa_decrypt_with_public(crypted[:key])
|
111
|
+
aes_decrypt(key, crypted[:data])
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Use the public key to RSA encrypt data
|
116
|
+
def rsa_encrypt_with_public(plain_string)
|
117
|
+
raise "No public key set" unless @public_key
|
118
|
+
|
119
|
+
@public_key.public_encrypt(plain_string)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Use the private key to RSA decrypt data
|
123
|
+
def rsa_decrypt_with_private(crypt_string)
|
124
|
+
raise "No private key set" unless @private_key
|
125
|
+
|
126
|
+
@private_key.private_decrypt(crypt_string)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Use the private key to RSA encrypt data
|
130
|
+
def rsa_encrypt_with_private(plain_string)
|
131
|
+
raise "No private key set" unless @private_key
|
132
|
+
|
133
|
+
@private_key.private_encrypt(plain_string)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Use the public key to RSA decrypt data
|
137
|
+
def rsa_decrypt_with_public(crypt_string)
|
138
|
+
raise "No public key set" unless @public_key
|
139
|
+
|
140
|
+
@public_key.public_decrypt(crypt_string)
|
141
|
+
end
|
142
|
+
|
143
|
+
# encrypts a string, returns a hash of key, iv and data
|
144
|
+
def aes_encrypt(plain_string)
|
145
|
+
cipher = OpenSSL::Cipher::Cipher.new(ssl_cipher)
|
146
|
+
cipher.encrypt
|
147
|
+
|
148
|
+
key = cipher.random_key
|
149
|
+
|
150
|
+
cipher.key = key
|
151
|
+
cipher.pkcs5_keyivgen(key)
|
152
|
+
encrypted_data = cipher.update(plain_string) + cipher.final
|
153
|
+
|
154
|
+
{:key => key, :data => encrypted_data}
|
155
|
+
end
|
156
|
+
|
157
|
+
# decrypts a string given key, iv and data
|
158
|
+
def aes_decrypt(key, crypt_string)
|
159
|
+
cipher = OpenSSL::Cipher::Cipher.new(ssl_cipher)
|
160
|
+
|
161
|
+
cipher.decrypt
|
162
|
+
cipher.key = key
|
163
|
+
cipher.pkcs5_keyivgen(key)
|
164
|
+
decrypted_data = cipher.update(crypt_string) + cipher.final
|
165
|
+
end
|
166
|
+
|
167
|
+
# Signs a string using the private key
|
168
|
+
def sign(string, base64=false)
|
169
|
+
sig = @private_key.sign(OpenSSL::Digest::SHA1.new, string)
|
170
|
+
|
171
|
+
base64 ? base64_encode(sig) : sig
|
172
|
+
end
|
173
|
+
|
174
|
+
# Using the public key verifies that a string was signed using the private key
|
175
|
+
def verify_signature(signature, string, base64=false)
|
176
|
+
signature = base64_decode(signature) if base64
|
177
|
+
|
178
|
+
@public_key.verify(OpenSSL::Digest::SHA1.new, signature, string)
|
179
|
+
end
|
180
|
+
|
181
|
+
# base 64 encode a string
|
182
|
+
def base64_encode(string)
|
183
|
+
SSL.base64_encode(string)
|
184
|
+
end
|
185
|
+
|
186
|
+
def self.base64_encode(string)
|
187
|
+
Base64.encode64(string)
|
188
|
+
end
|
189
|
+
|
190
|
+
# base 64 decode a string
|
191
|
+
def base64_decode(string)
|
192
|
+
SSL.base64_decode(string)
|
193
|
+
end
|
194
|
+
|
195
|
+
def self.base64_decode(string)
|
196
|
+
Base64.decode64(string)
|
197
|
+
end
|
198
|
+
|
199
|
+
def md5(string)
|
200
|
+
SSL.md5(string)
|
201
|
+
end
|
202
|
+
|
203
|
+
def self.md5(string)
|
204
|
+
Digest::MD5.hexdigest(string)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Reads either a :public or :private key from disk, uses an
|
208
|
+
# optional passphrase to read the private key
|
209
|
+
def read_key(type, key=nil, passphrase=nil)
|
210
|
+
return key if key.nil?
|
211
|
+
|
212
|
+
raise "Could not find key #{key}" unless File.exist?(key)
|
213
|
+
|
214
|
+
if type == :public
|
215
|
+
begin
|
216
|
+
key = OpenSSL::PKey::RSA.new(File.read(key))
|
217
|
+
rescue OpenSSL::PKey::RSAError
|
218
|
+
key = OpenSSL::X509::Certificate.new(File.read(key)).public_key
|
219
|
+
end
|
220
|
+
|
221
|
+
# Ruby < 1.9.3 had a bug where it does not correctly clear the
|
222
|
+
# queue of errors while reading a key. It tries various ways
|
223
|
+
# to read the key and each failing attempt pushes an error onto
|
224
|
+
# the queue. With pubkeys only the 3rd attempt pass leaving 2
|
225
|
+
# stale errors on the error queue.
|
226
|
+
#
|
227
|
+
# In 1.9.3 they fixed this by simply discarding the errors after
|
228
|
+
# every attempt. So we simulate this fix here for older rubies
|
229
|
+
# as without it we get SSL_read errors from the Stomp+TLS sessions
|
230
|
+
#
|
231
|
+
# We do this only on 1.8 relying on 1.9.3 to do the right thing
|
232
|
+
# and we do not support 1.9 less than 1.9.3
|
233
|
+
#
|
234
|
+
# See http://bugs.ruby-lang.org/issues/4550
|
235
|
+
OpenSSL.errors if Util.ruby_version =~ /^1.8/
|
236
|
+
|
237
|
+
return key
|
238
|
+
elsif type == :private
|
239
|
+
return OpenSSL::PKey::RSA.new(File.read(key), passphrase)
|
240
|
+
else
|
241
|
+
raise "Can only load :public or :private keys"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
end
|
246
|
+
end
|