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.
- data/History.txt +7 -0
- data/Manifest.txt +6 -0
- data/README.txt +65 -0
- data/Rakefile +16 -0
- data/bin/yak +5 -0
- data/lib/yak.rb +384 -0
- metadata +96 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
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/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
|
+
|