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 +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +19 -0
- data/README.md +97 -0
- data/Rakefile +23 -0
- data/bin/strongbox.rb +38 -0
- data/lib/strongboxio.rb +184 -191
- data/lib/strongboxio/version.rb +4 -0
- data/strongboxio.gemspec +28 -0
- data/test/test_strongboxio.rb +28 -20
- metadata +37 -5
data/.gitignore
ADDED
data/Gemfile
ADDED
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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
|
-
|
199
|
-
|
200
|
-
|
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
|
-
#
|
205
|
-
#
|
206
|
-
#
|
197
|
+
# def resembles_base64?
|
198
|
+
# self.length % 4 == 0 && self =~ /^[A-Za-z0-9+\/=]+\Z/
|
199
|
+
# end
|
207
200
|
#end
|
data/strongboxio.gemspec
ADDED
@@ -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
|
+
|
data/test/test_strongboxio.rb
CHANGED
@@ -3,26 +3,34 @@ require 'test_helper'
|
|
3
3
|
|
4
4
|
class StrongboxioTest < Test::Unit::TestCase
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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.
|
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:
|
13
|
-
dependencies:
|
14
|
-
|
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
|