password_safe 0.0.4
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/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +34 -0
- data/Rakefile +25 -0
- data/bin/password_safe +7 -0
- data/lib/password_safe/config.rb +19 -0
- data/lib/password_safe/crypt.rb +32 -0
- data/lib/password_safe/guard.rb +235 -0
- data/lib/password_safe/safe.rb +54 -0
- data/lib/password_safe/version.rb +3 -0
- data/lib/password_safe.rb +9 -0
- data/password_safe.gemspec +19 -0
- data/spec/config_spec.rb +47 -0
- data/spec/crypt_spec.rb +11 -0
- data/spec/guard_spec.rb +135 -0
- data/spec/safe_spec.rb +40 -0
- data/spec/spec_helper.rb +28 -0
- metadata +103 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Alec Tower
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# PasswordSafe
|
2
|
+
|
3
|
+
PasswordSafe allows you to store passwords in a local file using AES-CBC-256 encryption.
|
4
|
+
|
5
|
+
It is a simple command line application providing a minimal command line interface.
|
6
|
+
|
7
|
+
## Requirements
|
8
|
+
|
9
|
+
Ruby 1.9
|
10
|
+
|
11
|
+
Currently only supported on OSX
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
PasswordSafe can be installed using RubyGems:
|
16
|
+
|
17
|
+
$ gem install password_safe
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
user$ password_safe
|
22
|
+
|
23
|
+
Options:
|
24
|
+
-a <key> :: add password related to <key>
|
25
|
+
-g <key> :: get password related to <key>
|
26
|
+
-g :: get all keys
|
27
|
+
-d <key> :: delete password related to <key>
|
28
|
+
-d :: delete all passwords
|
29
|
+
-l :: print current location of safe
|
30
|
+
-l <path> :: move safe location to <path>
|
31
|
+
-c :: change password to safe
|
32
|
+
-r :: remove safe completely
|
33
|
+
|
34
|
+
Passwords will be copied to the clipboard for 5 seconds
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
namespace :spec do
|
6
|
+
desc 'Run specs for Crypt'
|
7
|
+
RSpec::Core::RakeTask.new :crypt do |t|
|
8
|
+
t.pattern = 'spec/crypt_spec.rb'
|
9
|
+
end
|
10
|
+
desc 'Run specs for Safe'
|
11
|
+
RSpec::Core::RakeTask.new :safe do |t|
|
12
|
+
t.pattern = 'spec/safe_spec.rb'
|
13
|
+
end
|
14
|
+
desc 'Run specs for Guard'
|
15
|
+
RSpec::Core::RakeTask.new :guard do |t|
|
16
|
+
t.pattern = 'spec/guard_spec.rb'
|
17
|
+
end
|
18
|
+
desc 'Rub specs for Config'
|
19
|
+
RSpec::Core::RakeTask.new :config do |t|
|
20
|
+
t.pattern = 'spec/config_spec.rb'
|
21
|
+
end
|
22
|
+
task :all => [:safe, :guard, :crypt, :config]
|
23
|
+
end
|
24
|
+
|
25
|
+
task :default => 'spec:all'
|
data/bin/password_safe
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module PasswordSafe
|
2
|
+
module Config
|
3
|
+
PASSWORD_SAFE = "#{ENV['HOME']}/.pswds"
|
4
|
+
|
5
|
+
def change_safe_location(new_location)
|
6
|
+
if new_location
|
7
|
+
FileUtils.mkpath File.dirname new_location unless Dir.exists? File.dirname new_location
|
8
|
+
old_file = File.readlink PASSWORD_SAFE if File.symlink? PASSWORD_SAFE
|
9
|
+
IO.readlines(PASSWORD_SAFE).empty? ? (File.new new_location, 'w' unless File.exists? new_location)
|
10
|
+
: (FileUtils.copy_file(PASSWORD_SAFE, new_location, true) unless File.exists? new_location)
|
11
|
+
FileUtils.ln_s(new_location, PASSWORD_SAFE, :force => true)
|
12
|
+
FileUtils.rm_f old_file if old_file
|
13
|
+
else
|
14
|
+
(puts "Current safe location is #{File.readlink PASSWORD_SAFE}"; return) if File.symlink? PASSWORD_SAFE
|
15
|
+
puts "Current safe location is #{PASSWORD_SAFE}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module PasswordSafe
|
2
|
+
# This module provides encrypt
|
3
|
+
# and decrypt methods using aes-256-cbc
|
4
|
+
module Crypt
|
5
|
+
|
6
|
+
# Encrypts data and returns
|
7
|
+
# binary encoded string
|
8
|
+
def encrypt(data, password)
|
9
|
+
Base64.encode64 aes(:encrypt, password, data)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Decrypts data from binary encoded string
|
13
|
+
def decrypt(data, password)
|
14
|
+
aes(:decrypt, password, Base64.decode64(data))
|
15
|
+
end
|
16
|
+
|
17
|
+
# Uses AES-256-CBC to encrypt data
|
18
|
+
# Key length is 32 bytes
|
19
|
+
# IV length is 16 bytes
|
20
|
+
# TODO Use random IV and include it in
|
21
|
+
# encrypted data rather than using the same
|
22
|
+
# IV every time.
|
23
|
+
def aes(mode, password, data)
|
24
|
+
sha256 = Digest::SHA2.new
|
25
|
+
aes = OpenSSL::Cipher.new("AES-256-CBC")
|
26
|
+
aes.send(mode)
|
27
|
+
aes.key = sha256.digest(password)
|
28
|
+
aes.iv = '0123456789012345'
|
29
|
+
'' << aes.update(data) << aes.final
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
module PasswordSafe
|
2
|
+
# Handles user requests and interacts
|
3
|
+
# with the Safe class.
|
4
|
+
#
|
5
|
+
# Main class to interface with user.
|
6
|
+
# The Guard protects the safe by
|
7
|
+
# authenticating the user, and making sure
|
8
|
+
# data is in the correct format to be
|
9
|
+
# held in the safe.
|
10
|
+
class Guard
|
11
|
+
include PasswordSafe::Config
|
12
|
+
|
13
|
+
# Create new guard object with specified safe.
|
14
|
+
# Default safe location is ENV['HOME']/.pswds
|
15
|
+
def initialize(safe = Safe.new)
|
16
|
+
@safe = safe
|
17
|
+
end
|
18
|
+
|
19
|
+
# Main method that validates arguments
|
20
|
+
# and processes input
|
21
|
+
def on_the_job
|
22
|
+
validate_args ? process_input : print_options
|
23
|
+
end
|
24
|
+
|
25
|
+
# Checks for the correct switches to be
|
26
|
+
# supplied by user
|
27
|
+
def validate_args
|
28
|
+
if ARGV.size > 0 && ARGV.size < 3
|
29
|
+
@command = ARGV[0]
|
30
|
+
@arg = ARGV[1]
|
31
|
+
(@command =~ /[(-c)]/ && !@arg.nil?) || (@command =~ /[(-a)(-g)(-l)]/ && @arg.nil?)
|
32
|
+
else
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Command line options
|
38
|
+
def print_options
|
39
|
+
puts "PasswordSafe v#{PasswordSafe::VERSION}\
|
40
|
+
\nOptions: \
|
41
|
+
\n\t-a <key> :: add password related to <key> \
|
42
|
+
\n\t-g <key> :: get password related to <key> \
|
43
|
+
\n\t-g :: get all keys \
|
44
|
+
\n\t-d <key> :: delete password related to <key> \
|
45
|
+
\n\t-d :: delete all passwords \
|
46
|
+
\n\t-l :: print current location of safe \
|
47
|
+
\n\t-l <path> :: move safe location to <path> \
|
48
|
+
\n\t-c :: change password to safe \
|
49
|
+
\n\t-r :: remove safe completely
|
50
|
+
\n\n\tPasswords will be copied to the clipboard for 5 seconds \
|
51
|
+
\n\n"
|
52
|
+
end
|
53
|
+
|
54
|
+
# Case statement for switches
|
55
|
+
# Main logic method
|
56
|
+
def process_input
|
57
|
+
case @command
|
58
|
+
when "-a"
|
59
|
+
add_value
|
60
|
+
when "-g"
|
61
|
+
get_value
|
62
|
+
when "-d"
|
63
|
+
delete_value
|
64
|
+
when "-l"
|
65
|
+
change_safe_location(@arg)
|
66
|
+
when "-c"
|
67
|
+
change_safe_password
|
68
|
+
when "-r"
|
69
|
+
remove_safe
|
70
|
+
else
|
71
|
+
print_options
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Adds a new value to the safe
|
76
|
+
# and binds it to the key provided
|
77
|
+
# by the user.
|
78
|
+
def add_value
|
79
|
+
if @arg
|
80
|
+
puts "Enter the password for the safe"
|
81
|
+
safe_password = get_password
|
82
|
+
puts "Enter the password for #{@arg}"
|
83
|
+
key_password = get_password
|
84
|
+
add_data "#{@arg}:#{key_password}", safe_password
|
85
|
+
else
|
86
|
+
puts "A key is needed in order to store a password"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Retrieves value from safe
|
91
|
+
# that is related to the key
|
92
|
+
# entered by the user
|
93
|
+
def get_value
|
94
|
+
(puts "The safe is empty"; return) if @safe.empty?
|
95
|
+
puts "Enter the password for the safe"
|
96
|
+
safe_password = get_password
|
97
|
+
case @arg
|
98
|
+
when /^\S+$/
|
99
|
+
data = retrieve_data(safe_password)
|
100
|
+
value = data[@arg] if data
|
101
|
+
if value
|
102
|
+
IO.popen('pbcopy', 'w') {|clipboard| clipboard.print "#{value}"}
|
103
|
+
sleep(5)
|
104
|
+
IO.popen('pbcopy', 'w') {|clipboard| clipboard.print ""}
|
105
|
+
else
|
106
|
+
puts "No value found for #{@arg}"
|
107
|
+
end
|
108
|
+
else
|
109
|
+
data = retrieve_data safe_password
|
110
|
+
if data
|
111
|
+
data.each_key { |key| puts key }
|
112
|
+
else
|
113
|
+
puts "Safe is empty"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Removes key and value from safe.
|
119
|
+
def delete_value
|
120
|
+
(puts " The safe is empty"; return) if @safe.empty?
|
121
|
+
case @arg
|
122
|
+
when /^\S+$/
|
123
|
+
puts "Enter the password for the safe"
|
124
|
+
safe_password = get_password
|
125
|
+
remove_key_value safe_password
|
126
|
+
when nil
|
127
|
+
empty_safe
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Gets password from user.
|
132
|
+
# Hides password on console by replacing
|
133
|
+
# input characters with "*"
|
134
|
+
def get_password
|
135
|
+
system "stty raw -echo"
|
136
|
+
password = ""
|
137
|
+
loop do
|
138
|
+
char = STDIN.getc
|
139
|
+
char != "\r" ? password << char : break
|
140
|
+
print "*"
|
141
|
+
end
|
142
|
+
password
|
143
|
+
ensure
|
144
|
+
puts
|
145
|
+
print "\033[ 1 E"
|
146
|
+
system "stty -raw echo"
|
147
|
+
end
|
148
|
+
|
149
|
+
# First retrieves old data to see if
|
150
|
+
# the key already exists in the safe,
|
151
|
+
# adds it if not.
|
152
|
+
def add_data(key_value, safe_password)
|
153
|
+
old_data = retrieve_data safe_password
|
154
|
+
if old_data && old_data[@arg]
|
155
|
+
puts "#@arg already exists"
|
156
|
+
return
|
157
|
+
end
|
158
|
+
merge_data(old_data, key_value, safe_password)
|
159
|
+
puts "Successfully added key value pair"
|
160
|
+
end
|
161
|
+
|
162
|
+
# Merges new key value pair with old data
|
163
|
+
def merge_data(old_data, key_value, safe_password)
|
164
|
+
new_data = ''
|
165
|
+
old_data.each { |key, value| new_data << "#{key}:#{value}\n" } if old_data
|
166
|
+
new_data << "#{key_value}\n" if key_value
|
167
|
+
@safe.lock(new_data, safe_password)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Retrieves data from safe and returns
|
171
|
+
# it in a key value hash
|
172
|
+
def retrieve_data(safe_password)
|
173
|
+
begin
|
174
|
+
return if !data = @safe.unlock(safe_password)
|
175
|
+
data.split("\n").map {|l| l.split(":")}.map {|k, v| Hash[k => v]}.reduce({}) {|h, i| h.merge i}
|
176
|
+
rescue OpenSSL::Cipher::CipherError
|
177
|
+
puts "You entered an incorrect password"
|
178
|
+
exit(0)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Prompts user for delete confirmation
|
183
|
+
# and empties safe if user entered "Y" or "y".
|
184
|
+
def empty_safe
|
185
|
+
(puts "The safe is already empty"; return) if @safe.empty?
|
186
|
+
puts "Are you sure? (y|Y)"
|
187
|
+
if STDIN.gets =~ /^[yY]{1}$/
|
188
|
+
@safe.empty_safe
|
189
|
+
puts "Safe was emptied!"
|
190
|
+
else
|
191
|
+
puts "Not empyting safe"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Attempts to remove key and value
|
196
|
+
# from safe if it exists.
|
197
|
+
# If new_data is empty, it means that
|
198
|
+
# there is only one key value in the safe,
|
199
|
+
# So the safe needs to be emptied rather
|
200
|
+
# re-storing the new information.
|
201
|
+
def remove_key_value(safe_password)
|
202
|
+
new_data = ''
|
203
|
+
old_data = retrieve_data safe_password
|
204
|
+
if old_data
|
205
|
+
puts "Deleting #@arg and related password"
|
206
|
+
old_data.each { |key, value| new_data << "#{key}:#{value}\n" if key !~ /^#@arg$/ }
|
207
|
+
if new_data.empty?
|
208
|
+
@safe.empty_safe
|
209
|
+
else
|
210
|
+
@safe.lock(new_data, safe_password)
|
211
|
+
end
|
212
|
+
else
|
213
|
+
puts "Safe is empty"
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Attempts to change the password for the safe.
|
218
|
+
# If the safe is empty, there is no password to be changed.
|
219
|
+
def change_safe_password
|
220
|
+
(puts "The safe is empty, no password is currently active"; return) if @safe.empty?
|
221
|
+
puts "Enter the old password for the safe"
|
222
|
+
old_safe_password = get_password
|
223
|
+
puts "Enter the new password for the safe"
|
224
|
+
new_safe_password = get_password
|
225
|
+
data = retrieve_data old_safe_password
|
226
|
+
puts "Successfully changed password" if (merge_data(data, nil, new_safe_password) if data)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Removes safe from file system
|
230
|
+
def remove_safe
|
231
|
+
@safe.self_destruct
|
232
|
+
puts "The safe has been removed!"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module PasswordSafe
|
2
|
+
# The Safe class interacts with a file (safe),
|
3
|
+
# storing, retrieving, or removing data from it.
|
4
|
+
class Safe
|
5
|
+
include PasswordSafe::Crypt
|
6
|
+
include PasswordSafe::Config
|
7
|
+
|
8
|
+
# Default initialization of safe
|
9
|
+
# in PASSWORD_SAFE location
|
10
|
+
def initialize(safe_location = PasswordSafe::Config::PASSWORD_SAFE)
|
11
|
+
@safe_location = safe_location
|
12
|
+
File.new @safe_location, 'w' unless File.exists?(@safe_location) || File.symlink?(@safe_location)
|
13
|
+
if File.symlink?(@safe_location) && !File.exists?(File.readlink @safe_location)
|
14
|
+
FileUtils.rm_f File.readlink @safe_location
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Empties safe by writing nil to file
|
19
|
+
def empty_safe
|
20
|
+
if File.exists? @safe_location
|
21
|
+
File.open(@safe_location, 'w') { |f| f << nil }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Checks for contents in safe
|
26
|
+
def empty?
|
27
|
+
if File.symlink? @safe_location
|
28
|
+
IO.readlines(File.readlink @safe_location).empty? if File.exists? File.readlink @safe_location
|
29
|
+
else
|
30
|
+
IO.readlines(@safe_location).empty?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Removes safe from file system
|
35
|
+
def self_destruct
|
36
|
+
FileUtils.rm_f File.readlink @safe_location if File.symlink? @safe_location
|
37
|
+
FileUtils.rm_f @safe_location
|
38
|
+
end
|
39
|
+
|
40
|
+
# Unlocks the safe by decrypting the data
|
41
|
+
# currently in the safe with the specified password
|
42
|
+
# If the password is incorrect an OpenSSL::Cipher::CipherError
|
43
|
+
# exception is thrown
|
44
|
+
def unlock(password)
|
45
|
+
decrypt(IO.readlines(@safe_location).join, password) unless File.zero? @safe_location
|
46
|
+
end
|
47
|
+
|
48
|
+
# Locks the safe by encrypting the data with the
|
49
|
+
# specified password
|
50
|
+
def lock(data, password)
|
51
|
+
File.open(@safe_location, 'w') { |f| f << encrypt(data, password) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/password_safe/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = "password_safe"
|
6
|
+
gem.version = PasswordSafe::VERSION
|
7
|
+
gem.authors = ["Alec Tower"]
|
8
|
+
gem.email = ["alectower@gmail.com"]
|
9
|
+
gem.description = %q{A ruby command line tool for storing encrypted passwords on *nix systems}
|
10
|
+
gem.summary = %q{A ruby command line tool that uses AES-256-CBC encryption to store passwords. It provides easy storage and retrieval on *nix systems}
|
11
|
+
gem.homepage = "https://github.com/uniosx/password_safe"
|
12
|
+
gem.platform = Gem::Platform::RUBY
|
13
|
+
gem.files = `git ls-files`.split($\)
|
14
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
15
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
gem.add_development_dependency 'rspec', '~>2.8'
|
18
|
+
gem.add_development_dependency 'cover_me', '~>1.2'
|
19
|
+
end
|
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PasswordSafe::Config do
|
4
|
+
include PasswordSafe::Config
|
5
|
+
before :all do
|
6
|
+
PasswordSafe::Config::PASSWORD_SAFE = "#{ENV['HOME']}/.tstpswds"
|
7
|
+
File.new PasswordSafe::Config::PASSWORD_SAFE, 'w'
|
8
|
+
self.instance_eval do
|
9
|
+
@new_location = "#{ENV['HOME']}/Dropbox/.tstpswds"
|
10
|
+
@newer_location = "#{ENV['HOME']}/Dropbox/.tst_pswds"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
after :each do
|
14
|
+
File.delete PasswordSafe::Config::PASSWORD_SAFE if File.exists? PasswordSafe::Config::PASSWORD_SAFE
|
15
|
+
File.delete @new_location if File.exists? @new_location
|
16
|
+
File.delete @newer_location if File.exists? @newer_location
|
17
|
+
end
|
18
|
+
it "should change the location of the password file" do
|
19
|
+
change_safe_location @new_location
|
20
|
+
File.exists?(@new_location).should eql true
|
21
|
+
File.symlink?(PasswordSafe::Config::PASSWORD_SAFE).should eql true
|
22
|
+
end
|
23
|
+
it "should move default safe location contents to a new safe" do
|
24
|
+
File.open(PasswordSafe::Config::PASSWORD_SAFE, 'a') {|f| f << "stuff"}
|
25
|
+
change_safe_location @new_location
|
26
|
+
IO.readlines(@new_location).should eql ["stuff"]
|
27
|
+
IO.readlines(PasswordSafe::Config::PASSWORD_SAFE).should eql ["stuff"]
|
28
|
+
end
|
29
|
+
it "should move sym linked safe location contents to a new safe" do
|
30
|
+
File.open(@new_location, 'a') {|f| f << "stuff"}
|
31
|
+
FileUtils.ln_s @new_location, PasswordSafe::Config::PASSWORD_SAFE, :force => true
|
32
|
+
change_safe_location @newer_location
|
33
|
+
IO.readlines(PasswordSafe::Config::PASSWORD_SAFE).should eql ["stuff"]
|
34
|
+
IO.readlines(@newer_location).should eql ["stuff"]
|
35
|
+
end
|
36
|
+
it "should delete old safe when moving to a new location" do
|
37
|
+
File.open(PasswordSafe::Config::PASSWORD_SAFE, 'a') {|f| f << "stuff"}
|
38
|
+
change_safe_location @new_location
|
39
|
+
change_safe_location @newer_location
|
40
|
+
IO.readlines(@newer_location).should eql ["stuff"]
|
41
|
+
File.exists?(@new_location).should eql false
|
42
|
+
end
|
43
|
+
it "should print the current safe location if no argument is supplied" do
|
44
|
+
$stdout.should_receive(:puts).with("Current safe location is #{PasswordSafe::Config::PASSWORD_SAFE}")
|
45
|
+
change_safe_location nil
|
46
|
+
end
|
47
|
+
end
|
data/spec/crypt_spec.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PasswordSafe::Crypt do
|
4
|
+
include PasswordSafe::Crypt
|
5
|
+
it "should encrypt data" do
|
6
|
+
encrypt("this is a test",'password').should_not eql "this is a test"
|
7
|
+
end
|
8
|
+
it "should decrypt data" do
|
9
|
+
decrypt(encrypt("this is a test",'password'), 'password').should eql "this is a test"
|
10
|
+
end
|
11
|
+
end
|
data/spec/guard_spec.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PasswordSafe::Guard do
|
4
|
+
before :each do
|
5
|
+
PasswordSafe::Guard.instance_eval do
|
6
|
+
attr_accessor :safe, :command, :arg
|
7
|
+
end
|
8
|
+
@guard = PasswordSafe::Guard.new mock("safe")
|
9
|
+
ARGV.clear
|
10
|
+
end
|
11
|
+
it "should validate 0 args" do
|
12
|
+
@guard.validate_args.should eql false
|
13
|
+
end
|
14
|
+
it "should validate 1 arg" do
|
15
|
+
ARGV << "-g"
|
16
|
+
@guard.validate_args.should eql true
|
17
|
+
end
|
18
|
+
it "should validate > 1 and < 3 args" do
|
19
|
+
ARGV << "-g" << "keys"
|
20
|
+
@guard.validate_args.should eql true
|
21
|
+
end
|
22
|
+
it "should validate >= 3 args" do
|
23
|
+
ARGV << "-g" << "all" << "too many"
|
24
|
+
@guard.validate_args.should eql false
|
25
|
+
end
|
26
|
+
it "should process input after validating args" do
|
27
|
+
@guard.stub(:validate_args).and_return true
|
28
|
+
@guard.should_receive(:validate_args)
|
29
|
+
@guard.should_receive(:process_input)
|
30
|
+
@guard.on_the_job.should
|
31
|
+
end
|
32
|
+
it "should print options if no args are provided" do
|
33
|
+
@guard.stub(:validate_args).and_return false
|
34
|
+
@guard.should_receive(:print_options)
|
35
|
+
@guard.on_the_job
|
36
|
+
end
|
37
|
+
it "should process the -a switch to add a key and value" do
|
38
|
+
$stdout.should_receive(:puts).with("Enter the password for the safe")
|
39
|
+
@guard.stub(:get_password).and_return("safepassword", "gmailpassword")
|
40
|
+
$stdout.should_receive(:puts).with("Enter the password for gmail")
|
41
|
+
@guard.stub(:add_data).with("gmail:gmailpassword", "safepassword").and_return "Successfully added key value pair"
|
42
|
+
@guard.command = "-a"
|
43
|
+
@guard.arg = "gmail"
|
44
|
+
@guard.process_input.should eql "Successfully added key value pair"
|
45
|
+
end
|
46
|
+
it "should process the -g switch with no argument" do
|
47
|
+
@guard.stub(:get_password).and_return "safepassword"
|
48
|
+
@guard.stub(:retrieve_data).with("safepassword").and_return nil
|
49
|
+
@guard.safe.should_receive(:empty?).and_return true
|
50
|
+
$stdout.should_receive(:puts).with("The safe is empty")
|
51
|
+
@guard.command = "-g"
|
52
|
+
@guard.process_input
|
53
|
+
end
|
54
|
+
it "should process the -g switch with an argument" do
|
55
|
+
@guard.safe.stub(:empty?).and_return false
|
56
|
+
@guard.stub(:get_password).and_return "safepassword"
|
57
|
+
@guard.stub(:retrieve_data).with("safepassword").and_return Hash["gmail" => "gmailpassword"]
|
58
|
+
IO.should_receive(:popen).with('pbcopy', 'w')
|
59
|
+
@guard.should_receive(:sleep).with(5)
|
60
|
+
IO.should_receive(:popen).with('pbcopy', 'w')
|
61
|
+
@guard.command = "-g"
|
62
|
+
@guard.arg = "gmail"
|
63
|
+
@guard.process_input
|
64
|
+
end
|
65
|
+
it "should process the -d switch with an argument" do
|
66
|
+
@guard.safe.stub(:empty?).and_return false
|
67
|
+
@guard.stub(:remove_key_value).with("safepassword")
|
68
|
+
@guard.stub(:get_password).and_return "safepassword"
|
69
|
+
$stdout.should_receive(:puts).with("Enter the password for the safe")
|
70
|
+
@guard.should_receive(:remove_key_value).with("safepassword")
|
71
|
+
@guard.command = "-d"
|
72
|
+
@guard.arg = "gmail"
|
73
|
+
@guard.process_input
|
74
|
+
end
|
75
|
+
it "should process the -d switch with no argument" do
|
76
|
+
@guard.safe.stub(:empty?).and_return false
|
77
|
+
@guard.should_receive(:empty_safe)
|
78
|
+
@guard.command = "-d"
|
79
|
+
@guard.arg = nil
|
80
|
+
@guard.process_input
|
81
|
+
end
|
82
|
+
it "should process the -l switch with a path as an argument" do
|
83
|
+
@guard.command = "-l"
|
84
|
+
@guard.arg = "~/.new_safe"
|
85
|
+
@guard.should_receive(:change_safe_location).with("~/.new_safe")
|
86
|
+
@guard.process_input
|
87
|
+
end
|
88
|
+
it "should process the -c switch" do
|
89
|
+
@guard.command = "-c"
|
90
|
+
@guard.should_receive(:change_safe_password)
|
91
|
+
@guard.process_input
|
92
|
+
end
|
93
|
+
it "should process the -r switch" do
|
94
|
+
@guard.command = "-r"
|
95
|
+
@guard.should_receive(:remove_safe)
|
96
|
+
@guard.process_input
|
97
|
+
end
|
98
|
+
it "should get a password from the user" do
|
99
|
+
@guard.stub!(:system)
|
100
|
+
@guard.should_receive(:system).with("stty raw -echo")
|
101
|
+
STDIN.stub(:getc).and_return("g", "m", "\r")
|
102
|
+
@guard.get_password.should eql "gm"
|
103
|
+
end
|
104
|
+
it "should add data to safe" do
|
105
|
+
@guard.stub(:retrieve_data).with("fakepassword").and_return nil
|
106
|
+
@guard.safe.stub(:lock).with("gmail:gmailpassword\n", "fakepassword")
|
107
|
+
$stdout.should_receive(:puts).with("Successfully added key value pair")
|
108
|
+
@guard.add_data "gmail:gmailpassword", "fakepassword"
|
109
|
+
end
|
110
|
+
it "should retreive data froms safe" do
|
111
|
+
@guard.safe.stub(:unlock).with("safepassword").and_return("gmail:gmailpassword")
|
112
|
+
@guard.retrieve_data("safepassword").should eql Hash["gmail" => "gmailpassword"]
|
113
|
+
end
|
114
|
+
it "should empty the safe" do
|
115
|
+
STDIN.stub(:gets).and_return("Y")
|
116
|
+
@guard.safe.stub(:empty?).and_return false
|
117
|
+
$stdout.should_receive(:puts).with("Are you sure? (y|Y)")
|
118
|
+
@guard.safe.should_receive(:empty_safe)
|
119
|
+
$stdout.should_receive(:puts).with("Safe was emptied!")
|
120
|
+
@guard.empty_safe
|
121
|
+
end
|
122
|
+
it "should remove key value from safe" do
|
123
|
+
@guard.stub(:retrieve_data).with("safepassword").and_return Hash["gmail" => "gmailpassword", "facebook" => "facebookpassword"]
|
124
|
+
$stdout.should_receive(:puts).with("Deleting gmail and related password")
|
125
|
+
@guard.safe.should_receive(:lock).with("facebook:facebookpassword\n", "safepassword")
|
126
|
+
@guard.arg = "gmail"
|
127
|
+
@guard.remove_key_value "safepassword"
|
128
|
+
end
|
129
|
+
it "should remove the safe" do
|
130
|
+
@guard.safe.stub(:self_destruct)
|
131
|
+
@guard.safe.should_receive(:self_destruct)
|
132
|
+
$stdout.should_receive(:puts).with("The safe has been removed!")
|
133
|
+
@guard.remove_safe
|
134
|
+
end
|
135
|
+
end
|
data/spec/safe_spec.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PasswordSafe::Safe do
|
4
|
+
include PasswordSafe::Crypt
|
5
|
+
before :each do
|
6
|
+
@safe = PasswordSafe::Safe.new "#{ENV['HOME']}/.tmp_pswds"
|
7
|
+
PasswordSafe::Safe.instance_eval do
|
8
|
+
attr_reader :safe_location
|
9
|
+
end
|
10
|
+
end
|
11
|
+
after :each do
|
12
|
+
@safe.self_destruct
|
13
|
+
end
|
14
|
+
it "should create a new safe if one doesn't already exist" do
|
15
|
+
File.exists?(@safe.safe_location).should eql true
|
16
|
+
end
|
17
|
+
it "should remove broken sym link of file doesn't exist" do
|
18
|
+
FileUtils.ln_s "#{ENV['HOME']}/.doesnt_exist", @safe.safe_location, :force => true
|
19
|
+
@safe = PasswordSafe::Safe.new "#{ENV['HOME']}/.tmp_pswds"
|
20
|
+
File.exists?("#{ENV['HOME']}/.doesnt_exist").should eql false
|
21
|
+
end
|
22
|
+
it "should lock data" do
|
23
|
+
data = "gmail:password"
|
24
|
+
@safe.lock(data, "password")
|
25
|
+
decrypt(IO.readlines(@safe.safe_location).to_s, "password").should eql data
|
26
|
+
end
|
27
|
+
it "should unlock data" do
|
28
|
+
@safe.lock "gmail:password", "password"
|
29
|
+
@safe.unlock("password").should eql "gmail:password"
|
30
|
+
end
|
31
|
+
it "should know if it's empty" do
|
32
|
+
@safe.empty?.should eql true
|
33
|
+
end
|
34
|
+
it "should remove sym link when self destructing" do
|
35
|
+
FileUtils.ln_s("#{ENV['HOME']}/.tmp_pswds_lnk", @safe.safe_location, :force => true)
|
36
|
+
@safe.self_destruct
|
37
|
+
File.exists?("#{ENV['HOME']}/.tmp_pswds_lnk").should eql false
|
38
|
+
File.exists?(@safe.safe_location).should eql false
|
39
|
+
end
|
40
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper.rb"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
|
8
|
+
require 'cover_me'
|
9
|
+
require 'password_safe'
|
10
|
+
require 'rspec'
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
14
|
+
end
|
15
|
+
|
16
|
+
at_exit do
|
17
|
+
CoverMe.instance_eval do
|
18
|
+
def complete
|
19
|
+
data_file = CoverMe.config.results.store
|
20
|
+
if File.exists?(data_file)
|
21
|
+
data = CoverMe::Results.read_results(data_file)
|
22
|
+
CoverMe::Processor.new(data).process!
|
23
|
+
File.delete(data_file)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
CoverMe.complete
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: password_safe
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Alec Tower
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.8'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.8'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: cover_me
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.2'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.2'
|
46
|
+
description: A ruby command line tool for storing encrypted passwords on *nix systems
|
47
|
+
email:
|
48
|
+
- alectower@gmail.com
|
49
|
+
executables:
|
50
|
+
- password_safe
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- .rspec
|
56
|
+
- Gemfile
|
57
|
+
- LICENSE
|
58
|
+
- README.md
|
59
|
+
- Rakefile
|
60
|
+
- bin/password_safe
|
61
|
+
- lib/password_safe.rb
|
62
|
+
- lib/password_safe/config.rb
|
63
|
+
- lib/password_safe/crypt.rb
|
64
|
+
- lib/password_safe/guard.rb
|
65
|
+
- lib/password_safe/safe.rb
|
66
|
+
- lib/password_safe/version.rb
|
67
|
+
- password_safe.gemspec
|
68
|
+
- spec/config_spec.rb
|
69
|
+
- spec/crypt_spec.rb
|
70
|
+
- spec/guard_spec.rb
|
71
|
+
- spec/safe_spec.rb
|
72
|
+
- spec/spec_helper.rb
|
73
|
+
homepage: https://github.com/uniosx/password_safe
|
74
|
+
licenses: []
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ! '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 1.8.23
|
94
|
+
signing_key:
|
95
|
+
specification_version: 3
|
96
|
+
summary: A ruby command line tool that uses AES-256-CBC encryption to store passwords.
|
97
|
+
It provides easy storage and retrieval on *nix systems
|
98
|
+
test_files:
|
99
|
+
- spec/config_spec.rb
|
100
|
+
- spec/crypt_spec.rb
|
101
|
+
- spec/guard_spec.rb
|
102
|
+
- spec/safe_spec.rb
|
103
|
+
- spec/spec_helper.rb
|