clandestine 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5349f88fdb91dc0ba318147f01dc441f43d27fa8
4
+ data.tar.gz: a10b5403a328c9d59f352d58a627fdd83a52249d
5
+ SHA512:
6
+ metadata.gz: 0b3b6a6ad52762bd9a245217290eb5be52ea2321504ba06dd4bd1129c677b18656755be0be8672471644dd8644a0ebd29a4354f85df70c77de466c6e64597a73
7
+ data.tar.gz: 33e414a62e14f96b4f0c7dfa799495b9eeef0aff8a70a6eee7d7a08b3f2028ba9f5d7212faf6aec32ffbb4d4baed44e700ab56c5f7f01b9a4bc539bb4824eba1
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage*
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .rspec
19
+ #*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in password_safe.gemspec
4
+ gemspec
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,32 @@
1
+ # Clandestine
2
+
3
+ Clandestine allows you to store passwords in a local file using AES-CBC-256 encryption.
4
+
5
+ ## Requirements
6
+
7
+ Ruby
8
+
9
+ Currently only supported on OSX
10
+
11
+ ## Installation
12
+
13
+ Clandestine can be installed using RubyGems:
14
+
15
+ $ gem install clandestine
16
+
17
+ ## Usage
18
+
19
+ user$ clandestine
20
+
21
+ Options:
22
+ -a <key> :: add password related to <key>
23
+ -g <key> :: get password related to <key>
24
+ -g :: get all keys
25
+ -d <key> :: delete password related to <key>
26
+ -d :: delete all passwords
27
+ -l :: print current location of safe
28
+ -l <path> :: move safe location to <path>
29
+ -c :: change password to safe
30
+ -r :: remove safe completely
31
+
32
+ Passwords will be copied to the clipboard for 10 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/clandestine ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << File.expand_path(File.dirname(__FILE__) + "/../lib") unless $:.include? File.expand_path(File.dirname(__FILE__) + "/../lib")
4
+
5
+ require 'clandestine'
6
+
7
+ Clandestine::Guard.new.on_the_job
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/clandestine/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "clandestine"
6
+ gem.version = Clandestine::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/clandestine"
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_dependency 'highline'
18
+ gem.add_development_dependency 'rspec', '~>2.8'
19
+ end
@@ -0,0 +1,25 @@
1
+ module Clandestine
2
+ module Config
3
+ CONFIG_PATH = "#{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 CONFIG_PATH if File.symlink? CONFIG_PATH
9
+ if IO.readlines(CONFIG_PATH).empty?
10
+ File.new new_location, 'w' unless File.exists? new_location
11
+ else
12
+ FileUtils.copy_file(CONFIG_PATH, new_location, true) unless File.exists? new_location
13
+ end
14
+ FileUtils.ln_s(new_location, CONFIG_PATH, :force => true)
15
+ FileUtils.rm_f old_file if old_file
16
+ else
17
+ if File.symlink? CONFIG_PATH
18
+ puts "Current safe location is #{File.readlink CONFIG_PATH}"
19
+ else
20
+ puts "Current safe location is #{CONFIG_PATH}"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ module Clandestine
2
+ # This module provides encrypt
3
+ # and decrypt methods using aes-256-cbc
4
+ module Crypt
5
+
6
+ def encrypt(data, password)
7
+ Base64.encode64 aes(:encrypt, password, data)
8
+ end
9
+
10
+ def decrypt(data, password)
11
+ aes(:decrypt, password, Base64.decode64(data))
12
+ end
13
+
14
+ # Uses AES-256-CBC to encrypt data
15
+ # Key length is 32 bytes
16
+ # IV length is 16 bytes
17
+ # TODO Use random IV and include it in
18
+ # encrypted data rather than using the same
19
+ # IV every time.
20
+ def aes(mode, password, data)
21
+ sha256 = Digest::SHA2.new
22
+ aes = OpenSSL::Cipher.new("AES-256-CBC")
23
+ aes.send(mode)
24
+ aes.key = sha256.digest(password)
25
+ aes.iv = '0123456789012345'
26
+ '' << aes.update(data) << aes.final
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,217 @@
1
+ require 'highline/import'
2
+
3
+ module Clandestine
4
+ # Handles user requests and interacts
5
+ # with the Safe class.
6
+ #
7
+ # Main class to interface with user.
8
+ # The Guard protects the safe by
9
+ # authenticating the user, and making sure
10
+ # data is in the correct format to be
11
+ # held in the safe.
12
+ class Guard
13
+ include Clandestine::Config
14
+
15
+ # Create new guard object with specified safe.
16
+ # Default safe location is ENV['HOME']/.pswds
17
+ def initialize(safe = Safe.new)
18
+ @safe = safe
19
+ end
20
+
21
+ # Main method that validates arguments
22
+ # and processes input
23
+ def on_the_job
24
+ validate_args ? process_input : print_options
25
+ end
26
+
27
+ # Checks for the correct switches to be
28
+ # supplied by user
29
+ def validate_args
30
+ if ARGV.size > 0 && ARGV.size < 3
31
+ @command = ARGV[0]
32
+ @arg = ARGV[1]
33
+ (@command =~ /[(-c)]/ && !@arg.nil?) || (@command =~ /[(-a)(-g)(-l)]/ && @arg.nil?)
34
+ else
35
+ false
36
+ end
37
+ end
38
+
39
+ # Command line options
40
+ def print_options
41
+ puts "Clandestine v#{Clandestine::VERSION}\
42
+ \nOptions: \
43
+ \n\t-a <key> :: add password related to <key> \
44
+ \n\t-g <key> :: get password related to <key> \
45
+ \n\t-g :: get all keys \
46
+ \n\t-d <key> :: delete password related to <key> \
47
+ \n\t-d :: delete all passwords \
48
+ \n\t-l :: print current location of safe \
49
+ \n\t-l <path> :: move safe location to <path> \
50
+ \n\t-c :: change password to safe \
51
+ \n\t-r :: remove safe completely
52
+ \n\n\tPasswords will be copied to the clipboard for 5 seconds \
53
+ \n\n"
54
+ end
55
+
56
+ # Case statement for switches
57
+ # Main logic method
58
+ def process_input
59
+ case @command
60
+ when "-a"
61
+ add_value
62
+ when "-g"
63
+ get_value
64
+ when "-d"
65
+ delete_value
66
+ when "-l"
67
+ change_safe_location(@arg)
68
+ when "-c"
69
+ change_safe_password
70
+ when "-r"
71
+ remove_safe
72
+ else
73
+ print_options
74
+ end
75
+ end
76
+
77
+ # Adds a new value to the safe
78
+ # and binds it to the key provided
79
+ # by the user.
80
+ def add_value
81
+ if @arg
82
+ safe_password = ask("Enter the password for the safe") { |q| q.echo = "*" }
83
+ key_password = ask("Enter the password for #{@arg}") { |q| q.echo = "*" }
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
+ safe_password = ask("Enter the password for the safe") { |q| q.echo = "*" }
96
+ case @arg
97
+ when /^\S+$/
98
+ data = retrieve_data(safe_password)
99
+ value = data[@arg] if data
100
+ if value
101
+ IO.popen('pbcopy', 'w') {|clipboard| clipboard.print "#{value}"}
102
+ say("Password on clipboard countdown: ")
103
+ 10.downto(1) do |num|
104
+ sleep(1)
105
+ num == 1 ? say("#{num}") : say("#{num} ")
106
+ end
107
+ IO.popen('pbcopy', 'w') {|clipboard| clipboard.print ""}
108
+ else
109
+ puts "No value found for #{@arg}"
110
+ end
111
+ else
112
+ data = retrieve_data safe_password
113
+ if data
114
+ data.each_key { |key| puts key }
115
+ else
116
+ puts "Safe is empty"
117
+ end
118
+ end
119
+ end
120
+
121
+ # Removes key and value from safe.
122
+ def delete_value
123
+ (puts " The safe is empty"; return) if @safe.empty?
124
+ case @arg
125
+ when /^\S+$/
126
+ safe_password = ask("Enter the password for the safe") { |q| q.echo = "*" }
127
+ remove_key_value safe_password
128
+ when nil
129
+ empty_safe
130
+ end
131
+ end
132
+
133
+ # First retrieves old data to see if
134
+ # the key already exists in the safe,
135
+ # adds it if not.
136
+ def add_data(key_value, safe_password)
137
+ old_data = retrieve_data safe_password
138
+ if old_data && old_data[@arg]
139
+ puts "#@arg already exists"
140
+ return
141
+ end
142
+ merge_data(old_data, key_value, safe_password)
143
+ puts "Successfully added key value pair"
144
+ end
145
+
146
+ # Merges new key value pair with old data
147
+ def merge_data(old_data, key_value, safe_password)
148
+ new_data = ''
149
+ old_data.each { |key, value| new_data << "#{key}:#{value}\n" } if old_data
150
+ new_data << "#{key_value}\n" if key_value
151
+ @safe.lock(new_data, safe_password)
152
+ end
153
+
154
+ # Retrieves data from safe and returns
155
+ # it in a key value hash
156
+ def retrieve_data(safe_password)
157
+ begin
158
+ return if !data = @safe.unlock(safe_password)
159
+ data.split("\n").map {|l| l.split(":")}.map {|k, v| Hash[k => v]}.reduce({}) {|h, i| h.merge i}
160
+ rescue OpenSSL::Cipher::CipherError
161
+ puts "You entered an incorrect password"
162
+ exit(0)
163
+ end
164
+ end
165
+
166
+ # Prompts user for delete confirmation
167
+ # and empties safe if user entered "Y" or "y".
168
+ def empty_safe
169
+ (puts "The safe is already empty"; return) if @safe.empty?
170
+ puts "Are you sure? (y|Y)"
171
+ if STDIN.gets =~ /^[yY]{1}$/
172
+ @safe.empty_safe
173
+ puts "Safe was emptied!"
174
+ else
175
+ puts "Not empyting safe"
176
+ end
177
+ end
178
+
179
+ # Attempts to remove key and value
180
+ # from safe if it exists.
181
+ # If new_data is empty, it means that
182
+ # there is only one key value in the safe,
183
+ # So the safe needs to be emptied rather
184
+ # re-storing the new information.
185
+ def remove_key_value(safe_password)
186
+ new_data = ''
187
+ old_data = retrieve_data safe_password
188
+ if old_data
189
+ puts "Deleting #@arg and related password"
190
+ old_data.each { |key, value| new_data << "#{key}:#{value}\n" if key !~ /^#@arg$/ }
191
+ if new_data.empty?
192
+ @safe.empty_safe
193
+ else
194
+ @safe.lock(new_data, safe_password)
195
+ end
196
+ else
197
+ puts "Safe is empty"
198
+ end
199
+ end
200
+
201
+ # Attempts to change the password for the safe.
202
+ # If the safe is empty, there is no password to be changed.
203
+ def change_safe_password
204
+ (puts "The safe is empty, no password is currently active"; return) if @safe.empty?
205
+ old_safe_password = ask("Enter the old password for the safe") { |q| q.echo = "*" }
206
+ new_safe_password = ask("Enter the new password for the safe") { |q| q.echo = "*" }
207
+ data = retrieve_data old_safe_password
208
+ puts "Successfully changed password" if (merge_data(data, nil, new_safe_password) if data)
209
+ end
210
+
211
+ # Removes safe from file system
212
+ def remove_safe
213
+ @safe.self_destruct
214
+ puts "The safe has been removed!"
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,54 @@
1
+ module Clandestine
2
+ # The Safe class interacts with a file (safe),
3
+ # storing, retrieving, or removing data from it.
4
+ class Safe
5
+ include Clandestine::Crypt
6
+ include Clandestine::Config
7
+
8
+ # Default initialization of safe
9
+ # in PASSWORD_SAFE location
10
+ def initialize(safe_location = Clandestine::Config::CONFIG_PATH)
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,3 @@
1
+ module Clandestine
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,9 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+ require 'digest/sha2'
4
+ require 'fileutils'
5
+ require 'clandestine/version'
6
+ require 'clandestine/crypt'
7
+ require 'clandestine/config'
8
+ require 'clandestine/safe'
9
+ require 'clandestine/guard'
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe Clandestine::Config do
4
+ include Clandestine::Config
5
+ before :all do
6
+ Clandestine::Config::CONFIG_PATH = "#{ENV['HOME']}/.tstpswds"
7
+ File.new Clandestine::Config::CONFIG_PATH, '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 Clandestine::Config::CONFIG_PATH if File.exists? Clandestine::Config::CONFIG_PATH
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?(Clandestine::Config::CONFIG_PATH).should eql true
22
+ end
23
+ it "should move default safe location contents to a new safe" do
24
+ File.open(Clandestine::Config::CONFIG_PATH, 'a') {|f| f << "stuff"}
25
+ change_safe_location @new_location
26
+ IO.readlines(@new_location).should eql ["stuff"]
27
+ IO.readlines(Clandestine::Config::CONFIG_PATH).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, Clandestine::Config::CONFIG_PATH, :force => true
32
+ change_safe_location @newer_location
33
+ IO.readlines(Clandestine::Config::CONFIG_PATH).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(Clandestine::Config::CONFIG_PATH, '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 #{Clandestine::Config::CONFIG_PATH}")
45
+ change_safe_location nil
46
+ end
47
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe Clandestine::Crypt do
4
+ include Clandestine::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
@@ -0,0 +1,127 @@
1
+ require 'spec_helper'
2
+
3
+ describe Clandestine::Guard do
4
+ before :each do
5
+ Clandestine::Guard.instance_eval do
6
+ attr_accessor :safe, :command, :arg
7
+ end
8
+ @guard = Clandestine::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
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
+ @guard.stub(:ask).and_return("safepassword", "gmailpassword")
39
+ @guard.stub(:add_data).with("gmail:gmailpassword", "safepassword").and_return "Successfully added key value pair"
40
+ @guard.command = "-a"
41
+ @guard.arg = "gmail"
42
+ @guard.process_input.should eql "Successfully added key value pair"
43
+ end
44
+ it "should process the -g switch with no argument" do
45
+ @guard.stub(:ask).and_return "safepassword"
46
+ @guard.stub(:retrieve_data).with("safepassword").and_return nil
47
+ @guard.safe.should_receive(:empty?).and_return true
48
+ $stdout.should_receive(:puts).with("The safe is empty")
49
+ @guard.command = "-g"
50
+ @guard.process_input
51
+ end
52
+ it "should process the -g switch with an argument" do
53
+ @guard.safe.stub(:empty?).and_return false
54
+ @guard.stub(:ask).and_return "safepassword"
55
+ @guard.stub(:retrieve_data).with("safepassword").and_return Hash["gmail" => "gmailpassword"]
56
+ IO.should_receive(:popen).with('pbcopy', 'w')
57
+ @guard.should_receive(:sleep).with(1).exactly(10).times
58
+ IO.should_receive(:popen).with('pbcopy', 'w')
59
+ @guard.command = "-g"
60
+ @guard.arg = "gmail"
61
+ @guard.process_input
62
+ end
63
+ it "should process the -d switch with an argument" do
64
+ @guard.safe.stub(:empty?).and_return false
65
+ @guard.stub(:remove_key_value).with("safepassword")
66
+ @guard.stub(:ask).and_return "safepassword"
67
+ @guard.should_receive(:ask).with("Enter the password for the safe")
68
+ @guard.should_receive(:remove_key_value).with("safepassword")
69
+ @guard.command = "-d"
70
+ @guard.arg = "gmail"
71
+ @guard.process_input
72
+ end
73
+ it "should process the -d switch with no argument" do
74
+ @guard.safe.stub(:empty?).and_return false
75
+ @guard.should_receive(:empty_safe)
76
+ @guard.command = "-d"
77
+ @guard.arg = nil
78
+ @guard.process_input
79
+ end
80
+ it "should process the -l switch with a path as an argument" do
81
+ @guard.command = "-l"
82
+ @guard.arg = "~/.new_safe"
83
+ @guard.should_receive(:change_safe_location).with("~/.new_safe")
84
+ @guard.process_input
85
+ end
86
+ it "should process the -c switch" do
87
+ @guard.command = "-c"
88
+ @guard.should_receive(:change_safe_password)
89
+ @guard.process_input
90
+ end
91
+ it "should process the -r switch" do
92
+ @guard.command = "-r"
93
+ @guard.should_receive(:remove_safe)
94
+ @guard.process_input
95
+ end
96
+ it "should add data to safe" do
97
+ @guard.stub(:retrieve_data).with("fakepassword").and_return nil
98
+ @guard.safe.stub(:lock).with("gmail:gmailpassword\n", "fakepassword")
99
+ $stdout.should_receive(:puts).with("Successfully added key value pair")
100
+ @guard.add_data "gmail:gmailpassword", "fakepassword"
101
+ end
102
+ it "should retreive data froms safe" do
103
+ @guard.safe.stub(:unlock).with("safepassword").and_return("gmail:gmailpassword")
104
+ @guard.retrieve_data("safepassword").should eql Hash["gmail" => "gmailpassword"]
105
+ end
106
+ it "should empty the safe" do
107
+ STDIN.stub(:gets).and_return("Y")
108
+ @guard.safe.stub(:empty?).and_return false
109
+ $stdout.should_receive(:puts).with("Are you sure? (y|Y)")
110
+ @guard.safe.should_receive(:empty_safe)
111
+ $stdout.should_receive(:puts).with("Safe was emptied!")
112
+ @guard.empty_safe
113
+ end
114
+ it "should remove key value from safe" do
115
+ @guard.stub(:retrieve_data).with("safepassword").and_return Hash["gmail" => "gmailpassword", "facebook" => "facebookpassword"]
116
+ $stdout.should_receive(:puts).with("Deleting gmail and related password")
117
+ @guard.safe.should_receive(:lock).with("facebook:facebookpassword\n", "safepassword")
118
+ @guard.arg = "gmail"
119
+ @guard.remove_key_value "safepassword"
120
+ end
121
+ it "should remove the safe" do
122
+ @guard.safe.stub(:self_destruct)
123
+ @guard.safe.should_receive(:self_destruct)
124
+ $stdout.should_receive(:puts).with("The safe has been removed!")
125
+ @guard.remove_safe
126
+ end
127
+ end
data/spec/safe_spec.rb ADDED
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Clandestine::Safe do
4
+ include Clandestine::Crypt
5
+ before :each do
6
+ @safe = Clandestine::Safe.new "#{ENV['HOME']}/.tmp_pswds"
7
+ Clandestine::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 = Clandestine::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
@@ -0,0 +1,13 @@
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 'clandestine'
9
+ require 'rspec'
10
+
11
+ RSpec.configure do |config|
12
+ config.treat_symbols_as_metadata_keys_with_true_values = true
13
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: clandestine
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Alec Tower
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-04-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: highline
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '2.8'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '2.8'
41
+ description: A ruby command line tool for storing encrypted passwords on *nix systems
42
+ email:
43
+ - alectower@gmail.com
44
+ executables:
45
+ - clandestine
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - .gitignore
50
+ - .rspec
51
+ - Gemfile
52
+ - LICENSE
53
+ - README.md
54
+ - Rakefile
55
+ - bin/clandestine
56
+ - clandestine.gemspec
57
+ - lib/clandestine.rb
58
+ - lib/clandestine/config.rb
59
+ - lib/clandestine/crypt.rb
60
+ - lib/clandestine/guard.rb
61
+ - lib/clandestine/safe.rb
62
+ - lib/clandestine/version.rb
63
+ - spec/config_spec.rb
64
+ - spec/crypt_spec.rb
65
+ - spec/guard_spec.rb
66
+ - spec/safe_spec.rb
67
+ - spec/spec_helper.rb
68
+ homepage: https://github.com/uniosx/clandestine
69
+ licenses: []
70
+ metadata: {}
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 2.0.3
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: A ruby command line tool that uses AES-256-CBC encryption to store passwords.
91
+ It provides easy storage and retrieval on *nix systems
92
+ test_files:
93
+ - spec/config_spec.rb
94
+ - spec/crypt_spec.rb
95
+ - spec/guard_spec.rb
96
+ - spec/safe_spec.rb
97
+ - spec/spec_helper.rb