keybox 1.0.0 → 1.1.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.
@@ -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