safe 0.2

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.
data/README ADDED
@@ -0,0 +1,154 @@
1
+ === Safe ===
2
+ Safe is a command line password management program. It securely stores all your
3
+ user IDs and passwords using a single password as the master key. It stores the
4
+ master key as a hash (SHA2), and uses it to encrypt the rest of the data using
5
+ Blowfish.
6
+
7
+ == License ==
8
+ Safe is licensed under the BSD License. Copyright (c) 2007, Rob Warner
9
+
10
+ All rights reserved.
11
+
12
+ Redistribution and use in source and binary forms, with or without modification,
13
+ are permitted provided that the following conditions are met:
14
+
15
+ * Redistributions of source code must retain the above copyright notice,
16
+ this list of conditions and the following disclaimer.
17
+ * Redistributions in binary form must reproduce the above copyright notice,
18
+ this list of conditions and the following disclaimer in the documentation
19
+ and/or other materials provided with the distribution.
20
+ * Neither the name of Rob Warner nor the names of its contributors may be
21
+ used to endorse or promote products derived from this software without
22
+ specific prior written permission.
23
+
24
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
30
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
31
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
32
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
33
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
34
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
+
36
+ == Installation ==
37
+ To install safe, you must have Ruby and Ruby Gems installed. From the command
38
+ line, type:
39
+
40
+ gem install safe --include-dependencies
41
+
42
+ safe depends on two gems:
43
+
44
+ 1) crypt
45
+ 2) highline
46
+
47
+ Also, you must set up an environment variable called SAFE_DIR that points to the
48
+ directory in which your encrypted password file (called .safe.xml) will be
49
+ stored. Examples:
50
+
51
+ Linux/Mac OS X:
52
+ export SAFE_DIR=~
53
+
54
+ Windows:
55
+ set SAFE_DIR=c:\data
56
+
57
+ == Version History ==
58
+ 0.1 -- May 12, 2007
59
+ 0.2 -- August 19, 2007
60
+
61
+ See Release-Notes.txt for more information
62
+
63
+ == Usage ==
64
+ Run safe by typing:
65
+
66
+ safe.rb
67
+
68
+ The first time you run safe, you will be prompted for a password, and a new
69
+ password file (.safe.xml) will be created in the directory specified by
70
+ SAFE_DIR, like this:
71
+
72
+ $ safe.rb
73
+ Password: ****
74
+ Creating new file . . .
75
+
76
+ If you have not set up the SAFE_DIR environment variable, you will see this
77
+ message:
78
+
79
+ Set environment variable SAFE_DIR to the directory for your safe file
80
+
81
+ To correct, set the SAFE_DIR environment variable.
82
+
83
+ If you have set up SAFE_DIR to point to a non-existent directory, you will see
84
+ this message:
85
+
86
+ Environment variable SAFE_DIR does not point to an existing directory
87
+ (/home/foo)
88
+
89
+ where "/home/foo" is the value of SAFE_DIR. To correct, point SAFE_DIR to an
90
+ existing directory.
91
+
92
+ If you type:
93
+
94
+ safe.rb -h
95
+
96
+ the available options display. They are:
97
+
98
+ Usage: safe.rb [options]
99
+
100
+ Options:
101
+ -a, --add NAME Add an entry
102
+ -c, --count Print the count of entries
103
+ -d, --delete NAME Delete an entry
104
+ -i, --import FILE Import a file in <name,ID,password> format
105
+ -l, --list NAME List an entry
106
+ -v, --version Print version
107
+ -p, --password Change password
108
+
109
+ For example, to add an entry to your password file for Rubyforge.org, you type:
110
+
111
+ safe.rb -a Rubyforge.org
112
+
113
+ You will be prompted for your master password, and then your Rubyforge.org
114
+ user ID and password, like this:
115
+
116
+ Password: ****
117
+ Rubyforge.org User ID: myuserid
118
+ Rubyforge.org Password: ****
119
+
120
+ NOTE: If NAME contains spaces, you must surround it in quotes, like this:
121
+
122
+ safe.rb -a "A Cool Site"
123
+
124
+ To list your Rubyforge.org password, you type:
125
+
126
+ safe.rb -l Rubyforge.org
127
+
128
+ You will be prompted for your master password, and then your Rubyforge.org
129
+ user ID and password will display, like this:
130
+
131
+ Rubyforge.org myuserid mypassword
132
+
133
+ NOTE: The NAME you pass to -l is not case sensitive, and will find all entries
134
+ beginning with NAME. For example, typing:
135
+
136
+ safe.rb -l r
137
+
138
+ will list all entries beginning with "r" or "R."
139
+
140
+ To delete your Rubyforge.org password, you type:
141
+
142
+ safe.rb -d Rubyforge.org
143
+
144
+ You will be prompted for your master password, and then your Rubyforge.org
145
+ password is irretrievably deleted. You will not be asked to confirm the
146
+ deletion.
147
+
148
+ == Backups ==
149
+ Please back up your .safe.xml file! See License section for disclaimer
150
+ information.
151
+
152
+ == Contact ==
153
+ You can contact me, Rob Warner, at rwarner@grailbox.com.
154
+
data/bin/safe.rb ADDED
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ########################################################################
4
+ # safe: Safe Storage for Passwords.
5
+ # Stores user IDs and passwords, encrypted by a password.
6
+ #
7
+ # Copyright (c) 2007, Rob Warner
8
+ #
9
+ # All rights reserved.
10
+ #
11
+ # Redistribution and use in source and binary forms, with or without
12
+ # modification, are permitted provided that the following conditions are met:
13
+ #
14
+ # * Redistributions of source code must retain the above copyright notice,
15
+ # this list of conditions and the following disclaimer.
16
+ # * Redistributions in binary form must reproduce the above copyright notice,
17
+ # this list of conditions and the following disclaimer in the documentation
18
+ # and/or other materials provided with the distribution.
19
+ # * Neither the name of Rob Warner nor the names of its contributors may be
20
+ # used to endorse or promote products derived from this software without
21
+ # specific prior written permission.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
27
+ # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
28
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
29
+ # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ ########################################################################
35
+ require 'rubygems'
36
+ require 'optparse'
37
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'safeentry')
38
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'safefile')
39
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'safeutils')
40
+
41
+ # TODO support list <NAME> without -l flag
42
+
43
+ class Safe
44
+ SAFE_VERSION = '0.2'
45
+
46
+ # Gets the command line
47
+ def self.get_command_line
48
+ options = {}
49
+ options[:action] = 'list'
50
+ opts = OptionParser.new
51
+ opts.banner = 'Usage: safe.rb [options]'
52
+ opts.separator ''
53
+ opts.separator 'Options:'
54
+ opts.on('-a', '--add NAME', 'Add an entry') do |val|
55
+ options[:name] = val
56
+ options[:action] = 'add'
57
+ end
58
+ opts.on('-c', '--count', 'Print the count of entries') do
59
+ options[:action] = 'count'
60
+ end
61
+ opts.on('-d', '--delete NAME', 'Delete an entry') do |val|
62
+ options[:name] = val
63
+ options[:action] = 'delete'
64
+ end
65
+ opts.on('-i', '--import FILE', 'Import a file in <name,ID,password> format') do |val|
66
+ options[:name] = val
67
+ options[:action] = 'import'
68
+ end
69
+ opts.on('-l', '--list NAME', 'List an entry') do |val|
70
+ options[:name] = val
71
+ options[:action] = 'list'
72
+ end
73
+ opts.on('-v', '--version', 'Print version') do
74
+ options[:action] = 'version'
75
+ end
76
+ opts.on('-p', '--password', 'Change password') do
77
+ options[:action] = 'password'
78
+ end
79
+ opts.parse! rescue abort [$!.message, opts].join("\n")
80
+ options
81
+ end
82
+
83
+ # Main
84
+ def self.run
85
+ options = get_command_line
86
+ if options[:action] == 'version'
87
+ puts "Safe version #{SAFE_VERSION}, Copyright (C) 2007 Rob Warner"
88
+ else
89
+ begin
90
+ dir = SafeUtils::get_safe_dir
91
+ rescue
92
+ abort $!.message
93
+ end
94
+ password = SafeUtils::get_password
95
+ file = SafeFile.new(password, dir)
96
+
97
+ # Perform the requested action
98
+ case options[:action]
99
+ when 'list': file.list(options[:name])
100
+ when 'add': file.add(options[:name])
101
+ when 'delete': file.delete(options[:name])
102
+ when 'count': puts file.count
103
+ when 'password': file.change_password
104
+ when 'import': SafeUtils::import(file, options[:name])
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ Safe.run
data/lib/safeentry.rb ADDED
@@ -0,0 +1,53 @@
1
+ ########################################################################
2
+ # safeentry.rb
3
+ # Copyright (c) 2007, Rob Warner
4
+ #
5
+ # All rights reserved.
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without
8
+ # modification, are permitted provided that the following conditions are met:
9
+ #
10
+ # * Redistributions of source code must retain the above copyright notice,
11
+ # this list of conditions and the following disclaimer.
12
+ # * Redistributions in binary form must reproduce the above copyright notice,
13
+ # this list of conditions and the following disclaimer in the documentation
14
+ # and/or other materials provided with the distribution.
15
+ # * Neither the name of Rob Warner nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23
+ # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25
+ # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+ ########################################################################
31
+
32
+ ##
33
+ # SafeEntry
34
+ # A single entry (name, ID, password) in the "safe"
35
+ ##
36
+ class SafeEntry
37
+ attr_accessor :name, :id, :password
38
+
39
+ def initialize(name, id, password)
40
+ @name = name
41
+ @id = id
42
+ @password = password
43
+ end
44
+
45
+ def to_s
46
+ "#@name\t#@id\t#@password"
47
+ end
48
+
49
+ def <=>(other)
50
+ self.name.upcase <=> other.name.upcase
51
+ end
52
+ end
53
+
data/lib/safefile.rb ADDED
@@ -0,0 +1,212 @@
1
+ ########################################################################
2
+ # safefile.rb
3
+ # Copyright (c) 2007, Rob Warner
4
+ #
5
+ # All rights reserved.
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without
8
+ # modification, are permitted provided that the following conditions are met:
9
+ #
10
+ # * Redistributions of source code must retain the above copyright notice,
11
+ # this list of conditions and the following disclaimer.
12
+ # * Redistributions in binary form must reproduce the above copyright notice,
13
+ # this list of conditions and the following disclaimer in the documentation
14
+ # and/or other materials provided with the distribution.
15
+ # * Neither the name of Rob Warner nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23
+ # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25
+ # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+ ########################################################################
31
+ require 'rubygems'
32
+ require 'crypt/blowfish'
33
+ require 'digest/sha2'
34
+ require 'rexml/document'
35
+ require 'fileutils'
36
+ require 'base64'
37
+
38
+ include REXML
39
+
40
+ ##
41
+ # SafeFile
42
+ # The file containing the safe entries
43
+ ##
44
+ class SafeFile
45
+ attr_accessor :hash, :salt, :password, :filename, :entries
46
+
47
+ def initialize(password, dir, filename = '.safe.xml')
48
+ @entries = Hash.new
49
+ @filename = "#{dir}/#{filename}"
50
+ @password = password
51
+ load
52
+ end
53
+
54
+ # Loads the file
55
+ def load
56
+ f = File.open(@filename, File::CREAT)
57
+ begin
58
+ if f.lstat.size == 0
59
+ create_file(f)
60
+ else
61
+ decrypt_file(f)
62
+ end
63
+ rescue
64
+ # TODO Determine whether error is can't create file, and print appropriate error
65
+ puts $!
66
+ ensure
67
+ f.close unless f.nil?
68
+ end
69
+ end
70
+
71
+ # Decrypts the file
72
+ def decrypt_file(file)
73
+ begin
74
+ doc = Document.new file
75
+ root = doc.root
76
+ @hash = root.elements["hash"].text
77
+ @salt = root.elements["salt"].text
78
+ if authorized?
79
+ crypt_key = Crypt::Blowfish.new(@password)
80
+ e_entries = root.elements["entries"]
81
+ e_entries.elements.each("entry") do |entry|
82
+ fields = crypt_key.decrypt_string(Base64::decode64(entry.cdatas[0].to_s)).split("\t")
83
+ if fields.length == 3
84
+ insert(fields[0], fields[1], fields[2])
85
+ else
86
+ puts "Cannot parse #{fields}, discarding."
87
+ end
88
+ end
89
+ else
90
+ abort('The password you entered is not valid')
91
+ end
92
+ rescue ParseException
93
+ puts "Cannot parse #{file.path}"
94
+ end
95
+ end
96
+
97
+ # Creates a new file
98
+ def create_file(file)
99
+ puts 'Creating new file . . .'
100
+ generate_hash_and_salt
101
+ save
102
+ end
103
+
104
+ # Saves the file
105
+ def save
106
+ crypt_key = Crypt::Blowfish.new(@password)
107
+ doc = Document.new
108
+ root = Element.new "safe"
109
+ doc.add_element root
110
+
111
+ root.add_element create_element("hash", @hash)
112
+ root.add_element create_element("salt", @salt)
113
+
114
+ e_entries = Element.new "entries"
115
+ @entries.each_value do |entry|
116
+ e = Element.new "entry"
117
+ CData.new Base64.encode64(crypt_key.encrypt_string(entry.to_s)), true, e
118
+ e_entries.add_element e
119
+ end
120
+ root.add_element e_entries
121
+
122
+ f = File.new(@filename, 'w')
123
+ doc.write f, 0
124
+ f.close
125
+ end
126
+
127
+ # Adds an entry
128
+ def add(name)
129
+ if can_insert? name
130
+ print "#{name} User ID: "
131
+ id = gets.chomp!
132
+ pw = SafeUtils::get_password("#{name} Password: ")
133
+ insert(name, id, pw)
134
+ save
135
+ end
136
+ end
137
+
138
+ # Inserts an entry
139
+ def insert(name, id, pw)
140
+ @entries.delete(name)
141
+ @entries[name] = SafeEntry.new(name, id, pw)
142
+ end
143
+
144
+ # Determines whether we can insert this entry--if it exists, we prompt for the OK
145
+ def can_insert?(name)
146
+ proceed = true
147
+ if @entries.has_key?(name)
148
+ print "Overwrite existing #{name} (y/N)? "
149
+ proceed = (gets.chomp == 'y')
150
+ end
151
+ proceed
152
+ end
153
+
154
+ # Deletes an entry
155
+ def delete(name)
156
+ if @entries.has_key?(name)
157
+ @entries.delete(name)
158
+ save
159
+ else
160
+ puts "Entry #{name} not found"
161
+ end
162
+ end
163
+
164
+ # Lists the desired entries
165
+ def list(name)
166
+ output = Array.new
167
+ @entries.each_value do |entry|
168
+ # TODO Inconsistent case handling
169
+ if name == nil || entry.name.upcase =~ /^#{name.upcase}/
170
+ output << entry
171
+ end
172
+ end
173
+ output.sort.each do |entry|
174
+ puts entry
175
+ end
176
+ end
177
+
178
+ # Returns the count of entries in the file
179
+ def count()
180
+ @entries.length
181
+ end
182
+
183
+ # Changes the file's password
184
+ def change_password
185
+ new_password = SafeUtils::get_password('Enter new password: ')
186
+ rpt_password = SafeUtils::get_password('Confirm password: ')
187
+ if new_password == rpt_password
188
+ @password = new_password
189
+ generate_hash_and_salt
190
+ save
191
+ else
192
+ puts 'Passwords do not match'
193
+ end
194
+ end
195
+
196
+ def generate_hash_and_salt
197
+ @salt = [Array.new(20){rand(256).chr}.join].pack('m').chomp
198
+ @hash = Digest::SHA256.hexdigest(@password + @salt)
199
+ end
200
+
201
+ # Returns whether the password is authorized
202
+ def authorized?
203
+ Digest::SHA256.hexdigest(@password + @salt) == @hash
204
+ end
205
+
206
+ # Helper method to create an XML element with a name and text
207
+ def create_element(name, text)
208
+ e = Element.new name
209
+ e.text = text
210
+ e
211
+ end
212
+ end
data/lib/safeutils.rb ADDED
@@ -0,0 +1,76 @@
1
+ ########################################################################
2
+ # safeutils.rb
3
+ # Copyright (c) 2007, Rob Warner
4
+ #
5
+ # All rights reserved.
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without
8
+ # modification, are permitted provided that the following conditions are met:
9
+ #
10
+ # * Redistributions of source code must retain the above copyright notice,
11
+ # this list of conditions and the following disclaimer.
12
+ # * Redistributions in binary form must reproduce the above copyright notice,
13
+ # this list of conditions and the following disclaimer in the documentation
14
+ # and/or other materials provided with the distribution.
15
+ # * Neither the name of Rob Warner nor the names of its contributors may be
16
+ # used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23
+ # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25
+ # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+ ########################################################################
31
+ require 'highline/import'
32
+
33
+ ##
34
+ # SafeUtils
35
+ # Utility methods for the Safe application
36
+ ##
37
+ class SafeUtils
38
+ # Gets the directory for the data file
39
+ def SafeUtils.get_safe_dir(var = 'SAFE_DIR')
40
+ dir = ENV[var]
41
+ raise "Set environment variable #{var} to the directory for your safe file" unless dir
42
+ begin
43
+ d = Dir.new(dir)
44
+ rescue
45
+ raise "Environment variable #{var} does not point to an existing directory (#{dir})"
46
+ end
47
+ dir
48
+ end
49
+
50
+ # Prompts the user for a password
51
+ def SafeUtils.get_password(prompt = 'Password: ', mask = '*')
52
+ ask(prompt) { |q| q.echo = mask }
53
+ end
54
+
55
+ # Imports a file in the format:
56
+ # name,ID,password
57
+ # name,ID,password
58
+ # etc.
59
+ def SafeUtils.import(safe_file, import_file)
60
+ begin
61
+ open(import_file).each do |line|
62
+ puts "Importing #{line}"
63
+ fields = line.split(',')
64
+ if fields.length == 3
65
+ safe_file.insert(fields[0], fields[1], fields[2])
66
+ else
67
+ puts "Cannot import; number of fields = #{fields.length}"
68
+ end
69
+ end
70
+ safe_file.save
71
+ rescue
72
+ puts $!
73
+ raise "Problem importing file #{import_file}"
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,37 @@
1
+ ########################################################################
2
+ # tc_safeentry.rb
3
+ # Copyright (c) 2007 Rob Warner.
4
+ # All rights reserved. This program and the accompanying materials
5
+ # are made available under the terms of the Eclipse Public License v1.0
6
+ # which accompanies this distribution, and is available at
7
+ # http://www.eclipse.org/legal/epl-v10.html
8
+ ########################################################################
9
+
10
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'safeentry')
11
+ require 'test/unit'
12
+
13
+ class SafeDirTest < Test::Unit::TestCase
14
+ def test_to_s
15
+ se = SafeEntry.new('name', 'id', 'password')
16
+ assert_equal "name\tid\tpassword", se.to_s
17
+
18
+ se = SafeEntry.new('', '', '')
19
+ assert_equal "\t\t", se.to_s
20
+
21
+ se = SafeEntry.new(' ', ' ', ' ')
22
+ assert_equal " \t \t ", se.to_s
23
+ end
24
+
25
+ def test_sort
26
+ test = Array.new
27
+ test << SafeEntry.new('apple', 'baker', 'charlie')
28
+ test << SafeEntry.new('zoo', 'yacht', 'x-ray')
29
+ test << SafeEntry.new('aardvark', 'kangaroo', 'wallaby')
30
+ test << SafeEntry.new('123', '456', '789')
31
+ test.sort!
32
+ assert_equal '123', test[0].name
33
+ assert_equal 'aardvark', test[1].name
34
+ assert_equal 'apple', test[2].name
35
+ assert_equal 'zoo', test[3].name
36
+ end
37
+ end
@@ -0,0 +1,120 @@
1
+ ########################################################################
2
+ # tc_safefile.rb
3
+ # Copyright (c) 2007 Rob Warner.
4
+ # All rights reserved. This program and the accompanying materials
5
+ # are made available under the terms of the Eclipse Public License v1.0
6
+ # which accompanies this distribution, and is available at
7
+ # http://www.eclipse.org/legal/epl-v10.html
8
+ ########################################################################
9
+
10
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'safefile')
11
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'safeentry')
12
+ require 'test/unit'
13
+ require 'fileutils'
14
+
15
+ class SafeFileTest < Test::Unit::TestCase
16
+ def test_insert_and_delete
17
+ f = SafeFile.new("password", ".")
18
+ assert_equal "./.safe.xml", f.filename
19
+ assert_equal 0, f.entries.length
20
+
21
+ f.insert('apple', 'baker', 'charlie')
22
+ assert_equal 1, f.entries.length
23
+
24
+ f.insert('apple', 'charlie', 'baker')
25
+ assert_equal 1, f.entries.length
26
+
27
+ f.insert('foo', 'bar', 'baz')
28
+ assert_equal 2, f.entries.length
29
+
30
+ f.delete('bar')
31
+ assert_equal 2, f.entries.length
32
+
33
+ f.delete('foo')
34
+ assert_equal 1, f.entries.length
35
+
36
+ f.delete('apple')
37
+ assert_equal 0, f.entries.length
38
+
39
+ f.delete('apple')
40
+ assert_equal 0, f.entries.length
41
+
42
+ FileUtils::rm("./.safe.xml")
43
+ end
44
+
45
+ def test_entry
46
+ f = SafeFile.new("password", ".")
47
+ f.insert('apple', 'baker', 'charlie')
48
+ e = f.entries['apple']
49
+ assert_equal 'apple', e.name
50
+ assert_equal 'baker', e.id
51
+ assert_equal 'charlie', e.password
52
+ assert_equal "apple\tbaker\tcharlie", e.to_s
53
+
54
+ FileUtils::rm("./.safe.xml")
55
+ end
56
+
57
+ def test_modify_entry
58
+ f = SafeFile.new("password", ".")
59
+ f.insert('apple', 'baker', 'charlie')
60
+ e = f.entries['apple']
61
+ assert_equal 'apple', e.name
62
+ assert_equal 'baker', e.id
63
+ assert_equal 'charlie', e.password
64
+ assert_equal "apple\tbaker\tcharlie", e.to_s
65
+
66
+ f.insert('apple', 'foo', 'bar')
67
+ e = f.entries['apple']
68
+ assert_equal 'apple', e.name
69
+ assert_equal 'foo', e.id
70
+ assert_equal 'bar', e.password
71
+ assert_equal "apple\tfoo\tbar", e.to_s
72
+
73
+ FileUtils::rm("./.safe.xml")
74
+ end
75
+
76
+ def test_authorized
77
+ f = SafeFile.new("password", ".")
78
+ assert_equal true, f.authorized?
79
+
80
+ f.password = "foo"
81
+ assert_equal false, f.authorized?
82
+
83
+ FileUtils::rm("./.safe.xml")
84
+ end
85
+
86
+ def test_count
87
+ f = SafeFile.new("password", ".")
88
+
89
+ assert_equal 0, f.count
90
+
91
+ f.insert('apple', 'baker', 'charlie')
92
+ assert_equal 1, f.count
93
+
94
+ f.insert('apple1', 'baker', 'charlie')
95
+ assert_equal 2, f.count
96
+
97
+ f.insert('apple1', 'baker', 'charlie')
98
+ assert_equal 2, f.count
99
+
100
+ f.insert('apple2', 'baker', 'charlie')
101
+ assert_equal 3, f.count
102
+
103
+ f.delete('apple')
104
+ assert_equal 2, f.count
105
+
106
+ f.delete('apple')
107
+ assert_equal 2, f.count
108
+
109
+ f.delete('apple1')
110
+ assert_equal 1, f.count
111
+
112
+ f.delete('apple2')
113
+ assert_equal 0, f.count
114
+
115
+ f.delete('apple2')
116
+ assert_equal 0, f.count
117
+
118
+ FileUtils::rm("./.safe.xml")
119
+ end
120
+ end
@@ -0,0 +1,45 @@
1
+ ########################################################################
2
+ # tc_safeutils.rb
3
+ # Copyright (c) 2007 Rob Warner.
4
+ # All rights reserved. This program and the accompanying materials
5
+ # are made available under the terms of the Eclipse Public License v1.0
6
+ # which accompanies this distribution, and is available at
7
+ # http://www.eclipse.org/legal/epl-v10.html
8
+ ########################################################################
9
+
10
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'safeutils')
11
+ require 'test/unit'
12
+ require 'fileutils'
13
+
14
+ class SafeUtilsTest < Test::Unit::TestCase
15
+ def test_get_safe_dir
16
+ ev = 'MY_SAFE_ENV_VAR'
17
+
18
+ # Make sure we're not clobbering an environment variable
19
+ temp = ENV[ev]
20
+ assert_equal nil, temp
21
+
22
+ # Test when the variable isn't set
23
+ begin
24
+ dir = SafeUtils::get_safe_dir(ev)
25
+ rescue
26
+ assert_equal "Set environment variable #{ev} to the directory for your safe file", $!.message
27
+ end
28
+
29
+ # Test when the directory doesn't exist
30
+ ENV[ev] = 'Does not exist'
31
+ begin
32
+ dir = SafeUtils::get_safe_dir(ev)
33
+ rescue
34
+ assert_equal "Environment variable #{ev} does not point to an existing directory (#{ENV[ev]})", $!.message
35
+ end
36
+
37
+ # Test when the directory exists
38
+ ENV[ev] = '.'
39
+ dir = SafeUtils::get_safe_dir(ev)
40
+ assert_equal '.', dir
41
+
42
+ # Make sure the directory exists
43
+ FileUtils::cd dir
44
+ end
45
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
4
+ name: safe
5
+ version: !ruby/object:Gem::Version
6
+ version: "0.2"
7
+ date: 2007-08-19 00:00:00 -04:00
8
+ summary: A command-line password storage program
9
+ require_paths:
10
+ - lib
11
+ email: rwarner@grailbox.com
12
+ homepage: http://grailbox.com/safe
13
+ rubyforge_project:
14
+ description: safe safely stores all your user IDs and passwords, encrypted by a password of your choosing.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Rob Warner
31
+ files:
32
+ - README
33
+ - lib/safeentry.rb
34
+ - lib/safefile.rb
35
+ - lib/safeutils.rb
36
+ test_files:
37
+ - test/tc_safefile.rb
38
+ - test/tc_safeentry.rb
39
+ - test/tc_safeutils.rb
40
+ rdoc_options: []
41
+
42
+ extra_rdoc_files: []
43
+
44
+ executables:
45
+ - safe.rb
46
+ extensions: []
47
+
48
+ requirements: []
49
+
50
+ dependencies:
51
+ - !ruby/object:Gem::Dependency
52
+ name: crypt
53
+ version_requirement:
54
+ version_requirements: !ruby/object:Gem::Version::Requirement
55
+ requirements:
56
+ - - ">"
57
+ - !ruby/object:Gem::Version
58
+ version: 0.0.0
59
+ version:
60
+ - !ruby/object:Gem::Dependency
61
+ name: highline
62
+ version_requirement:
63
+ version_requirements: !ruby/object:Gem::Version::Requirement
64
+ requirements:
65
+ - - ">"
66
+ - !ruby/object:Gem::Version
67
+ version: 0.0.0
68
+ version: