Class: Yak
- Inherits:
-
Object
- Object
- Yak
- Defined in:
- lib/yak.rb
Overview
Yak is a simple command line app to store and retrieve passwords securely. Retrieved passwords get copied to the clipboard by default. Config can be set in ~/.yakrc:
:session: 30
Session is the length of time in seconds that Yak will remember the master password. If using sessions is not desired, set:
:session: false
To always set the password by default, use:
:password: plain_text_password
To turn off password confirmation prompts:
:confirm_prompt: false
To set the path to the yak data file:
:data_file: /path/to/file
Using bash completion for stored keys:
:bash_completion: true #=> completion only available during session
:bash_completion: :always #=> completion always available
Constant Summary collapse
- VERSION =
Version of Yak.
"1.0.8"
- DEFAULT_CONFIG =
Default config used.
{:session => 30, :bash_completion => true}
- CIPHER_ERROR =
Different versions of ruby have a different namespace for CipherError
OpenSSL::Cipher::CipherError rescue OpenSSL::CipherError
Instance Attribute Summary collapse
-
#data ⇒ Object
readonly
Returns the value of attribute data.
-
#use_completion ⇒ Object
readonly
Returns the value of attribute use_completion.
-
#user ⇒ Object
readonly
Returns the value of attribute user.
Class Method Summary collapse
-
.check_user_setup(user) ⇒ Object
Setup yak for first run if it hasn’t been.
-
.delete_data(yak) ⇒ Object
Delete the data file of a yak instance after confirming with the user.
-
.list(yak, name = nil) ⇒ Object
List matched keys of a yak instance.
-
.load_config(user) ⇒ Object
Load the ~/.yakrc file and return.
-
.make_config_file(user, new_config = DEFAULT_CONFIG) ⇒ Object
Create a new user config file.
-
.new_password(yak, value = nil) ⇒ Object
Assign a new master password to a yak instance.
-
.parse_args(argv) ⇒ Object
Parse ARGV data.
-
.print_password(yak, name) ⇒ Object
Get a password value from a yak instance and output it to the stdout.
-
.prompt_data_loc(user, hl) ⇒ Object
Prompt the user for the location of the data file.
-
.remove(yak, name) ⇒ Object
Remove a key/value pair from a yak instance.
-
.retrieve(yak, name) ⇒ Object
Get a password value from a yak instance and copy it to the clipboard.
-
.run(argv = ARGV) ⇒ Object
Run Yak with argv: Yak.run %wkey Yak.run %wkey …
-
.send_to_clipboard(string) ⇒ Object
Send the passed string to the keyboard.
-
.setup_bash_completion ⇒ Object
Check and setup bash completion.
-
.store(yak, name, value = nil) ⇒ Object
Add a key/value pair to a yak instance.
-
.yak_config_file(user) ⇒ Object
Get a user’s yak config file.
Instance Method Summary collapse
-
#connect_data ⇒ Object
Loads and decrypts the data file into the @data attribute.
-
#data_file_exists? ⇒ Boolean
Check if the data file exists.
-
#decrypt(string, password = nil) ⇒ Object
Decrypt a string with a given password.
-
#delete_data_file!(confirm = false) ⇒ Object
Deletes the user’s data file forever!.
-
#encrypt(string, password = nil) ⇒ Object
Encrypt a string with a given password.
-
#end_session ⇒ Object
Stop a session.
-
#get_password(plain_pswd = nil) ⇒ Object
Get a password from either the password file or by prompting the user if a password file is unavailable.
-
#has_session? ⇒ Boolean
Check if a session is active.
-
#initialize(user, options = {}) ⇒ Yak
constructor
Create a new Yak instance for a given user: Yak.new “my_user” Yak.new “my_user”, :session => 10 Yak.new ‘whoami`.chomp, :session => false.
-
#new_password(password = nil) ⇒ Object
Prompt the user for a new password (replacing and old one).
-
#remove(name) ⇒ Object
Remove a key/value pair.
-
#remove_session_files ⇒ Object
Deletes files used during a session.
-
#retrieve(name) ⇒ Object
Retrieve a value for a given key.
-
#sha_password ⇒ Object
Get the SHA-encrypted password used for encoding data.
-
#start_session ⇒ Object
Start a new session during which Yak will remember the user’s password.
-
#store(name, value = nil) ⇒ Object
Add a key/value pair.
-
#write_data(password = nil) ⇒ Object
Encrypt and write the Yak data back to the data file.
-
#write_key_list ⇒ Object
Write the key list file.
Constructor Details
#initialize(user, options = {}) ⇒ Yak
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 |
# File 'lib/yak.rb', line 345 def initialize user, ={} @user = user @input = HighLine.new $stdin, $stderr @confirm_prompt = true @confirm_prompt = [:confirm_prompt] if .has_key? :confirm_prompt @yak_dir = File. "~#{user}/.yak" FileUtils.mkdir @yak_dir unless File.directory? @yak_dir @pid_file = File.join @yak_dir, "pid" @password_file = File.join @yak_dir, "password" @data_file = [:data_file] || File.join(@yak_dir, "data") @key_list_file = File.join @yak_dir, "keys" @use_completion = [:bash_completion] @session_pid = nil @session_pid = File.read(@pid_file).to_i if File.file? @pid_file @password = nil @cipher = OpenSSL::Cipher::Cipher.new "aes-256-cbc" @session_length = .has_key?(:session) ? [:session] : 30 end |
Instance Attribute Details
#data ⇒ Object (readonly)
Returns the value of attribute data.
337 338 339 |
# File 'lib/yak.rb', line 337 def data @data end |
#use_completion ⇒ Object (readonly)
Returns the value of attribute use_completion.
337 338 339 |
# File 'lib/yak.rb', line 337 def use_completion @use_completion end |
#user ⇒ Object (readonly)
Returns the value of attribute user.
337 338 339 |
# File 'lib/yak.rb', line 337 def user @user end |
Class Method Details
.check_user_setup(user) ⇒ Object
Setup yak for first run if it hasn’t been.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/yak.rb', line 77 def self.check_user_setup user user_config_file = yak_config_file user return if File.file? user_config_file hl = HighLine.new $stdin, $stderr hl.say "\n\nThanks for installing Yak!\n\n" setup_bash_completion data_file = prompt_data_loc user, hl new_config = DEFAULT_CONFIG.merge(:data_file => data_file) make_config_file user, new_config end |
.delete_data(yak) ⇒ Object
Delete the data file of a yak instance after confirming with the user.
203 204 205 |
# File 'lib/yak.rb', line 203 def self.delete_data yak yak.delete_data_file! true end |
.list(yak, name = nil) ⇒ Object
List matched keys of a yak instance.
211 212 213 214 215 216 217 |
# File 'lib/yak.rb', line 211 def self.list yak, name=nil key_regex = /#{name || ".+"}/ yak.data.each do |key, value| $stdout << "#{key}\n" if key =~ key_regex end end |
.load_config(user) ⇒ Object
Load the ~/.yakrc file and return.
146 147 148 149 150 |
# File 'lib/yak.rb', line 146 def self.load_config user user_config_file = yak_config_file user YAML.load_file user_config_file end |
.make_config_file(user, new_config = DEFAULT_CONFIG) ⇒ Object
Create a new user config file.
156 157 158 159 160 161 162 163 |
# File 'lib/yak.rb', line 156 def self.make_config_file user, new_config=DEFAULT_CONFIG user_config_file = yak_config_file user config_str = new_config.to_yaml File.open(user_config_file, "w+"){|f| f.write config_str } $stderr << "Created Yak config file #{user_config_file}:\n" $stderr << "#{config_str}---\n\n" end |
.new_password(yak, value = nil) ⇒ Object
Assign a new master password to a yak instance. Prompts the user if no value is given.
224 225 226 227 228 |
# File 'lib/yak.rb', line 224 def self.new_password yak, value=nil yak.new_password value yak.write_data yak.start_session end |
.parse_args(argv) ⇒ Object
Parse ARGV data.
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 |
# File 'lib/yak.rb', line 260 def self.parse_args argv = {} opts = OptionParser.new do |opt| opt.program_name = File.basename $0 opt.version = VERSION opt.release = nil opt. = <<-EOF Yak is a simple app to store and retrieve passwords securely. Retrieved passwords get copied to the clipboard by default. Usage: #{opt.program_name} [options] [key] [password] Examples: #{opt.program_name} -a gmail [password] #{opt.program_name} gmail #{opt.program_name} -r gmail #{opt.program_name} --list Options: EOF opt.on('-a', '--add KEY', 'Add a new password for a given key') do |key| [:action] = :store [:key] = key end opt.on('-r', '--remove KEY', 'Remove the password for a given key') do |key| [:action] = :remove [:key] = key end opt.on('-l', '--list [REGEX]', 'List keys to the stdout') do |key| [:action] = :list [:key] = key end opt.on('-n', '--new-password', 'Update the password used for encryption') do |value| [:action] = :new_password end opt.on('-p', '--print KEY', 'Print the password for the given key to stdout') do |key| [:action] = :print_password [:key] = key end opt.on('--delete-data', 'Delete the data file - lose all saved info') do [:action] = :delete_data end end opts.parse! argv [:action] ||= :retrieve [:key] ||= argv.shift [:value] ||= argv.shift raise OptionParser::InvalidOption if [:action] == :retrieve && [:key].nil? rescue OptionParser::InvalidOption $stderr << "\nError: Invalid option\n\n" $stderr << opts.to_s exit 1 end |
.print_password(yak, name) ⇒ Object
Get a password value from a yak instance and output it to the stdout.
195 196 197 |
# File 'lib/yak.rb', line 195 def self.print_password yak, name $stdout << "#{yak.retrieve(name)}\n" end |
.prompt_data_loc(user, hl) ⇒ Object
Prompt the user for the location of the data file.
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/yak.rb', line 122 def self.prompt_data_loc user, hl data_file_opts = [] usrhome = File. "~#{user}/" dropbox = File. "~#{user}/Dropbox" data_file_opts << dropbox if File.directory? dropbox data_file_opts << usrhome if File.directory? usrhome data_path = hl.choose do || .prompt = "Where would you like your data file to live?" .choices(*data_file_opts) .choice "other" do hl.ask "Enter path:" end end File.join data_path, ".yakdata" end |
.remove(yak, name) ⇒ Object
Remove a key/value pair from a yak instance.
169 170 171 172 |
# File 'lib/yak.rb', line 169 def self.remove yak, name yak.remove name yak.write_data end |
.retrieve(yak, name) ⇒ Object
Get a password value from a yak instance and copy it to the clipboard.
187 188 189 |
# File 'lib/yak.rb', line 187 def self.retrieve yak, name send_to_clipboard yak.retrieve(name) end |
.run(argv = ARGV) ⇒ Object
Run Yak with argv:
Yak.run %w{key}
Yak.run %w{--add key}
...
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/yak.rb', line 46 def self.run argv=ARGV trap("INT") { exit 1 } user = `whoami`.chomp check_user_setup user config = DEFAULT_CONFIG.merge load_config(user) = parse_args argv yak = new user, config yak.start_session yak.connect_data args = [[:action], yak, [:key], [:value]].compact self.send(*args) rescue CIPHER_ERROR => e yak.end_session $stderr << "Bad password.\n" exit 1 end |
.send_to_clipboard(string) ⇒ Object
Send the passed string to the keyboard. Only supports darwin (pbcopy), linux (xclip) and cygwin (putclip).
235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/yak.rb', line 235 def self.send_to_clipboard string copy_cmd = case RUBY_PLATFORM when /darwin/ then "pbcopy" when /linux/ then "xclip -selection clipboard" when /cygwin/ then "putclip" else $stderr << "No clipboard cmd for platform #{RUBY_PLATFORM}\n" exit 1 end Session::Bash.new.execute "echo -n \"#{string}\" | #{copy_cmd}" end |
.setup_bash_completion ⇒ Object
Check and setup bash completion.
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/yak.rb', line 98 def self.setup_bash_completion completion_dir = "/etc/bash_completion.d" completion_file = File.join File.dirname(__FILE__), "../script/yak_completion" completion_file = File. completion_file sudo = "sudo" unless RUBY_PLATFORM =~ /cygwin/ success = `#{sudo} cp #{completion_file} #{completion_dir}/. && echo 'true'`.chomp if success == "true" $stdout << "\nCopied yak_completion to #{completion_dir}\n\n" else $stderr << "\nError: Could not copy yak_completion #{completion_dir}\n" $stderr << "If you would like to use yak's bash completion, " $stderr << "make sure to source #{completion_file} in .bashrc\n\n" end end |
.store(yak, name, value = nil) ⇒ Object
Add a key/value pair to a yak instance.
178 179 180 181 |
# File 'lib/yak.rb', line 178 def self.store yak, name, value=nil yak.store name, value yak.write_data end |
.yak_config_file(user) ⇒ Object
Get a user’s yak config file. Typically ~user/.yakrc.
252 253 254 |
# File 'lib/yak.rb', line 252 def self.yak_config_file user File. "~#{user}/.yakrc" end |
Instance Method Details
#connect_data ⇒ Object
Loads and decrypts the data file into the @data attribute.
477 478 479 480 481 482 483 484 485 486 487 488 489 490 |
# File 'lib/yak.rb', line 477 def connect_data if data_file_exists? data = "" File.open(@data_file, "rb"){|f| data << f.read } @data = YAML.load decrypt(data) write_key_list if @use_completion else @data = {} write_data end end |
#data_file_exists? ⇒ Boolean
Check if the data file exists.
426 427 428 |
# File 'lib/yak.rb', line 426 def data_file_exists? File.file? @data_file end |
#decrypt(string, password = nil) ⇒ Object
Decrypt a string with a given password.
521 522 523 |
# File 'lib/yak.rb', line 521 def decrypt string, password=nil get_cypher_out :decrypt, string, password end |
#delete_data_file!(confirm = false) ⇒ Object
Deletes the user’s data file forever!
434 435 436 437 |
# File 'lib/yak.rb', line 434 def delete_data_file! confirm=false confirmed = confirm ? @input.agree("Delete all passwords? (y/n)") : true FileUtils.rm_f(@data_file) if confirmed end |
#encrypt(string, password = nil) ⇒ Object
Encrypt a string with a given password.
529 530 531 |
# File 'lib/yak.rb', line 529 def encrypt string, password=nil get_cypher_out :encrypt, string, password end |
#end_session ⇒ Object
Stop a session.
399 400 401 402 403 |
# File 'lib/yak.rb', line 399 def end_session return unless @session_pid Process.kill 9, @session_pid rescue false remove_session_files end |
#get_password(plain_pswd = nil) ⇒ Object
Get a password from either the password file or by prompting the user if a password file is unavailable. Returns a sha1 of the password passed as an arg.
453 454 455 456 457 458 459 460 461 |
# File 'lib/yak.rb', line 453 def get_password plain_pswd=nil password = File.read @password_file if File.file?(@password_file) plain_pswd ||= request_password "Master Password" if !password password ||= Digest::SHA1.hexdigest plain_pswd password end |
#has_session? ⇒ Boolean
Check if a session is active.
418 419 420 |
# File 'lib/yak.rb', line 418 def has_session? Process.kill(0, @session_pid) && @session_pid rescue false end |
#new_password(password = nil) ⇒ Object
Prompt the user for a new password (replacing and old one). Prompts for password confirmation as well.
468 469 470 471 |
# File 'lib/yak.rb', line 468 def new_password password=nil password ||= request_new_password "Set New Master Password" @password = Digest::SHA1.hexdigest password end |
#remove(name) ⇒ Object
Remove a key/value pair.
496 497 498 |
# File 'lib/yak.rb', line 496 def remove name @data.delete(name) end |
#remove_session_files ⇒ Object
Deletes files used during a session.
409 410 411 412 |
# File 'lib/yak.rb', line 409 def remove_session_files FileUtils.rm_f [@password_file, @pid_file] FileUtils.rm_f @key_list_file unless @use_completion == :always end |
#retrieve(name) ⇒ Object
Retrieve a value for a given key.
504 505 506 |
# File 'lib/yak.rb', line 504 def retrieve name @data[name] end |
#sha_password ⇒ Object
Get the SHA-encrypted password used for encoding data.
443 444 445 |
# File 'lib/yak.rb', line 443 def sha_password @password ||= data_file_exists? ? get_password : new_password end |
#start_session ⇒ Object
Start a new session during which Yak will remember the user’s password.
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 |
# File 'lib/yak.rb', line 377 def start_session return unless @session_length pswd = sha_password # Do stdio before writing to file! end_session if has_session? @session_pid = fork do sleep @session_length remove_session_files end File.open(@password_file, "w+"){|f| f.write pswd } File.open(@pid_file, "w+"){|f| f.write @session_pid } Process.detach @session_pid end |
#store(name, value = nil) ⇒ Object
Add a key/value pair. If no value is passed, will prompt the user for one.
512 513 514 515 |
# File 'lib/yak.rb', line 512 def store name, value=nil value ||= request_new_password "'#{name}' Password" @data[name] = value end |
#write_data(password = nil) ⇒ Object
Encrypt and write the Yak data back to the data file.
537 538 539 540 541 542 |
# File 'lib/yak.rb', line 537 def write_data password=nil data = encrypt @data.to_yaml, password File.open(@data_file, "w+"){|f| f.write data} write_key_list if @use_completion == :always end |
#write_key_list ⇒ Object
Write the key list file. Used for bash completion.
548 549 550 |
# File 'lib/yak.rb', line 548 def write_key_list File.open(@key_list_file, "w+"){|f| f.write @data.keys.join(" ") } end |