safe 0.2

Sign up to get free protection for your applications and to get access to all the features.
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: