strongboxio 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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