tome 0.1.0.pre → 0.1.1.pre

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/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  ## Tome
2
2
 
3
- Tome is a lightweight password manager with a humane command-line interface. It is meant to be simple and secure.
3
+ Tome is a lightweight password manager with a humane command-line interface.
4
+
5
+ Tome stores your passwords in an encrypted file which you manage with a single master password.
6
+ You can keep track of multiple complex passwords without having to remember any of them.
4
7
 
5
8
  *Disclaimer* I am not a security expert. I've only had limited formal training in security and cryptography.
6
9
  Now that I've scared off all but the bravest, feel free to look [under the hood](#under-the-hood) or
@@ -9,13 +12,16 @@ at the security bits in [crypt.rb](https://github.com/schmich/tome/blob/master/l
9
12
  ## Installation
10
13
 
11
14
  * Requires [Ruby 1.9.3](http://www.ruby-lang.org/en/downloads/) or newer.
12
- * *Coming soon* `gem install tome`
15
+ * `gem install tome --pre`
16
+ * `tome` is now available on the command-line.
13
17
 
14
18
  ## Usage
15
19
 
16
20
  The first time you run `tome`, you'll be asked to create a master password for your encrypted password database.
17
21
  Any operations involving your password database will require this master password.
18
22
 
23
+ Creating a new password is simple:
24
+
19
25
  > tome set linkedin.com
20
26
  Creating tome database.
21
27
  Master password:
@@ -24,7 +30,7 @@ Any operations involving your password database will require this master passwor
24
30
  Password (verify):
25
31
  Created password for linkedin.com.
26
32
 
27
- Recalling a password is easy:
33
+ Recalling a password is just as easy:
28
34
 
29
35
  > tome get linkedin.com
30
36
  Master password:
@@ -1,4 +1,6 @@
1
1
  require 'tome/tome'
2
2
  require 'tome/command'
3
3
  require 'tome/crypt'
4
+ require 'tome/padding'
5
+ require 'tome/usage'
4
6
  require 'tome/version'
@@ -29,7 +29,12 @@ module Tome
29
29
  begin
30
30
  handle_command(args)
31
31
  rescue CommandError => error
32
- @err.puts error.message
32
+ @err.puts "Error: #{error.message}"
33
+ return 1
34
+ rescue FileFormatError => error
35
+ # Fix file separators for Windows.
36
+ filename = @tome_filename.gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
37
+ @err.puts "Error: Cannot read #{filename}: #{error.message}"
33
38
  return 1
34
39
  end
35
40
 
@@ -53,9 +58,9 @@ module Tome
53
58
  commands = {
54
59
  /\A(help|-h|--help)\z/i => :help,
55
60
  /\A(version|ver|-v|--version)\z/i => :version,
56
- /\A(set|s)\z/i => :set,
61
+ /\A(set|s|add)\z/i => :set,
57
62
  /\A(get|g|show)\z/i => :get,
58
- /\A(delete|del|d|rm|remove)\z/i => :delete,
63
+ /\A(delete|del|rm|remove)\z/i => :delete,
59
64
  /\A(generate|gen)\z/i => :generate,
60
65
  /\A(copy|cp)\z/i => :copy,
61
66
  /\A(rename|ren|rn)\z/i => :rename,
@@ -340,12 +345,25 @@ module Tome
340
345
  end
341
346
 
342
347
  def input_password
343
- @in.noecho { |stdin|
344
- password = stdin.gets.sub(/[\r\n]+\z/, '')
345
- @err.puts
348
+ input = proc { |stdin|
349
+ raw = stdin.gets
350
+ return nil if raw.nil?
351
+
352
+ password = raw.strip
353
+ @out.puts
346
354
 
347
355
  return password
348
356
  }
357
+
358
+ begin
359
+ @in.noecho { |stdin|
360
+ input.call stdin
361
+ }
362
+ rescue Errno::EBADF
363
+ # This can happen when stdin refers to a file or pipe.
364
+ # In this case, we ignore 'no echo' and do normal input.
365
+ input.call @in
366
+ end
349
367
  end
350
368
 
351
369
  def prompt_confirm(prompt)
@@ -381,7 +399,12 @@ module Tome
381
399
  tome = Tome.new(@tome_filename, master_password)
382
400
  rescue MasterPasswordError
383
401
  @err.puts 'Incorrect master password.'
384
- retry
402
+
403
+ if master_password.nil?
404
+ raise CommandError, 'Authentication failed.'
405
+ else
406
+ retry
407
+ end
385
408
  end
386
409
 
387
410
  return tome
@@ -397,199 +420,4 @@ module Tome
397
420
  end
398
421
  end
399
422
  end
400
-
401
- # TODO: Complete these.
402
- $usage = <<END
403
- Usage:
404
-
405
- tome set [user@]<domain> [password]
406
-
407
- Create or update the password for an account.
408
- Example: tome set foo@gmail.com
409
-
410
- tome generate [user@]<domain>
411
-
412
- Generate a random password for an account.
413
- Example: tome generate reddit.com
414
-
415
- tome get <pattern>
416
-
417
- Show the passwords for all accounts matching the pattern.
418
- Example: tome get youtube
419
-
420
- tome copy <pattern>
421
-
422
- Copy the password for the account matching the pattern.
423
- Example: tome copy news.ycombinator.com
424
-
425
- tome list
426
-
427
- Show all stored accounts and passwords.
428
- Example: tome list
429
-
430
- tome delete [user@]<domain>
431
-
432
- Delete the password for an account.
433
- Example: tome delete foo@slashdot.org
434
-
435
- tome rename <old> <new>
436
-
437
- Rename the account information stored.
438
- Example: tome rename twitter.com foo@twitter.com
439
-
440
- tome help
441
-
442
- Shows help for a specific command.
443
- Example: tome help set
444
-
445
- tome version
446
-
447
- Shows the version of tome.
448
- Example: tome version
449
- END
450
-
451
- $help_usage = <<END
452
- tome help
453
-
454
- Shows help for a specific command.
455
-
456
- Usage:
457
-
458
- tome help
459
- tome help <command>
460
-
461
- Examples:
462
-
463
- tome help
464
- tome help set
465
- tome help help (so meta)
466
-
467
- Alias: help, --help, -h
468
- END
469
-
470
- $set_usage = <<END
471
- tome set
472
-
473
- Create or update the password for an account. The user is optional.
474
- If you do not specify a password, you will be prompted for one.
475
-
476
- Usage:
477
-
478
- tome set [user@]<domain> [password]
479
-
480
- Examples:
481
-
482
- tome set gmail.com
483
- tome set gmail.com p4ssw0rd
484
- tome set foo@gmail.com
485
- tome set foo@gmail.com p4ssw0rd
486
-
487
- Alias: set, s
488
- END
489
-
490
- $get_usage = <<END
491
- tome get
492
-
493
- Show the passwords for all accounts matching the pattern.
494
- Matching is done with substring search. Wildcards are not supported.
495
-
496
- Usage:
497
-
498
- tome get <pattern>
499
-
500
- Examples:
501
-
502
- tome get gmail
503
- tome get foo@
504
- tome get foo@gmail.com
505
-
506
- Alias: get, g, show
507
- END
508
-
509
- $delete_usage = <<END
510
- tome delete
511
-
512
- Delete the password for an account.
513
-
514
- Usage:
515
-
516
- tome delete [user@]<domain>
517
-
518
- Examples:
519
-
520
- tome delete gmail.com
521
- tome delete foo@gmail.com
522
-
523
- Alias: delete, del, d, remove, rm
524
- END
525
-
526
- $generate_usage = <<END
527
- tome generate
528
-
529
- Generate a random password for an account. The user is optional.
530
-
531
- Usage:
532
-
533
- tome generate [user@]<domain>
534
-
535
- Examples:
536
-
537
- tome generate gmail.com
538
- tome generate foo@gmail.com
539
-
540
- Alias: generate, gen
541
- END
542
-
543
- $copy_usage = <<END
544
- tome copy
545
-
546
- Copy the password for the account matching the pattern.
547
- If more than one account matches the pattern, nothing happens.
548
- Matching is done with substring search. Wildcards are not supported.
549
-
550
- Usage:
551
-
552
- tome copy <pattern>
553
-
554
- Examples:
555
-
556
- tome copy gmail
557
- tome copy foo@
558
- tome copy foo@gmail.com
559
-
560
- Alias: copy, cp
561
- END
562
-
563
- $list_usage = <<END
564
- tome list
565
-
566
- Show all stored accounts and passwords.
567
-
568
- Usage:
569
-
570
- tome list
571
-
572
- Examples:
573
-
574
- tome list
575
-
576
- Alias: list, ls
577
- END
578
-
579
- $rename_usage = <<END
580
- tome rename
581
-
582
- Rename the account information stored.
583
-
584
- Usage:
585
-
586
- tome rename <old> <new>
587
-
588
- Examples:
589
-
590
- tome rename gmail.com foo@gmail.com
591
- tome rename foo@gmail.com bar@gmail.com
592
-
593
- Alias: rename, ren, rn
594
- END
595
- end
423
+ end
@@ -0,0 +1,16 @@
1
+ require 'yaml'
2
+ require 'securerandom'
3
+
4
+ module Tome
5
+ class Padding
6
+ def self.pad(value, min_pad, max_pad)
7
+ padding = Random.rand(min_pad..max_pad)
8
+ YAML.dump(:value => value, :padding => SecureRandom.random_bytes(padding))
9
+ end
10
+
11
+ def self.unpad(inflated_value)
12
+ yaml = YAML.load(inflated_value)
13
+ yaml[:value]
14
+ end
15
+ end
16
+ end
@@ -5,17 +5,36 @@ module Tome
5
5
  class MasterPasswordError < RuntimeError
6
6
  end
7
7
 
8
+ class FileFormatError < RuntimeError
9
+ end
10
+
8
11
  class Tome
9
12
  def self.exists?(tome_filename)
10
13
  return !load_tome(tome_filename).nil?
11
14
  end
12
15
 
13
16
  def self.create!(tome_filename, master_password, stretch = 100_000)
17
+ if tome_filename.nil? || tome_filename.empty?
18
+ raise ArgumentError
19
+ end
20
+
21
+ if master_password.nil? || master_password.empty?
22
+ raise MasterPasswordError
23
+ end
24
+
14
25
  save_tome(tome_filename, new_tome(stretch), {}, master_password)
15
26
  return Tome.new(tome_filename, master_password)
16
27
  end
17
28
 
18
29
  def initialize(tome_filename, master_password)
30
+ if tome_filename.nil? || tome_filename.empty?
31
+ raise ArgumentError
32
+ end
33
+
34
+ if master_password.nil? || master_password.empty?
35
+ raise MasterPasswordError
36
+ end
37
+
19
38
  @tome_filename = tome_filename
20
39
  @master_password = master_password
21
40
 
@@ -145,23 +164,53 @@ module Tome
145
164
  end
146
165
 
147
166
  def self.load_tome(tome_filename)
167
+ if tome_filename.nil? || tome_filename.empty?
168
+ raise ArgumentError
169
+ end
170
+
148
171
  return nil if !File.exist?(tome_filename)
149
172
 
150
173
  contents = File.open(tome_filename, 'rb') { |file| file.read }
151
174
  return nil if contents.length == 0
152
175
 
153
- values = YAML.load(contents)
154
- return nil if !values
155
-
156
- # TODO: Throw if these values are nil.
157
- # TODO: Verify version number, raise if incompatible.
158
- return {
159
- :version => values[:version],
160
- :salt => values[:salt],
161
- :iv => values[:iv],
162
- :stretch => values[:stretch],
163
- :store => values[:store]
164
- }
176
+ tome = YAML.load(contents)
177
+ return nil if !tome
178
+
179
+ validate_tome(tome)
180
+
181
+ return tome
182
+ end
183
+
184
+ def self.validate_tome(tome)
185
+ if tome[:version].nil? || tome[:version].class != Fixnum
186
+ raise FileFormatError, 'The tome database is invalid (missing or invalid version).'
187
+ end
188
+
189
+ if tome[:version] > FILE_VERSION
190
+ raise FileFormatError, "The tome database comes from a newer version of tome (v#{tome[:version]} > v#{FILE_VERSION}). Try updating tome."
191
+ end
192
+
193
+ if tome[:version] < FILE_VERSION
194
+ raise FileFormatError, "The tome database is incompatible with this version of tome (v#{tome[:version]} < v#{FILE_VERSION})."
195
+ end
196
+
197
+ # TODO: Check version number, do file format migration if necessary.
198
+
199
+ if tome[:salt].nil? || tome[:salt].class != String || tome[:salt].empty?
200
+ raise FileFormatError, 'The tome database is invalid (missing or invalid salt).'
201
+ end
202
+
203
+ if tome[:iv].nil? || tome[:iv].class != String || tome[:iv].empty?
204
+ raise FileFormatError, 'The tome database is invalid (missing or invalid IV).'
205
+ end
206
+
207
+ if tome[:stretch].nil? || tome[:stretch].class != Fixnum || tome[:stretch] < 0
208
+ raise FileFormatError, 'The tome database is invalid (missing or invalid key stretch).'
209
+ end
210
+
211
+ if tome[:store].nil? || tome[:store].class != String || tome[:store].empty?
212
+ raise FileFormatError, 'The tome database is invalid (missing or invalid store).'
213
+ end
165
214
  end
166
215
 
167
216
  def load_store(tome)
@@ -170,20 +219,19 @@ module Tome
170
219
  end
171
220
 
172
221
  begin
173
- store_yaml = Crypt.decrypt(
222
+ padded_store_yaml = Crypt.decrypt(
174
223
  :value => tome[:store],
175
224
  :password => @master_password,
176
225
  :stretch => tome[:stretch],
177
226
  :salt => tome[:salt],
178
227
  :iv => tome[:iv]
179
228
  )
180
- rescue ArgumentError
181
- # TODO: Should probably be raising an error here.
182
- return {}
183
229
  rescue OpenSSL::Cipher::CipherError
184
230
  raise MasterPasswordError
185
231
  end
186
232
 
233
+ store_yaml = Padding.unpad(padded_store_yaml)
234
+
187
235
  store = YAML.load(store_yaml)
188
236
  return store || {}
189
237
  end
@@ -194,12 +242,13 @@ module Tome
194
242
  end
195
243
 
196
244
  store_yaml = YAML.dump(store)
245
+ padded_store_yaml = Padding.pad(store_yaml, 1024, 4096)
197
246
 
198
247
  new_salt = Crypt.new_salt
199
248
  new_iv = Crypt.new_iv
200
249
 
201
250
  encrypted_store = Crypt.encrypt(
202
- :value => store_yaml,
251
+ :value => padded_store_yaml,
203
252
  :password => master_password,
204
253
  :salt => new_salt,
205
254
  :iv => new_iv,
@@ -259,6 +308,6 @@ module Tome
259
308
  readable_store { }
260
309
  end
261
310
 
262
- FILE_VERSION = 1
311
+ FILE_VERSION = 2
263
312
  end
264
313
  end
@@ -0,0 +1,193 @@
1
+ $usage = <<END
2
+ Usage:
3
+
4
+ tome set [user@]<domain> [password]
5
+
6
+ Create or update the password for an account.
7
+ Example: tome set foo@gmail.com
8
+
9
+ tome generate [user@]<domain>
10
+
11
+ Generate a random password for an account.
12
+ Example: tome generate reddit.com
13
+
14
+ tome get <pattern>
15
+
16
+ Show the passwords for all accounts matching the pattern.
17
+ Example: tome get youtube
18
+
19
+ tome copy <pattern>
20
+
21
+ Copy the password for the account matching the pattern.
22
+ Example: tome copy news.ycombinator.com
23
+
24
+ tome list
25
+
26
+ Show all stored accounts and passwords.
27
+ Example: tome list
28
+
29
+ tome delete [user@]<domain>
30
+
31
+ Delete the password for an account.
32
+ Example: tome delete foo@slashdot.org
33
+
34
+ tome rename <old> <new>
35
+
36
+ Rename the account information stored.
37
+ Example: tome rename twitter.com foo@twitter.com
38
+
39
+ tome help
40
+
41
+ Shows help for a specific command.
42
+ Example: tome help set
43
+
44
+ tome version
45
+
46
+ Shows the version of tome.
47
+ Example: tome version
48
+ END
49
+
50
+ $help_usage = <<END
51
+ tome help
52
+
53
+ Shows help for a specific command.
54
+
55
+ Usage:
56
+
57
+ tome help
58
+ tome help <command>
59
+
60
+ Examples:
61
+
62
+ tome help
63
+ tome help set
64
+ tome help help (so meta)
65
+
66
+ Alias: help, --help, -h
67
+ END
68
+
69
+ $set_usage = <<END
70
+ tome set
71
+
72
+ Create or update the password for an account. The user is optional.
73
+ If you do not specify a password, you will be prompted for one.
74
+
75
+ Usage:
76
+
77
+ tome set [user@]<domain> [password]
78
+
79
+ Examples:
80
+
81
+ tome set gmail.com
82
+ tome set gmail.com p4ssw0rd
83
+ tome set foo@gmail.com
84
+ tome set foo@gmail.com p4ssw0rd
85
+
86
+ Alias: set, s, add
87
+ END
88
+
89
+ $get_usage = <<END
90
+ tome get
91
+
92
+ Show the passwords for all accounts matching the pattern.
93
+ Matching is done with substring search. Wildcards are not supported.
94
+
95
+ Usage:
96
+
97
+ tome get <pattern>
98
+
99
+ Examples:
100
+
101
+ tome get gmail
102
+ tome get foo@
103
+ tome get foo@gmail.com
104
+
105
+ Alias: get, g, show
106
+ END
107
+
108
+ $delete_usage = <<END
109
+ tome delete
110
+
111
+ Delete the password for an account.
112
+
113
+ Usage:
114
+
115
+ tome delete [user@]<domain>
116
+
117
+ Examples:
118
+
119
+ tome delete gmail.com
120
+ tome delete foo@gmail.com
121
+
122
+ Alias: delete, del, remove, rm
123
+ END
124
+
125
+ $generate_usage = <<END
126
+ tome generate
127
+
128
+ Generate a random password for an account. The user is optional.
129
+
130
+ Usage:
131
+
132
+ tome generate [user@]<domain>
133
+
134
+ Examples:
135
+
136
+ tome generate gmail.com
137
+ tome generate foo@gmail.com
138
+
139
+ Alias: generate, gen
140
+ END
141
+
142
+ $copy_usage = <<END
143
+ tome copy
144
+
145
+ Copy the password for the account matching the pattern.
146
+ If more than one account matches the pattern, nothing happens.
147
+ Matching is done with substring search. Wildcards are not supported.
148
+
149
+ Usage:
150
+
151
+ tome copy <pattern>
152
+
153
+ Examples:
154
+
155
+ tome copy gmail
156
+ tome copy foo@
157
+ tome copy foo@gmail.com
158
+
159
+ Alias: copy, cp
160
+ END
161
+
162
+ $list_usage = <<END
163
+ tome list
164
+
165
+ Show all stored accounts and passwords.
166
+
167
+ Usage:
168
+
169
+ tome list
170
+
171
+ Examples:
172
+
173
+ tome list
174
+
175
+ Alias: list, ls
176
+ END
177
+
178
+ $rename_usage = <<END
179
+ tome rename
180
+
181
+ Rename the account information stored.
182
+
183
+ Usage:
184
+
185
+ tome rename <old> <new>
186
+
187
+ Examples:
188
+
189
+ tome rename gmail.com foo@gmail.com
190
+ tome rename foo@gmail.com bar@gmail.com
191
+
192
+ Alias: rename, ren, rn
193
+ END
@@ -1 +1 @@
1
- $version = '0.1.0.pre'
1
+ $version = '0.1.1.pre'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tome
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre
4
+ version: 0.1.1.pre
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -69,7 +69,9 @@ extra_rdoc_files: []
69
69
  files:
70
70
  - lib/tome/command.rb
71
71
  - lib/tome/crypt.rb
72
+ - lib/tome/padding.rb
72
73
  - lib/tome/tome.rb
74
+ - lib/tome/usage.rb
73
75
  - lib/tome/version.rb
74
76
  - lib/tome.rb
75
77
  - bin/tome