keybox 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,13 +1,13 @@
1
- require 'openssl'
1
+ require 'digest/sha2'
2
2
  module Keybox
3
3
  #
4
4
  # By default Keybox wants to use sha-256 as the hashing function.
5
- # This is available in OpenSSL 0.9.8 or greater.
5
+ # a sha-256 library ships with ruby in 'digest/sha2'
6
6
  #
7
7
  module Digest
8
8
  SHA_256 = "sha256"
9
- CLASSES = { SHA_256 => ::OpenSSL::Digest::SHA256 }
10
9
  DEFAULT_ALGORITHM = SHA_256
11
- ALGORITHMS = CLASSES.keys
10
+ CLASSES = { SHA_256 => ::Digest::SHA256 }
11
+ ALGORITHMS = CLASSES.keys
12
12
  end
13
13
  end
@@ -0,0 +1,91 @@
1
+ begin
2
+ # first try to use the gem
3
+ require 'rubygems'
4
+ gem 'highline', ">=1.2.6"
5
+ rescue Gem::LoadError
6
+ # If that fails, then use what ships with keybox
7
+ $: << File.join(Keybox::APP_VENDOR_DIR,"highline")
8
+ end
9
+
10
+ require 'highline'
11
+
12
+ module Keybox
13
+ # including this module assumes that the class included has
14
+ # an @highline variable
15
+ module HighLineUtil
16
+
17
+ # the scheme to apply when no scheme is allowed so that
18
+ # everything works as normal
19
+ NONE_SCHEME = {
20
+ :prompt => [ :clear ],
21
+ :header => [ :clear ],
22
+ :header_bar => [ :clear ],
23
+ :line_number => [ :clear ],
24
+ :even_row => [ :clear ],
25
+ :odd_row => [ :clear ],
26
+ :information => [ :clear ],
27
+ :error => [ :clear ],
28
+ :private => [ :clear ],
29
+ :separator => [ :clear ],
30
+ :separator_bar => [ :clear ],
31
+ :name => [ :clear ],
32
+ :value => [ :clear ],
33
+ :normal => [ :clear ],
34
+ }
35
+
36
+ #
37
+ # A whole line of input needs a particular color. This makes it
38
+ # easy to have all the ERB happening in one spot to avoid
39
+ # escaping issues.
40
+ def hsay(output,color_scheme)
41
+ @highline.say("<%= color(%Q{#{output}},'#{color_scheme}') %>")
42
+ end
43
+
44
+ def hagree(output)
45
+ @highline.agree("<%= color(%Q{#{output}},:prompt) %> ")
46
+ end
47
+
48
+ #
49
+ # Prompt for input, returning what was typed. Options can be
50
+ # passed as If echo is false,
51
+ # then '*' is printed out for each character typed in. If it is
52
+ # any other character then that is output instead.
53
+ #
54
+ # If validate is set to true, then it will prompt twice and make
55
+ # sure that the two values match
56
+ def prompt(p,options)
57
+ validated = false
58
+ line = ""
59
+ extra_prompt = " (again)"
60
+ original_prompt = p
61
+ validation_prompt = original_prompt + extra_prompt
62
+
63
+ echo = options[:echo].nil? ? true : options[:echo]
64
+ width = options[:width] || 30
65
+ validate = options[:validate] || false
66
+
67
+ until validated do
68
+ line = @highline.ask("<%= color('#{original_prompt.rjust(width)}',:prompt) %> : ") { |q| q.echo = echo }
69
+ # ensure we are at the beginning of the line
70
+ @stdout.print "\r"
71
+
72
+ # if we are validating then prompt again to validate
73
+ if validate then
74
+ v = @highline.ask("<%= color('#{validation_prompt.rjust(width)}', :prompt) %> : ") { |q| q.echo = echo }
75
+ # ensure we are at the beginning of the line
76
+ @stdout.print "\r"
77
+
78
+ # line on some terminals
79
+ if v != line then
80
+ @highline.say("<%= color('Entries do not match, try again.', :error) %>")
81
+ else
82
+ validated = true
83
+ end
84
+ else
85
+ validated = true
86
+ end
87
+ end
88
+ return line
89
+ end
90
+ end
91
+ end
@@ -68,15 +68,18 @@ module Keybox
68
68
 
69
69
  attr_reader :records
70
70
 
71
- ITERATIONS = 2048
71
+ ITERATIONS = 2048
72
+ MINIMUM_PHRASE_LENGTH = 4
72
73
  def initialize(passphrase,path)
73
74
  super()
74
75
 
76
+
75
77
  @path = path
76
78
  @passphrase = passphrase
77
79
  @records = []
78
80
 
79
81
  if not load_from_file then
82
+ strength_check(@passphrase)
80
83
  self.version = Keybox::VERSION
81
84
 
82
85
  self.key_calc_iterations = ITERATIONS
@@ -98,6 +101,7 @@ module Keybox
98
101
  # Change the master password of the container
99
102
  #
100
103
  def passphrase=(new_passphrase)
104
+ strength_check(new_passphrase)
101
105
  @passphrase = new_passphrase
102
106
  self.key_digest_salt = Keybox::RandomDevice.random_bytes(32)
103
107
  self.key_digest = calculated_key_digest(new_passphrase)
@@ -239,6 +243,20 @@ module Keybox
239
243
 
240
244
  private
241
245
 
246
+ #
247
+ # validate the passphrase against some criteria. Right now
248
+ # this criteria is very minimal and should be changed at
249
+ # some point.
250
+ #
251
+ # An invalid passphrase raises a Keybox::ValidationException
252
+ #
253
+ def strength_check(phrase)
254
+ if phrase.to_s.length < MINIMUM_PHRASE_LENGTH
255
+ raise Keybox::ValidationError, "Passphrase is not strong enough. It is too short."
256
+ end
257
+ true
258
+ end
259
+
242
260
  #
243
261
  # calculate the key digest of the input pass phrase and
244
262
  # compare that to the digest in the data file. If they are
@@ -10,7 +10,7 @@ context "Keybox Base Application" do
10
10
 
11
11
  specify "executing with no args should have output on stdout" do
12
12
  kba = Keybox::Application::Base.new(nil)
13
- kba.stdout = StringIO.new
13
+ kba.set_io(StringIO.new,StringIO.new,StringIO.new)
14
14
  kba.run
15
15
  kba.stdout.string.size.should_be > 0
16
16
  end
@@ -18,8 +18,7 @@ context "Keybox Base Application" do
18
18
 
19
19
  specify "invalid options set the error message, exit 1 and have output on stderr" do
20
20
  kba = Keybox::Application::Base.new(["--invalid-option"])
21
- kba.stderr = StringIO.new
22
- kba.stdout = StringIO.new
21
+ kba.set_io(StringIO.new,StringIO.new,StringIO.new)
23
22
  begin
24
23
  kba.run
25
24
  rescue SystemExit => se
@@ -32,7 +31,7 @@ context "Keybox Base Application" do
32
31
 
33
32
  specify "help has output on stdout and exits 0" do
34
33
  kba = Keybox::Application::Base.new(["--h"])
35
- kba.stdout = StringIO.new
34
+ kba.set_io(StringIO.new,StringIO.new,StringIO.new)
36
35
  begin
37
36
  kba.run
38
37
  rescue SystemExit => se
@@ -43,7 +42,7 @@ context "Keybox Base Application" do
43
42
 
44
43
  specify "version has output on stdout and exits 0" do
45
44
  kba = Keybox::Application::Base.new(["--ver"])
46
- kba.stdout = StringIO.new
45
+ kba.set_io(StringIO.new,StringIO.new,StringIO.new)
47
46
  begin
48
47
  kba.run
49
48
  rescue SystemExit => se
@@ -37,6 +37,7 @@ context "Keybox Password Safe Application" do
37
37
  @import_csv.unlink
38
38
  @bad_import_csv.unlink
39
39
  @export_csv.unlink
40
+
40
41
  end
41
42
 
42
43
  specify "nil argv should do nothing" do
@@ -46,8 +47,7 @@ context "Keybox Password Safe Application" do
46
47
 
47
48
  specify "executing with no args should have output on stdout" do
48
49
  kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path])
49
- kps.stdout = StringIO.new
50
- kps.stdin = StringIO.new(@passphrase)
50
+ kps.set_io(StringIO.new(@passphrase),StringIO.new,StringIO.new)
51
51
  kps.run
52
52
  kps.stdout.string.size.should_be > 0
53
53
  end
@@ -63,8 +63,7 @@ context "Keybox Password Safe Application" do
63
63
 
64
64
  specify "more than one command options is an error" do
65
65
  kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--add", "account", "--edit", "account"])
66
- kps.stderr = StringIO.new
67
- kps.stdout = StringIO.new
66
+ kps.set_io(StringIO.new,StringIO.new,StringIO.new)
68
67
  begin
69
68
  kps.run
70
69
  rescue SystemExit => se
@@ -76,19 +75,19 @@ context "Keybox Password Safe Application" do
76
75
 
77
76
  specify "space separated words are okay for names" do
78
77
  kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--add", "An Example"])
79
- kps.stderr = StringIO.new
80
- kps.stdout = StringIO.new
81
78
  prompted_values = [@passphrase, "An example"] + %w(example.com someuser apassword apassword noinfo yes)
82
- kps.stdin = StringIO.new(prompted_values.join("\n"))
79
+ stdin = StringIO.new(prompted_values.join("\n"))
80
+ kps.set_io(stdin,StringIO.new,StringIO.new)
83
81
  kps.run
84
82
  kps.db.records.size.should_eql 3
85
83
  end
86
84
 
87
85
 
88
86
  specify "invalid options set the error message, exit 1 and have output on stderr" do
89
- kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path,"--invalid-option"])
90
- kps.stderr = StringIO.new
91
- kps.stdout = StringIO.new
87
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path,
88
+ "-c", @testing_cfg.path,
89
+ "--invalid-option"])
90
+ kps.set_io(StringIO.new,StringIO.new,StringIO.new)
92
91
  begin
93
92
  kps.run
94
93
  rescue SystemExit => se
@@ -101,7 +100,7 @@ context "Keybox Password Safe Application" do
101
100
 
102
101
  specify "help has output on stdout and exits 0" do
103
102
  kps = Keybox::Application::PasswordSafe.new(["--h"])
104
- kps.stdout = StringIO.new
103
+ kps.set_io(StringIO.new,StringIO.new,StringIO.new)
105
104
  begin
106
105
  kps.run
107
106
  rescue SystemExit => se
@@ -112,7 +111,7 @@ context "Keybox Password Safe Application" do
112
111
 
113
112
  specify "version has output on stdout and exits 0" do
114
113
  kps = Keybox::Application::PasswordSafe.new(["--ver"])
115
- kps.stdout = StringIO.new
114
+ kps.set_io(StringIO.new,StringIO.new,StringIO.new)
116
115
  begin
117
116
  kps.run
118
117
  rescue SystemExit => se
@@ -124,8 +123,8 @@ context "Keybox Password Safe Application" do
124
123
  specify "prompted for password twice to create database initially" do
125
124
  File.unlink(@testing_db.path)
126
125
  kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path])
127
- kps.stdout = StringIO.new
128
- kps.stdin = StringIO.new([@passphrase,@passphrase].join("\n"))
126
+ stdin = StringIO.new([@passphrase,@passphrase].join("\n"))
127
+ kps.set_io(stdin,StringIO.new,StringIO.new)
129
128
  kps.run
130
129
  kps.db.records.size.should_eql 0
131
130
  kps.stdout.string.should_satisfy { |msg| msg =~ /Creating initial database./m }
@@ -134,26 +133,26 @@ context "Keybox Password Safe Application" do
134
133
 
135
134
  specify "file can be opened with password" do
136
135
  kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path])
137
- kps.stdout = StringIO.new
138
- kps.stdin = StringIO.new(@passphrase + "\n")
136
+ stdin = StringIO.new(@passphrase + "\n")
137
+ kps.set_io(stdin,StringIO.new,StringIO.new)
139
138
  kps.run
140
139
  kps.db.records.size.should_eql 2
141
140
  end
142
141
 
143
142
  specify "adding an entry to the database works" do
144
143
  kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--add", "example.com"])
145
- kps.stdout = StringIO.new
146
144
  prompted_values = [@passphrase] + %w(example.com example.com someuser apassword apassword noinfo yes)
147
- kps.stdin = StringIO.new(prompted_values.join("\n"))
145
+ stdin = StringIO.new(prompted_values.join("\n"))
146
+ kps.set_io(stdin,StringIO.new,StringIO.new)
148
147
  kps.run
149
148
  kps.db.records.size.should_eql 3
150
149
  end
151
150
 
152
151
  specify "editing an entry in the database works" do
153
152
  kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--edit", "localhost"])
154
- kps.stdout = StringIO.new
155
153
  prompted_values = [@passphrase] + %w(yes example.com example.com someother anewpassword anewpassword someinfo yes)
156
- kps.stdin = StringIO.new(prompted_values.join("\n"))
154
+ stdin = StringIO.new(prompted_values.join("\n"))
155
+ kps.set_io(stdin,StringIO.new,StringIO.new)
157
156
  kps.run
158
157
  kps.db.records.size.should_eql 2
159
158
  kps.db.find("someother")[0].additional_info.should_eql "someinfo"
@@ -161,27 +160,29 @@ context "Keybox Password Safe Application" do
161
160
 
162
161
  specify "add a url entry to the database" do
163
162
  kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--use-hash-for-url", "--add", "http://www.example.com"])
164
- kps.stdout = StringIO.new
165
163
  prompted_values = [@passphrase] + %w(www.example.com http://www.example.com someuser noinfo yes)
166
- kps.stdin = StringIO.new(prompted_values.join("\n"))
164
+ stdin = StringIO.new(prompted_values.join("\n"))
165
+ kps.set_io(stdin,StringIO.new,StringIO.new)
167
166
  kps.run
168
167
  kps.db.records.size.should_eql 3
169
168
  end
170
169
 
171
170
  specify "double prompting on failed password for entry to the database works" do
172
- kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--add", "example.com"])
173
- kps.stdout = StringIO.new
171
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path,
172
+ "-c", @testing_cfg.path,
173
+ "--add", "example.com"])
174
174
  prompted_values = [@passphrase, ""] + %w(example.com someuser apassword abadpassword abcdef abcdef noinfo yes)
175
- kps.stdin = StringIO.new(prompted_values.join("\n"))
175
+ stdin = StringIO.new(prompted_values.join("\n"))
176
+ kps.set_io(stdin,StringIO.new,StringIO.new)
176
177
  kps.run
177
178
  kps.db.records.size.should_eql 3
178
179
  end
179
180
 
180
181
  specify "able to delete an entry" do
181
182
  kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--delete", "example"])
182
- kps.stdout = StringIO.new
183
183
  prompted_values = [@passphrase] + %w(Yes)
184
- kps.stdin = StringIO.new(prompted_values.join("\n"))
184
+ stdin = StringIO.new(prompted_values.join("\n"))
185
+ kps.set_io(stdin,StringIO.new,StringIO.new)
185
186
  kps.run
186
187
  kps.db.records.size.should_eql 1
187
188
  kps.stdout.string.should_satisfy { |msg| msg =~ /example' deleted/ }
@@ -189,9 +190,9 @@ context "Keybox Password Safe Application" do
189
190
 
190
191
  specify "able to cancel deletion" do
191
192
  kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--delete", "example"])
192
- kps.stdout = StringIO.new
193
193
  prompted_values = [@passphrase] + %w(No)
194
- kps.stdin = StringIO.new(prompted_values.join("\n"))
194
+ stdin = StringIO.new(prompted_values.join("\n"))
195
+ kps.set_io(stdin,StringIO.new,StringIO.new)
195
196
  kps.run
196
197
  kps.db.records.size.should_eql 2
197
198
  kps.stdout.string.should_satisfy { |msg| msg =~ /example' deleted/ }
@@ -199,24 +200,24 @@ context "Keybox Password Safe Application" do
199
200
 
200
201
  specify "list all the entries" do
201
202
  kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--list"])
202
- kps.stdout = StringIO.new
203
- kps.stdin = StringIO.new(@passphrase)
203
+ stdin = StringIO.new(@passphrase)
204
+ kps.set_io(stdin,StringIO.new,StringIO.new)
204
205
  kps.run
205
206
  kps.stdout.string.should_satisfy { |msg| msg =~ /2./m }
206
207
  end
207
208
 
208
209
  specify "listing no entries found" do
209
210
  kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--list", "nothing"])
210
- kps.stdout = StringIO.new
211
- kps.stdin = StringIO.new(@passphrase)
211
+ stdin = StringIO.new(@passphrase)
212
+ kps.set_io(stdin,StringIO.new,StringIO.new)
212
213
  kps.run
213
214
  kps.stdout.string.should_satisfy { |msg| msg =~ /No matching records were found./ }
214
215
  end
215
216
 
216
217
  specify "showing no entries found" do
217
218
  kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--show", "nothing"])
218
- kps.stdout = StringIO.new
219
- kps.stdin = StringIO.new(@passphrase)
219
+ stdin = StringIO.new(@passphrase)
220
+ kps.set_io(stdin,StringIO.new,StringIO.new)
220
221
  kps.run
221
222
  kps.stdout.string.should_satisfy { |msg| msg =~ /No matching records were found./ }
222
223
  end
@@ -224,32 +225,44 @@ context "Keybox Password Safe Application" do
224
225
 
225
226
  specify "show all the entries" do
226
227
  kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--show"])
227
- kps.stdout = StringIO.new
228
- kps.stdin = StringIO.new(@passphrase)
228
+ stdin = StringIO.new([@passphrase, "", ""].join("\n"))
229
+ kps.set_io(stdin,StringIO.new,StringIO.new)
229
230
  kps.run
230
231
  kps.stdout.string.should_satisfy { |msg| msg =~ /2./m }
231
232
  end
232
233
 
233
234
  specify "changing master password works" do
234
235
  kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--master-password"])
235
- kps.stdout = StringIO.new
236
- kps.stdin = StringIO.new([@passphrase, "I really love ruby.", "I really love ruby."].join("\n"))
236
+ stdin = StringIO.new([@passphrase, "I really love ruby.", "I really love ruby."].join("\n"))
237
+ kps.set_io(stdin,StringIO.new,StringIO.new)
237
238
  kps.run
238
239
  kps.stdout.string.should_satisfy { |msg| msg =~ /New master password set/m }
239
240
  end
240
241
 
242
+ specify "master password must be non-zero" do
243
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path, "--master-password"])
244
+ stdin = StringIO.new([@passphrase, "", ""].join("\n"))
245
+ kps.set_io(stdin,StringIO.new,StringIO.new)
246
+ begin
247
+ kps.run
248
+ rescue SystemExit => se
249
+ kps.stdout.string.should_satisfy { |msg| msg =~ /Passphrase is not strong enough./m }
250
+ se.status.should_eql 1
251
+ end
252
+ end
253
+
241
254
  specify "importing from a valid csv file" do
242
255
  kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path,"-i", @import_csv.path])
243
- kps.stdout = StringIO.new
244
- kps.stdin = StringIO.new([@passphrase, @passphrase].join("\n"))
256
+ stdin = StringIO.new([@passphrase, @passphrase].join("\n"))
257
+ kps.set_io(stdin,StringIO.new,StringIO.new)
245
258
  kps.run
246
259
  kps.stdout.string.should_satisfy { |msg| msg =~ /Imported \d* records from/m }
247
260
  end
248
261
 
249
262
  specify "Error message give on invalid imported csv" do
250
263
  kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path,"-i", @bad_import_csv.path])
251
- kps.stdout = StringIO.new
252
- kps.stdin = StringIO.new([@passphrase, @passphrase].join("\n"))
264
+ stdin = StringIO.new([@passphrase, @passphrase].join("\n"))
265
+ kps.set_io(stdin,StringIO.new,StringIO.new)
253
266
  begin
254
267
  kps.run
255
268
  rescue SystemExit => se
@@ -260,9 +273,35 @@ context "Keybox Password Safe Application" do
260
273
 
261
274
  specify "able to export to a csv" do
262
275
  kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path,"-x", @export_csv.path])
263
- kps.stdout = StringIO.new
264
- kps.stdin = StringIO.new([@passphrase, @passphrase].join("\n"))
276
+ stdin = StringIO.new([@passphrase, @passphrase].join("\n"))
277
+ kps.set_io(stdin,StringIO.new,StringIO.new)
265
278
  kps.run
266
279
  kps.stdout.string.should_satisfy { |msg| msg =~ /Exported \d* records to/m }
267
280
  end
281
+
282
+ specify "able to turn off color schemes" do
283
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path,"--color","none"])
284
+ kps.set_io(StringIO.new(@passphrase),StringIO.new,StringIO.new)
285
+ kps.run
286
+ kps.options.color_scheme.should_equal :none
287
+ end
288
+
289
+ specify "an invalid color scheme results in a no color scheme" do
290
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path,"--color","dne"])
291
+ kps.set_io(StringIO.new(@passphrase),StringIO.new,StringIO.new)
292
+ kps.run
293
+ kps.options.color_scheme.should_equal :none
294
+ end
295
+
296
+ specify "an incomplete color scheme results in an error message and then 'none' color scheme" do
297
+ bad_color_scheme = { :bad => [:bold, :white, :on_magenta] }
298
+ File.open("/tmp/test.color_scheme.yaml", "w+") { |f| f.write(bad_color_scheme.to_yaml) }
299
+ kps = Keybox::Application::PasswordSafe.new(["-f", @testing_db.path, "-c", @testing_cfg.path,"--color","test"])
300
+ kps.set_io(StringIO.new(@passphrase),StringIO.new,StringIO.new)
301
+ kps.run
302
+
303
+ File.unlink("/tmp/test.color_scheme.yaml")
304
+ kps.stdout.string.should_satisfy { |msg| msg =~ /It is missing the following items/m }
305
+ kps.options.color_scheme.should_equal :none
306
+ end
268
307
  end