pws 0.1.3 → 0.9.0

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/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 Jan Lelis
1
+ Copyright (c) 2010-2012 Jan Lelis
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README CHANGED
@@ -1,5 +1,13 @@
1
- pws password safe
1
+ pws is a command-line password safe/manager written in Ruby. Install it with
2
2
 
3
- http://rbjl.net/41-tutorial-build-your-own-password-safe-with-ruby
3
+ $ gem install pws
4
+
5
+ Please run `pws help` for usage information.
6
+
7
+ Trust the code by reading the source! Originally based on: http://rbjl.net/41-tutorial-build-your-own-password-safe-with-ruby
8
+
9
+ Cucumber specs loosely based on https://github.com/thecatwasnot/passwordsafe/blob/master/features/add.feature by thecatwasnot - thanks.
10
+
11
+ Copyright: 2010-2012 Jan Lelis, MIT-LICENSE
4
12
 
5
13
  J-_-L
data/Rakefile CHANGED
@@ -1,19 +1,34 @@
1
- require 'rubygems'
2
- require 'rake'
3
-
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "pws"
8
- gem.summary = %Q{pws is a little password safe. See pws help for more information}
9
- gem.email = "mail@janlelis.de"
10
- gem.homepage = "http://rbjl.net/41-tutorial-build-your-own-password-safe-with-ruby"
11
- gem.authors = ["Jan Lelis"]
12
- gem.executables = %w[pws]
13
- gem.add_dependency 'clipboard'
14
- gem.add_dependency 'zucker'
15
- end
16
- Jeweler::GemcutterTasks.new
17
- rescue LoadError
18
- puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
1
+ require 'fileutils'
2
+
3
+ NAME = Dir['*.gemspec'].first
4
+
5
+ def gemspec
6
+ @gemspec ||= eval(File.read(NAME), binding, NAME)
7
+ end
8
+
9
+ desc "Build the gem"
10
+ task :gem => :gemspec do
11
+ sh "gem build #{NAME}"
12
+ FileUtils.mkdir_p 'pkg'
13
+ FileUtils.mv "#{gemspec.name}-#{gemspec.version}.gem", 'pkg'
14
+ end
15
+
16
+ desc "Install the gem locally"
17
+ task :install => :gem do
18
+ sh %{gem install pkg/#{gemspec.name}-#{gemspec.version} --no-rdoc --no-ri}
19
19
  end
20
+
21
+ desc "Generate the gemspec"
22
+ task :generate do
23
+ puts gemspec.to_ruby
24
+ end
25
+
26
+ desc "Validate the gemspec"
27
+ task :gemspec do
28
+ gemspec.validate
29
+ end
30
+
31
+ require 'cucumber/rake/task'
32
+ Cucumber::Rake::Task.new(:spec)
33
+
34
+ task :default => :spec
data/bin/pws CHANGED
@@ -1,259 +1,89 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # pws
4
- # see http://rbjl.net/41-tutorial-build-your-own-password-safe-with-ruby for more information
3
+ require_relative '../lib/pws'
5
4
 
6
- require 'rubygems' if RUBY_VERSION[2] == ?8
5
+ action_or_namespace = $*.shift
7
6
 
8
- require 'openssl'
9
- require 'fileutils'
10
- require 'clipboard' # gem install clipboard
11
- require 'zucker/alias_for' # gem install zucker
12
- require 'zucker/kernel'
13
- require 'zucker/version'
14
-
15
- class PasswordSafe
16
- VERSION = "0.1.3".freeze
17
-
18
- Entry = Struct.new :description, :password
19
-
20
- class NoAccess < StandardError; end
21
-
22
- # Creates a new password safe. Takes the path to the password file, by default: ~/.pws
23
- def initialize( filename = File.expand_path('~/.pws') )
24
- @pwfile = filename
25
-
26
- access_safe
27
- read_safe
28
- end
29
-
30
- # Add a password entry, params: name, description (optional), password (optional, opens prompt if not given)
31
- def add(key, description = nil, password = nil)
32
- if @pwdata[key]
33
- print "Do you really want to overwrite the exisiting password entry? (press <Enter> to proceed)"
34
- return unless (a = $stdin.getc) == 10 || a == 13
35
- else
36
- @pwdata[key] = Entry.new
37
- end
38
- @pwdata[key].password = password || ask_for_password( "please enter a password for #{key}" )
39
- @pwdata[key].description = description
40
- write_safe
41
-
42
- puts "The password safe has been updated"
43
- end
44
- aliases_for :add, :a, :set, :create, :update, :[]= # using zucker/alias_for
45
-
46
- # Gets the password entry and copies it to the clipboard. The second parameter is the time in seconds it stays there
47
- def get(key, seconds = 20)
48
- if pw_plaintext = @pwdata[key] && @pwdata[key].password
49
- Clipboard.copy pw_plaintext
50
- if seconds && seconds.to_i > 0
51
- puts "The password is available in your clipboard for #{seconds.to_i} seconds"
52
- sleep seconds.to_i
53
- Clipboard.clear
54
- else
55
- puts "The password has been copied to your clipboard"
56
- end
57
- else
58
- puts "No password entry found for #{key}"
59
- end
60
- end
61
- aliases_for :get, :g, :entry, :[]
62
-
63
- # Removes a specific password entry
64
- def remove(key)
65
- if @pwdata.delete key
66
- puts "#{key} has been removed"
67
- else
68
- puts "Nothing removed"
69
- end
70
- end
71
- aliases_for :remove, :r, :delete
72
-
73
- # Shows a password entry list
74
- def show
75
- puts "Available passwords \n" +
76
-
77
- if @pwdata.empty?
78
- ' (none)'
79
- else
80
- @pwdata.map{ |key, pwentry|
81
- " #{key}" + if pwentry.description then ": #{pwentry.description}" else '' end
82
- }*"\n"
83
- end
84
- end
85
- aliases_for :show, :s, :list
86
-
87
- # Shows descriptions of some password entries
88
- def description(*keys)
89
- keys.each{ |key|
90
- if @pwdata[key]
91
- puts "#{key}: #{@pwdata[key].description || key}"
92
- else
93
- puts "No password entry found for #{key}"
94
- end
95
- }
96
- end
97
-
98
- # Changes the master password
99
- def master
100
- @pwhash = Encryptor.hash ask_for_password 'please enter a new master password'
101
- write_safe
102
- puts 'The master password has been changed'
103
- end
104
- aliases_for :master, :m
105
-
106
- # Adds a password entry with a fresh generated random password
107
- def generate( key, description = nil, length = 128, chars = (32..126).map(&:chr) )
108
- add key, nil, (1..length).map{ chars[rand chars.size] }.join # possible in 1.9: chars.sample
109
- end
110
- alias_for :generate, :gen
111
-
112
- # Prevents accidental displaying, e.g. in irb
113
- def to_s
114
- '#<just another password safe>'
115
- end
116
- alias_for :to_s, :inspect
7
+ if action_or_namespace =~ /^-.*$/
8
+ @action = $*.shift
9
+ @namespace = $& unless $& == ?-
10
+ else
11
+ @action = action_or_namespace
12
+ @namespace = nil
13
+ end
117
14
 
118
- private
15
+ @action = @action ? @action.to_sym : :show
16
+ @args = [*$*]
119
17
 
120
- # Tries to load and decrypt the password safe from the pwfile
121
- def read_safe
122
- pwdata_encrypted = File.read @pwfile
123
- pwdata_dump = Encryptor.decrypt( pwdata_encrypted, @pwhash )
124
- @pwdata = remove_dummy_data( Marshal.load(pwdata_dump) ) || {}
125
- rescue
126
- fail NoAccess, 'Could not decrypt/load the password safe!'
127
- end
128
-
129
- # Tries to encrypt and save the password safe into the pwfile
130
- def write_safe
131
- pwdata_dump = Marshal.dump add_dummy_data( @pwdata || {} )
132
- pwdata_encrypted = Encryptor.encrypt pwdata_dump, @pwhash
133
- File.open( @pwfile, 'w' ){ |f| f.write pwdata_encrypted }
134
- rescue
135
- fail NoAccess, 'Could not encrypt/safe the password safe!'
136
- end
18
+ begin
19
+ case @action
20
+ when :v, :version
21
+ puts "pws #{PWS::VERSION} by " + Paint["J-_-L", :bold] + " <https://github.com/janlelis/pws>"
22
+ when :help, :actions, :commands
23
+ puts \
24
+ <<HELP
25
+
26
+ #{Paint["Usage", :underline]}
27
+
28
+ #{Paint['pws', :bold]} [-namespace] action [arguments]
29
+
30
+ #{Paint["Info", :underline]}
31
+
32
+ pws allows you to manage passwords in encryted password files (safes). It
33
+ operates on the file specified in the environment variable PWS or on "~/.pws".
34
+ You can apply a namespace as first parameter that will be appended to the
35
+ filename, e.g. `pws -work show` with usual env would use "~/.pws-work".
137
36
 
138
- # Checks if the file is accessible or create a new one
139
- def access_safe
140
- if !File.file? @pwfile
141
- puts "No password safe detected, creating one at #@pwfile"
142
- FileUtils.touch @pwfile
143
- @pwhash = Encryptor.hash ask_for_password 'please enter a new master password'
144
- write_safe
37
+ #{Paint["Available actions", :underline]}
38
+
39
+ #{Paint['ls', :bold]} / list / show / status
40
+ Lists all available password entries.
41
+
42
+ #{Paint['add', :bold]} / set / store / create ( name, password = nil )
43
+ Stores a new password entry. The second argument can be the password, but
44
+ it's recommended to not pass it, but enter it interactively.
45
+
46
+ #{Paint['get', :bold]} / entry / copy / password / for ( name, seconds = 10 )
47
+ Copies the password for <name> to the clipboard. The second argument specifies,
48
+ how long the password is kept in the clipboard (0 = no deletion).
49
+
50
+ #{Paint['gen', :bold]} / generate ( name, seconds = 10, length = 64, char_pool )
51
+ Generates a new password for <name> and then copies it to the clipboard, like
52
+ get (the second argument is the time - it gets passed to get). The third
53
+ argument sets the password length. The fourth argument allows you to pass a
54
+ character pool that is used for generating the passwords.
55
+
56
+ #{Paint['rm', :bold]} / remove / del / delete ( name )
57
+ Removes a password entry.
58
+
59
+ #{Paint['mv', :bold]} / move / rename ( old_name, new_name )
60
+ Renames a password entry.
61
+
62
+ #{Paint['master', :bold]} ( password = nil )
63
+ Changes the master password.
64
+
65
+ #{Paint['v', :bold]} / version
66
+ Displays version and website.
67
+
68
+ #{Paint['help', :bold]} / actions / commands
69
+ Displays this help.
70
+
71
+ HELP
72
+ else # redirect to safe
73
+ if PWS.public_instance_methods(false).include?(@action)
74
+ PWS.new(nil, @namespace).send @action, *@args
145
75
  else
146
- @pwhash = Encryptor.hash ask_for_password 'master password'
147
- end
148
- rescue
149
- raise NoAccess, "Could not access the password safe at #@pwfile!"
150
- end
151
-
152
- # Adds some redundancy
153
- def add_dummy_data(pwdata)
154
- (5000 - pwdata.size).abs.times.map{ rand 42424242 } + # or whatever
155
- [pwdata]
156
- end
157
-
158
- def remove_dummy_data(pwdata)
159
- pwdata.last
160
- end
161
-
162
- # Prompts the user for a password
163
- def ask_for_password(prompt = 'new password')
164
- print "#{prompt}: ".capitalize
165
- system 'stty -echo' # no more terminal output
166
- pw_plaintext = ($stdin.gets||'').chop # gets without $stdin would mistakenly read_safe from ARGV
167
- system 'stty echo' # restore terminal output
168
- puts
169
-
170
- pw_plaintext
171
- end
172
-
173
- class << Encryptor = Module.new
174
- CIPHER = 'aes-256-cbc'
175
-
176
- def decrypt( iv_and_data, pwhash )
177
- iv, data = iv_and_data[0,16], iv_and_data[16..-1]
178
- crypt :decrypt, data, pwhash, iv
179
- end
180
-
181
- def encrypt( data, pwhash )
182
- iv = random_iv
183
- encrypted_data = crypt :encrypt, data, pwhash, iv
184
- iv + encrypted_data
185
- end
186
-
187
- def hash( plaintext )
188
- OpenSSL::Digest::SHA512.new( plaintext ).digest
189
- end
190
-
191
- # you need a random iv for cbc mode. It is prepended to the encrypted text.
192
- def random_iv
193
- a = OpenSSL::Cipher.new CIPHER
194
- a.random_iv
76
+ pa "Unknown action: #@action\nPlease see `pws help` for a list of available commands!", :red
195
77
  end
196
-
197
- private
198
-
199
- # Encrypts or decrypts the data with the password hash as key
200
- # NOTE: encryption exceptions do not get caught!
201
- def crypt( decrypt_or_encrypt, data, pwhash, iv )
202
- c = OpenSSL::Cipher.new CIPHER
203
- c.send decrypt_or_encrypt.to_sym
204
- c.key = pwhash
205
- c.iv = iv
206
- c.update( data ) << c.final
207
- end
208
- end
209
- end
210
-
211
- # Command line action
212
- if standalone? # using zucker/kernel (instead of __FILE__ == $0)
213
- if $*.empty?
214
- action = :show
215
- args = []
216
- else
217
- action = $*.shift[/^-{0,2}(.*)$/, 1].to_sym # also accept first argument, if it is prefixed with - or --
218
- args = [*$*]
219
- end
220
-
221
- begin
222
- case action
223
- when :h, :help, :commands
224
- puts %q{Available commands
225
- s/show/list shows all available entry names
226
- g/get/entry( name, seconds = 20 )
227
- copies the password of the entry into the clipboard
228
- d/description( names ) shows a description for the password entries
229
- a/add/set/create( name, description = nil, password = nil )
230
- creates or updates an entry. second parameter is a
231
- description. third parameter can be a password, but
232
- it's recommended to not use it and enter it, when prompted
233
- gen/generate( name, description = nil, length=128, chars = (32..126).map(&:chr) )
234
- creates or updates an entry, but generates a new
235
- random password. you can customize the length and
236
- used characters
237
- r/remove/delete( name ) deletes an password entry
238
- m/master changes the master password
239
- v/version displays version
240
- h/help/commands displays this help}
241
- when :v, :version
242
- puts "pws #{PasswordSafe::VERSION}\n J-_-L"
243
- else # redirect to safe
244
- if PasswordSafe.public_instance_methods(false).include?(
245
- if RubyVersion.is?(1.8) then action.to_s else action end ) # using zucker/version
246
-
247
- pws = PasswordSafe.new
248
- pws.send action, *args
249
- else
250
- puts "Unknown command: #{action}. Use 'help' to get a command list!"
251
- end
252
- end
253
-
254
- rescue PasswordSafe::NoAccess => e
255
- warn e.message
256
78
  end
79
+ rescue PWS::NoAccess
80
+ # pa $!.message.capitalize, :red, :bold
81
+ pa "NO ACCESS", :red, :bold
82
+ rescue ArgumentError
83
+ pa $!.message.capitalize, :red
84
+ rescue Interrupt
85
+ system 'stty echo' if $stdin.tty? # ensure terminal's working
86
+ pa "..canceled", :red
257
87
  end
258
88
 
259
89
  # J-_-L
data/lib/pws.rb CHANGED
@@ -1 +1,213 @@
1
- load File.expand_path( '../bin/pws', File.dirname(__FILE__) )
1
+ require_relative 'pws/version'
2
+ require_relative 'pws/encryptor'
3
+
4
+ require 'fileutils'
5
+ require 'clipboard'
6
+ require 'securerandom'
7
+ require 'zucker/alias_for'
8
+ require 'paint/pa'
9
+
10
+ class PWS
11
+ class NoAccess < StandardError; end
12
+
13
+ # Creates a new password safe. Takes the path to the password file, by default: ~/.pws
14
+ # Second parameter allows namespaces that get appended to the file name (uses another safe)
15
+ # You can pass the master password as third parameter (not recommended)
16
+ def initialize(filename = nil, namespace = nil, password = nil)
17
+ @pw_file = File.expand_path(filename || ENV["PWS"] || '~/.pws')
18
+ @pw_file << namespace if namespace
19
+ access_safe(password)
20
+ read_safe
21
+ end
22
+
23
+ # Shows a password entry list
24
+ def show
25
+ if @pw_data.empty?
26
+ pa %[There aren't any passwords stored at #{@pw_file}, yet], :red
27
+ else
28
+ puts Paint["Entries", :underline] + %[ in ] + @pw_file
29
+ puts @pw_data.keys.sort.map{ |key| %[- #{key}\n] }.join
30
+ end
31
+ return true
32
+ end
33
+ aliases_for :show, :ls, :list, :status
34
+
35
+ # Add a password entry, params: name, password (optional, opens prompt if not given)
36
+ def add(key, password = nil)
37
+ if @pw_data[key]
38
+ pa %[There is already a password stored for #{key}. You need to remove it before creating a new one!], :red
39
+ return false
40
+ else
41
+ @pw_data[key] = password || ask_for_password(%[please enter a password for #{key}], :yellow)
42
+ if @pw_data[key].empty?
43
+ pa %[Cannot add an empty password!], :red
44
+ return false
45
+ else
46
+ write_safe
47
+ pa %[The password for #{key} has been added], :green
48
+ return true
49
+ end
50
+ end
51
+ end
52
+ aliases_for :add, :set, :store, :create, :[]= # using zucker/alias_for
53
+
54
+ # Gets the password entry and copies it to the clipboard. The second parameter is the time in seconds it stays there
55
+ def get(key, seconds = 10)
56
+ if pw_plaintext = @pw_data[key]
57
+ if seconds && seconds.to_i > 0
58
+ original_clipboard_content = Clipboard.paste
59
+ Clipboard.copy pw_plaintext
60
+ pa %[The password for #{key} is now available in your clipboard for #{seconds.to_i} second#{?s if seconds.to_i > 1}], :green
61
+ begin
62
+ sleep seconds.to_i
63
+ rescue Interrupt
64
+ Clipboard.copy original_clipboard_content
65
+ raise
66
+ end
67
+ Clipboard.copy original_clipboard_content
68
+ return true
69
+ else
70
+ Clipboard.copy pw_plaintext
71
+ pa %[The password for #{key} has been copied to your clipboard], :green
72
+ return true
73
+ end
74
+ else
75
+ pa %[No password found for #{key}!], :red
76
+ return false
77
+ end
78
+ end
79
+ aliases_for :get, :entry, :copy, :password, :for, :[]
80
+
81
+ # Adds a password entry with a freshly generated random password
82
+ def generate(
83
+ key,
84
+ seconds = 10,
85
+ length = 64,
86
+ char_pool = (32..126).map(&:chr).join.gsub(/\s/, '')
87
+ )
88
+ char_pool_size = char_pool.size
89
+ new_pw = (1..length.to_i).map{
90
+ char_pool[SecureRandom.random_number(char_pool_size)]
91
+ }.join
92
+
93
+ if add(key, new_pw)
94
+ get(key, seconds)
95
+ end
96
+ end
97
+ alias_for :generate, :gen
98
+
99
+ # Removes a specific password entry
100
+ def remove(key)
101
+ if @pw_data.delete key
102
+ write_safe
103
+ pa %[The password for #{key} has been removed], :green
104
+ return true
105
+ else
106
+ pa %[No password found for #{key}!], :red
107
+ return false
108
+ end
109
+ end
110
+ aliases_for :remove, :rm, :del, :delete
111
+
112
+ # Removes a specific password entry
113
+ def rename(old_key, new_key)
114
+ if !@pw_data[old_key]
115
+ pa %[No password found for #{old_key}!], :red
116
+ return false
117
+ elsif @pw_data[new_key]
118
+ pa %[There is already a password stored for #{new_key}. You need to remove it before naming another one #{new_key}!], :red
119
+ return false
120
+ else
121
+ @pw_data[new_key] = @pw_data.delete(old_key)
122
+ write_safe
123
+ pa %[The password entry #{old_key} has been renamed to #{new_key}], :green
124
+ return true
125
+ end
126
+ end
127
+ aliases_for :rename, :mv, :move
128
+
129
+ # Changes the master password
130
+ def master(password = nil)
131
+ @pw_hash = Encryptor.hash password || ask_for_password(%[please enter a new master password], :yellow, :bold)
132
+ write_safe
133
+ pa %[The master password has been changed], :green
134
+ return true
135
+ end
136
+
137
+ # Prevents accidental displaying, e.g. in irb
138
+ def to_s
139
+ %[#<password safe>]
140
+ end
141
+ alias_for :to_s, :inspect
142
+
143
+ private
144
+
145
+ # Tries to load and decrypt the password safe from the pwfile
146
+ def read_safe
147
+ pwdata_raw = File.read(@pw_file)
148
+ pwdata_encrypted = pwdata_raw.force_encoding("ascii")
149
+ pwdata_dump = Encryptor.decrypt(pwdata_encrypted, @pw_hash)
150
+ pwdata_with_redundancy = Marshal.load(pwdata_dump)
151
+ @pw_data = remove_redundancy(pwdata_with_redundancy)
152
+ pa %[ACCESS GRANTED], :green
153
+ rescue
154
+ fail NoAccess, %[Could not load and decrypt the password safe!]
155
+ end
156
+
157
+ # Tries to encrypt and save the password safe into the pwfile
158
+ def write_safe
159
+ pwdata_with_redundancy = add_redundancy(@pw_data || {})
160
+ pwdata_dump = Marshal.dump(pwdata_with_redundancy)
161
+ pwdata_encrypted = Encryptor.encrypt(pwdata_dump, @pw_hash)
162
+ File.open(@pw_file, 'w'){ |f| f.write(pwdata_encrypted) }
163
+ rescue
164
+ fail NoAccess, %[Could not encrypt and save the password safe!]
165
+ end
166
+
167
+ # Checks if the file is accessible or create a new one
168
+ def access_safe(password = nil)
169
+ if !File.file? @pw_file
170
+ pa %[No password safe detected, creating one at #@pw_file], :blue, :bold
171
+ @pw_hash = Encryptor.hash password || ask_for_password(%[please enter a new master password], :yellow, :bold)
172
+ write_safe
173
+ else
174
+ print %[Access password safe at #@pw_file | ]
175
+ @pw_hash = Encryptor.hash password || ask_for_password(%[master password])
176
+ end
177
+ end
178
+
179
+ # Adds some redundancy (to conceal how much you have stored)
180
+ def add_redundancy(pw_data)
181
+ entries = 8000 + SecureRandom.random_number(4000)
182
+ position = SecureRandom.random_number(entries)
183
+
184
+ ret = entries.times.map{ # or whatever... just create noise ;)
185
+ { SecureRandom.uuid.chars.to_a.shuffle.join => SecureRandom.uuid.chars.to_a.shuffle.join }
186
+ }
187
+ ret[position] = pw_data
188
+ ret << position
189
+
190
+ ret
191
+ end
192
+
193
+ # And remove it
194
+ def remove_redundancy(pw_data)
195
+ position = pw_data[-1]
196
+ pw_data[position]
197
+ end
198
+
199
+ # Prompts the user for a password
200
+ def ask_for_password(prompt = 'new password', *colors)
201
+ print Paint["#{prompt}:".capitalize, *colors] + " "
202
+ system 'stty -echo' if $stdin.tty? # no more terminal output
203
+ pw_plaintext = ($stdin.gets||'').chop # gets without $stdin would mistakenly read_safe from ARGV
204
+ system 'stty echo' if $stdin.tty? # restore terminal output
205
+ puts "\e[999D\e[K\e[1A" if $stdin.tty? # re-use prompt line in terminal
206
+
207
+ pw_plaintext
208
+ end
209
+ end
210
+
211
+ # Command line action in bin/pws
212
+
213
+ # J-_-L
@@ -0,0 +1,40 @@
1
+ require 'openssl'
2
+
3
+ module PWS::Encryptor
4
+ class << self
5
+ CIPHER = 'aes-256-cbc'
6
+
7
+ def decrypt( iv_and_data, pwhash )
8
+ iv, data = iv_and_data[0,16], iv_and_data[16..-1]
9
+ crypt :decrypt, data, pwhash, iv
10
+ end
11
+
12
+ def encrypt( data, pwhash )
13
+ iv = random_iv
14
+ encrypted_data = crypt :encrypt, data, pwhash, iv
15
+ iv + encrypted_data
16
+ end
17
+
18
+ def hash( plaintext )
19
+ OpenSSL::Digest::SHA512.new( plaintext ).digest
20
+ end
21
+
22
+ # you need a random iv for cbc mode. It is prepended to the encrypted text.
23
+ def random_iv
24
+ a = OpenSSL::Cipher.new CIPHER
25
+ a.random_iv
26
+ end
27
+
28
+ private
29
+
30
+ # Encrypts or decrypts the data with the password hash as key
31
+ # NOTE: encryption exceptions do not get caught here!
32
+ def crypt( decrypt_or_encrypt, data, pwhash, iv )
33
+ c = OpenSSL::Cipher.new CIPHER
34
+ c.send decrypt_or_encrypt.to_sym
35
+ c.key = pwhash
36
+ c.iv = iv
37
+ c.update( data ) << c.final
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,3 @@
1
+ class PWS
2
+ VERSION = '0.9.0'
3
+ end
data/pws.gemspec CHANGED
@@ -1,52 +1,33 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
1
  # -*- encoding: utf-8 -*-
2
+ name = 'pws'
5
3
 
4
+ require File.dirname(__FILE__) + "/lib/#{name}/version"
5
+
6
6
  Gem::Specification.new do |s|
7
- s.name = %q{pws}
8
- s.version = "0.1.3"
7
+ s.required_ruby_version = '>= 1.9'
8
+ s.name = name
9
+ s.version = PWS::VERSION
10
+ s.authors = ["Jan Lelis"]
11
+ s.email = "mail@janlelis.de"
12
+ s.homepage = 'https://github.com/janlelis/pws'
13
+ s.summary = "pws is a cli password safe."
14
+ s.description = "pws is a command-line password safe. Please run `pws help` for usage information."
15
+ s.files = Dir.glob(%w[{lib,test}/**/*.rb bin/* [A-Z]*.{txt,rdoc} ext/**/*.{rb,c}]) + %w{Rakefile pws.gemspec}
16
+ s.extra_rdoc_files = ["README", "LICENSE"]
17
+ s.license = 'MIT'
18
+ s.executables = ['pws']
19
+ s.add_dependency 'clipboard', '~> 1.0.0'
20
+ s.add_dependency 'zucker', '>= 12.1'
21
+ s.add_dependency 'paint', '>= 0.8.4'
22
+ s.add_development_dependency 'rake'
23
+ s.add_development_dependency 'cucumber'
24
+ s.add_development_dependency 'aruba'
9
25
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Jan Lelis"]
12
- s.date = %q{2010-11-02}
13
- s.default_executable = %q{pws}
14
- s.email = %q{mail@janlelis.de}
15
- s.executables = ["pws"]
16
- s.extra_rdoc_files = [
17
- "LICENSE",
18
- "README"
19
- ]
20
- s.files = [
21
- ".gitignore",
22
- "LICENSE",
23
- "README",
24
- "Rakefile",
25
- "VERSION",
26
- "bin/pws",
27
- "lib/pws.rb",
28
- "pws.gemspec"
29
- ]
30
- s.homepage = %q{http://rbjl.net/41-tutorial-build-your-own-password-safe-with-ruby}
31
- s.rdoc_options = ["--charset=UTF-8"]
32
- s.require_paths = ["lib"]
33
- s.rubygems_version = %q{1.3.7}
34
- s.summary = %q{pws is a little password safe. See pws help for more information}
35
-
36
- if s.respond_to? :specification_version then
37
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
38
- s.specification_version = 3
39
-
40
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
41
- s.add_runtime_dependency(%q<clipboard>, [">= 0"])
42
- s.add_runtime_dependency(%q<zucker>, [">= 0"])
43
- else
44
- s.add_dependency(%q<clipboard>, [">= 0"])
45
- s.add_dependency(%q<zucker>, [">= 0"])
46
- end
47
- else
48
- s.add_dependency(%q<clipboard>, [">= 0"])
49
- s.add_dependency(%q<zucker>, [">= 0"])
50
- end
26
+ len = s.homepage.size
27
+ s.post_install_message = \
28
+ (" ┌── " + "info ".ljust(len-2,'%') + "─┐\n" +
29
+ " J-_-L │ " + s.homepage + " │\n" +
30
+ " ├── " + "usage ".ljust(len-2,'%') + "─┤\n" +
31
+ " │ " + "pws help".ljust(len,' ') + " │\n" +
32
+ " └─" + '─'*len + "─┘").gsub('%', '─')
51
33
  end
52
-
metadata CHANGED
@@ -1,102 +1,125 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: pws
3
- version: !ruby/object:Gem::Version
4
- hash: 29
5
- prerelease: false
6
- segments:
7
- - 0
8
- - 1
9
- - 3
10
- version: 0.1.3
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ prerelease:
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Jan Lelis
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2010-11-02 00:00:00 +01:00
19
- default_executable: pws
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
12
+ date: 2012-01-19 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
22
15
  name: clipboard
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: &17531100 !ruby/object:Gem::Requirement
25
17
  none: false
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- hash: 3
30
- segments:
31
- - 0
32
- version: "0"
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.0
33
22
  type: :runtime
34
- version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
23
+ prerelease: false
24
+ version_requirements: *17531100
25
+ - !ruby/object:Gem::Dependency
36
26
  name: zucker
27
+ requirement: &17530380 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '12.1'
33
+ type: :runtime
37
34
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
35
+ version_requirements: *17530380
36
+ - !ruby/object:Gem::Dependency
37
+ name: paint
38
+ requirement: &17529720 !ruby/object:Gem::Requirement
39
39
  none: false
40
- requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- hash: 3
44
- segments:
45
- - 0
46
- version: "0"
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: 0.8.4
47
44
  type: :runtime
48
- version_requirements: *id002
49
- description:
45
+ prerelease: false
46
+ version_requirements: *17529720
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: &17528920 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *17528920
58
+ - !ruby/object:Gem::Dependency
59
+ name: cucumber
60
+ requirement: &17528420 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *17528420
69
+ - !ruby/object:Gem::Dependency
70
+ name: aruba
71
+ requirement: &17527960 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *17527960
80
+ description: pws is a command-line password safe. Please run `pws help` for usage
81
+ information.
50
82
  email: mail@janlelis.de
51
- executables:
83
+ executables:
52
84
  - pws
53
85
  extensions: []
54
-
55
- extra_rdoc_files:
56
- - LICENSE
86
+ extra_rdoc_files:
57
87
  - README
58
- files:
59
- - .gitignore
60
88
  - LICENSE
61
- - README
62
- - Rakefile
63
- - VERSION
64
- - bin/pws
89
+ files:
90
+ - lib/pws/encryptor.rb
91
+ - lib/pws/version.rb
65
92
  - lib/pws.rb
93
+ - bin/pws
94
+ - Rakefile
66
95
  - pws.gemspec
67
- has_rdoc: true
68
- homepage: http://rbjl.net/41-tutorial-build-your-own-password-safe-with-ruby
69
- licenses: []
70
-
71
- post_install_message:
72
- rdoc_options:
73
- - --charset=UTF-8
74
- require_paths:
96
+ - README
97
+ - LICENSE
98
+ homepage: https://github.com/janlelis/pws
99
+ licenses:
100
+ - MIT
101
+ post_install_message: ! " ┌── info ─────────────────────────┐\n J-_-L │ https://github.com/janlelis/pws
102
+ │\n ├── usage ────────────────────────┤\n │ pws help │\n
103
+ \ └─────────────────────────────────┘"
104
+ rdoc_options: []
105
+ require_paths:
75
106
  - lib
76
- required_ruby_version: !ruby/object:Gem::Requirement
107
+ required_ruby_version: !ruby/object:Gem::Requirement
77
108
  none: false
78
- requirements:
79
- - - ">="
80
- - !ruby/object:Gem::Version
81
- hash: 3
82
- segments:
83
- - 0
84
- version: "0"
85
- required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ! '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '1.9'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
114
  none: false
87
- requirements:
88
- - - ">="
89
- - !ruby/object:Gem::Version
90
- hash: 3
91
- segments:
92
- - 0
93
- version: "0"
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
94
119
  requirements: []
95
-
96
120
  rubyforge_project:
97
- rubygems_version: 1.3.7
121
+ rubygems_version: 1.8.11
98
122
  signing_key:
99
123
  specification_version: 3
100
- summary: pws is a little password safe. See pws help for more information
124
+ summary: pws is a cli password safe.
101
125
  test_files: []
102
-
data/.gitignore DELETED
@@ -1,21 +0,0 @@
1
- ## MAC OS
2
- .DS_Store
3
-
4
- ## TEXTMATE
5
- *.tmproj
6
- tmtags
7
-
8
- ## EMACS
9
- *~
10
- \#*
11
- .\#*
12
-
13
- ## VIM
14
- *.swp
15
-
16
- ## PROJECT::GENERAL
17
- coverage
18
- rdoc
19
- pkg
20
-
21
- ## PROJECT::SPECIFIC
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.1.3