yak 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (7) hide show
  1. data/History.txt +7 -0
  2. data/Manifest.txt +6 -0
  3. data/README.txt +65 -0
  4. data/Rakefile +16 -0
  5. data/bin/yak +5 -0
  6. data/lib/yak.rb +384 -0
  7. metadata +96 -0
data/History.txt ADDED
@@ -0,0 +1,7 @@
1
+ === 1.0.1 / 2010-02-22
2
+
3
+ * Renaming to Yak
4
+
5
+ === 1.0.0 / 2010-02-22
6
+
7
+ * First release!
data/Manifest.txt ADDED
@@ -0,0 +1,6 @@
1
+ bin/yak
2
+ lib/yak.rb
3
+ History.txt
4
+ Manifest.txt
5
+ Rakefile
6
+ README.txt
data/README.txt ADDED
@@ -0,0 +1,65 @@
1
+ = Yak
2
+
3
+ == Description
4
+
5
+ Yak is a simple command line app to store and retrieve passwords securely
6
+ under a master password, and allows one password repository per system user.
7
+ Retrieved passwords get copied to the clipboard by default.
8
+
9
+
10
+ == Configuration
11
+
12
+ Config can be set in ~/.yakrc.
13
+
14
+ Session is the length of time in seconds that Yak will remember the
15
+ master password:
16
+ :session: 30
17
+
18
+ If using sessions is not desired and you want to enter the master, set:
19
+ :session: false
20
+
21
+ Always set the password by default, use:
22
+ :password: plain_text_password
23
+
24
+ Turn off password confirmation prompts when a new password is entered:
25
+ :confirm_prompt: false
26
+
27
+
28
+ == Usage
29
+
30
+ Yak will always prompt you for the master password unless a yak session is
31
+ present, or the :password option is set in ~/.yakrc.
32
+ Yak sessions get refreshed everytime yak is called.
33
+
34
+ Adding a new password:
35
+ $ yak -a gmail
36
+ # prompts user for gmail password to save
37
+
38
+ $ yak -a gmail my_password
39
+ # uses my_password as gmail password and overwrites old value
40
+
41
+ Retrieving a saved password:
42
+ $ yak gmail
43
+ # copies the gmail password to the clipboard
44
+
45
+ $ yak --list gmail
46
+ >> gmail: my_password
47
+ # matches all password keys to /gmail/ and outputs to stdout
48
+
49
+ Removing a stored password:
50
+ $ yak -r gmail
51
+ # deletes gmail entry completely
52
+
53
+ Changing the master password:
54
+ $ yak -n
55
+ # prompts for old password first, then the new password
56
+
57
+ Listing key/password pairs:
58
+ $ yak --list
59
+ # returns all saved pairs
60
+
61
+ $ yak --list key
62
+ # returns all saved pairs with a key matching /key/
63
+
64
+ $ yak --list ^key$
65
+ # returns unique saved pair with a key matching /^key$/
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # -*- ruby -*-
2
+ require 'rubygems'
3
+ require 'hoe'
4
+ require 'rake'
5
+ require 'rake/testtask'
6
+
7
+
8
+ Hoe.plugin :isolate
9
+
10
+ Hoe.spec 'yak' do |p|
11
+ developer('Jeremie Castagna', 'yaksnrainbows@gmail.com')
12
+ self.extra_deps << ['highline', '>= 1.5.1']
13
+ self.extra_deps << ['session', '>= 2.4.0']
14
+ end
15
+
16
+ # vim: syntax=Ruby
data/bin/yak ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ require 'yak'
4
+
5
+ Yak.run
data/lib/yak.rb ADDED
@@ -0,0 +1,384 @@
1
+ require 'fileutils'
2
+ require 'openssl'
3
+ require 'digest/sha1'
4
+ require 'optparse'
5
+
6
+ require 'rubygems'
7
+ require 'highline'
8
+ require 'session'
9
+
10
+
11
+ ##
12
+ # Yak is a simple command line app to store and retrieve passwords securely.
13
+ # Retrieved passwords get copied to the clipboard by default.
14
+ # Config can be set in ~/.yakrc:
15
+ # :session: 30
16
+ # Session is the length of time in seconds that Yak will remember the
17
+ # master password. If using sessions is not desired, set:
18
+ # :session: false
19
+ # To always set the password by default, use:
20
+ # :password: plain_text_password
21
+ # To turn off password confirmation prompts:
22
+ # :confirm_prompt: false
23
+
24
+ class Yak
25
+
26
+ VERSION = "1.0.2"
27
+
28
+ DEFAULT_CONFIG = {:session => 30}
29
+
30
+ ##
31
+ # Run Yak with argv:
32
+ # Yak.run %w{key}
33
+ # Yak.run %w{--add key}
34
+ # ...
35
+
36
+ def self.run argv=ARGV
37
+ config = DEFAULT_CONFIG.merge load_config
38
+
39
+ options = parse_args argv
40
+
41
+ yak = new `whoami`.chomp, config
42
+
43
+ args = [options[:action], yak, options[:key], options[:value]].compact
44
+
45
+ self.send(*args)
46
+
47
+ rescue OpenSSL::CipherError => e
48
+ $stderr << "Bad password.\n"
49
+ exit 1
50
+ end
51
+
52
+
53
+ ##
54
+ # Load the ~/.yakrc file and return. Creates ~/.yakrc with the
55
+ # default config if missing.
56
+
57
+ def self.load_config
58
+ config_file = File.expand_path "~/.yakrc"
59
+
60
+ if !File.file?(config_file)
61
+ File.open(config_file, "w+"){|f| f.write DEFAULT_CONFIG.to_yaml }
62
+ $stderr << "Created Yak config file #{config_file}\n"
63
+ end
64
+
65
+ YAML.load_file config_file
66
+ end
67
+
68
+
69
+ def self.remove yak, name
70
+ yak.remove name
71
+ yak.write_data
72
+ end
73
+
74
+
75
+ def self.store yak, name, value=nil
76
+ yak.store name, value
77
+ yak.write_data
78
+ end
79
+
80
+
81
+ def self.retrieve yak, name
82
+ send_to_clipboard yak.retrieve(name)
83
+ end
84
+
85
+
86
+ def self.list yak, name=nil
87
+ key_regex = /#{name || ".+"}/
88
+
89
+ yak.data.each do |key, value|
90
+ $stdout << "#{key}: #{value}\n" if key =~ key_regex
91
+ end
92
+ end
93
+
94
+
95
+ def self.new_password yak, value=nil
96
+ yak.new_password value
97
+ yak.write_data
98
+ yak.start_session
99
+ end
100
+
101
+
102
+ def self.send_to_clipboard string
103
+ copy_cmd = case RUBY_PLATFORM
104
+ when /darwin/
105
+ "echo -n \"#{string}\" | pbcopy"
106
+ when /linux/
107
+ "echo -n \"#{string}\" | xclip"
108
+ when /cigwin/
109
+ "echo -n \"#{string}\" | putclip"
110
+ when /(win|mingw)/
111
+ "echo \"#{string}\" | clip"
112
+ else
113
+ $stderr << "No clipboad cmd for platform #{RUBY_PLATFORM}\n"
114
+ exit 1
115
+ end
116
+
117
+ Session::Bash.new.execute copy_cmd
118
+ end
119
+
120
+
121
+ def self.parse_args argv
122
+ options = {}
123
+
124
+ opts = OptionParser.new do |opt|
125
+ opt.program_name = File.basename $0
126
+ opt.version = VERSION
127
+ opt.release = nil
128
+
129
+ opt.banner = <<-EOF
130
+ #{opt.program_name} is a simple app to store and retrieve passwords securely.
131
+ Retrieved passwords get copied to the clipboard by default.
132
+
133
+ Usage:
134
+ #{opt.program_name} [options] [key] [password]
135
+
136
+ Examples:
137
+ #{opt.program_name} -a gmail [password]
138
+ #{opt.program_name} gmail
139
+ #{opt.program_name} -r gmail
140
+ #{opt.program_name} --list
141
+
142
+ Options:
143
+ EOF
144
+
145
+ opt.on('-a', '--add KEY',
146
+ 'Add a new password for a given key') do |key|
147
+ options[:action] = :store
148
+ options[:key] = key
149
+ end
150
+
151
+ opt.on('-r', '--remove KEY',
152
+ 'Remove the password for a given key') do |key|
153
+ options[:action] = :remove
154
+ options[:key] = key
155
+ end
156
+
157
+ opt.on('-l', '--list [REGEX]',
158
+ 'List key/password pairs to the stdout') do |key|
159
+ options[:action] = :list
160
+ options[:key] = key
161
+ end
162
+
163
+ opt.on('-n', '--new-password',
164
+ 'Update the password used for encryption') do |value|
165
+ options[:action] = :new_password
166
+ end
167
+ end
168
+
169
+ opts.parse! argv
170
+
171
+ options[:action] ||= :retrieve
172
+ options[:key] ||= argv.shift
173
+ options[:value] ||= argv.shift
174
+
175
+ options
176
+ end
177
+
178
+
179
+ attr_reader :user, :data
180
+
181
+ ##
182
+ # Create a new Yak instance for a given user:
183
+ # Yak.new "my_user"
184
+ # Yak.new "my_user", :session => 10
185
+ # Yak.new `whoami`.chomp, :session => false
186
+
187
+ def initialize user, options={}
188
+ @user = user
189
+ @input = HighLine.new $stdin, $stderr
190
+
191
+ @confirm_prompt = true
192
+ @confirm_prompt = options[:confirm_prompt] if
193
+ options.has_key? :confirm_prompt
194
+
195
+ @yak_dir = File.expand_path "~#{@user}/.yak"
196
+ FileUtils.mkdir @yak_dir unless File.directory? @yak_dir
197
+
198
+ @pid_file = File.join @yak_dir, "pid"
199
+ @password_file = File.join @yak_dir, "password"
200
+ @data_file = File.join @yak_dir, "data"
201
+
202
+ @session_pid = nil
203
+ @session_pid = File.read(@pid_file).to_i if File.file? @pid_file
204
+
205
+ @password = get_password options[:password]
206
+
207
+ @cipher = OpenSSL::Cipher::Cipher.new "aes-256-cbc"
208
+
209
+ @session_length = options.has_key?(:session) ? options[:session] : 30
210
+
211
+ connect_data
212
+ start_session
213
+ end
214
+
215
+
216
+ ##
217
+ # Start a new session during which Yak will remember the user's password.
218
+
219
+ def start_session
220
+ return unless @session_length
221
+
222
+ end_session if has_session?
223
+
224
+ pid = fork do
225
+ sleep @session_length
226
+ FileUtils.rm_f [@password_file, @pid_file]
227
+ end
228
+
229
+ File.open(@pid_file, "w+"){|f| f.write pid }
230
+ File.open(@password_file, "w+"){|f| f.write @password }
231
+
232
+ Process.detach pid
233
+ end
234
+
235
+
236
+ ##
237
+ # Stop a session.
238
+
239
+ def end_session
240
+ return unless @session_pid
241
+ Process.kill 9, @session_pid rescue false
242
+ FileUtils.rm_f [@password_file, @pid_file]
243
+ end
244
+
245
+
246
+ ##
247
+ # Check if a session is active.
248
+
249
+ def has_session?
250
+ Process.kill(0, @session_pid) && @session_pid rescue false
251
+ end
252
+
253
+
254
+ ##
255
+ # Get a password from either the password file or by prompting the
256
+ # user if a password file is unavailable. Returns a sha1 of the password
257
+ # passed as an arg.
258
+
259
+ def get_password plain_password=nil
260
+ password = File.read @password_file if File.file? @password_file
261
+
262
+ password ||=
263
+ Digest::SHA1.hexdigest(plain_password || request_password("Yak Password"))
264
+
265
+ password
266
+ end
267
+
268
+
269
+ ##
270
+ # Prompt the user for a new password (replacing and old one).
271
+ # Prompts for password confirmation as well.
272
+
273
+ def new_password password=nil
274
+ password ||= request_new_password "New Password"
275
+ @password = Digest::SHA1.hexdigest password if password
276
+ end
277
+
278
+
279
+ ##
280
+ # Loads and decrypts the data file into the @data attribute.
281
+
282
+ def connect_data
283
+ @data = if File.file? @data_file
284
+ data = ""
285
+ File.open(@data_file, "rb"){|f| data << f.read }
286
+ YAML.load decrypt(data)
287
+ else
288
+ {}
289
+ end
290
+ end
291
+
292
+
293
+ ##
294
+ # Remove a key/value pair.
295
+
296
+ def remove name
297
+ @data.delete(name)
298
+ end
299
+
300
+
301
+ ##
302
+ # Retrieve a value for a given key.
303
+
304
+ def retrieve name
305
+ @data[name]
306
+ end
307
+
308
+
309
+ ##
310
+ # Add a key/value pair. If no value is passed, will prompt the user for one.
311
+
312
+ def store name, value=nil
313
+ value ||= request_new_password "'#{name}' Password"
314
+ @data[name] = value
315
+ end
316
+
317
+
318
+ ##
319
+ # Decrypt a string with a given password.
320
+
321
+ def decrypt string, password=@password
322
+ @cipher.decrypt
323
+ @cipher.key = password
324
+ get_cypher_out string
325
+ end
326
+
327
+
328
+ ##
329
+ # Encrypt a string with a given password.
330
+
331
+ def encrypt string, password=@password
332
+ @cipher.encrypt
333
+ @cipher.key = password
334
+ get_cypher_out string
335
+ end
336
+
337
+
338
+ ##
339
+ # Encrypt and write the Yak data back to the data file.
340
+
341
+ def write_data password=@password
342
+ data = encrypt @data.to_yaml, password
343
+ File.open(@data_file, "w+"){|f| f.write data}
344
+ end
345
+
346
+
347
+ private
348
+
349
+
350
+ ##
351
+ # Prompts for a new password (password and confirmation).
352
+ # Doesn't prompt for confirmation if @confirm_prompt is false.
353
+
354
+ def request_new_password req_str="Password"
355
+ password = request_password "#{req_str}"
356
+
357
+ password_confirm = if @confirm_prompt
358
+ request_password "#{req_str} (confirm)"
359
+ else
360
+ password
361
+ end
362
+
363
+ if password != password_confirm
364
+ $stderr << "Password and password confirmation did not match.\n"
365
+ else
366
+ password.chomp
367
+ end
368
+ end
369
+
370
+
371
+ ##
372
+ # Prompt the user for a password.
373
+
374
+ def request_password req_str="Password"
375
+ @input.ask("#{req_str}:"){|q| q.echo = false}
376
+ end
377
+
378
+
379
+ def get_cypher_out string
380
+ out = @cipher.update string
381
+ out << @cipher.final
382
+ out
383
+ end
384
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yak
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Jeremie Castagna
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-22 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: highline
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.5.1
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: session
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.4.0
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: hoe
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 2.3.3
44
+ version:
45
+ description: |-
46
+ Yak is a simple command line app to store and retrieve passwords securely
47
+ under a master password, and allows one password repository per system user.
48
+ Retrieved passwords get copied to the clipboard by default.
49
+ email:
50
+ - yaksnrainbows@gmail.com
51
+ executables:
52
+ - yak
53
+ extensions: []
54
+
55
+ extra_rdoc_files:
56
+ - History.txt
57
+ - Manifest.txt
58
+ - README.txt
59
+ files:
60
+ - bin/yak
61
+ - lib/yak.rb
62
+ - History.txt
63
+ - Manifest.txt
64
+ - Rakefile
65
+ - README.txt
66
+ has_rdoc: true
67
+ homepage:
68
+ licenses: []
69
+
70
+ post_install_message:
71
+ rdoc_options:
72
+ - --main
73
+ - README.txt
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: "0"
81
+ version:
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: "0"
87
+ version:
88
+ requirements: []
89
+
90
+ rubyforge_project: yak
91
+ rubygems_version: 1.3.5
92
+ signing_key:
93
+ specification_version: 3
94
+ summary: Yak is a simple command line app to store and retrieve passwords securely under a master password, and allows one password repository per system user
95
+ test_files: []
96
+