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.

Files changed (103) hide show
  1. data/bin/mc-call-agent +54 -0
  2. data/bin/mco +27 -0
  3. data/lib/mcollective.rb +70 -0
  4. data/lib/mcollective/agents.rb +160 -0
  5. data/lib/mcollective/application.rb +354 -0
  6. data/lib/mcollective/applications.rb +145 -0
  7. data/lib/mcollective/client.rb +292 -0
  8. data/lib/mcollective/config.rb +202 -0
  9. data/lib/mcollective/connector.rb +18 -0
  10. data/lib/mcollective/connector/base.rb +24 -0
  11. data/lib/mcollective/facts.rb +39 -0
  12. data/lib/mcollective/facts/base.rb +86 -0
  13. data/lib/mcollective/log.rb +103 -0
  14. data/lib/mcollective/logger.rb +5 -0
  15. data/lib/mcollective/logger/base.rb +73 -0
  16. data/lib/mcollective/logger/console_logger.rb +61 -0
  17. data/lib/mcollective/logger/file_logger.rb +46 -0
  18. data/lib/mcollective/logger/syslog_logger.rb +53 -0
  19. data/lib/mcollective/matcher.rb +16 -0
  20. data/lib/mcollective/matcher/parser.rb +93 -0
  21. data/lib/mcollective/matcher/scanner.rb +123 -0
  22. data/lib/mcollective/message.rb +201 -0
  23. data/lib/mcollective/monkey_patches.rb +104 -0
  24. data/lib/mcollective/optionparser.rb +164 -0
  25. data/lib/mcollective/pluginmanager.rb +180 -0
  26. data/lib/mcollective/pluginpackager.rb +26 -0
  27. data/lib/mcollective/pluginpackager/agent_definition.rb +79 -0
  28. data/lib/mcollective/pluginpackager/standard_definition.rb +59 -0
  29. data/lib/mcollective/registration.rb +16 -0
  30. data/lib/mcollective/registration/base.rb +75 -0
  31. data/lib/mcollective/rpc.rb +188 -0
  32. data/lib/mcollective/rpc/actionrunner.rb +142 -0
  33. data/lib/mcollective/rpc/agent.rb +441 -0
  34. data/lib/mcollective/rpc/audit.rb +38 -0
  35. data/lib/mcollective/rpc/client.rb +793 -0
  36. data/lib/mcollective/rpc/ddl.rb +258 -0
  37. data/lib/mcollective/rpc/helpers.rb +339 -0
  38. data/lib/mcollective/rpc/progress.rb +63 -0
  39. data/lib/mcollective/rpc/reply.rb +61 -0
  40. data/lib/mcollective/rpc/request.rb +51 -0
  41. data/lib/mcollective/rpc/result.rb +41 -0
  42. data/lib/mcollective/rpc/stats.rb +185 -0
  43. data/lib/mcollective/runnerstats.rb +90 -0
  44. data/lib/mcollective/security.rb +26 -0
  45. data/lib/mcollective/security/base.rb +237 -0
  46. data/lib/mcollective/shell.rb +87 -0
  47. data/lib/mcollective/ssl.rb +246 -0
  48. data/lib/mcollective/unix_daemon.rb +37 -0
  49. data/lib/mcollective/util.rb +274 -0
  50. data/lib/mcollective/vendor.rb +41 -0
  51. data/lib/mcollective/vendor/require_vendored.rb +2 -0
  52. data/lib/mcollective/windows_daemon.rb +25 -0
  53. data/spec/Rakefile +16 -0
  54. data/spec/fixtures/application/test.rb +7 -0
  55. data/spec/fixtures/test-cert.pem +15 -0
  56. data/spec/fixtures/test-private.pem +15 -0
  57. data/spec/fixtures/test-public.pem +6 -0
  58. data/spec/monkey_patches/instance_variable_defined.rb +7 -0
  59. data/spec/spec.opts +1 -0
  60. data/spec/spec_helper.rb +25 -0
  61. data/spec/unit/agents_spec.rb +280 -0
  62. data/spec/unit/application_spec.rb +636 -0
  63. data/spec/unit/applications_spec.rb +155 -0
  64. data/spec/unit/array.rb +30 -0
  65. data/spec/unit/config_spec.rb +148 -0
  66. data/spec/unit/facts/base_spec.rb +118 -0
  67. data/spec/unit/facts_spec.rb +39 -0
  68. data/spec/unit/log_spec.rb +71 -0
  69. data/spec/unit/logger/base_spec.rb +110 -0
  70. data/spec/unit/logger/syslog_logger_spec.rb +86 -0
  71. data/spec/unit/matcher/parser_spec.rb +106 -0
  72. data/spec/unit/matcher/scanner_spec.rb +71 -0
  73. data/spec/unit/message_spec.rb +401 -0
  74. data/spec/unit/optionparser_spec.rb +113 -0
  75. data/spec/unit/pluginmanager_spec.rb +173 -0
  76. data/spec/unit/pluginpackager/agent_definition_spec.rb +130 -0
  77. data/spec/unit/pluginpackager/standard_definition_spec.rb +75 -0
  78. data/spec/unit/plugins/mcollective/connector/activemq_spec.rb +533 -0
  79. data/spec/unit/plugins/mcollective/connector/stomp/eventlogger_spec.rb +34 -0
  80. data/spec/unit/plugins/mcollective/connector/stomp_spec.rb +417 -0
  81. data/spec/unit/plugins/mcollective/packagers/ospackage_spec.rb +229 -0
  82. data/spec/unit/plugins/mcollective/security/psk_spec.rb +156 -0
  83. data/spec/unit/registration/base_spec.rb +77 -0
  84. data/spec/unit/rpc/actionrunner_spec.rb +213 -0
  85. data/spec/unit/rpc/agent_spec.rb +155 -0
  86. data/spec/unit/rpc/client_spec.rb +523 -0
  87. data/spec/unit/rpc/ddl_spec.rb +388 -0
  88. data/spec/unit/rpc/helpers_spec.rb +55 -0
  89. data/spec/unit/rpc/reply_spec.rb +143 -0
  90. data/spec/unit/rpc/request_spec.rb +115 -0
  91. data/spec/unit/rpc/result_spec.rb +66 -0
  92. data/spec/unit/rpc/stats_spec.rb +288 -0
  93. data/spec/unit/runnerstats_spec.rb +40 -0
  94. data/spec/unit/security/base_spec.rb +279 -0
  95. data/spec/unit/shell_spec.rb +144 -0
  96. data/spec/unit/ssl_spec.rb +244 -0
  97. data/spec/unit/symbol.rb +11 -0
  98. data/spec/unit/unix_daemon.rb +41 -0
  99. data/spec/unit/util_spec.rb +342 -0
  100. data/spec/unit/vendor_spec.rb +34 -0
  101. data/spec/unit/windows_daemon.rb +43 -0
  102. data/spec/windows_spec.opts +1 -0
  103. 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