fun_with_passwords 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YmE0NWE2NGNiZWY2MWRlMjcyNmNiYzFhMjU1MDM4YThkYmY0ZTJjZA==
5
+ data.tar.gz: !binary |-
6
+ YTNlZTk5MTFkNjg1YzhjNWI5OTkwYTZiNmQyZmEyZTYxMDFmZjYzNw==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MDVkZWMxZTY2MmVkMTQ3MWU2MDcxZjU2MTI2MzRhNTliZGZkOWE3NGQ1M2Fk
10
+ NWJiYWE1MTcxMGQwODdhYjE2YTc0NzZiY2NjMDU3ZjlhMjI1NTBmY2ZmN2Yy
11
+ MDBjMDkxYzFhNjk3YjYzNDZiZDFjNjYzMTgxZDYzYTJiN2I3NGU=
12
+ data.tar.gz: !binary |-
13
+ ZWY1OGZiYjU5ODY2ODUzMjJmMGNiMmVkOTE4MGU2YWJmN2JkNjZiNmFiYTZm
14
+ ZmFlNWQ2YWQyNzdhNmM1YmM2NGEzNzhjMDhjYzQ1ZTFjM2E1YTEzYTAwYzI0
15
+ MWFhZWY3NjYxMjQ1NDFiN2Y0ODFlYWZmMDFmZTgxNTgzOGRkMmU=
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,20 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "shoulda", "~> 3.5"
10
+ gem "rdoc", "~> 3.12"
11
+ gem "bundler", "~> 1.5"
12
+ gem "jeweler", "~> 2.0"
13
+ gem "debugger", "~> 1.6"
14
+ end
15
+
16
+ gem "fun_with_files", "~> 0"
17
+ gem "fun_with_configurations", "~> 0"
18
+ gem "fun_with_version_strings", "~> 0"
19
+ gem "highline", "~> 1.6"
20
+ gem "xdg", "~> 2.2"
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Bryce Anderson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,22 @@
1
+ = fun_with_passwords
2
+
3
+ Creates an encrypted password store that your applications can access.
4
+
5
+ Keychain.
6
+
7
+
8
+ == Contributing to fun_with_passwords
9
+
10
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
11
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
12
+ * Fork the project.
13
+ * Start a feature/bugfix branch.
14
+ * Commit and push until you are happy with your contribution.
15
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
16
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
17
+
18
+ == Copyright
19
+
20
+ Copyright (c) 2013 Bryce Anderson. See LICENSE.txt for
21
+ further details.
22
+
data/Rakefile ADDED
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "fun_with_passwords"
18
+ gem.homepage = "http://github.com/darthschmoo/fun_with_passwords"
19
+ gem.license = "MIT"
20
+ gem.summary = "Create an encrypted password store that your scripts can access."
21
+ gem.description = "Because passwords are obnoxious."
22
+ gem.email = "keeputahweird@gmail.com"
23
+ gem.authors = ["Bryce Anderson"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'lib' << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+
36
+ task :default => :test
37
+
38
+ require 'rdoc/task'
39
+ Rake::RDocTask.new do |rdoc|
40
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
41
+
42
+ rdoc.rdoc_dir = 'rdoc'
43
+ rdoc.title = "fun_with_passwords #{version}"
44
+ rdoc.rdoc_files.include('README*')
45
+ rdoc.rdoc_files.include('lib/**/*.rb')
46
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.2
data/bin/fwpass ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative File.join( "..", "lib", "fun_with_passwords" )
3
+
4
+ FunWith::Passwords::CommandLine.new( ARGV ).run
5
+
@@ -0,0 +1,184 @@
1
+ module FunWith
2
+ module Passwords
3
+ ACTION_KEYWORDS_TO_ACTIONS = {
4
+ "init" => :initialize,
5
+ "initialize" => :initialize,
6
+ "new" => :initialize,
7
+ "create" => :initialize,
8
+ "add" => :add,
9
+ "insert" => :add,
10
+ "remove" => :remove,
11
+ "rm" => :remove,
12
+ "rmv" => :remove,
13
+ "drop" => :remove,
14
+ "delete" => :delete,
15
+ "del" => :delete,
16
+ "display" => :display,
17
+ "disp" => :display,
18
+ "reveal" => :display,
19
+ "show" => :display,
20
+ "view" => :display,
21
+ "help" => :help,
22
+ "h" => :help,
23
+ "rekey" => :rekey
24
+ }
25
+
26
+ class CommandLine
27
+ def initialize( args )
28
+ @args = args
29
+ @result = CommandLineResult.new( @args.dup ) # false shuts it up
30
+
31
+ parse_args()
32
+
33
+ @console = Console.new
34
+ end
35
+
36
+ def verbose( verbosity = nil )
37
+ @verbose = verbosity unless verbosity.nil?
38
+ @result.verbose( @verbose )
39
+ !!@verbose
40
+ end
41
+
42
+ def run
43
+ return @result if @result.failed?
44
+
45
+ case @action
46
+ when :initialize
47
+ if @file.exist?
48
+ @result.puts "File #{@file} already exists"
49
+ @result.fail!
50
+ return @result
51
+ end
52
+
53
+ get_master_key()
54
+ @keychain = Keychain.new( :keys => {}, :master_key => @master_key, :file => @file )
55
+ @keychain.save
56
+ @result.puts( "Saved new password file at #{@file}" )
57
+ when :add
58
+ unlock()
59
+ if @keychain[@key].nil? || Console.new.confirm( "Replace current password for key #{@key}?" )
60
+ @keychain[@key] = @pass
61
+ @keychain.save
62
+ end
63
+ when :remove
64
+ unlock()
65
+ pass = @keychain.delete( @key )
66
+
67
+ if pass.fwf_blank?
68
+ @result.puts "No such key as #{@key}. No action taken."
69
+ return @result
70
+ end
71
+
72
+ @keychain.save
73
+ when :display
74
+ unlock()
75
+ for key, pass in @keychain
76
+ @result.puts "#{key} : #{pass}"
77
+ end
78
+ when :rekey
79
+ unlock()
80
+ new_key = @new_master_key || Console.ask_for_asterisks( "Enter the NEW master key for #{@file}? " )
81
+ @keychain.save( new_key )
82
+ when :help
83
+ print_help
84
+ end
85
+
86
+ return @result
87
+ rescue OpenSSL::Cipher::CipherError => e
88
+ @result.stderr( "Cipher error, probably bad master password given. message: #{e.message}")
89
+ @result.fail!
90
+ @result
91
+ end
92
+
93
+ protected
94
+
95
+ def parse_args()
96
+ if @args.length == 0
97
+ @action = :help
98
+ else
99
+ @action = ACTION_KEYWORDS_TO_ACTIONS[ (keyword = @args.shift) ]
100
+
101
+
102
+ case @action
103
+ when nil
104
+ @result.puts_error( "Unrecognized keyword #{keyword}" )
105
+ @result.fail!
106
+ print_help
107
+ when :add
108
+ @key, @pass = key_and_pass( @args.shift )
109
+ when :remove
110
+ @key = @args.shift
111
+ when :display
112
+ # do nothing
113
+ else
114
+ # do nothing
115
+ end
116
+
117
+ while @args.length > 0
118
+ arg = @args.shift
119
+
120
+ if m = /^--file=(.*)$/.match(arg)
121
+ @file = unquote( m[1] ).fwf_filepath.expand
122
+ elsif m = /--master=(.*)$/.match(arg)
123
+ @master_key = unquote( m[1] )
124
+ elsif m = /--new_master=(.*)$/.match(arg)
125
+ @new_master_key = unquote( m[1] )
126
+ elsif arg == "--quiet"
127
+ @verbose = false
128
+ @result.verbose( false )
129
+ else
130
+ @result.puts_error( "Unrecognized option #{arg}" )
131
+ @result.fail!
132
+ end
133
+ end
134
+
135
+ @file ||= FileStore.default_file
136
+ end
137
+ end
138
+
139
+
140
+ def unquote( str )
141
+ if str =~ /^(?<q>["']).*(?<q>)$/
142
+ str[1..-2]
143
+ else
144
+ str
145
+ end
146
+ end
147
+
148
+ def print_help
149
+ @result.puts "\tfwpass new (opts) (create new password file)"
150
+ @result.puts "\tfwpass add <KEY>=<PASS> (opts) (add a key to an existing password file)"
151
+ @result.puts "\tfwpass rm <KEY> (opts) (remove a key from a password file)"
152
+ @result.puts "\tfwpass show (opts) (show contents of password file)"
153
+ @result.puts "\tfwpass rekey (opts) (swap the master key for a new one. must know the current key)"
154
+ @result.puts "\tOptions:"
155
+ @result.puts "\t\t--file=<PASSWORD_FILE> (default file: #{FileStore.default_file})"
156
+ @result.puts "\t\t--master=<PASSWORD> ('master key' to unlock the password file)"
157
+ @result.puts "\t\t--new_master=<PASSWORD> (the new 'master key' that will unlock the file hereafter) (option ignored except by rekey command)"
158
+ end
159
+
160
+ def get_master_key
161
+ @master_key ||= @console.ask_for_master_key(@file)
162
+ end
163
+
164
+ def unlock
165
+ get_master_key()
166
+ @keychain = Keychain.new( :master_key => @master_key, :file => @file ).unlock
167
+ end
168
+
169
+ def key_and_pass( str )
170
+ # matches things like "key"='password' key="P4s5w0rd"
171
+ # regexp = /^(?<q1>["']?)(?<key>.*)(?<q1>)=(?<q2>["']?)(?<pass>.*)(?<q2>)$/
172
+ regexp = /^(?<key>(?<q1>["']?).*?(?<q1>))=(?<pass>(?<q2>["']?).*(?<q2>))$/ # wrong: reruns expression
173
+ regexp = /^(?<key>(?<q1>["']?).*?\k<q1>)=(?<pass>(?<q2>["']?).*\k<q2>)$/ # right: looks for the matched text
174
+ match_data = regexp.match( str )
175
+
176
+ if match_data
177
+ [unquote(match_data["key"]), unquote(match_data["pass"])]
178
+ else
179
+ [nil, nil]
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,56 @@
1
+ module FunWith
2
+ module Passwords
3
+ class CommandLineResult
4
+ attr_reader :success, :output, :errors, :args
5
+
6
+ def initialize( args )
7
+ @args = args
8
+ @errors = []
9
+ @output = ""
10
+ end
11
+
12
+ def stderr( msg )
13
+ STDERR.puts( msg ) if @verbose
14
+ @errors << msg
15
+ end
16
+
17
+ def stdout( msg )
18
+ STDOUT.write( msg ) if @verbose
19
+ @output << msg
20
+ end
21
+
22
+ def puts( msg )
23
+ stdout( msg + "\n" )
24
+ end
25
+
26
+ def puts_error( msg )
27
+ stderr( msg + "\n" )
28
+ end
29
+
30
+ def fail!
31
+ @success = false
32
+ end
33
+
34
+ def failed?
35
+ @success == false
36
+ end
37
+
38
+ def succeed!
39
+ @success = true
40
+ end
41
+
42
+ def success?
43
+ @success
44
+ end
45
+
46
+ def verbose( verbosity = nil )
47
+ @verbose = !!verbosity unless verbosity.nil?
48
+ @verbose
49
+ end
50
+
51
+ def verbose?
52
+
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,42 @@
1
+ module FunWith
2
+ module Passwords
3
+ class Console
4
+ attr_accessor :pre_inputs
5
+
6
+ def initialize
7
+ @pre_inputs = []
8
+ end
9
+
10
+ def ask_for_password( key = nil )
11
+ key ||= ask( "Enter the key associated with the password: ")
12
+ pass = ask_for_asterisks( "Enter the password for key #{key}: " )
13
+ [key, pass]
14
+ end
15
+
16
+ def ask_for_master_key( file )
17
+ ask_for_asterisks( "Enter the master key to unlock #{file}: " )
18
+ end
19
+
20
+ def ask_for_new_master_key( file )
21
+ ask_for_asterisks( "Enter the NEW master key for #{file}: " )
22
+ end
23
+
24
+ def confirm( q )
25
+ ask( "#{q} (Y/N)").upcase == "Y"
26
+ end
27
+
28
+ def ask_for_asterisks( msg )
29
+ ask( msg ){ |q| q.echo = "*" }
30
+ end
31
+
32
+ protected
33
+ def ask( *args, &block )
34
+ if @pre_inputs.fwf_blank?
35
+ HighLine.ask( *args, &block )
36
+ else
37
+ @pre_inputs.shift
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,38 @@
1
+ module FunWith
2
+ module Passwords
3
+ class Crypt
4
+ IV = "b2f7zaf14kagpd3j76ulddjxytvhjnzvna7diacozrui7afx4d7kj0cxj4ch1of1z7in376vah4kwkarwls0vbtosraovy7d4ci"
5
+
6
+ def self.decrypt( encrypted_message, key )
7
+ cipher = OpenSSL::Cipher::AES256.new( :CBC )
8
+ cipher.decrypt
9
+ cipher.key = self.stretch_key(key)
10
+ cipher.iv = IV
11
+
12
+ msg = cipher.update( encrypted_message )
13
+ msg << cipher.final
14
+ msg
15
+ end
16
+
17
+ def self.encrypt( plaintext, key )
18
+ cipher = OpenSSL::Cipher::AES256.new( :CBC )
19
+ cipher.encrypt
20
+ cipher.key = self.stretch_key(key)
21
+ cipher.iv = IV
22
+
23
+ encrypted_message = cipher.update( plaintext )
24
+ encrypted_message << cipher.final
25
+ encrypted_message
26
+ end
27
+
28
+
29
+ protected
30
+ # Only advantage of doing this is lengthening short, insecure master passwords to
31
+ # randomish-looking key of length needed by the crypto cipher. Short passwords?
32
+ # Still insecure. Film at 11.
33
+ def self.stretch_key( key )
34
+ (Digest::MD5.hexdigest(key) + Digest::MD5.hexdigest(key.reverse) ).to_i(16).to_s(36)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,78 @@
1
+ module FunWith
2
+ module Passwords
3
+
4
+ # The FileStore knows only where to save itself, and how to encrypt
5
+ # and decrypt the file. The contents of a file are a mystery for
6
+ # Keychain to deal with
7
+ class FileStore
8
+ DEFAULT_DIR = XDG['CONFIG'].fwf_filepath.join("fun_with_passwords")
9
+ DEFAULT_EXT = "aes256.dat"
10
+ DEFAULT_FILENAME = "password_store"
11
+
12
+ attr_accessor :password_file, :key_chain
13
+
14
+ def self.default_file
15
+ DEFAULT_DIR.join( "#{DEFAULT_FILENAME}.#{DEFAULT_EXT}" )
16
+ end
17
+
18
+ # if the key is nil, waits until a password is requested or added to decrypt password file
19
+ def initialize( filename = nil )
20
+ @password_file = expand_filepath( filename )
21
+ @password_file.touch
22
+ end
23
+
24
+ # sends back a Keychain that knows where its store is
25
+ def unlock( master_key )
26
+ if @password_file.file? && !@password_file.empty?
27
+ YAML.load( Crypt.decrypt( @password_file.read, master_key ) )
28
+ else
29
+ {}
30
+ end
31
+ end
32
+ #
33
+ # def initialize_key_chain_if_needed( keychain_keys, master_key )
34
+ # unless @key_chain
35
+ # @key_chain = Keychain.new( :keys => keychain_keys, :master_key => master_key )
36
+ # @key_chain.file_store = self
37
+ # end
38
+ # end
39
+
40
+ def save( yaml, master_key )
41
+ encrypted_message = Crypt.encrypt( yaml, master_key )
42
+ @password_file.write( encrypted_message )
43
+ true
44
+ rescue Exception => e
45
+ false
46
+ end
47
+
48
+ protected
49
+ # Just the filename? Send it to the default directory.
50
+ # nil? Use the default filename and default directory.
51
+ # Only a directory given? append default filename
52
+ # It's an existing file? Overwrite, I suppose.
53
+ # doesn't exist? Assume filename if a dot is included
54
+ def expand_filepath( filename = nil )
55
+ if filename.nil?
56
+ return DEFAULT_DIR.join( "#{DEFAULT_FILENAME}.#{DEFAULT_EXT}" )
57
+ end
58
+
59
+ file = filename.fwf_filepath
60
+
61
+ if file.directory?
62
+ return file.join( "#{DEFAULT_FILENAME}.#{DEFAULT_EXT}" )
63
+ elsif file.file?
64
+ return file
65
+ end
66
+
67
+ file = file.expand
68
+
69
+ if file.directory?
70
+ return file.join( "#{DEFAULT_FILENAME}.#{DEFAULT_EXT}" )
71
+ else
72
+ file.touch
73
+ return file
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,105 @@
1
+ module FunWith
2
+ module Passwords
3
+ class Keychain
4
+ NAMESPACE_CHAR = ":"
5
+
6
+ attr_accessor :file_store
7
+
8
+ # Load an existing
9
+ def self.load( master_key, opts = {} )
10
+ file = opts[:file] || FileStore.default_file
11
+ master_key = Console.new.ask_for_master_key if master_key == :new
12
+ self.new( :file => file, :master_key => master_key ).unlock
13
+ end
14
+
15
+ def initialize( opts = {} )
16
+ @options = opts
17
+ set_keys
18
+ set_master_key
19
+ set_file_store
20
+ end
21
+
22
+ # if no new master key is given, save with the old one.
23
+ def save( master_key = nil)
24
+ @master_key = master_key if master_key
25
+ if @file_store
26
+ @file_store.save( @keys.to_yaml, @master_key )
27
+ else
28
+ false
29
+ end
30
+ end
31
+
32
+ def unlock( master_key = nil )
33
+ @master_key = master_key if master_key
34
+
35
+ if @file_store
36
+ set_keys( @file_store.unlock( @master_key ) )
37
+ end
38
+
39
+ self
40
+ end
41
+
42
+ def set_options
43
+ if @opts[:interactive]
44
+ @ask_on_fail = true
45
+ end
46
+ end
47
+
48
+ # After @keys is set, subsequent set_keys(nil) calls have no effect.
49
+ def set_keys( hash = nil )
50
+ if hash
51
+ @keys = hash
52
+ else
53
+ @keys ||= @options[:keys] || {}
54
+ @options.delete(:keys)
55
+ end
56
+ end
57
+
58
+ def set_master_key( key = nil )
59
+ if key
60
+ @master_key = key
61
+ else
62
+ @master_key ||= @options.delete(:master_key)
63
+ @master_key ||= Console.new.ask_for_master_key if @ask_on_fail
64
+ end
65
+ end
66
+
67
+ def set_file_store
68
+ @file_store = FileStore.new( @options[:file] ) if @options[:file]
69
+ end
70
+
71
+ def []=( key, password )
72
+ @keys[key] = password
73
+ end
74
+
75
+ def []( key )
76
+ password = @keys[key]
77
+
78
+ if !password && @ask_on_fail
79
+ password = Console.new.ask_for_password( key )
80
+ self[key] = password
81
+ end
82
+
83
+ password
84
+ end
85
+
86
+ def each( &block )
87
+ if block_given?
88
+ @keys.each(&block)
89
+ else
90
+ @keys.each
91
+ end
92
+ end
93
+
94
+ def delete( key )
95
+ @keys.delete( key )
96
+ end
97
+
98
+ def printout
99
+ self.each do |key, password|
100
+ puts "#{key}: #{password}"
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,14 @@
1
+ # It seems this isn't used.
2
+ #
3
+ # module FunWith
4
+ # module Files
5
+ # class Password
6
+ # attr_accessor :label, :password
7
+ #
8
+ # def initialize( label, password )
9
+ # @label = label
10
+ # @password = password
11
+ # end
12
+ # end
13
+ # end
14
+ # end
@@ -0,0 +1,19 @@
1
+ require 'fun_with_files'
2
+ require 'fun_with_configurations'
3
+ require 'fun_with_version_strings'
4
+ require 'highline/import'
5
+ require 'xdg'
6
+ require 'openssl'
7
+ require 'yaml'
8
+
9
+ module FunWith
10
+ module Passwords
11
+ end
12
+ end
13
+
14
+ FunWith::Files::RootPath.rootify( FunWith::Passwords, __FILE__.fwf_filepath.dirname.up )
15
+ FunWith::VersionStrings.versionize( FunWith::Passwords )
16
+
17
+ FunWith::Passwords.root( "lib","fun_with" ).requir
18
+
19
+
@@ -0,0 +1,9 @@
1
+ k=v|k|v
2
+ key=value|key|value
3
+ 'key'='value'|key|value
4
+ key='value'|key|value
5
+ key="value"|key|value
6
+ "key"=value|key|value
7
+ "key"='value'|key|value
8
+ "site=flow.xmission.com"='xjJ3~=""'|site=flow.xmission.com|xjJ3~=""
9
+ godzilla:v:tokyo="&&^32='*h<>!="|godzilla:v:tokyo|&&^32='*h<>!=
data/test/helper.rb ADDED
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ require 'debugger'
4
+ begin
5
+ Bundler.setup(:default, :development)
6
+ rescue Bundler::BundlerError => e
7
+ $stderr.puts e.message
8
+ $stderr.puts "Run `bundle install` to install missing gems"
9
+ exit e.status_code
10
+ end
11
+ require 'test/unit'
12
+ require 'shoulda'
13
+
14
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
15
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
16
+
17
+ require 'fun_with_passwords'
18
+
19
+ class Test::Unit::TestCase
20
+ end
21
+
22
+ class FunWith::Passwords::TestCase < Test::Unit::TestCase
23
+ include FunWith::Passwords
24
+ def tmpdir( &block )
25
+ FunWith::Files::FilePath.tmpdir do |tmp|
26
+ @tmpdir = tmp
27
+ warn( "temporary dir not writable. Some tests may fail." ) unless @tmpdir.directory? && @tmpdir.writable?
28
+ yield
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,76 @@
1
+ require 'helper'
2
+
3
+ class TestCommandLine < FunWith::Passwords::TestCase
4
+ should "test basics" do
5
+ tmpdir do
6
+ file = @tmpdir.join( 'passwords.aes256.dat' )
7
+ file_arg = "--file=#{file}"
8
+ master_arg = "--master=pass1"
9
+ master_arg2 = "--master=pass2"
10
+ new_master_arg = "--new_master=pass2"
11
+ bad_password = "--master=wrong"
12
+ refute file.exist?
13
+ # how to simulate user entering password...
14
+ run_console( ["new", file_arg, master_arg, "--quiet"] )
15
+ assert file.exist?
16
+
17
+ run_console( ["add", "site1:mysql=pass1", file_arg, master_arg, "--quiet"] )
18
+ run_console( ["add", "site2:mysql=pass2", file_arg, master_arg, "--quiet"] )
19
+
20
+ run_console( ["show", file_arg, master_arg, "--quiet"] ) do |result|
21
+ assert_equal "site1:mysql : pass1\nsite2:mysql : pass2", result.output.strip
22
+ end
23
+
24
+ run_console( ["rekey", file_arg, master_arg, new_master_arg])
25
+
26
+ run_console( ["rm", "site1:mysql", file_arg, master_arg2, "--quiet"] )
27
+ run_console( ["rm", "site2:mysql", file_arg, master_arg2, "--quiet"] )
28
+
29
+ run_console( ["rm", "site2:mysql", file_arg, bad_password, "--quiet"] ) do |result|
30
+ assert_match /Cipher error/, result.errors.first
31
+ refute result.success?
32
+ end
33
+
34
+
35
+ # puts "\tfwpass new <OPTIONAL_FILENAME> (create new password file)"
36
+ # puts "\tfwpass add key=password <OPTIONAL_FILENAME> (add a key to an existing password file)"
37
+ # puts "\tfwpass rm key <OPTIONAL_FILENAME> (remove a key from a password file)"
38
+ # puts "\tfwpass show <OPTIONAL_FILENAME> (show contents of password file)"
39
+ # puts "\tfwpass rekey <OPTIONAL_FILENAME> (swap the master key for a new one. must know the current key)"
40
+ # puts "\t(default file: #{FileStore.default_file})"
41
+ end
42
+ end
43
+
44
+ should "properly parse key=value pairs" do
45
+ successes = FunWith::Passwords.root("test", "data", "kvpair_successes.txt").read.split("\n")
46
+
47
+ cmd = CommandLine.new(["help"])
48
+
49
+ for line in successes
50
+ str, expected_key, expected_pass = line.split("|")
51
+
52
+ actual_key, actual_pass = cmd.send( :key_and_pass, str )
53
+
54
+ refute actual_key.nil?
55
+ refute actual_pass.nil?
56
+ refute actual_key.fwf_blank?
57
+ refute actual_pass.fwf_blank?
58
+
59
+ assert_equal( expected_key, actual_key )
60
+ assert_equal( expected_pass, actual_pass )
61
+
62
+ end
63
+ end
64
+
65
+
66
+
67
+ def run_console( args, &block )
68
+ result = CommandLine.new( args ).run
69
+
70
+ if block_given?
71
+ yield result
72
+ else
73
+ result
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,13 @@
1
+ require 'helper'
2
+
3
+ class TestCrypt < FunWith::Passwords::TestCase
4
+ should "encrypt and decrypt using Crypt functions" do
5
+ message = "secret message"
6
+ key = "secret key"
7
+
8
+ encrypted_message = Crypt.encrypt( message, key )
9
+ assert_not_equal( message, encrypted_message )
10
+ decrypted_message = Crypt.decrypt( encrypted_message, key )
11
+ assert_equal( message, decrypted_message )
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ require 'helper'
2
+
3
+ class TestFunWithPasswords < FunWith::Passwords::TestCase
4
+ should "test basics" do
5
+ assert defined?( FunWith::Passwords ) # utterly useless test
6
+ assert defined?( FunWith::Passwords::Console )
7
+ assert defined?( FunWith::Passwords::Crypt )
8
+ end
9
+
10
+ should "test fun_with_files integrating properly" do
11
+ assert defined?(FunWith::Files)
12
+ assert defined?(FunWith::Files::FilePath)
13
+ assert [].fwf_blank?
14
+ end
15
+ end
@@ -0,0 +1,56 @@
1
+ require 'helper'
2
+
3
+ class TestKeychain < FunWith::Passwords::TestCase
4
+ KEYHASH = { "hey:sailor" => "password",
5
+ "orangutan" => "marigold",
6
+ "hitch:hiker:steve" => "hoboname"
7
+ }
8
+
9
+ def setup
10
+ @keychain = Keychain.new( :keys => KEYHASH.clone, :master_key => "master_key" )
11
+ end
12
+
13
+ should "get the correct keys" do
14
+ assert_equal( "password", @keychain["hey:sailor"] )
15
+ assert_equal( "marigold", @keychain["orangutan"] )
16
+ end
17
+
18
+ should "update a key" do
19
+ @keychain["orangutan"] = "password_orangutan"
20
+ @keychain["hey:sailor"] = "password_sailor"
21
+
22
+ assert_equal( "password_orangutan", @keychain["orangutan"] )
23
+ assert_equal( "password_sailor", @keychain["hey:sailor"] )
24
+ assert_equal( @keychain["hitch:hiker:steve"], "hoboname")
25
+ end
26
+
27
+ should "test each(){}" do
28
+ expected = [["hey:sailor", "password"], ["orangutan", "marigold"], ["hitch:hiker:steve", "hoboname"]]
29
+
30
+ i = 0
31
+ @keychain.each do |item|
32
+ assert_equal( expected[i], item )
33
+ i += 1
34
+ end
35
+ end
36
+
37
+ should "write passwords to a file and read them back" do
38
+ tmpdir do
39
+ assert @keychain.file_store.nil?
40
+ pwfile = @tmpdir.join("password_store.aes256.dat")
41
+ assert !pwfile.exist?
42
+
43
+ @keychain.file_store = FileStore.new(@tmpdir)
44
+ assert_equal( pwfile, @keychain.file_store.password_file )
45
+
46
+ @keychain.save
47
+ assert @keychain.file_store.password_file.exist?
48
+
49
+ @keychain = Keychain.load( "master_key", :file => pwfile )
50
+ @keychain.unlock
51
+
52
+ assert_equal( "password", @keychain["hey:sailor"] )
53
+ assert_equal( "marigold", @keychain["orangutan"] )
54
+ end
55
+ end
56
+ end
metadata ADDED
@@ -0,0 +1,207 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fun_with_passwords
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Bryce Anderson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fun_with_files
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: fun_with_configurations
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: fun_with_version_strings
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: highline
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.6'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: xdg
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '2.2'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '2.2'
83
+ - !ruby/object:Gem::Dependency
84
+ name: shoulda
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '3.5'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '3.5'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rdoc
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: '3.12'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: '3.12'
111
+ - !ruby/object:Gem::Dependency
112
+ name: bundler
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: '1.5'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ~>
123
+ - !ruby/object:Gem::Version
124
+ version: '1.5'
125
+ - !ruby/object:Gem::Dependency
126
+ name: jeweler
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ~>
130
+ - !ruby/object:Gem::Version
131
+ version: '2.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ~>
137
+ - !ruby/object:Gem::Version
138
+ version: '2.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: debugger
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ~>
144
+ - !ruby/object:Gem::Version
145
+ version: '1.6'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ~>
151
+ - !ruby/object:Gem::Version
152
+ version: '1.6'
153
+ description: Because passwords are obnoxious.
154
+ email: keeputahweird@gmail.com
155
+ executables:
156
+ - fwpass
157
+ extensions: []
158
+ extra_rdoc_files:
159
+ - LICENSE.txt
160
+ - README.rdoc
161
+ files:
162
+ - .document
163
+ - Gemfile
164
+ - LICENSE.txt
165
+ - README.rdoc
166
+ - Rakefile
167
+ - VERSION
168
+ - bin/fwpass
169
+ - lib/fun_with/passwords/command_line.rb
170
+ - lib/fun_with/passwords/command_line_result.rb
171
+ - lib/fun_with/passwords/console.rb
172
+ - lib/fun_with/passwords/crypt.rb
173
+ - lib/fun_with/passwords/file_store.rb
174
+ - lib/fun_with/passwords/keychain.rb
175
+ - lib/fun_with/passwords/password.rb
176
+ - lib/fun_with_passwords.rb
177
+ - test/data/kvpair_successes.txt
178
+ - test/helper.rb
179
+ - test/test_command_line.rb
180
+ - test/test_crypt.rb
181
+ - test/test_fun_with_passwords.rb
182
+ - test/test_keychain.rb
183
+ homepage: http://github.com/darthschmoo/fun_with_passwords
184
+ licenses:
185
+ - MIT
186
+ metadata: {}
187
+ post_install_message:
188
+ rdoc_options: []
189
+ require_paths:
190
+ - lib
191
+ required_ruby_version: !ruby/object:Gem::Requirement
192
+ requirements:
193
+ - - ! '>='
194
+ - !ruby/object:Gem::Version
195
+ version: '0'
196
+ required_rubygems_version: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ! '>='
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
201
+ requirements: []
202
+ rubyforge_project:
203
+ rubygems_version: 2.2.2
204
+ signing_key:
205
+ specification_version: 4
206
+ summary: Create an encrypted password store that your scripts can access.
207
+ test_files: []