logstash-filter-cipher 2.0.2 → 2.0.3

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.
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