nm_datafile 0.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d0e2702e30b76edee854437b9198f80aa8a0e17f
4
+ data.tar.gz: 2d6c9faeded184a7f7b460541c386be6f6a9a03b
5
+ SHA512:
6
+ metadata.gz: 599da7c695dca546ddf93b02e2020454acd3aa4a90119dc8cf9ac0e16424797c757a867736002288f9862b5496e5259a44ea811d735a2966b66184b0ecbb235a
7
+ data.tar.gz: bead10acc0a21dc9332ef46d87c9d0269efee22175d8728610a78cbd93a0c82d4c2867de358bbfa91b234be21c5dd91ded066c48478f7768238e4a8d9db61790
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in nm_datafile.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 antisec
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # NmDatafile
2
+
3
+ NmDatafile is a library that defines a file format that makes adding files and strings to an easy, selfencrypting file that can use either asymetric or symetric cryptography.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'nm_datafile'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install nm_datafile
20
+
21
+
22
+ ## Code Example
23
+
24
+ nmd = NmDatafile.new(:shippable_file)
25
+
26
+ nmd.sales = [1,2,3,4]
27
+ nmd.ready_for_shipment_batch = 1
28
+
29
+ nmd.save(path_to_file)
30
+
31
+ nmd_loaded = NmDatafile::Load(path_to_file)
32
+
33
+ nmd_loaded.sales #=> [1,2,3,4]
34
+
35
+
36
+ ## Usage
37
+
38
+ First define a file Schema.
39
+ For instance, if you wanted a file type called 'shippable_file' and you wanted it to have many records of 'sales', 'line_items', etc,
40
+ and you wanted your file to have a string of data named 'ready_for_shipment_batch', then you would define it like so.
41
+
42
+ $nm_datafile_schemas = {
43
+ schemas: {
44
+ :shippable_file => {
45
+ data_collections: [:sales, :line_items, :discounts, :addresses, :ubws, :encryption_pairs], # name the data that is input into the NMDatafile as an array
46
+ data_objects: [:ready_for_shipment_batch]
47
+ }
48
+ }
49
+ }
50
+
51
+ TODO: Specify encryption type on file as well...
52
+
53
+ Then you'd want to create the NMD file:
54
+
55
+ And finally save it as shown below:
56
+
57
+
58
+ ## Contributing
59
+
60
+ 1. Fork it ( https://github.com/[my-github-username]/nm_datafile/fork )
61
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
62
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
63
+ 4. Push to the branch (`git push origin my-new-feature`)
64
+ 5. Create a new Pull Request
65
+
66
+
67
+
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ task :default do
6
+ RSpec::Core::RakeTask.new(:spec)
7
+ Rake::Task["spec"].execute
8
+ end
9
+
data/changelog.md ADDED
@@ -0,0 +1,16 @@
1
+ ### 0.0.1
2
+
3
+ * Code migrated into this repo
4
+ * Created some tests
5
+ * Get pull out the class methods from the class
6
+ ** 1. Pull all those methods into a module
7
+ ** 2. Include them on the class where needed.
8
+ ** 3. Extend them onto the parent namespace module
9
+ ** 4. Plug the gem into rails and see if it still works.
10
+
11
+
12
+ ### 0.0.0
13
+
14
+ Skeleton
15
+
16
+
@@ -0,0 +1,33 @@
1
+ # https://gist.github.com/nono/2995118
2
+
3
+ require "openssl"
4
+
5
+ module NmDatafile
6
+
7
+ class BF < Struct.new(:key, :pad_with_spaces)
8
+ def encrypt(str)
9
+ cipher = OpenSSL::Cipher.new('bf-ecb').encrypt
10
+ if pad_with_spaces
11
+ str += " " until str.bytesize % 8 == 0
12
+ cipher.padding = 0
13
+ end
14
+ cipher.key = key
15
+ binary_data = cipher.update(str) << cipher.final
16
+ hex_encoded = binary_data.unpack('H*').first
17
+ end
18
+
19
+ def decrypt(hex_encoded)
20
+ cipher = OpenSSL::Cipher.new('bf-ecb').decrypt
21
+ cipher.padding = 0 if pad_with_spaces
22
+ cipher.key = key
23
+ binary_data = [hex_encoded].pack('H*')
24
+ str = cipher.update(binary_data) << cipher.final
25
+ str.force_encoding(Encoding::UTF_8)
26
+ str
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+
33
+
@@ -0,0 +1,146 @@
1
+ require 'base64'
2
+
3
+ module NmDatafile
4
+
5
+ module Crypto
6
+ # The zip implementation... should use 7z though and throw this out when it's done... or truecript or gpg...
7
+ def decode_protected_zip_old_zip_based(password, decryptable_portion)
8
+ encryptable_portion = Tempfile.new('encryptable_portion', "#{Rails.root}/tmp")
9
+ FileUtils.rm(encryptable_portion.path)
10
+ File.binwrite(encryptable_portion.path, decryptable_portion)
11
+
12
+ supress_errors = "2>/dev/null"
13
+ decompress_zip_cmd = "funzip -'#{password}' '#{encryptable_portion.path}' #{supress_errors}"
14
+
15
+ clear_text_hash = `#{decompress_zip_cmd}`.chomp
16
+
17
+ clear_text_hash = convert_newline_chars_back_to_symbols(clear_text_hash)
18
+
19
+ FileUtils.rm(encryptable_portion.path)
20
+ clear_text_hash
21
+ end
22
+
23
+ # TODu: rename to decode_string_as_password_protected
24
+ def decode_string_as_password_protected(password, decryptable_portion)
25
+ #require 'b_f'
26
+ bf = BF.new(password, true)
27
+
28
+ bf.decrypt(decryptable_portion)
29
+ end
30
+
31
+
32
+ # Takes in a password and binary data of protected archive file and outputs
33
+ # a string of it's first entry in clear text which should be a hash
34
+ # of {:file_name, "file_data"}
35
+ def decode_password_protected_string(password, decryptable_portion)
36
+ decode = decode_string_as_password_protected(password, decryptable_portion)
37
+ end
38
+
39
+
40
+
41
+ # converts the string pairs into symbol/ string pairs
42
+ def symbolize_keys(decode)
43
+ p = {}
44
+ decode.each do |key_pair|
45
+ p.merge!( { key_pair[0].to_sym => key_pair[1] } )
46
+ end
47
+ p
48
+ end
49
+
50
+
51
+
52
+
53
+ def convert_newline_chars_back_to_symbols(clear_text_hash)
54
+ clear_text_hash.gsub("\n", "\\n")
55
+ end
56
+
57
+ # I think 7z is considered secure and zip is considered insecure so write this
58
+ def decode_protected_7z(password, decryptable_portion)
59
+ # TODu: implement
60
+ end
61
+
62
+
63
+ def decrypt_encryptable_data!(password, hash)
64
+ return if hash[:crypt].nil? # leave this function if there's no 'crypt' entry to decrypt
65
+
66
+ decode = decode_string_into_NMDatafile_stores(password, hash[:crypt])
67
+
68
+ hash.delete :crypt
69
+
70
+ hash.merge!(decode)
71
+ end
72
+
73
+ def decode_string_into_NMDatafile_stores(password, crypt_string)
74
+ decode = YAML::load decode_password_protected_string(password, crypt_string)
75
+ decode = symbolize_keys(decode)
76
+ end
77
+
78
+
79
+
80
+ def clean_decrypt_string(string)
81
+ # string = Base64.decode64 string
82
+ # decode_password_protected_string(@@unsecure_pass, string)
83
+ NMDatafile.fast_decrypt_string_with_pass(@@unsecure_pass, string)
84
+ end
85
+
86
+
87
+
88
+ def fast_encrypt_string_with_pass(pass, string)
89
+ encoded_as_base64 = Base64.encode64(string)
90
+ rearranged = rearrangement(encoded_as_base64)
91
+ obfs = obfuscated_ending(rearranged)
92
+ Base64.encode64(obfs)
93
+ end
94
+
95
+ def fast_decrypt_string_with_pass(pass, string)
96
+ obfs = Base64.decode64(string)
97
+ rearranged = obfuscated_ending_undo(obfs)
98
+ encoded_as_base64 = rearrangement_undo(rearranged)
99
+ Base64.decode64(encoded_as_base64)
100
+ end
101
+
102
+ def rearrangement(s)
103
+ s = the_last_three_chars(s) + the_string_minus_the_last_three_chars(s)
104
+ end
105
+
106
+ def rearrangement_undo(s)
107
+ s = the_string_minus_the_first_three_chars(s) + the_first_three_chars(s)
108
+ end
109
+
110
+
111
+ def obfuscated_ending(s)
112
+ junk = "tlD3=\n"
113
+ s + junk
114
+ end
115
+
116
+ def obfuscated_ending_undo(s)
117
+ junk = "tlD3=\n"
118
+ s[0...(-1*junk.length)]
119
+ end
120
+
121
+ # hide these somewhere, so ugly
122
+ def the_last_three_chars(s)
123
+ s[-3..-1]
124
+ end
125
+
126
+ def the_first_three_chars(s)
127
+ s[0..3]
128
+ end
129
+
130
+ def the_string_minus_the_last_three_chars(s)
131
+ s[0...-3]
132
+ end
133
+
134
+ def the_string_minus_the_first_three_chars(s)
135
+ s[2..-1]
136
+ end
137
+
138
+
139
+ def encrypt_using_gpg(pass, string)
140
+ crypto = GPGME::Crypto.new :symmetric => true, :password => "gpgme"
141
+ encrypted_data = crypto.encrypt "string"
142
+ encrypted_data.read
143
+ end
144
+ end
145
+
146
+ end
@@ -0,0 +1,643 @@
1
+ module NmDatafile
2
+
3
+ # This class describes a way for AP to convert data into a portable format
4
+ # (zip_file.nmd). nms == aNonyMousDatafile
5
+ #
6
+ # To create a new file, you first must define a schema. As of right now,
7
+ # there are two public schemas. As defined in these schemas, NmDatafiles
8
+ # consist of collections of objects, and individual objects... eg
9
+ # An instance might have nmd.important_numbers which is an array of number,
10
+ # whereas it might also have a data_object, nmd#stupidest_number which is a
11
+ # single number.
12
+ #
13
+ # So to create an NmDatafile, do
14
+ # `NmDatafile.new(:shippable_file, sales, line_items, addresses, rfsb)`
15
+ # that will work if you have a schema that has 3 data_collections specified
16
+ # and 1 data_object specified in the schema named :shippable_file.
17
+ #
18
+ # Using NmDatafiles allows you access to utility functions such as
19
+ # - #save_to_string
20
+ # - #save_to_file
21
+ # - tons of testing methods
22
+ #
23
+ # The NmDatafile is just a zip, but it's a password protected zip, and
24
+ # it can also be obfuscated to confuse the feds.
25
+ #
26
+ # Creating a new APDatafile means creates, in memory, a way to:
27
+ # - Render the data to the hdd as a zip file
28
+ # - Render the data as a string which can be streamed as a zip file
29
+ # - Reading a file
30
+ # - Encrypt the data (assymetrically?) YES
31
+ # - Define the standard for formating
32
+ # - Create mock data outputs easily (helpful in testing)
33
+ # - Specify the version of the file
34
+ # - File can non-destructively corrupt itself so it doesn't look so much like a zip file to cryptographic analists
35
+ # - Object is responsive to what ever schema it's set to... so it will be able to do
36
+ # shippable_file = NmDatafile.new(:shippable_file); shippable_file.sales << Sale.new
37
+ # - loads data, nmd.load_data([sales, line_items, addresses, [ready_for_shipment_batch]])
38
+ # - loads variables nmd.load_data([sales, line_items, addresses, ready_for_shipment_batch])
39
+
40
+ # Encrypt the data (asymetrically?) YES
41
+ # To do this, all the files should be lumped together as one huge string (a hash of file names and data strings)... split by a special split marker and file name definer such as
42
+ # Data Encryption:
43
+ # Meth. 1) Once this massive string is created, a PGP key is used to encrypt the whole file.
44
+ # Meth. 2) The data can be encrypted using a randomly generated password, and then the password get's encrypted with PGP <- better
45
+ #
46
+ #
47
+
48
+ # (r) file_type: Can be
49
+ # - shippable_file, used to export shipping data from AP book shopping app
50
+ # - address_completion_file, used to transfer data from shipping machine to AP book shopping app
51
+ # - db_backup_file, used to download a complete backup of a webserver
52
+ # - etc, etc, used for other organization objectives
53
+ #
54
+ # (w) set_public_key: Specify a public PGP key to encode the data with
55
+ #
56
+ # (r) get_public_key: Shows the email and fingerprint of the key that's been set (for debugging)
57
+ #
58
+ # (r) show_current_schema: Shows the data schema for the file type
59
+ #
60
+ # (w) set_current_schema: deletes all data and sets the schema
61
+ #
62
+ # (r) save_to_string: creates the zip file, handleing
63
+ #
64
+ # (rw) protected: bool, specifies whether the file is secured with PGP or if it's just a zip file
65
+ #
66
+ # (m) Load: loads a file into memory as an NmDatafile
67
+ #
68
+ # (m) load_data: loads array of data into memory as an NmDatafile object
69
+
70
+
71
+ # PRIVATE
72
+ # (m) encrypt_file!: Does all encryption procedures before rendering to a string or file
73
+ #
74
+ # (m) decrypt_file!: reverse of encrypt_file!
75
+ #
76
+ # (m) encrypt_string: symetrically encrypts a string using a password
77
+ #
78
+ # (m) decrypt_string: reverse of encrypt_string
79
+ #
80
+ # (m) encrypt_symetric_key: uses PGP to encrypt the password that encrypted the data
81
+ #
82
+ # (m) decrypt_symetric_key: reverse of encrypt_symetric_key.
83
+ #
84
+ # (m) corrupt_zip: corrupts the zip file so it doesn't look like a zip file, make it look like a jpeg
85
+ #
86
+ # (m) uncorrupt_zip: reverses corrupt_zip file so the zip can be processed
87
+ #
88
+ # (m)
89
+ # aNonyMousDatafile
90
+ class NmDatafile
91
+ @@schemas = ::NmDatafile::SCHEMA[:schemas] # TODO: move to initialize
92
+ @@unsecure_pass = $FrontDoorKey # ppl with root access can decrypt nmd files
93
+ @@clear_text_path = "clear_text_protected_nmd" # used for using system calls to decrypt and encrypt using a zip password
94
+ attr_reader :file_type, :password
95
+
96
+ extend Crypto
97
+
98
+
99
+
100
+
101
+ ###############################
102
+ # Loading and Dumping Methods #
103
+ ###############################
104
+
105
+ # (m) Load: loads a file into memory as an NmDatafile
106
+ def self.Load(file_path)
107
+ zip_data = File.read(file_path)
108
+ LoadBinaryData(zip_data)
109
+ end
110
+
111
+ def self.LoadBinaryData(binary_data)
112
+ hash = extract_entities_from_binary_data(binary_data)
113
+
114
+ file_type = determine_file_type(hash[:attributes])
115
+ nmd = self.new( file_type )
116
+
117
+ nmd.load_attributes(hash[:attributes]) unless hash[:attributes].nil?
118
+ nmd.load_encryption(hash[:encryption])
119
+
120
+ nmd.load_data([*hash[:data_collections], *hash[:data_objects]])
121
+ end
122
+
123
+ def self.determine_file_type(attributes_hash)
124
+ attributes_hash = YAML::load attributes_hash
125
+ attributes_hash["file_type"].to_sym
126
+ end
127
+
128
+ def self.determine_password(hash)
129
+ d = YAML::load hash[:encryption]
130
+ clean_decrypt_string(d["password"])
131
+ end
132
+
133
+
134
+
135
+
136
+
137
+
138
+ def initialize(file_type, *args)
139
+ set_file_type(file_type)
140
+
141
+ load_data(args)
142
+
143
+ setup_object_for_schema
144
+ end
145
+
146
+ def load_attributes(attribute_data)
147
+ d = YAML::load attribute_data
148
+ @file_type = d["file_type"].to_sym
149
+ @build_date = Time.zone.parse d["build_date"] unless d["build_date"].nil?
150
+ end
151
+
152
+ def load_encryption(encryption_data)
153
+ d = YAML::load encryption_data
154
+ @integrity_hash = d["integrity_hash"] unless d["integrity_hash"].nil?
155
+ @password = clean_decrypt_string(d["password"]) unless d["password"].nil?
156
+ end
157
+
158
+ # (m) load_data: loads array of data into memory as an NmDatafile object
159
+ # clears the data arrays so it's like reinitializing the whole file
160
+ def load_data(*args)
161
+ init_data_arrays # wipes all preexisting data in @data_collections and @data_objects
162
+
163
+ args[0].each.with_index do |array_or_variable, i|
164
+ if i < data_collection_names.count
165
+ @data_collections[i] += array_or_variable
166
+ else
167
+ j = i - data_collection_names.count
168
+ @data_objects[j] = array_or_variable # if array_or_variable.class != Array
169
+ end
170
+ end
171
+ self
172
+ end
173
+
174
+
175
+ def save_to_string
176
+ set_password
177
+
178
+ clear_text = build_encryptable_portion
179
+ encrypted_data = encode_string_as_password_protected(clear_text)
180
+
181
+ hash_of_entries = { :crypt => encrypted_data,
182
+ :encryption => build_encryption,
183
+ :attributes => build_attributes }
184
+
185
+ encode_datafiles_as_zip(hash_of_entries)
186
+ end
187
+
188
+ def save_to_file(path)
189
+ File.write(path, save_to_string)
190
+ end
191
+
192
+ def set_password
193
+ len = 41
194
+
195
+ password = ""
196
+ chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + ['!', '@', '#', '$', '%', '^', '&', '*', '(', ')']
197
+
198
+ 1.upto(len) { |i| password << chars[rand(chars.size-1)]}
199
+
200
+ space = rand(len-1)
201
+ password[space] = ("0".."9").to_a[rand(9)] # ensure there's number and letter
202
+ space += 1
203
+ space = 0 if space == len
204
+ password[space] = ("a".."z").to_a[rand(22)]
205
+
206
+ @password = password
207
+ end
208
+
209
+ # TODO del me
210
+ def version
211
+ "0.0.0"
212
+ end
213
+
214
+ def integrity_hash
215
+ encryptable_portion = build_encryptable_portion
216
+ Digest::SHA2.hexdigest(encryptable_portion)
217
+ end
218
+
219
+ def build_data_collections
220
+ @data_collections.to_json
221
+ end
222
+
223
+ def build_data_objects
224
+ @data_objects.to_json
225
+ end
226
+
227
+ def build_attributes
228
+ hash = { file_type: @file_type
229
+ # build_date: Time.zone.now, # TODu: change me to the date the data was last modified...
230
+ }.to_json
231
+ end
232
+
233
+ def build_encryption
234
+ hash = { integrity_hash: integrity_hash,
235
+ password: clean_encrypt_string(@password)
236
+ }.to_json
237
+ end
238
+
239
+ def build_encryptable_portion
240
+ e = { :data_collections => @data_collections,
241
+ :data_objects => @data_objects
242
+ }.to_json
243
+ end
244
+
245
+
246
+
247
+ #######################
248
+ # schema related junk #
249
+ #######################
250
+
251
+ def schema
252
+ @@schemas[@file_type]
253
+ end
254
+
255
+ def data_collection_names
256
+ schema[:data_collections]
257
+ end
258
+
259
+ def data_object_names
260
+ schema[:data_objects]
261
+ end
262
+
263
+ def var_names
264
+ data_collection_names + data_object_names
265
+ end
266
+
267
+ # specify the schema type
268
+ def set_file_type(file_type)
269
+ @file_type = file_type
270
+ init_data_arrays
271
+ end
272
+
273
+ def init_data_arrays
274
+ @data_collections = []
275
+ data_collection_names.count.times { @data_collections << [] }
276
+ @data_objects = []
277
+ data_object_names.count.times { @data_objects << [] }
278
+ end
279
+
280
+ # This makes it so you can call file.attribute to get direct access to an attribute
281
+ def setup_object_for_schema
282
+ data_collection_names.each.with_index do |data_collection_name, i|
283
+ # define getter
284
+ self.define_singleton_method(var_names[i]) do
285
+ @data_collections[i]
286
+ end
287
+ # define setter
288
+ self.define_singleton_method((data_collection_names[i].to_s + "=").to_sym) do |val|
289
+ @data_collections[i] = val
290
+ end
291
+ end
292
+
293
+ data_object_names.each.with_index do |data_object_name, i|
294
+ # getters
295
+ self.define_singleton_method(data_object_name) do
296
+ @data_objects[i]
297
+ end
298
+ # setters
299
+ self.define_singleton_method((data_object_name.to_s + "=").to_sym) do |val|
300
+ @data_objects[i] = val
301
+ end
302
+ end
303
+
304
+ end
305
+
306
+
307
+
308
+
309
+ # This method get's the temp directory. If it's a rails
310
+ # app, that would be Rails.root/tmp, else just /tmp
311
+ def get_temp_directory
312
+ defined?(Rails) ? "#{Rails.root}/tmp" : "/tmp"
313
+ end
314
+
315
+ ###############
316
+ # zip methods #
317
+ ###############
318
+ require 'zip'
319
+
320
+ # hash consists of {file1: string_data}
321
+ # output is a binary string representing a zip file of the entries specified
322
+ def encode_datafiles_as_zip(hash_of_entries)
323
+ temp_file = Tempfile.new(file_type.to_s, get_temp_directory)
324
+ FileUtils.rm temp_file.path
325
+
326
+ stream = ::Zip::OutputStream.write_buffer do |zos|
327
+ hash_of_entries.each do |entry_name, data|
328
+ zos.put_next_entry entry_name
329
+ zos.write data
330
+ end
331
+ end
332
+
333
+ stream.rewind
334
+ stream.read
335
+ end
336
+
337
+ # This method peers through a zip binary data blob and returns a hash consisting of
338
+ # { file_name1: file_data1, etc }
339
+ def self.extract_entities_from_binary_data(binary_data)
340
+ binary_data_io = StringIO.new(binary_data)
341
+
342
+ hash = {}
343
+ ::Zip::InputStream.open(binary_data_io) do |io|
344
+ while (entry = io.get_next_entry)
345
+ hash[entry.name.to_sym] = io.read
346
+ end
347
+ end
348
+
349
+ password = self.determine_password(hash)
350
+
351
+ decrypt_encryptable_data!(password, hash)
352
+ hash
353
+ end
354
+
355
+ # Play:
356
+ # Below will write to stdout as long as stdout isn't the terminal (so works in irb and ruby)
357
+ # zip -P 'fp5!IZbVxgx2hWh8m*UQyc@d5nCGCrbiqPx73hh&' - file_to_add
358
+ #
359
+ # Below is attempt to read file from stdin:
360
+ # out = `echo 'hi' | zip -P 'fp5!IZbVxgx2hWh8m*UQyc@d5nCGCrbiqPx73hh&' - -`
361
+ #
362
+ #
363
+ # Zip commandline is
364
+ # zip -P 'fp5!IZbVxgx2hWh8m*UQyc@d5nCGCrbiqPx73hh&' zip_to_make.zip file_to_add
365
+ # maybe password has invalid chars
366
+ def encode_string_as_password_protected_old_zip_based(encryptable_portion, pass = nil)
367
+ pish = @password
368
+ pish = pass unless pass.nil?
369
+
370
+ supress_errors = "2>/dev/null"
371
+ # this will read that file... let's try passing it in from stdin pipes though
372
+ # alt = "zip -P '#{pish}' - #{@@clear_text_path}"
373
+
374
+ # TODu: escape single quotes or this command breaks...
375
+ raise "tried to encrypt an encryptable_portion which contained illegal character ' which would break the command being piped to zip" if encryptable_portion =~ /\'/
376
+ # passing in clear_text through pipes
377
+ alt = "echo '#{encryptable_portion}'| zip -P '#{pish}' - - #{supress_errors}"
378
+ #alt = "echo '#{encryptable_portion.gsub("\\n", "")}'| zip -P '#{pish}' - -"
379
+
380
+ binary_output = `#{alt}`
381
+ end
382
+
383
+
384
+
385
+
386
+ def encode_string_as_password_protected(encryptable_portion, pass = nil)
387
+ #require 'b_f'
388
+
389
+ pish = @password
390
+ pish = pass unless pass.nil?
391
+ raise "error, password given was too long, must be 56 or less chars" if pish.length > 56
392
+
393
+ bf = BF.new(pish, true)
394
+
395
+ encrypted = bf.encrypt(encryptable_portion)
396
+ end
397
+
398
+
399
+ def decode_password_protected_string(password, decryptable_portion)
400
+ NmDatafile.decode_password_protected_string(password, decryptable_portion)
401
+ end
402
+
403
+ def obfuscate_file_format
404
+ # TODu: implement me
405
+ end
406
+
407
+ def deobfuscate_file_format
408
+
409
+ end
410
+
411
+
412
+ def clean_encrypt_string(string)
413
+ # Base64.encode64(encode_string_as_password_protected(string, @@unsecure_pass))
414
+ NmDatafile.fast_encrypt_string_with_pass(@@unsecure_pass, string)
415
+ end
416
+
417
+ def clean_decrypt_string(string)
418
+ NmDatafile.clean_decrypt_string(string)
419
+ end
420
+
421
+
422
+
423
+
424
+
425
+
426
+ # `gpg -c --no-use-agent`
427
+
428
+ ###################
429
+ # Testing methods #
430
+ ###################
431
+
432
+ # do as address_completion_file to create an rfsb that should have already existed, but didn't because a fresh db was used
433
+ # for testing
434
+ def simulate_rfsb_existance_on_webserver
435
+ rfsb = self.ready_for_shipment_batch
436
+ ReadyForShipmentBatch.create(batch_stamp: rfsb["batch_stamp"], sf_integrity_hash: rfsb["integrity_hash"])
437
+ end
438
+
439
+ # creates an address_completion file based on the contents of the current shippable_file object
440
+ def simulate_address_completion_response(n_shipped)
441
+ raise "can't make an address_completion_response unless it's a shippable_file" if @file_type != :shippable_file
442
+ setup_object_for_schema # TODu: I put this in, there was a bug in the test where it needs to be run... does it not run on init somehow?
443
+
444
+ shipped_sales = []
445
+ erroneous_addresses = []
446
+ sales.each.with_index do |sale, i|
447
+ if i < n_shipped
448
+ shipped_sales << { "id" => sale["id"] }
449
+ else
450
+ erroneous_addresses << { "id" => sale["id"] }
451
+ end
452
+ end
453
+
454
+ simulated_response = [shipped_sales, erroneous_addresses, ready_for_shipment_batch]
455
+
456
+ nmd_address_completion = NmDatafile.new(:address_completion_file, *simulated_response)
457
+ end
458
+
459
+ def generate_upload_params(action = "upload_shippable_file")
460
+ temp_zip = Tempfile.new('temp_zip', "#{Rails.root}/tmp")
461
+ save_to_file(temp_zip.path)
462
+
463
+ upload_shippable_params = {
464
+ "file_upload" => { "my_file" => Rack::Test::UploadedFile.new(temp_zip.path, "application/zip") },
465
+ "controller"=>"admin_pages",
466
+ "action"=>action
467
+ }
468
+ end
469
+
470
+ # Don't use push on #sales, it will use the push on the array. TODu: to fix this, make the @sales arrays collections
471
+ # and give those collections special properties when push is invoked
472
+ def add_sale(sale)
473
+ # add to collection the sale
474
+ self.sales << sale
475
+ regenerate_rfsb if should_gen_a_new_rfsb?(sale)
476
+ end
477
+
478
+ # we should NOT make a new rfsb if we're adding an old erroneous sale to the object
479
+ # to check for age... see if it already has an rfsb_id
480
+ def should_gen_a_new_rfsb?(sale)
481
+ return true if sale.ready_for_shipment_batch_id.nil?
482
+ false
483
+ end
484
+
485
+ def regenerate_rfsb
486
+ rfsb = ReadyForShipmentBatch.gen
487
+ self.ready_for_shipment_batch = rfsb
488
+ rfsb.delete
489
+ end
490
+
491
+ # create's some sales, line_items and
492
+ def create_sales_for_shippable_file(n, e=nil)
493
+
494
+ if @file_type == :shippable_file
495
+ s, l, a, rfsb = create_sales_and_return_data(n)
496
+ self.sales += s
497
+ self.line_items += l
498
+ self.addresses += a
499
+ self.ready_for_shipment_batch = rfsb
500
+ rfsb.delete
501
+ Sale.deep_delete_sales(s)
502
+ elsif @file_type == :address_completion_file
503
+ s, e, rfsb = create_sales_and_return_data_address_completion_file(n, e)
504
+ self.sales += s
505
+ self.erroneous_sales += e
506
+
507
+ self.ready_for_shipment_batch = rfsb
508
+ rfsb.delete
509
+ Sale.deep_delete_sales(s)
510
+ end
511
+
512
+ end
513
+ alias create_sales create_sales_for_shippable_file
514
+
515
+ def create_sales_and_return_data_address_completion_file(n, e)
516
+ e = 0 if e.nil?
517
+ sales = []
518
+ errors = []
519
+ n.times { sales << FactoryGirl.create(:sale_with_1_book) }
520
+ e.times { errors << FactoryGirl.create(:sale_with_1_book) }
521
+ rfsb = ReadyForShipmentBatch.gen
522
+
523
+ sales.each {|s| s.ready_for_shipment_batch_id = rfsb.id}
524
+ errors.each {|s| s.ready_for_shipment_batch_id = rfsb.id}
525
+
526
+ [ sales, errors, rfsb ]
527
+ end
528
+
529
+ def create_sales_and_return_data(n)
530
+ sales = []
531
+ n.times { sales << FactoryGirl.create(:sale_with_1_book) }
532
+ rfsb = ReadyForShipmentBatch.gen
533
+ sales.each {|s| s.ready_for_shipment_batch_id = rfsb.id}
534
+ l = capture_line_items(sales)
535
+ a = capture_addresses(sales)
536
+
537
+ [sales, l, a, rfsb]
538
+ end
539
+
540
+ def capture_line_items(sales)
541
+ l = []
542
+ sales.each do |s|
543
+ l += s.line_items
544
+ end
545
+ l
546
+ end
547
+
548
+ def capture_addresses(sales)
549
+ a = []
550
+ sales.each do |s|
551
+ a << s.address
552
+ end
553
+ a
554
+ end
555
+
556
+
557
+
558
+ #################
559
+ # Debug methods #
560
+ #################
561
+ # render a count of sales
562
+ def to_s
563
+ string = "NmDatafile: \n"
564
+ data_collection_names.each.with_index do |collection_name, i|
565
+ string << " #{collection_name}: #{@data_collections[i].count} \n"
566
+ end
567
+
568
+ data_object_names.each.with_index do |variable_name, i|
569
+ string << " #{variable_name}: #{1} \n"
570
+ end
571
+
572
+ string
573
+ end
574
+
575
+ def inspect
576
+ puts self.to_s
577
+ end
578
+
579
+ def ==(other)
580
+ return false if other.class != self.class
581
+ return true if all_data_matches?(other)
582
+ false
583
+ end
584
+
585
+ def all_data_matches?(other)
586
+ if self.integrity_hash == other.integrity_hash
587
+ if self.build_attributes == other.build_attributes
588
+ return true
589
+ end
590
+ end
591
+ false
592
+ end
593
+
594
+ #####################################################
595
+ # batch checking, high conasence with Importable #
596
+ #####################################################
597
+
598
+ # Move to... NmDatafile
599
+ def duplicate_batch?(previous_batch)
600
+ return false if previous_batch.nil?
601
+
602
+ previous_batch.sf_integrity_hash == integrity_hash
603
+ end
604
+
605
+
606
+ end
607
+
608
+
609
+ # Zip Entry Structure
610
+ # data_collections: array of data collections... atm, contains
611
+ # data_objects: array of data_objects
612
+ # schema: Schema of the data... this should be encrypted if data1 is encrypted
613
+ # encryption: integrity_hash: SHA2 hash of data for fingerprinting, password: password used to encrypt the data entrys
614
+ # attributes: - @file_type, nmd_version, build_date, PGP key id used for encrypting the encryption entry
615
+
616
+ # Zip Entry Structure after full protection
617
+ # crypt (encrypted: data_collections, data_objects, schema)
618
+ # encryption (encrypted)
619
+ # attributes (clear_text)
620
+
621
+
622
+ # This hack is for... some tricky bullshit, I forgot about
623
+ def passfunc(hook, uid_hint, passphrase_info, prev_was_bad, fd)
624
+ $stderr.write("Passphrase for #{uid_hint}: ")
625
+ $stderr.flush
626
+ begin
627
+ system('stty -echo')
628
+ io = IO.for_fd(fd, 'w')
629
+ io.puts(gets)
630
+ io.flush
631
+ ensure
632
+ (0 ... $_.length).each do |i| $_[i] = ?0 end if $_
633
+ system('stty echo')
634
+ end
635
+ $stderr.puts
636
+ end
637
+
638
+
639
+ end
640
+
641
+
642
+
643
+
@@ -0,0 +1,16 @@
1
+ # A default schema is defined here... this needs to be converted into
2
+ # something more generic
3
+ module NmDatafile
4
+ SCHEMA = { schemas:
5
+ { :shippable_file => {
6
+ data_collections: [:sales, :line_items, :discounts, :addresses, :ubws, :encryption_pairs], # name the data that is input into the NMDatafile as an array
7
+ data_objects: [:ready_for_shipment_batch]
8
+ },
9
+
10
+ :address_completion_file => {
11
+ data_collections: [:sales, :erroneous_sales],
12
+ data_objects: [:ready_for_shipment_batch]
13
+ }
14
+ }
15
+ }
16
+ end
@@ -0,0 +1,3 @@
1
+ module NmDatafile
2
+ VERSION = "0.0.0"
3
+ end
@@ -0,0 +1,21 @@
1
+ require 'json'
2
+
3
+ require "nm_datafile/version"
4
+ require "nm_datafile/schema"
5
+ require 'nm_datafile/b_f'
6
+ require 'nm_datafile/crypto'
7
+ require "nm_datafile/nm_datafile"
8
+
9
+
10
+ module NmDatafile
11
+ # Your code goes here...
12
+
13
+ def self.new(file_type, *args)
14
+ NmDatafile.new(file_type, args)
15
+ end
16
+
17
+
18
+ end
19
+
20
+
21
+
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'nm_datafile/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "nm_datafile"
8
+ spec.version = NmDatafile::VERSION
9
+ spec.authors = ["antisec"]
10
+ spec.email = ["antisec@antisec.com"]
11
+ spec.summary = %q{A gem that saves files into a secure encrypted file.}
12
+ spec.description = %q{A gem that creates a data file based on arrays or strings that you feed it. When you save the file, you can choose from multiple encryption methods, asymetric, symetric, etc. etc.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "rubyzip"
22
+ spec.add_development_dependency "bundler", "~> 1.7"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "pry"
25
+ spec.add_development_dependency "rspec"
26
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe "nm_datafile" do
4
+
5
+ before :each do
6
+ #@sample_data = get_sample_data
7
+ @sale = {"address_id"=>1, "created_at"=>"2015-03-08T03:54:51Z", "currency_used"=>"BTC"}
8
+ @sample_data = [ @sale ]
9
+ end
10
+
11
+
12
+ it "should be instantiable" do
13
+ NmDatafile.new(:shippable_file)
14
+ end
15
+
16
+
17
+ it "should be instantiable with data" do
18
+ #return_array = [ @sales,
19
+ # @line_items,
20
+ # @discounts,
21
+ # @addresses,
22
+ # @utilized_bitcoin_wallets,
23
+ # @encryption_pairs,
24
+ # rfsb ]
25
+
26
+ nmd_shippable = NmDatafile.new(:shippable_file, *@sample_data)
27
+
28
+ str = nmd_shippable.save_to_string
29
+
30
+ # TODO: actually test data and make more data in @sample_data
31
+
32
+
33
+ end
34
+
35
+ end
36
+
37
+
@@ -0,0 +1,82 @@
1
+ require 'nm_datafile'
2
+ require 'pry'
3
+
4
+ RSpec.configure do |config|
5
+ # rspec-expectations config goes here. You can use an alternate
6
+ # assertion/expectation library such as wrong or the stdlib/minitest
7
+ # assertions if you prefer.
8
+ config.expect_with :rspec do |expectations|
9
+ # This option will default to `true` in RSpec 4. It makes the `description`
10
+ # and `failure_message` of custom matchers include text for helper methods
11
+ # defined using `chain`, e.g.:
12
+ # be_bigger_than(2).and_smaller_than(4).description
13
+ # # => "be bigger than 2 and smaller than 4"
14
+ # ...rather than:
15
+ # # => "be bigger than 2"
16
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
17
+ end
18
+
19
+ # rspec-mocks config goes here. You can use an alternate test double
20
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
21
+ config.mock_with :rspec do |mocks|
22
+ # Prevents you from mocking or stubbing a method that does not exist on
23
+ # a real object. This is generally recommended, and will default to
24
+ # `true` in RSpec 4.
25
+ mocks.verify_partial_doubles = true
26
+ end
27
+
28
+ # The settings below are suggested to provide a good initial experience
29
+ # with RSpec, but feel free to customize to your heart's content.
30
+ =begin
31
+ # These two settings work together to allow you to limit a spec run
32
+ # to individual examples or groups you care about by tagging them with
33
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
34
+ # get run.
35
+ config.filter_run :focus
36
+ config.run_all_when_everything_filtered = true
37
+
38
+ # Limits the available syntax to the non-monkey patched syntax that is
39
+ # recommended. For more details, see:
40
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
41
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
42
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
43
+ config.disable_monkey_patching!
44
+
45
+ # This setting enables warnings. It's recommended, but in some cases may
46
+ # be too noisy due to issues in dependencies.
47
+ config.warnings = true
48
+
49
+ # Many RSpec users commonly either run the entire suite or an individual
50
+ # file, and it's useful to allow more verbose output when running an
51
+ # individual spec file.
52
+ if config.files_to_run.one?
53
+ # Use the documentation formatter for detailed output,
54
+ # unless a formatter has already been configured
55
+ # (e.g. via a command-line flag).
56
+ config.default_formatter = 'doc'
57
+ end
58
+
59
+ # Print the 10 slowest examples and example groups at the
60
+ # end of the spec run, to help surface which specs are running
61
+ # particularly slow.
62
+ config.profile_examples = 10
63
+
64
+ # Run specs in random order to surface order dependencies. If you find an
65
+ # order dependency and want to debug it, you can fix the order by providing
66
+ # the seed, which is printed after each run.
67
+ # --seed 1234
68
+ config.order = :random
69
+
70
+ # Seed global randomization in this process using the `--seed` CLI option.
71
+ # Setting this allows you to use `--seed` to deterministically reproduce
72
+ # test failures related to randomization by passing the same `--seed` value
73
+ # as the one that triggered the failure.
74
+ Kernel.srand config.seed
75
+ =end
76
+ end
77
+
78
+
79
+
80
+ def get_sample_data
81
+
82
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nm_datafile
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - antisec
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rubyzip
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: A gem that creates a data file based on arrays or strings that you feed
84
+ it. When you save the file, you can choose from multiple encryption methods, asymetric,
85
+ symetric, etc. etc.
86
+ email:
87
+ - antisec@antisec.com
88
+ executables: []
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - .gitignore
93
+ - .rspec
94
+ - Gemfile
95
+ - LICENSE.txt
96
+ - README.md
97
+ - Rakefile
98
+ - changelog.md
99
+ - lib/nm_datafile.rb
100
+ - lib/nm_datafile/b_f.rb
101
+ - lib/nm_datafile/crypto.rb
102
+ - lib/nm_datafile/nm_datafile.rb
103
+ - lib/nm_datafile/schema.rb
104
+ - lib/nm_datafile/version.rb
105
+ - nm_datafile.gemspec
106
+ - spec/nm_datafile_spec.rb
107
+ - spec/spec_helper.rb
108
+ homepage: ''
109
+ licenses:
110
+ - MIT
111
+ metadata: {}
112
+ post_install_message:
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubyforge_project:
128
+ rubygems_version: 2.4.6
129
+ signing_key:
130
+ specification_version: 4
131
+ summary: A gem that saves files into a secure encrypted file.
132
+ test_files:
133
+ - spec/nm_datafile_spec.rb
134
+ - spec/spec_helper.rb