keybox 1.0.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.
@@ -0,0 +1,132 @@
1
+ require 'keybox'
2
+ require 'keybox/application/password_generator'
3
+
4
+ context "Keybox Password Generator Application" do
5
+ setup do
6
+ end
7
+
8
+ specify "nil argv should do nothing" do
9
+ kpg = Keybox::Application::PasswordGenerator.new(nil)
10
+ kpg.error_message.should_be nil
11
+ end
12
+
13
+ specify "invalid options set the error message, exit 1 and have output on stderr" do
14
+ kpg = Keybox::Application::PasswordGenerator.new(["--invalid-option"])
15
+ kpg.stderr = StringIO.new
16
+ begin
17
+ kpg.run
18
+ rescue SystemExit => se
19
+ kpg.error_message.should_satisfy { |msg| msg =~ /Try.*--help/m }
20
+ kpg.stderr.string.should_satisfy { |msg| msg =~ /Try.*--help/m }
21
+ se.status.should == 1
22
+ end
23
+ end
24
+
25
+ specify "can set the algorithm" do
26
+ kpg = Keybox::Application::PasswordGenerator.new(["--alg", "pron"])
27
+ kpg.stdout = StringIO.new
28
+ kpg.run
29
+ kpg.options.algorithm.should == :pronounceable
30
+ end
31
+
32
+ specify "can specify the number of passwords created " do
33
+ kpg = Keybox::Application::PasswordGenerator.new(["--num", "4"])
34
+ kpg.stdout = StringIO.new
35
+ kpg.run
36
+ kpg.options.number_to_generate.should_eql 4
37
+ kpg.stdout.string.split(/\s+/).size.should == 4
38
+ end
39
+
40
+ specify "help has output on stdout and exits 0" do
41
+ kpg = Keybox::Application::PasswordGenerator.new(["--h"])
42
+ kpg.stdout = StringIO.new
43
+ begin
44
+ kpg.run
45
+ rescue SystemExit => se
46
+ se.status.should_eql 0
47
+ kpg.stdout.string.length.should_be > 0
48
+ end
49
+ kpg.stdout.string.should_satisfy { |msg| msg =~ /--help/m }
50
+ end
51
+
52
+ specify "version has output on stdout and exits 0" do
53
+ kpg = Keybox::Application::PasswordGenerator.new(["--version"])
54
+ kpg.stdout = StringIO.new
55
+ begin
56
+ kpg.run
57
+ rescue SystemExit => se
58
+ se.status.should_eql 0
59
+ end
60
+ kpg.stdout.string.should_satisfy { |msg| msg =~ /version 1.0.0/m }
61
+ end
62
+
63
+ specify "minimum length can be set and all generated passwords will have length >= minimum length" do
64
+ kpg = Keybox::Application::PasswordGenerator.new(["--min", "4"])
65
+ kpg.stdout = StringIO.new
66
+ kpg.run
67
+
68
+ kpg.options.min_length.should_eql 4
69
+ kpg.stdout.string.split("\n").each do |pass|
70
+ pass.length.should_satisfy { |s| s >= kpg.options.min_length }
71
+ end
72
+ end
73
+
74
+ specify "maximum length can be set and all generated passwords will have length <= maximum length" do
75
+ kpg = Keybox::Application::PasswordGenerator.new(["--max", "4", "--min", "3"])
76
+ kpg.stdout = StringIO.new
77
+ kpg.run
78
+
79
+ kpg.options.max_length.should_eql 4
80
+ kpg.stdout.string.split("\n").each do |pass|
81
+ pass.length.should_satisfy { |s| s <= 4 }
82
+ end
83
+ end
84
+
85
+ specify "setting an invalid required symbol set exits 1 and outputs data on stderr" do
86
+ kpg = Keybox::Application::PasswordGenerator.new(["--req","bunk"])
87
+ kpg.stderr = StringIO.new
88
+ begin
89
+ kpg.run
90
+ rescue SystemExit => se
91
+ kpg.error_message.should_satisfy { |msg| msg =~ /Try.*--help/m }
92
+ kpg.stderr.string.should_satisfy { |msg| msg =~ /Try.*--help/m }
93
+ se.status.should == 1
94
+ end
95
+
96
+ end
97
+
98
+ specify "setting an invalid use symbol set exits 1 and outputs data on stderr" do
99
+ kpg = Keybox::Application::PasswordGenerator.new(["--use","bunk"])
100
+ kpg.stderr = StringIO.new
101
+ begin
102
+ kpg.run
103
+ rescue SystemExit => se
104
+ kpg.error_message.should_satisfy { |msg| msg =~ /Try.*--help/m }
105
+ kpg.stderr.string.should_satisfy { |msg| msg =~ /Try.*--help/m }
106
+ se.status.should == 1
107
+ end
108
+
109
+ end
110
+
111
+ specify "setting an valid use symbol works" do
112
+ kpg = Keybox::Application::PasswordGenerator.new(["--use","l"])
113
+ kpg.stdout = StringIO.new
114
+ kpg.run
115
+ kpg.options.use_symbols.should_include Keybox::SymbolSet::LOWER_ASCII
116
+ kpg.stdout.string.split(/\s+/).size.should == 6
117
+ end
118
+
119
+ specify "setting an valid required symbol works" do
120
+ kpg = Keybox::Application::PasswordGenerator.new(["--req","l"])
121
+ kpg.stdout = StringIO.new
122
+ kpg.run
123
+ kpg.options.require_symbols.should_include Keybox::SymbolSet::LOWER_ASCII
124
+ kpg.stdout.string.split(/\s+/).size.should == 6
125
+ end
126
+
127
+
128
+
129
+
130
+
131
+ end
132
+
@@ -0,0 +1,11 @@
1
+ require 'keybox'
2
+ context "PasswordHash" do
3
+ setup do
4
+ @pwd_hash = Keybox::PasswordHash.new("i love ruby")
5
+ end
6
+
7
+ specify "creates string for password" do
8
+ pwd = @pwd_hash.password_for_url("http://www.nytimes.com")
9
+ pwd.should == "2f85a2e2f"
10
+ end
11
+ end
@@ -0,0 +1,116 @@
1
+ require 'keybox/randomizer'
2
+
3
+ context "a random device class" do
4
+ specify "should have a default source" do
5
+ Keybox::RandomDevice.default.should_eql "/dev/urandom"
6
+ end
7
+
8
+ specify "should produce strings" do
9
+ Keybox::RandomDevice.random_bytes.should_be_an_instance_of(String)
10
+ end
11
+
12
+ specify "should produce strings of a given length" do
13
+ Keybox::RandomDevice.random_bytes(4).size.should_eql 4
14
+ end
15
+
16
+ specify "should raise exception when given an invalid device" do
17
+ Proc.new {Keybox::RandomDevice.default = "/tmp/junk" }.should_raise ArgumentError
18
+ end
19
+
20
+ specify "should be able to assign a new default source" do
21
+ (Keybox::RandomDevice.default = "/dev/random").should_eql "/dev/random"
22
+ Keybox::RandomDevice.default = "/dev/urandom"
23
+ end
24
+ end
25
+
26
+ context "a random device instance" do
27
+ setup do
28
+ @random_device = Keybox::RandomDevice.new
29
+ end
30
+ specify "should have a source" do
31
+ @random_device.source.should_eql "/dev/urandom"
32
+ end
33
+
34
+ specify "should produce strings" do
35
+ @random_device.random_bytes.should_be_an_instance_of(String)
36
+ end
37
+
38
+ specify "should produce strings of a given length" do
39
+ @random_device.random_bytes(20).size.should_eql 20
40
+ end
41
+
42
+ specify "should default to the RandomDevice default when given an invalid device " do
43
+ rd = Keybox::RandomDevice.new("/tmp/bad-random-device")
44
+ rd.source.should_eql Keybox::RandomDevice.default
45
+ end
46
+
47
+ specify "should accept a valid readable device" do
48
+ rd = Keybox::RandomDevice.new("/dev/random")
49
+ rd.source.should_eql "/dev/random"
50
+ end
51
+ end
52
+
53
+ context "a random source class " do
54
+ setup do
55
+ @random_source_class = mock("JunkRandomSource")
56
+ @random_source_class.should_receive("random_bytes").at_least(0).times
57
+ end
58
+ specify "should have a default" do
59
+ Keybox::RandomSource.source.should_not_equal nil
60
+ end
61
+
62
+ specify "invalid default class should throw exception " do
63
+ lambda { Keybox::RandomSource.source = String }.should_raise ArgumentError
64
+ end
65
+
66
+ specify "valid class should allow default to be set" do
67
+ (Keybox::RandomSource.source = @random_source_class).should_eql @random_source_class
68
+ Keybox::RandomSource.source_classes.should_have(3).entries
69
+ Keybox::RandomSource.source_classes.delete(@random_source_class)
70
+ Keybox::RandomSource.source_classes.should_have(2).entries
71
+ Keybox::RandomSource.source.should_eql ::Keybox::RandomDevice
72
+ end
73
+
74
+ specify "rand with no parameters should return a value between 0 and 1" do
75
+ r = Keybox::RandomSource.rand
76
+ r.should_satisfy { |arg| arg >= 0.0 and arg < 1.0 }
77
+ end
78
+
79
+ specify "rand with parameters should return an integer value between 0 and that value" do
80
+ r = Keybox::RandomSource.rand(42)
81
+ r.should_be_a_kind_of(Integer)
82
+ r.should_be < 42
83
+ end
84
+ end
85
+
86
+ context "a randomzier instance" do
87
+ setup do
88
+ @randomizer = Keybox::Randomizer.new
89
+ @hash = { :stuff => "stuff", :things => "things" }
90
+ @array = ["foo", "bar", "baz" ]
91
+ end
92
+ specify "should have a default random source" do
93
+ @randomizer.random_source.should_not_be nil
94
+ end
95
+
96
+ specify "giving an invalid default random source should raise an exception" do
97
+ lambda { r = Keybox::Randomizer.new(Array) }.should_raise ArgumentError
98
+ end
99
+
100
+ specify "picking from a non-array should raise an exception" do
101
+ lambda { @randomizer.pick_count_from(@hash) }.should_raise ArgumentError
102
+ end
103
+
104
+ specify "picking one from an array should be okay" do
105
+ @randomizer.pick_one_from(@array).should_satisfy do |x|
106
+ @array.include?(x)
107
+ end
108
+ end
109
+
110
+ specify "picking N from an array should result in an array" do
111
+ @randomizer.pick_count_from(@array,3).should_have(3).entries
112
+ @randomizer.pick_count_from(@array,9).each do |arg|
113
+ @array.should_include(arg)
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,99 @@
1
+ require 'keybox'
2
+ require 'keybox/storage'
3
+ require 'keybox/error'
4
+ require 'keybox/entry'
5
+ require 'openssl'
6
+ require 'tempfile'
7
+ require 'yaml'
8
+
9
+ context 'a storage container' do
10
+ setup do
11
+ @passphrase = "i love ruby"
12
+ @keybox_file = Tempfile.new("keybox").path
13
+ @testing_file = "/tmp/testing.yml"
14
+ @container = Keybox::Storage::Container.new(@passphrase, @keybox_file)
15
+ @container << Keybox::HostAccountEntry.new("test account","localhost","guest", "rubyrocks")
16
+ @container << Keybox::URLAccountEntry.new("the times", "http://www.nytimes.com", "rubyhacker")
17
+ @container.save
18
+ end
19
+
20
+ teardown do
21
+ File.unlink(@testing_file) if File.exists?(@testing_file)
22
+ File.unlink(@keybox_file) if File.exists?(@keybox_file)
23
+ end
24
+
25
+ specify 'should have a uuid' do
26
+ @container.uuid.should_satisfy { |uuid| uuid.to_s.length == 36 }
27
+ end
28
+
29
+ specify 'should have a valid key ' do
30
+ @container.key_digest.should_satisfy { |kd| kd.length == 64 }
31
+ end
32
+
33
+ specify 'should save correctly to a file' do
34
+ @container.save(@testing_file)
35
+ File.size(@testing_file).should_satisfy { |s| s > 0 }
36
+ end
37
+
38
+ specify "should load correctly from a file" do
39
+ @container.save(@testing_file)
40
+ new_container = Keybox::Storage::Container.new(@passphrase,@testing_file)
41
+ new_container.should_be_not_modified
42
+ new_container.uuid.should == @container.uuid
43
+ end
44
+
45
+ specify "should validate passphrase" do
46
+ nc = Keybox::Storage::Container.new("i love ruby", @keybox_file)
47
+ nc.save(@testing_file)
48
+ nc.key_digest.should_eql @container.key_digest
49
+ lambda { Keybox::Storage::Container.new("i hate ruby", @testing_file) }.should_raise Keybox::ValidationError
50
+
51
+ end
52
+
53
+ specify "url accounts should have the correct password after save" do
54
+ @container.save(@testing_file)
55
+ new_container = Keybox::Storage::Container.new(@passphrase, @testing_file)
56
+ recs = new_container.find_by_url("nytimes")
57
+ new_container.records.size.should_eql 2
58
+ recs.size.should_eql 1
59
+ recs[0].password.should == "2f85a2e2f"
60
+ end
61
+
62
+ specify "can find matching records" do
63
+ matches = @container.find(/times/)
64
+ matches.size.should_be 1
65
+ end
66
+
67
+ specify "changing the password is safe" do
68
+ @container.save(@testing_file)
69
+ copy_of_container= Keybox::Storage::Container.new(@passphrase, @testing_file)
70
+ times_1 = copy_of_container.find_by_url("nytimes").first
71
+
72
+ @container.passphrase = "I love ruby too!"
73
+ @container.save(@keybox_file)
74
+ @container = Keybox::Storage::Container.new("I love ruby too!", @keybox_file)
75
+ times_2 = @container.find_by_url("nytimes").first
76
+ times_1.should_eql times_2
77
+ end
78
+
79
+ specify "should not be modified upon load" do
80
+ @container.modified?.should_eql false
81
+ end
82
+
83
+ specify "a modified db can be detected" do
84
+ l1 = @container.find("localhost").first
85
+ l1.username = "new username"
86
+ @container.modified?.should_eql true
87
+ end
88
+
89
+ specify "deleting an item should modify the container" do
90
+ ll = @container.find("localhost").first
91
+ @container.delete(ll)
92
+ @container.modified?.should_eql true
93
+ end
94
+
95
+ specify "able to see how many items are in the container" do
96
+ @container.size.should_eql 2
97
+ @container.length.should_eql 2
98
+ end
99
+ end
@@ -0,0 +1,63 @@
1
+ require 'keybox/storage'
2
+
3
+ context 'a storage record entry' do
4
+ setup do
5
+ @data_fields = %w(title username password url additional_data)
6
+ end
7
+ specify 'has a creation date set on instantiation' do
8
+ e = Keybox::Storage::Record.new
9
+ e.creation_time.should_be_instance_of(Time)
10
+ end
11
+
12
+ specify "assigning to a non-existant field creates the appropriate member " do
13
+ e = Keybox::Storage::Record.new
14
+ e.junk = "junk"
15
+ e.junk.should == "junk"
16
+ end
17
+
18
+ specify 'default values for non-existant fields is nil' do
19
+ e = Keybox::Storage::Record.new
20
+ @data_fields.each do |f|
21
+ e.send(f).should_be nil
22
+ end
23
+ end
24
+
25
+ specify "assigning to a field makes the modification time > creation time" do
26
+ e = Keybox::Storage::Record.new
27
+ sleep 1
28
+ e.testing = "testing"
29
+ e.modification_time.should_satisfy { |m| m > e.creation_time }
30
+ e.last_access_time.should == e.modification_time
31
+ end
32
+
33
+ specify "reading a field after assignment the access time > modification time " do
34
+ e = Keybox::Storage::Record.new
35
+ e.testing = "testing"
36
+ sleep 1
37
+ e.testing
38
+ e.last_access_time.should_satisfy { |la| la > e.creation_time }
39
+ e.last_access_time.should_satisfy { |la| la > e.modification_time }
40
+ end
41
+
42
+ specify "assigning to a modification, creation or acces_time should raise and exception " do
43
+ e = Keybox::Storage::Record.new
44
+ lambda {e.modification_time = Time.now}.should_raise NoMethodError
45
+ end
46
+
47
+ specify "assiging multiple items should raise an argument exception" do
48
+ e = Keybox::Storage::Record.new
49
+ lambda {e.send(:stuff=,1,2)}.should_raise ArgumentError
50
+ end
51
+
52
+ specify "calling a method with arguments should raise exception" do
53
+ e = Keybox::Storage::Record.new
54
+ lambda {e.stuff(1,2)}.should_raise NoMethodError
55
+ end
56
+
57
+ specify "comparison between records is valid" do
58
+ e = Keybox::Storage::Record.new
59
+ f = e.dup
60
+ e.should_eql e.uuid
61
+ e.should_eql f
62
+ end
63
+ end
@@ -0,0 +1,114 @@
1
+ require 'keybox'
2
+ context "string generator" do
3
+ setup do
4
+ @generator = Keybox::StringGenerator.new
5
+ end
6
+
7
+ specify "should not be used alone" do
8
+ lambda { @generator.generate }.should_raise Keybox::KeyboxError
9
+ end
10
+
11
+ specify "cannot have a min length greater than a max length" do
12
+ @generator.min_length = 90
13
+ lambda { @generator.generate }.should_raise Keybox::ValidationError
14
+ @generator.min_length = 8
15
+ end
16
+
17
+ specify "cannot have a max length less than a min length" do
18
+ @generator.max_length = 2
19
+ lambda { @generator.generate }.should_raise Keybox::ValidationError
20
+ @generator.max_length = 10
21
+ end
22
+
23
+ specify "initially there are no chunks" do
24
+ @generator.chunks.should_have(0).entries
25
+ end
26
+ end
27
+
28
+ context "chargram generator" do
29
+ setup do
30
+ @generator = Keybox::CharGramGenerator.new
31
+ end
32
+
33
+ specify "should have a positive size" do
34
+ @generator.size.should_be > 26
35
+ end
36
+
37
+ specify "should emit a string with length > 0" do
38
+ @generator.generate_chunk.size.should_be > 0
39
+ end
40
+
41
+ specify "should emit an array " do
42
+ @generator.generate_chunk.should_be_instance_of String
43
+ end
44
+
45
+ specify "2 succesive emits should have a common first and last character" do
46
+ one = @generator.generate_chunk
47
+ two = @generator.generate_chunk
48
+ one[-1].should == two[0]
49
+ end
50
+
51
+ specify "2 calls to generate_chunk should have a string that is 1 less than the 2 chunks" do
52
+ one = @generator.generate_chunk
53
+ two = @generator.generate_chunk
54
+ @generator.to_s.length.should == (one.length + two.length - 1)
55
+ end
56
+ end
57
+
58
+ context "SymbolSetGenerator" do
59
+ setup do
60
+ @generator = Keybox::SymbolSetGenerator.new
61
+ end
62
+
63
+ specify "symbol sets have the right number or characters" do
64
+ Keybox::SymbolSetGenerator::ALL.size.should == 92
65
+ end
66
+
67
+ specify "generating chunks should produce an array" do
68
+ 12.times do
69
+ @generator.generate_chunk
70
+ end
71
+ @generator.chunks.should_have(12).entries
72
+ end
73
+
74
+ specify "generate should produce a string" do
75
+ @generator.generate.should_be_instance_of(String)
76
+ @generator.to_s.size.should_be > 0
77
+ end
78
+
79
+ specify "generating chunks can be cleared" do
80
+ @generator.generate
81
+ @generator.clear
82
+ @generator.chunks.should_have(0).entries
83
+ end
84
+
85
+ specify "min and max lengths are respected" do
86
+ @generator.max_length = 25
87
+ @generator.min_length = 25
88
+ @generator.generate.size.should_be == 25
89
+ end
90
+
91
+ specify "required sets are utilized" do
92
+ gen = Keybox::SymbolSetGenerator.new([Keybox::SymbolSet::NUMERAL_ASCII, Keybox::SymbolSet::LOWER_ASCII])
93
+ gen.required_sets << Keybox::SymbolSet::UPPER_ASCII
94
+ p = gen.generate
95
+ gen.required_sets.flatten.uniq.should_include(p[0].chr)
96
+ end
97
+ specify "required sets are merged with symbol sets" do
98
+ gen = Keybox::SymbolSetGenerator.new([Keybox::SymbolSet::NUMERAL_ASCII, Keybox::SymbolSet::LOWER_ASCII])
99
+ gen.required_sets << Keybox::SymbolSet::UPPER_ASCII
100
+ gen.required_sets.flatten.uniq.each do |c|
101
+ gen.symbols.should_include(c)
102
+ end
103
+ end
104
+
105
+ specify "generated passwords autoclear" do
106
+ @generator.generate.should_not == @generator.generate
107
+ end
108
+
109
+ specify "setting min and max should not affect " do
110
+ g = Keybox::SymbolSetGenerator.new(Keybox::SymbolSet::ALL)
111
+ g.generate.should_be_instance_of(String)
112
+ end
113
+
114
+ end