logstash-filter-cipher 2.0.2 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3186e0367bb687d4d9fb244aec75e2961c16d675
4
- data.tar.gz: c9b0629f9cd62859626220dd0120e13d1e40bd7b
3
+ metadata.gz: beeedcce9482610f01afe1d5a6abb3dadf1be9d8
4
+ data.tar.gz: e8ff3189814e38d63e9729893642978404386191
5
5
  SHA512:
6
- metadata.gz: 40432e0006c8d3d5c7cc322175853fac6c086cc14f49c8a0b4854965ef587b56156aa4f7951e9de0fb0564ae8d21dea61028d72bdb61c9e6a1faa49e9240526d
7
- data.tar.gz: d25448e42cc9d12d0d1cd6213ad36d96a2e9affc1e0367e3933aa31fd4f53a972bfa1181dd18b20e60a20ab1ec40c8633f8eba117a250b6123d7a515afdd3b80
6
+ metadata.gz: 90237b428e896c39f895e85b68d781225b1ff2d7edcbdb3103cbec327d2de918c8dd3843b799b98024bfc36762583f673bb3c4d637f4b42a787a565e44be6fa9
7
+ data.tar.gz: 7df726409a16267b2f71a9027b7a0a25fc4b962d7c8816534592f886f9d2842ccd51f39fd03fde880ff97f668c6cbb8b404c4fef27059b683a9ef71c7fe836cc
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ ## 2.0.3
2
+ - fixes base64 encoding issue, adds support for random IVs
3
+
1
4
  ## 2.0.0
2
5
  - Plugins were updated to follow the new shutdown semantic, this mainly allows Logstash to instruct input plugins to terminate gracefully,
3
6
  instead of using Thread.raise on the plugins' threads. Ref: https://github.com/elastic/logstash/pull/3895
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Logstash Plugin
2
2
 
3
+ [![Build
4
+ Status](http://build-eu-00.elastic.co/view/LS%20Plugins/view/LS%20Filters/job/logstash-plugin-filter-cipher-unit/badge/icon)](http://build-eu-00.elastic.co/view/LS%20Plugins/view/LS%20Filters/job/logstash-plugin-filter-cipher-unit/)
5
+
3
6
  This is a plugin for [Logstash](https://github.com/elastic/logstash).
4
7
 
5
8
  It is fully free and fully open source. The license is Apache 2.0, meaning you are pretty much free to use it however you want in whatever way.
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
  require "logstash/filters/base"
3
3
  require "logstash/namespace"
4
+ require "openssl"
4
5
 
5
6
 
6
7
  # This filter parses a source and apply a cipher or decipher before
@@ -31,23 +32,31 @@ class LogStash::Filters::Cipher < LogStash::Filters::Base
31
32
  config :base64, :validate => :boolean, :default => true
32
33
 
33
34
  # The key to use
35
+ #
36
+ # NOTE: If you encounter an error message at runtime containing the following:
37
+ #
38
+ # "java.security.InvalidKeyException: Illegal key size: possibly you need to install
39
+ # Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files for your JRE"
40
+ #
41
+ # Please read the following: https://github.com/jruby/jruby/wiki/UnlimitedStrengthCrypto
42
+ #
34
43
  config :key, :validate => :string
35
44
 
36
45
  # The key size to pad
37
46
  #
38
- # It depends of the cipher algorythm.I your key don't need
47
+ # It depends of the cipher algorithm. If your key doesn't need
39
48
  # padding, don't set this parameter
40
49
  #
41
- # Example, for AES-256, we must have 32 char long key
50
+ # Example, for AES-128, we must have 16 char long key. AES-256 = 32 chars
42
51
  # [source,ruby]
43
- # filter { cipher { key_size => 32 }
52
+ # filter { cipher { key_size => 16 }
44
53
  #
45
- config :key_size, :validate => :number, :default => 32
54
+ config :key_size, :validate => :number, :default => 16
46
55
 
47
56
  # The character used to pad the key
48
57
  config :key_pad, :default => "\0"
49
58
 
50
- # The cipher algorythm
59
+ # The cipher algorithm
51
60
  #
52
61
  # A list of supported algorithms can be obtained by
53
62
  # [source,ruby]
@@ -59,12 +68,12 @@ class LogStash::Filters::Cipher < LogStash::Filters::Base
59
68
  # Valid values are encrypt or decrypt
60
69
  config :mode, :validate => :string, :required => true
61
70
 
62
- # Cypher padding to use. Enables or disables padding.
71
+ # Cipher padding to use. Enables or disables padding.
63
72
  #
64
- # By default encryption operations are padded using standard block padding
65
- # and the padding is checked and removed when decrypting. If the pad
66
- # parameter is zero then no padding is performed, the total amount of data
67
- # encrypted or decrypted must then be a multiple of the block size or an
73
+ # By default encryption operations are padded using standard block padding
74
+ # and the padding is checked and removed when decrypting. If the pad
75
+ # parameter is zero then no padding is performed, the total amount of data
76
+ # encrypted or decrypted must then be a multiple of the block size or an
68
77
  # error will occur.
69
78
  #
70
79
  # See EVP_CIPHER_CTX_set_padding for further information.
@@ -73,16 +82,57 @@ class LogStash::Filters::Cipher < LogStash::Filters::Base
73
82
  # If you want to change it, set this parameter. If you want to disable
74
83
  # it, Set this parameter to 0
75
84
  # [source,ruby]
76
- # filter { cipher { padding => 0 }}
85
+ # filter { cipher { cipher_padding => 0 }}
77
86
  config :cipher_padding, :validate => :string
78
87
 
79
- # The initialization vector to use
88
+ # The initialization vector to use (statically hard-coded). For
89
+ # a random IV see the iv_random_length property
90
+ #
91
+ # NOTE: If iv_random_length is set, it takes precedence over any value set for "iv"
80
92
  #
81
93
  # The cipher modes CBC, CFB, OFB and CTR all need an "initialization
82
94
  # vector", or short, IV. ECB mode is the only mode that does not require
83
95
  # an IV, but there is almost no legitimate use case for this mode
84
96
  # because of the fact that it does not sufficiently hide plaintext patterns.
85
- config :iv, :validate => :string
97
+ #
98
+ # For AES algorithms set this to a 16 byte string.
99
+ # [source,ruby]
100
+ # filter { cipher { iv => "1234567890123456" }}
101
+ #
102
+ # Deprecated: Please use `iv_random_length` instead
103
+ config :iv, :validate => :string, :deprecated => "Please use 'iv_random_length'"
104
+
105
+ # Force an random IV to be used per encryption invocation and specify
106
+ # the length of the random IV that will be generated via:
107
+ #
108
+ # OpenSSL::Random.random_bytes(int_length)
109
+ #
110
+ # If iv_random_length is set, it takes precedence over any value set for "iv"
111
+ #
112
+ # Enabling this will force the plugin to generate a unique
113
+ # random IV for each encryption call. This random IV will be prepended to the
114
+ # encrypted result bytes and then base64 encoded. On decryption "iv_random_length" must
115
+ # also be set to utilize this feature. Random IV's are better than statically
116
+ # hardcoded IVs
117
+ #
118
+ # For AES algorithms you can set this to a 16
119
+ # [source,ruby]
120
+ # filter { cipher { iv_random_length => 16 }}
121
+ config :iv_random_length, :validate => :number
122
+
123
+ # If this is set the internal Cipher instance will be
124
+ # re-used up to @max_cipher_reuse times before being
125
+ # reset() and re-created from scratch. This is an option
126
+ # for efficiency where lots of data is being encrypted
127
+ # and decrypted using this filter. This lets the filter
128
+ # avoid creating new Cipher instances over and over
129
+ # for each encrypt/decrypt operation.
130
+ #
131
+ # This is optional, the default is no re-use of the Cipher
132
+ # instance and max_cipher_reuse = 1 by default
133
+ # [source,ruby]
134
+ # filter { cipher { max_cipher_reuse => 1000 }}
135
+ config :max_cipher_reuse, :validate => :number, :default => 1
86
136
 
87
137
  def register
88
138
  require 'base64' if @base64
@@ -96,30 +146,82 @@ class LogStash::Filters::Cipher < LogStash::Filters::Base
96
146
 
97
147
  #If decrypt or encrypt fails, we keep it it intact.
98
148
  begin
149
+
150
+ if (event[@source].nil? || event[@source].empty?)
151
+ @logger.debug("Event to filter, event 'source' field: " + @source + " was null(nil) or blank, doing nothing")
152
+ return
153
+ end
154
+
99
155
  #@logger.debug("Event to filter", :event => event)
100
156
  data = event[@source]
101
157
  if @mode == "decrypt"
102
- data = Base64.decode64(data) if @base64 == true
158
+ data = Base64.strict_decode64(data) if @base64 == true
159
+
160
+ if !@iv_random_length.nil?
161
+ @random_iv = data.byteslice(0,@iv_random_length)
162
+ data = data.byteslice(@iv_random_length..data.length)
163
+ end
164
+
165
+ end
166
+
167
+ if !@iv_random_length.nil? and @mode == "encrypt"
168
+ @random_iv = OpenSSL::Random.random_bytes(@iv_random_length)
103
169
  end
170
+
171
+ # if iv_random_length is specified, generate a new one
172
+ # and force the cipher's IV = to the random value
173
+ if !@iv_random_length.nil?
174
+ @cipher.iv = @random_iv
175
+ end
176
+
104
177
  result = @cipher.update(data) + @cipher.final
178
+
105
179
  if @mode == "encrypt"
106
- data = Base64.encode64(data) if @base64 == true
180
+
181
+ # if we have a random_iv, prepend that to the crypted result
182
+ if !@random_iv.nil?
183
+ result = @random_iv + result
184
+ end
185
+
186
+ result = Base64.strict_encode64(result).encode("utf-8") if @base64 == true
107
187
  end
188
+
108
189
  rescue => e
109
190
  @logger.warn("Exception catch on cipher filter", :event => event, :error => e)
191
+
192
+ # force a re-initialize on error to be safe
193
+ init_cipher
194
+
110
195
  else
196
+ @total_cipher_uses += 1
197
+
198
+ result = result.force_encoding("utf-8") if @mode == "decrypt"
199
+
111
200
  event[@target]= result
201
+
112
202
  #Is it necessary to add 'if !result.nil?' ? exception have been already catched.
113
203
  #In doubt, I keep it.
114
204
  filter_matched(event) if !result.nil?
115
- #Too much bad result can be a problem, reinit cipher prevent this.
116
- init_cipher
205
+
206
+ if !@max_cipher_reuse.nil? and @total_cipher_uses >= @max_cipher_reuse
207
+ @logger.debug("max_cipher_reuse["+@max_cipher_reuse.to_s+"] reached, total_cipher_uses = "+@total_cipher_uses.to_s)
208
+ init_cipher
209
+ end
210
+
117
211
  end
118
212
  end # def filter
119
213
 
120
214
  def init_cipher
121
215
 
216
+ if !@cipher.nil?
217
+ @cipher.reset
218
+ @cipher = nil
219
+ end
220
+
122
221
  @cipher = OpenSSL::Cipher.new(@algorithm)
222
+
223
+ @total_cipher_uses = 0
224
+
123
225
  if @mode == "encrypt"
124
226
  @cipher.encrypt
125
227
  elsif @mode == "decrypt"
@@ -136,11 +238,18 @@ class LogStash::Filters::Cipher < LogStash::Filters::Base
136
238
 
137
239
  @cipher.key = @key
138
240
 
139
- @cipher.iv = @iv if @iv
241
+ if !@iv.nil? and !@iv.empty? and @iv_random_length.nil?
242
+ @cipher.iv = @iv if @iv
243
+
244
+ elsif !@iv_random_length.nil?
245
+ @logger.debug("iv_random_length is configured, ignoring any statically defined value for 'iv'", :iv_random_length => @iv_random_length)
246
+ else
247
+ raise "cipher plugin: either 'iv' or 'iv_random_length' must be configured, but not both; aborting"
248
+ end
140
249
 
141
250
  @cipher.padding = @cipher_padding if @cipher_padding
142
251
 
143
- @logger.debug("Cipher initialisation done", :mode => @mode, :key => @key, :iv => @iv, :cipher_padding => @cipher_padding)
252
+ @logger.debug("Cipher initialisation done", :mode => @mode, :key => @key, :iv => @iv, :iv_random => @iv_random, :cipher_padding => @cipher_padding)
144
253
  end # def init_cipher
145
254
 
146
255
 
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
 
3
3
  s.name = 'logstash-filter-cipher'
4
- s.version = '2.0.2'
4
+ s.version = '2.0.3'
5
5
  s.licenses = ['Apache License (2.0)']
6
6
  s.summary = "This filter parses a source and apply a cipher or decipher before storing it in the target"
7
7
  s.description = "This gem is a logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/plugin install gemname. This gem is not a stand-alone program"
@@ -1,5 +1,215 @@
1
- require "logstash/devutils/rspec/spec_helper"
2
- require 'logstash/filters/cipher'
1
+ # encoding: utf-8
2
+
3
+ require "logstash/devutils/rspec/spec_helper"
4
+ require 'logstash/filters/cipher'
5
+
6
+ describe LogStash::Filters::Cipher do
7
+
8
+ let(:cleartext) do
9
+ 'شسيبشن٤٤ت٥ت داھدساققبمر фывапролдзщшгнекутцйячсмить asdfghjklqpoiuztreyxcvbnm,.-öäü+ä123ß´yö.,;LÖÜ*O 來少精清皆人址法田手扌打表氵開日大木裝 1234567890#$%^&*()!@#;:\'?.>,<testAESEn+=_-~`}]'
10
+ end
11
+
12
+ let(:event) do
13
+ "{\"message\":\"#{cleartext}\"}"
14
+ end
15
+
16
+ let(:pipeline) { LogStash::Pipeline.new(config) }
17
+
18
+ let(:events) do
19
+ arr = event.is_a?(Array) ? event : [event]
20
+ arr.map do |evt|
21
+ LogStash::Event.new(evt.is_a?(String) ? LogStash::Json.load(evt) : evt)
22
+ end
23
+ end
24
+
25
+ let(:results) do
26
+ pipeline.instance_eval { @filters.each(&:register) }
27
+ results = []
28
+ events.each do |evt|
29
+ # filter call the block on all filtered events, included new events added by the filter
30
+ pipeline.filter(evt) do |filtered_event|
31
+ results.push(filtered_event)
32
+ end
33
+ end
34
+ pipeline.flush_filters(:final => true) { |flushed_event| results << flushed_event }
35
+
36
+ results.select { |e| !e.cancelled? }
37
+ end
38
+
39
+ describe 'single event, encrypt/decrypt aes-128-cbc, 16b RANDOM IV, 16b key, b64 encode' do
40
+
41
+ let(:config) do
42
+ <<-CONFIG
43
+ filter {
44
+
45
+ cipher {
46
+ algorithm => "aes-128-cbc"
47
+ cipher_padding => 1
48
+ iv_random_length => 16
49
+ key => "1234567890123456"
50
+ key_size => 16
51
+ mode => "encrypt"
52
+ source => "message"
53
+ target => "message_crypted"
54
+ base64 => true
55
+ max_cipher_reuse => 1
56
+ }
57
+
58
+ cipher {
59
+ algorithm => "aes-128-cbc"
60
+ cipher_padding => 1
61
+ iv_random_length => 16
62
+ key => "1234567890123456"
63
+ key_size => 16
64
+ mode => "decrypt"
65
+ source => "message_crypted"
66
+ target => "message_decrypted"
67
+ base64 => true
68
+ max_cipher_reuse => 1
69
+ }
70
+ }
71
+ CONFIG
72
+ end
73
+
74
+ it 'validate initial cleartext message' do
75
+ result = results.first
76
+ expect(result["message"]).to eq(cleartext)
77
+ end
78
+
79
+ it 'validate decrypted message' do
80
+ result = results.first
81
+ expect(result["message_decrypted"]).to eq(result["message"])
82
+ end
83
+
84
+ it 'validate encrypted message is not equal to message' do
85
+ result = results.first
86
+ expect(result["message"]).not_to eq(result["message_crypted"])
87
+ end
88
+
89
+ end
90
+
91
+
92
+ describe 'single event, encrypt/decrypt aes-128-cbc, 16b STATIC IV, 16b key, b64 encode' do
93
+
94
+ let(:config) do
95
+ <<-CONFIG
96
+ filter {
97
+
98
+ cipher {
99
+ algorithm => "aes-128-cbc"
100
+ cipher_padding => 1
101
+ iv => "1234567890123456"
102
+ key => "1234567890123456"
103
+ key_size => 16
104
+ mode => "encrypt"
105
+ source => "message"
106
+ target => "message_crypted"
107
+ base64 => true
108
+ max_cipher_reuse => 1
109
+ }
110
+
111
+ cipher {
112
+ algorithm => "aes-128-cbc"
113
+ cipher_padding => 1
114
+ iv => "1234567890123456"
115
+ key => "1234567890123456"
116
+ key_size => 16
117
+ mode => "decrypt"
118
+ source => "message_crypted"
119
+ target => "message_decrypted"
120
+ base64 => true
121
+ max_cipher_reuse => 1
122
+ }
123
+ }
124
+ CONFIG
125
+ end
126
+
127
+ it 'validate initial cleartext message' do
128
+ result = results.first
129
+ expect(result["message"]).to eq(cleartext)
130
+ end
131
+
132
+ it 'validate decrypted message' do
133
+ result = results.first
134
+ expect(result["message_decrypted"]).to eq(result["message"])
135
+ end
136
+
137
+ it 'validate encrypted message is not equal to message' do
138
+ result = results.first
139
+ expect(result["message"]).not_to eq(result["message_crypted"])
140
+ end
141
+
142
+ end
143
+
144
+
145
+ describe '1000 events, 11 re-use, encrypt/decrypt aes-128-cbc, 16b RANDOM IV, 16b key, b64 encode' do
146
+
147
+ total_events = 1000
148
+
149
+ let(:event) do
150
+ events = []
151
+ (1..total_events).each do |i|
152
+ events.push("{\"message\":\"#{cleartext}\"}")
153
+ end
154
+ return events
155
+ end
156
+
157
+ let(:config) do
158
+ <<-CONFIG
159
+ filter {
160
+
161
+ cipher {
162
+ algorithm => "aes-128-cbc"
163
+ cipher_padding => 1
164
+ iv_random_length => 16
165
+ key => "1234567890123456"
166
+ key_size => 16
167
+ mode => "encrypt"
168
+ source => "message"
169
+ target => "message_crypted"
170
+ base64 => true
171
+ max_cipher_reuse => 11
172
+ }
173
+
174
+ cipher {
175
+ algorithm => "aes-128-cbc"
176
+ cipher_padding => 1
177
+ iv_random_length => 16
178
+ key => "1234567890123456"
179
+ key_size => 16
180
+ mode => "decrypt"
181
+ source => "message_crypted"
182
+ target => "message_decrypted"
183
+ base64 => true
184
+ max_cipher_reuse => 11
185
+ }
186
+ }
187
+ CONFIG
188
+ end
189
+
190
+ it 'validate total events' do
191
+ expect(results.length).to eq(total_events)
192
+ end
193
+
194
+ it 'validate initial cleartext message' do
195
+ results.each do |result|
196
+ expect(result["message"]).to eq(cleartext)
197
+ end
198
+ end
199
+
200
+ it 'validate decrypted message' do
201
+ results.each do |result|
202
+ expect(result["message_decrypted"]).to eq(result["message"])
203
+ end
204
+ end
205
+
206
+ it 'validate encrypted message is not equal to message' do
207
+ results.each do |result|
208
+ expect(result["message"]).not_to eq(result["message_crypted"])
209
+ end
210
+ end
211
+
212
+ end
213
+
214
+ end
3
215
 
4
- describe LogStash::Filters::Cipher do
5
- end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-filter-cipher
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 2.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-14 00:00:00.000000000 Z
11
+ date: 2016-01-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement