strongboxio 0.1.0 → 0.2.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.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ *.swp
2
+ *.gem
3
+ /.rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # gem dependencies specified in strongboxio.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Aric Batkovic
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,97 @@
1
+ Strongbox
2
+ =========
3
+
4
+ Ruby gem and command-line interface for decrypting and reading www.Strongbox.io files.
5
+
6
+ Description
7
+ -----------
8
+
9
+ **Strongbox** (https://www.strongbox.io/) provides a simple way to effectively
10
+ organize, secure, and share sensitive textual data, the kind that has no other
11
+ home: passwords, credentials, credit card & account numbers, encryption keys,
12
+ certificates, etc. - essentially, anything we're not comfortable simply putting
13
+ into an unencrypted text document file or sharing via email, Skype, etc.
14
+
15
+ **This gem** enables decrypting and reading Strongbox files using the following
16
+ Ruby Standard Libraries:
17
+
18
+ * `openssl` (for decryption)
19
+ * `zlib` (for decompression)
20
+ * `base64` (for decoding)
21
+
22
+ and the following third-party libraries:
23
+
24
+ * `nokogiri` (for xml parsing)
25
+ * `highline` (for password input)
26
+
27
+ **This gem** also ships with a command-line interface (an executable Ruby
28
+ program). See *command-line usage* below.
29
+
30
+ Examples
31
+ --------
32
+
33
+ Given a Strongbox file and password, `Strongbox.decrypt` will handle everything
34
+ from opening the Strongbox file (extension `.sbox`), reading the XML content,
35
+ extracting the XML's Data node, decoding from Base64, decrypting
36
+ (`AES-256-CBC`), decompressing, and returning the raw content (which itself is
37
+ XML structured data, represented below as variable `d`).
38
+
39
+ At this point, the data just needs to be rendered (displayed); so we create a
40
+ strongbox object (`sb`) and call `render` on it.
41
+
42
+ d = Strongbox.decrypt(filename, password)
43
+ sb = Strongbox.new(d)
44
+ sb.render
45
+
46
+ Command-line usage
47
+ ==================
48
+
49
+ RVM users: you probably want to install this gem into your default Ruby's global gemset.
50
+
51
+ Examples
52
+ --------
53
+
54
+ These examples assume `strongbox.rb` is executable and in a directory in `PATH`.
55
+
56
+ $ strongbox.rb
57
+ Usage /Users/abatko/bin/strongbox.rb input.sbox
58
+
59
+ $ strongbox.rb the_case_of_everything.sbox
60
+ Enter the password to unlock this box: ********
61
+ 2012-11-27T23:29:23.18873Z
62
+
63
+ VanCity bank info
64
+ credit card, debit card, online banking
65
+ Credit, Debit, Card, Online, Banking
66
+ credit card number:
67
+ 1234 5678 9012 3456
68
+ credit card expiration:
69
+ 11/2012
70
+ credit card verification value:
71
+ 123
72
+ debit card PIN:
73
+ 1234
74
+ online banking password:
75
+ qwerty
76
+
77
+ fake Gmail account
78
+ used for Stackoverflow
79
+ Fake Gmail Stackoverflow
80
+ username:
81
+ ima_fake@gmail.com
82
+ password:
83
+ password1
84
+ gender:
85
+ other
86
+ backup:
87
+ bart_simpson@gmail.com
88
+
89
+ Contributing
90
+ ============
91
+
92
+ 1. Fork it
93
+ 2. Create your feature branch (`git checkout -b my_new_feature`)
94
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
95
+ 4. Push to the branch (`git push origin my_new_feature`)
96
+ 5. Create new Pull Request
97
+
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ lib = File.expand_path('../lib/', __FILE__)
2
+ $:.unshift lib unless $:.include?(lib)
3
+ require 'strongboxio/version'
4
+
5
+ require 'rake/testtask'
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.libs << 'test'
9
+ end
10
+
11
+ desc 'Run tests'
12
+ task :default => :test
13
+
14
+ desc 'Build gem'
15
+ task :build do
16
+ system 'gem build strongboxio.gemspec'
17
+ end
18
+
19
+ desc 'Release gem'
20
+ task :release => :build do
21
+ system "gem push strongboxio-#{Strongboxio::VERSION}.gem"
22
+ end
23
+
data/bin/strongbox.rb ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems' if defined?RUBY_VERSION && RUBY_VERSION =~ /^1.8/ # for requiring gem dependency in Ruby 1.8
4
+ require 'highline/import' # ask
5
+ require 'strongboxio'
6
+
7
+ def get_filename(filename=ARGV[0])
8
+ if filename.nil?
9
+ puts "Usage #{$0} input.sbox" ; exit
10
+ else
11
+ if File.exist?(filename)
12
+ if !File.readable?(filename)
13
+ puts "file #{filename} is not readable!" ; exit
14
+ end
15
+ else
16
+ puts "file #{filename} does not exist!" ; exit
17
+ end
18
+ end
19
+ filename
20
+ end
21
+
22
+ filename = get_filename
23
+
24
+ password = ask('Enter the password to unlock this box: ') { |q| q.echo = '*' }
25
+
26
+ d = Strongboxio.decrypt(filename, password)
27
+
28
+ begin
29
+ sbox = Strongboxio.new(d)
30
+ rescue => e
31
+ puts "Error: #{e}"
32
+ yn = ask('Continue anyway? [yN] ')
33
+ exit unless yn =~ /^[yY]\Z/
34
+ sbox = Strongboxio.new(d, true)
35
+ end
36
+
37
+ sbox.render
38
+
data/lib/strongboxio.rb CHANGED
@@ -4,204 +4,197 @@ require 'base64' # for decoding
4
4
  require 'rubygems' if defined?RUBY_VERSION && RUBY_VERSION =~ /^1.8/ # for requiring gem dependency in Ruby 1.8
5
5
  require 'nokogiri' # for xml parsing
6
6
 
7
+ require 'strongboxio/version'
8
+
7
9
  class Strongboxio
8
10
 
9
- VERSION = '0.1.0'
10
-
11
- STRONGBOX_VERSION = 3
12
- VERSION_LENGTH = 1
13
- SALT_LENGTH = 64
14
- IV_LENGTH = 16
15
- KEY_LENGTH = 32
16
- RANDOM_VALUE_LENGTH = 64
17
- CIPHER = 'AES-256-CBC'
18
- PAYLOAD_SCHEMA_VERSION = '2.4'
19
- UNIX_EPOCH_IN_100NS_INTERVALS = 621355968000000000 # .NET time format: number of 100-nanosecond intervals since .NET epoch: January 1, 0001 at 00:00:00.000 (midnight)
20
-
21
- def self.resembles_base64?(string)
22
- string.length % 4 == 0 && string =~ /^[A-Za-z0-9+\/=]+\Z/
23
- end
24
-
25
- def self.decrypt(sbox_filename, password)
26
- # open the xml file
27
- f = File.open(sbox_filename)
28
- data = Nokogiri::XML(f)
29
- f.close
30
-
31
- # extract the Data node
32
- data = data.xpath('//Data').text
33
- base64_error_msg = 'expected Base64 encoded byte string from the StrongBox.Payload.Data element of the xml of a Strongbox file'
34
- raise "#{base64_error_msg}, but got nothing" if data.length == 0
35
- raise "#{base64_error_msg}, but it does not resemble Base64" unless self.resembles_base64?(data)
36
-
37
- data = Base64.decode64(data)
38
-
39
- #version = data.getbyte(0) # ruby 1.9
40
- version = data.bytes.to_a[0] # ruby 1.8 friendly
41
- raise "expected version number #{STRONGBOX_VERSION}, but got #{version}" unless version == STRONGBOX_VERSION
42
-
43
- salt = data.slice(1, SALT_LENGTH)
44
- raise "expected salt length #{SALT_LENGTH}, but got #{salt.length}" unless salt.length == SALT_LENGTH
45
-
46
- iv = data.bytes.to_a.slice((1+64), IV_LENGTH).pack('C*')
47
- raise "expected iv length #{IV_LENGTH}, but got #{iv.length}" unless iv.length == IV_LENGTH
48
-
49
- key = Digest::SHA256.digest(salt + password)
50
- raise "expected key length #{KEY_LENGTH}, but got #{key.length}" unless key.length == KEY_LENGTH
51
-
52
- # prepare for decryption
53
- d = OpenSSL::Cipher.new(CIPHER)
54
- d.decrypt
55
- d.key = key
56
- d.iv = iv
11
+ STRONGBOX_VERSION = 3
12
+ VERSION_LENGTH = 1
13
+ SALT_LENGTH = 64
14
+ IV_LENGTH = 16
15
+ KEY_LENGTH = 32
16
+ RANDOM_VALUE_LENGTH = 64
17
+ CIPHER = 'AES-256-CBC'
18
+ PAYLOAD_SCHEMA_VERSION = '2.4'
19
+ UNIX_EPOCH_IN_100NS_INTERVALS = 621355968000000000 # .NET time format: number of 100-nanosecond intervals since .NET epoch: January 1, 0001 at 00:00:00.000 (midnight)
20
+
21
+ attr_accessor :sbox
22
+
23
+ def self.decrypt(sbox_filename, password)
24
+ # open the xml file
25
+ f = File.open(sbox_filename)
26
+ data = Nokogiri::XML(f)
27
+ f.close
28
+
29
+ # extract the Data node
30
+ data = data.xpath('//Data').text
31
+ base64_error_msg = 'expected Base64 encoded byte string from the StrongBox.Payload.Data element of the xml of a Strongbox file'
32
+ raise "#{base64_error_msg}, but got nothing" if data.length == 0
33
+ raise "#{base64_error_msg}, but it does not resemble Base64" unless self.resembles_base64?(data)
34
+
35
+ data = Base64.decode64(data)
36
+
37
+ #version = data.getbyte(0) # ruby 1.9
38
+ version = data.bytes.to_a[0] # ruby 1.8 friendly
39
+ raise "expected version number #{STRONGBOX_VERSION}, but got #{version}" unless version == STRONGBOX_VERSION
40
+
41
+ salt = data.slice(1, SALT_LENGTH)
42
+ raise "expected salt length #{SALT_LENGTH}, but got #{salt.length}" unless salt.length == SALT_LENGTH
43
+
44
+ iv = data.bytes.to_a.slice((1+64), IV_LENGTH).pack('C*')
45
+ raise "expected iv length #{IV_LENGTH}, but got #{iv.length}" unless iv.length == IV_LENGTH
46
+
47
+ key = Digest::SHA256.digest(salt + password)
48
+ raise "expected key length #{KEY_LENGTH}, but got #{key.length}" unless key.length == KEY_LENGTH
49
+
50
+ # prepare for decryption
51
+ d = OpenSSL::Cipher.new(CIPHER)
52
+ d.decrypt
53
+ d.key = key
54
+ d.iv = iv
57
55
 
58
- # decrypt the portion beyond the header
59
- begin
60
- data = '' << d.update(data.slice((VERSION_LENGTH+SALT_LENGTH+IV_LENGTH)..-1)) << d.final
61
- rescue => e
62
- raise "Error decrypting. You probably entered the password incorrectly. Specific error: #{e}"
63
- end
64
-
65
- # decompress the portion beyond the random value
66
- #z = Zlib::Inflate.new
67
- #z = Zlib::Inflate.new(-Zlib::BEST_COMPRESSION) # works for Strongbox
68
- z = Zlib::Inflate.new(-Zlib::MAX_WBITS) # works for Strongbox!
69
- #z = Zlib::Inflate.new(Zlib::MAX_WBITS) # works for roundtrip
70
- data = z.inflate(data.slice(RANDOM_VALUE_LENGTH..-1))
71
- z.finish
72
- z.close
73
-
74
- data
75
- end
76
-
77
- def self.render(decrypted_sbox, continue_despite_unexpected_payload_schema_version=false)
78
- data = Nokogiri::XML(decrypted_sbox)
79
-
80
- payload_schema_version = data.xpath('//Payload').xpath('SchemaVersion').text
81
- unless payload_schema_version == PAYLOAD_SCHEMA_VERSION
82
- raise "expected schema version #{PAYLOAD_SCHEMA_VERSION}, but got #{payload_schema_version}" unless continue_despite_unexpected_payload_schema_version
83
- end
84
-
85
- mt = data.xpath('//Payload').xpath('PayloadInfo').xpath('MT').text
86
- puts mt
87
-
88
- data.xpath('//PayloadData').each { |payload_data|
89
- payload_data.xpath('//SBE').each { |entity|
90
- puts
91
- name = entity.xpath('N').text
92
- puts "#{name}" if name.length > 0
93
- description = entity.xpath('D').text
94
- puts "#{description}" if description.length > 0
95
- tags = entity.xpath('T').text
96
- puts "#{tags}" if tags.length > 0
97
- ce = entity.xpath('CE')
98
- ce.xpath('TFE').each { |tfe|
99
- name = tfe.xpath('N').text
100
- puts "#{name}:" if name.length > 0
101
- content = tfe.xpath('C').text
102
- puts "#{content}" if content.length > 0
103
- }
104
- }
105
- }
106
- end
107
-
108
- def assemble(decrypted_sbox, continue_despite_unexpected_payload_schema_version=false)
109
- data = Nokogiri::XML(decrypted_sbox)
110
-
111
- payload_schema_version = data.xpath('//Payload').xpath('SchemaVersion').text
112
- unless payload_schema_version == PAYLOAD_SCHEMA_VERSION
113
- raise "expected schema version #{PAYLOAD_SCHEMA_VERSION}, but got #{payload_schema_version}" unless continue_despite_unexpected_payload_schema_version
114
- end
115
-
116
- sbox = {}
117
-
118
- mt = data.xpath('//Payload').xpath('PayloadInfo').xpath('MT').text
119
- sbox['MT'] = mt
120
-
121
- data.xpath('//PayloadData').each { |payload_data|
122
-
123
- sbox['PayloadData'] = []
124
-
125
- payload_data.xpath('//SBE').each_with_index { |strongbox_entity, sbe_index|
126
-
127
- sbox['PayloadData'][sbe_index] = {}
128
-
129
- sbe_mt = strongbox_entity.attr('MT') # ModifiedTimestamp.Ticks
130
- sbox['PayloadData'][sbe_index]['MT'] = sbe_mt if sbe_mt.length > 0
131
-
132
- sbe_ct = strongbox_entity.attr('CT') # CreatedTimestamp.Ticks
133
- sbox['PayloadData'][sbe_index]['CT'] = sbe_ct if sbe_ct.length > 0
134
-
135
- sbe_ac = strongbox_entity.attr('AC') # accessCount
136
- sbox['PayloadData'][sbe_index]['AC'] = sbe_ac if sbe_ac.length > 0
137
-
138
- sbe_name = strongbox_entity.xpath('N').text
139
- sbox['PayloadData'][sbe_index]['N'] = sbe_name if sbe_name.length > 0
140
-
141
- sbe_description = strongbox_entity.xpath('D').text
142
- sbox['PayloadData'][sbe_index]['D'] = sbe_description if sbe_description.length > 0
143
-
144
- sbe_tags = strongbox_entity.xpath('T').text
145
- sbox['PayloadData'][sbe_index]['T'] = sbe_tags if sbe_tags.length > 0
146
-
147
- child_entity = strongbox_entity.xpath('CE')
148
- if child_entity.length > 0
149
- sbox['PayloadData'][sbe_index]['CE'] = []
150
-
151
- child_entity.xpath('TFE').each_with_index { |text_field_entity, ce_index|
152
-
153
- sbox['PayloadData'][sbe_index]['CE'][ce_index] = {}
154
-
155
- tfe_name = text_field_entity.xpath('N').text
156
- sbox['PayloadData'][sbe_index]['CE'][ce_index]['N'] = tfe_name if tfe_name.length > 0
157
-
158
- tfe_content = text_field_entity.xpath('C').text
159
- sbox['PayloadData'][sbe_index]['CE'][ce_index]['C'] = tfe_content if tfe_content.length > 0
160
- }
161
- end
162
- }
163
- }
164
-
165
- sbox
166
- end
167
-
168
- def render(verbose=false)
169
- puts sbox['MT']
170
-
171
- sbox['PayloadData'].each { |payload_data|
172
- puts
173
- puts payload_data['N'] unless payload_data['N'].nil?
174
- puts payload_data['D'] unless payload_data['D'].nil?
175
- puts payload_data['T'] unless payload_data['T'].nil?
176
- payload_data['CE'].each { |strongbox_entity|
177
- puts strongbox_entity['N'] + ': ' unless strongbox_entity['N'].nil?
178
- puts strongbox_entity['C'] unless strongbox_entity['C'].nil?
179
- }
180
- puts "Access Count: #{payload_data['AC']}" if !payload_data['AC'].nil? && verbose
181
- puts "#{convert_time_from_dot_net_epoch(payload_data['MT'].to_i)} (modify time)" if !payload_data['MT'].nil? && verbose
182
- puts "#{convert_time_from_dot_net_epoch(payload_data['CT'].to_i)} (create time)" if !payload_data['CT'].nil? && verbose
183
- }
184
- end
185
-
186
- #attr_accessor :decrypted_sbox
187
- attr_accessor :sbox
188
-
189
- # create an instance of Strongbox
190
- def initialize(decrypted_sbox, continue_despite_unexpected_payload_schema_version=false)
191
- super()
192
- #self.decrypted_sbox = decrypted_sbox
193
- self.sbox = assemble(decrypted_sbox, continue_despite_unexpected_payload_schema_version)
194
- end
56
+ # decrypt the portion beyond the header
57
+ begin
58
+ data = '' << d.update(data.slice((VERSION_LENGTH+SALT_LENGTH+IV_LENGTH)..-1)) << d.final
59
+ rescue => e
60
+ raise "Error decrypting. You probably entered the password incorrectly. Specific error: #{e}"
61
+ end
62
+
63
+ # decompress the portion beyond the random value
64
+ #z = Zlib::Inflate.new
65
+ #z = Zlib::Inflate.new(-Zlib::BEST_COMPRESSION) # works for Strongbox
66
+ z = Zlib::Inflate.new(-Zlib::MAX_WBITS) # works for Strongbox!
67
+ #z = Zlib::Inflate.new(Zlib::MAX_WBITS) # works for roundtrip
68
+ data = z.inflate(data.slice(RANDOM_VALUE_LENGTH..-1))
69
+ z.finish
70
+ z.close
71
+
72
+ data
73
+ end
74
+
75
+ def self.render(decrypted_sbox, continue_despite_unexpected_payload_schema_version=false)
76
+ data = Nokogiri::XML(decrypted_sbox)
77
+
78
+ payload_schema_version = data.xpath('//Payload').xpath('SchemaVersion').text
79
+ unless payload_schema_version == PAYLOAD_SCHEMA_VERSION
80
+ raise "expected schema version #{PAYLOAD_SCHEMA_VERSION}, but got #{payload_schema_version}" unless continue_despite_unexpected_payload_schema_version
81
+ end
82
+
83
+ puts data.xpath('//Payload').xpath('PayloadInfo').xpath('MT').text
84
+
85
+ data.xpath('//PayloadData').each { |payload_data|
86
+ payload_data.xpath('//SBE').each { |entity|
87
+ puts
88
+ name = entity.xpath('N').text
89
+ puts "#{name}" if name.length > 0
90
+ description = entity.xpath('D').text
91
+ puts "#{description}" if description.length > 0
92
+ tags = entity.xpath('T').text
93
+ puts "#{tags}" if tags.length > 0
94
+ ce = entity.xpath('CE')
95
+ ce.xpath('TFE').each { |tfe|
96
+ name = tfe.xpath('N').text
97
+ puts "#{name}:" if name.length > 0
98
+ content = tfe.xpath('C').text
99
+ puts "#{content}" if content.length > 0
100
+ }
101
+ }
102
+ }
103
+ 0
104
+ end
105
+
106
+ def initialize(decrypted_sbox, continue_despite_unexpected_payload_schema_version=false)
107
+ super()
108
+
109
+ data = Nokogiri::XML(decrypted_sbox)
110
+
111
+ payload_schema_version = data.xpath('//Payload').xpath('SchemaVersion').text
112
+ unless payload_schema_version == PAYLOAD_SCHEMA_VERSION
113
+ raise "expected schema version #{PAYLOAD_SCHEMA_VERSION}, but got #{payload_schema_version}" unless continue_despite_unexpected_payload_schema_version
114
+ end
115
+
116
+ @sbox = {}
117
+
118
+ mt = data.xpath('//Payload').xpath('PayloadInfo').xpath('MT').text
119
+ @sbox['MT'] = mt
120
+
121
+ data.xpath('//PayloadData').each { |payload_data|
122
+
123
+ @sbox['PayloadData'] = []
124
+
125
+ payload_data.xpath('//SBE').each_with_index { |strongbox_entity, sbe_index|
126
+
127
+ @sbox['PayloadData'][sbe_index] = {}
128
+
129
+ sbe_mt = strongbox_entity.attr('MT') # ModifiedTimestamp.Ticks
130
+ @sbox['PayloadData'][sbe_index]['MT'] = sbe_mt if sbe_mt.length > 0
131
+
132
+ sbe_ct = strongbox_entity.attr('CT') # CreatedTimestamp.Ticks
133
+ @sbox['PayloadData'][sbe_index]['CT'] = sbe_ct if sbe_ct.length > 0
134
+
135
+ sbe_ac = strongbox_entity.attr('AC') # accessCount
136
+ @sbox['PayloadData'][sbe_index]['AC'] = sbe_ac if sbe_ac.length > 0
137
+
138
+ sbe_name = strongbox_entity.xpath('N').text
139
+ @sbox['PayloadData'][sbe_index]['N'] = sbe_name if sbe_name.length > 0
140
+
141
+ sbe_description = strongbox_entity.xpath('D').text
142
+ @sbox['PayloadData'][sbe_index]['D'] = sbe_description if sbe_description.length > 0
143
+
144
+ sbe_tags = strongbox_entity.xpath('T').text
145
+ @sbox['PayloadData'][sbe_index]['T'] = sbe_tags if sbe_tags.length > 0
146
+
147
+ child_entity = strongbox_entity.xpath('CE')
148
+ if child_entity.length > 0
149
+ @sbox['PayloadData'][sbe_index]['CE'] = []
150
+
151
+ child_entity.xpath('TFE').each_with_index { |text_field_entity, ce_index|
152
+
153
+ @sbox['PayloadData'][sbe_index]['CE'][ce_index] = {}
154
+
155
+ tfe_name = text_field_entity.xpath('N').text
156
+ @sbox['PayloadData'][sbe_index]['CE'][ce_index]['N'] = tfe_name if tfe_name.length > 0
157
+
158
+ tfe_content = text_field_entity.xpath('C').text
159
+ @sbox['PayloadData'][sbe_index]['CE'][ce_index]['C'] = tfe_content if tfe_content.length > 0
160
+ }
161
+ end
162
+ }
163
+ }
164
+ end
165
+
166
+ def render(verbose=false)
167
+ puts sbox['MT']
168
+
169
+ sbox['PayloadData'].each { |payload_data|
170
+ puts
171
+ puts payload_data['N'] unless payload_data['N'].nil?
172
+ puts payload_data['D'] unless payload_data['D'].nil?
173
+ puts payload_data['T'] unless payload_data['T'].nil?
174
+ payload_data['CE'].each { |strongbox_entity|
175
+ puts strongbox_entity['N'] + ': ' unless strongbox_entity['N'].nil?
176
+ puts strongbox_entity['C'] unless strongbox_entity['C'].nil?
177
+ }
178
+ puts "Access Count: #{payload_data['AC']}" if !payload_data['AC'].nil? && verbose
179
+ puts "#{convert_time_from_dot_net_epoch(payload_data['MT'].to_i)} (modify time)" if !payload_data['MT'].nil? && verbose
180
+ puts "#{convert_time_from_dot_net_epoch(payload_data['CT'].to_i)} (create time)" if !payload_data['CT'].nil? && verbose
181
+ }
182
+ 0
183
+ end
195
184
 
196
185
  private
197
186
 
198
- def convert_time_from_dot_net_epoch(t)
199
- Time.at((t-UNIX_EPOCH_IN_100NS_INTERVALS)*1e-7).utc.getlocal
200
- end
187
+ def self.resembles_base64?(string)
188
+ string.length % 4 == 0 && string =~ /^[A-Za-z0-9+\/=]+\Z/
189
+ end
190
+
191
+ def convert_time_from_dot_net_epoch(t)
192
+ Time.at((t-UNIX_EPOCH_IN_100NS_INTERVALS)*1e-7).utc.getlocal
193
+ end
201
194
  end
202
195
 
203
196
  #class String
204
- # def resembles_base64?
205
- # self.length % 4 == 0 && self =~ /^[A-Za-z0-9+\/=]+\Z/
206
- # end
197
+ # def resembles_base64?
198
+ # self.length % 4 == 0 && self =~ /^[A-Za-z0-9+\/=]+\Z/
199
+ # end
207
200
  #end
@@ -0,0 +1,4 @@
1
+ class Strongboxio
2
+ VERSION = '0.2.0'
3
+ end
4
+
@@ -0,0 +1,28 @@
1
+ lib = File.expand_path('../lib/', __FILE__)
2
+ $:.unshift lib unless $:.include?(lib)
3
+
4
+ require 'strongboxio/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'strongboxio'
8
+ s.version = Strongboxio::VERSION
9
+
10
+ s.summary = 'Decrypt and read www.Strongbox.io files'
11
+ s.description = "#{s.summary}. This is a combination gem and command-line utility."
12
+
13
+ s.authors = ['Alex Batko']
14
+ s.email = ['alexbatko@gmail.com']
15
+
16
+ s.homepage = 'https://github.com/abatko/strongboxio'
17
+
18
+ s.add_runtime_dependency 'nokogiri'
19
+ s.add_runtime_dependency 'highline'
20
+
21
+ s.files = `git ls-files`.split($/)
22
+ s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
23
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
24
+ s.require_paths = ['lib']
25
+
26
+ s.license = 'MIT'
27
+ end
28
+
@@ -3,26 +3,34 @@ require 'test_helper'
3
3
 
4
4
  class StrongboxioTest < Test::Unit::TestCase
5
5
 
6
- FILENAME = 'test/2fe22f4d-bd41-476a-8159-c6e0b6487f7d.sbox'
7
- PASSWORD = 'pWY4ic9q'
8
-
9
- def test_decrypt_from_file
10
- expect='<Payload><SchemaVersion>2.4</SchemaVersion><PayloadInfo><SchemaVersion>1.0</SchemaVersion><IP>PyOQP3GmNEA3T5BjBOiPg,DU4Qm4btjyejD1TYGoUjQ,nC9jzQ4Tv3RsrfxHzghe4A,YlVFCZO1FqBT91H0M3XmQA,WIVON6o61vebxLKJhXKQ,sQgzfRSQEkCJo25tVi47Q</IP><MT>2012-11-27T23:29:23.18873Z</MT><SD>xJ2VL0VourNV3a7cOJkinAMkVVEJJahV17MlbE5x8w</SD></PayloadInfo><PayloadData><T /><CE><SBE ID="7ijIF8xabBZjKsEKpUFnag" II="4" MT="634896555753103810" CT="634896555753102130" AC="0"><N>VanCity bank info</N><D>credit card, debit card, online banking</D><T>Credit, Debit, Card, Online, Banking</T><CE><TFE ID="K87JOu1lge4X7SSKE9elDQ" II="4" MT="634896555753103820"><N>credit card number</N><C>1234 5678 9012 3456</C></TFE><TFE ID="PTsGYmJb7f1B8nIquZfAMQ" II="4" MT="634896555753104130"><N>credit card expiration</N><C>11/2012</C></TFE><TFE ID="sfRgujycbbKIad58FkC1vg" II="4" MT="634896555753104380"><N>credit card verification value</N><C>123</C></TFE><TFE ID="bK7Vba0t2oupLiRGjEkt5Q" II="4" MT="634896555753104690"><N>debit card PIN</N><C>1234</C></TFE><TFE ID="2j9hKQJqCvbrVpmfRqJVA" II="4" MT="634896555753104970"><N>online banking password</N><C>qwerty</C></TFE></CE></SBE><SBE ID="L35bmTNw34lQnA70hmEVmg" II="3" MT="634896550747551430" CT="634896550747549900" AC="0"><N>fake Gmail account</N><D>used for Stackoverflow</D><T>Fake Gmail Stackoverflow</T><CE><TFE ID="Zmjyh5cMoj5s1AuH18EJA" II="3" MT="634896550747551440"><N>username</N><C>ima_fake@gmail.com</C></TFE><TFE ID="bOudR7nlwP40bo6V23jw" II="3" MT="634896550747551750"><N>password</N><C>password1</C></TFE><TFE ID="klbjFvsyyDEs956gkduKg" II="3" MT="634896550747552080"><N>gender</N><C>other</C></TFE><TFE ID="ckdoh9oM478UliFoeg" II="3" MT="634896550747552370"><N>backup</N><C>bart_simpson@gmail.com</C></TFE></CE></SBE><SBE ID="SwH6NoXPhg7qXLOUlDQyQ" II="2" MT="634896545666557670" CT="634896545666556630" AC="0"><N>just a name</N><D></D><T /><CE><TFE ID="u9LEZ2H2jQlzqc7a5vD2iA" II="2" MT="634896545666557680"><N /><C /></TFE></CE></SBE><SBE ID="ZoPVBFNFdOlRLT0FLO9BVQ" II="5" MT="634896557631887530" CT="634896545082402010" AC="0"><N>test item name</N><D>test item description</D><T>Test, Item, Tag1, Tag2, Tag3</T><CE><TFE ID="NyRRL7tmBMr0VrDoyzfwg" II="1" MT="634896545082426060"><N>test field name</N><C>test secure text</C></TFE></CE></SBE></CE></PayloadData></Payload>'
11
-
12
- assert_equal expect,
13
- Strongboxio.decrypt(FILENAME, PASSWORD)
14
- end
15
-
16
- def test_strongboxio_instantiation
17
- # #<Strongboxio:0x1011b2e28 @sbox={"PayloadData"=>[{"CE"=>[{"C"=>"1234 5678 9012 3456", "N"=>"credit card number"}, {"C"=>"11/2012", "N"=>"credit card expiration"}, {"C"=>"123", "N"=>"credit card verification value"}, {"C"=>"1234", "N"=>"debit card PIN"}, {"C"=>"qwerty", "N"=>"online banking password"}], "AC"=>"0", "N"=>"VanCity bank info", "D"=>"credit card, debit card, online banking", "CT"=>"634896555753102130", "MT"=>"634896555753103810", "T"=>"Credit, Debit, Card, Online, Banking"}, {"CE"=>[{"C"=>"ima_fake@gmail.com", "N"=>"username"}, {"C"=>"password1", "N"=>"password"}, {"C"=>"other", "N"=>"gender"}, {"C"=>"bart_simpson@gmail.com", "N"=>"backup"}], "AC"=>"0", "N"=>"fake Gmail account", "D"=>"used for Stackoverflow", "CT"=>"634896550747549900", "MT"=>"634896550747551430", "T"=>"Fake Gmail Stackoverflow"}, {"CE"=>[{}], "AC"=>"0", "N"=>"just a name", "CT"=>"634896545666556630", "MT"=>"634896545666557670"}, {"CE"=>[{"C"=>"test secure text", "N"=>"test field name"}], "AC"=>"0", "N"=>"test item name", "D"=>"test item description", "CT"=>"634896545082402010", "MT"=>"634896557631887530", "T"=>"Test, Item, Tag1, Tag2, Tag3"}], "MT"=>"2012-11-27T23:29:23.18873Z"}>
18
-
19
- d = Strongboxio.decrypt(FILENAME, PASSWORD)
20
- sbox = Strongboxio.new(d)
21
- assert_equal 'Strongboxio',
22
- sbox.class.to_s
23
- assert_equal '2012-11-27T23:29:23.18873Z',
24
- sbox.sbox['MT']
25
- end
6
+ FILENAME = 'test/2fe22f4d-bd41-476a-8159-c6e0b6487f7d.sbox'
7
+ PASSWORD = 'pWY4ic9q'
8
+
9
+ def test_decrypt_from_file
10
+ expect = '<Payload><SchemaVersion>2.4</SchemaVersion><PayloadInfo><SchemaVersion>1.0</SchemaVersion><IP>PyOQP3GmNEA3T5BjBOiPg,DU4Qm4btjyejD1TYGoUjQ,nC9jzQ4Tv3RsrfxHzghe4A,YlVFCZO1FqBT91H0M3XmQA,WIVON6o61vebxLKJhXKQ,sQgzfRSQEkCJo25tVi47Q</IP><MT>2012-11-27T23:29:23.18873Z</MT><SD>xJ2VL0VourNV3a7cOJkinAMkVVEJJahV17MlbE5x8w</SD></PayloadInfo><PayloadData><T /><CE><SBE ID="7ijIF8xabBZjKsEKpUFnag" II="4" MT="634896555753103810" CT="634896555753102130" AC="0"><N>VanCity bank info</N><D>credit card, debit card, online banking</D><T>Credit, Debit, Card, Online, Banking</T><CE><TFE ID="K87JOu1lge4X7SSKE9elDQ" II="4" MT="634896555753103820"><N>credit card number</N><C>1234 5678 9012 3456</C></TFE><TFE ID="PTsGYmJb7f1B8nIquZfAMQ" II="4" MT="634896555753104130"><N>credit card expiration</N><C>11/2012</C></TFE><TFE ID="sfRgujycbbKIad58FkC1vg" II="4" MT="634896555753104380"><N>credit card verification value</N><C>123</C></TFE><TFE ID="bK7Vba0t2oupLiRGjEkt5Q" II="4" MT="634896555753104690"><N>debit card PIN</N><C>1234</C></TFE><TFE ID="2j9hKQJqCvbrVpmfRqJVA" II="4" MT="634896555753104970"><N>online banking password</N><C>qwerty</C></TFE></CE></SBE><SBE ID="L35bmTNw34lQnA70hmEVmg" II="3" MT="634896550747551430" CT="634896550747549900" AC="0"><N>fake Gmail account</N><D>used for Stackoverflow</D><T>Fake Gmail Stackoverflow</T><CE><TFE ID="Zmjyh5cMoj5s1AuH18EJA" II="3" MT="634896550747551440"><N>username</N><C>ima_fake@gmail.com</C></TFE><TFE ID="bOudR7nlwP40bo6V23jw" II="3" MT="634896550747551750"><N>password</N><C>password1</C></TFE><TFE ID="klbjFvsyyDEs956gkduKg" II="3" MT="634896550747552080"><N>gender</N><C>other</C></TFE><TFE ID="ckdoh9oM478UliFoeg" II="3" MT="634896550747552370"><N>backup</N><C>bart_simpson@gmail.com</C></TFE></CE></SBE><SBE ID="SwH6NoXPhg7qXLOUlDQyQ" II="2" MT="634896545666557670" CT="634896545666556630" AC="0"><N>just a name</N><D></D><T /><CE><TFE ID="u9LEZ2H2jQlzqc7a5vD2iA" II="2" MT="634896545666557680"><N /><C /></TFE></CE></SBE><SBE ID="ZoPVBFNFdOlRLT0FLO9BVQ" II="5" MT="634896557631887530" CT="634896545082402010" AC="0"><N>test item name</N><D>test item description</D><T>Test, Item, Tag1, Tag2, Tag3</T><CE><TFE ID="NyRRL7tmBMr0VrDoyzfwg" II="1" MT="634896545082426060"><N>test field name</N><C>test secure text</C></TFE></CE></SBE></CE></PayloadData></Payload>'
11
+ d = Strongboxio.decrypt(FILENAME, PASSWORD)
12
+ assert_equal expect, d
13
+ end
14
+
15
+ def test_static_render
16
+ d = Strongboxio.decrypt(FILENAME, PASSWORD)
17
+ assert_equal 0, Strongboxio.render(d) # not checking puts (and don't want to waste memory on creating string)
18
+ end
19
+
20
+ def test_strongboxio_instantiation
21
+ # #<Strongboxio:0x1011b2e28 @sbox={"PayloadData"=>[{"CE"=>[{"C"=>"1234 5678 9012 3456", "N"=>"credit card number"}, {"C"=>"11/2012", "N"=>"credit card expiration"}, {"C"=>"123", "N"=>"credit card verification value"}, {"C"=>"1234", "N"=>"debit card PIN"}, {"C"=>"qwerty", "N"=>"online banking password"}], "AC"=>"0", "N"=>"VanCity bank info", "D"=>"credit card, debit card, online banking", "CT"=>"634896555753102130", "MT"=>"634896555753103810", "T"=>"Credit, Debit, Card, Online, Banking"}, {"CE"=>[{"C"=>"ima_fake@gmail.com", "N"=>"username"}, {"C"=>"password1", "N"=>"password"}, {"C"=>"other", "N"=>"gender"}, {"C"=>"bart_simpson@gmail.com", "N"=>"backup"}], "AC"=>"0", "N"=>"fake Gmail account", "D"=>"used for Stackoverflow", "CT"=>"634896550747549900", "MT"=>"634896550747551430", "T"=>"Fake Gmail Stackoverflow"}, {"CE"=>[{}], "AC"=>"0", "N"=>"just a name", "CT"=>"634896545666556630", "MT"=>"634896545666557670"}, {"CE"=>[{"C"=>"test secure text", "N"=>"test field name"}], "AC"=>"0", "N"=>"test item name", "D"=>"test item description", "CT"=>"634896545082402010", "MT"=>"634896557631887530", "T"=>"Test, Item, Tag1, Tag2, Tag3"}], "MT"=>"2012-11-27T23:29:23.18873Z"}>
22
+
23
+ d = Strongboxio.decrypt(FILENAME, PASSWORD)
24
+ sbox = Strongboxio.new(d)
25
+ assert_equal 'Strongboxio', sbox.class.to_s
26
+ assert_equal '2012-11-27T23:29:23.18873Z', sbox.sbox['MT']
27
+ end
28
+
29
+ def test_instance_render
30
+ d = Strongboxio.decrypt(FILENAME, PASSWORD)
31
+ sbox = Strongboxio.new(d)
32
+ assert_equal 0, sbox.render # not checking puts (and don't want to waste memory on creating string)
33
+ end
26
34
 
27
35
  end
28
36
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strongboxio
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,16 +9,48 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-29 00:00:00.000000000Z
13
- dependencies: []
14
- description: Decrypt and read www.Strongbox.io files.
12
+ date: 2013-01-03 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: &2153457760 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2153457760
25
+ - !ruby/object:Gem::Dependency
26
+ name: highline
27
+ requirement: &2153457340 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *2153457340
36
+ description: Decrypt and read www.Strongbox.io files. This is a combination gem and
37
+ command-line utility.
15
38
  email:
16
39
  - alexbatko@gmail.com
17
- executables: []
40
+ executables:
41
+ - strongbox.rb
18
42
  extensions: []
19
43
  extra_rdoc_files: []
20
44
  files:
45
+ - .gitignore
46
+ - Gemfile
47
+ - LICENSE.txt
48
+ - README.md
49
+ - Rakefile
50
+ - bin/strongbox.rb
21
51
  - lib/strongboxio.rb
52
+ - lib/strongboxio/version.rb
53
+ - strongboxio.gemspec
22
54
  - test/2fe22f4d-bd41-476a-8159-c6e0b6487f7d.sbox
23
55
  - test/test_helper.rb
24
56
  - test/test_strongboxio.rb