yamp 0.1.0 → 1.0.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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +40 -0
  3. data/bin/yamp +44 -22
  4. data/lib/yamp.rb +41 -7
  5. metadata +5 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: af599784fb31d967ffc9f696ddc11691a5a294e8
4
- data.tar.gz: 288685df62e89aa8ec9c77e9b837c06e561b7419
3
+ metadata.gz: 95c693293fc5c7bd9d42cf0f3aeca7361f02e4b9
4
+ data.tar.gz: d9de29b05eda8ee531f14e319c6527eec01a77cc
5
5
  SHA512:
6
- metadata.gz: 4eec8131dc8f1f9b9c90992e32f6d37aaa79abf9b0092f51048c27669ed7159241cf88c09eb5cbfaad6d8e9bf4b39798523dbc2d2ca4f0f6151f3f3e78302b8d
7
- data.tar.gz: 91b4c2ed2b91d8ac24eb6cc6b7e7c0ce4df2445400a4a7e62ff1726ebf7fe804fcad9e4a1ae8fc896df42823793e93411863eaf6b206a6552b969c6bf2ca6557
6
+ metadata.gz: 9b99277a33f585c6cd412b21d60bfec039d103825d7ccc5cb0b45f1e288bc794398d571f1d44cec45ce1afce2e7829ca36908cd0fe9586582a4b22561bf31428
7
+ data.tar.gz: c455b39c38c2378c36502415272d21c0d90f067d27d9e4d0d943e6295d2d475b3368b95218810c9e7eeebbea5c683a67e8ec3cce86ce8ae5d7ac2a09f7abbc2e
data/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # YAMP
2
+
3
+ **Y**et **A**nother **M**inimalistic **P**asswordmanager
4
+
5
+ ## Description
6
+
7
+ A minimal CLI-based password manager built upon redis-cache.
8
+
9
+ ## Installation
10
+
11
+ *yamp* is available at [rubygems](https://rubygems.org). You can install it
12
+ by typing `gem install yamp` in your terminal or download it directly from https://rubygems.org/gems/yamp.
13
+
14
+ ## Setup
15
+
16
+ ### General
17
+
18
+ A running [redis](https://redis.io) server is required.
19
+
20
+ You'll be prompted for the master password on every action and if *yamp* hasn't
21
+ already been set up on the specified redis db instance the input will be used as the
22
+ new master password, else the input gets validated against an existing master password.
23
+
24
+ ### Configuration File
25
+
26
+ *yamp* creates a YAML configuration file in your home directory called *.yamp*
27
+ where you can customize some behavior and specify another redis server to connect to
28
+ instead of the local unprotected standard redis instance which is used by default.
29
+
30
+ ## Usage
31
+
32
+ Each command prompts interactively for the master password which is used to encrypt and
33
+ decrypt the data on the inside. When creating entries and no password is specified
34
+ explicitly a random and secure 32 byte password will be generated and used instead.
35
+ While a password is always mandatory for an entry a username is not.
36
+
37
+ ## Important notes
38
+
39
+ This project is still under active development and is yet to see many upcoming
40
+ changes, bug fixes and also feature implementations.
data/bin/yamp CHANGED
@@ -8,6 +8,9 @@ require 'yamp'
8
8
 
9
9
  ARGV << '-h' if ARGV.empty?
10
10
 
11
+ CONFIG = ENV['HOME'] + "/.yamp"
12
+ PASSWORD_CHARS = [*'0'..'9', *'a'..'z',*'A'..'Z','#','!','?','$','/','(',')','[',']']
13
+
11
14
  trap "SIGINT" do puts "\nk bye!"; exit(130) end
12
15
 
13
16
  class String
@@ -16,9 +19,6 @@ class String
16
19
  def yellow; "\033[33m#{self}\033[0m" end
17
20
  end
18
21
 
19
- CONFIG = ENV['HOME'] + "/.yamp"
20
- PASSWORD_CHARS = [*'0'..'9', *'a'..'z',*'A'..'Z','#','!','?','$','/','(',')','[',']']
21
-
22
22
  options = {}
23
23
  OptionParser.new do |opts|
24
24
  opts.banner = %q(
@@ -32,18 +32,20 @@ OptionParser.new do |opts|
32
32
  yamp -g twitter --to-clipboard
33
33
  yamp --delete facebook
34
34
  )
35
- opts.on('-a', '--add ENTRY', 'create an entry') {|id| options[:add] = id}
36
- opts.on('-g', '--get ENTRY', 'read an entry') {|id| options[:get] = id}
37
- opts.on('-u', '--update ENTRY', 'update an entry') {|id| options[:upd] = id}
38
- opts.on('-d', '--delete ENTRY', 'delete an entry') {|id| options[:del] = id}
39
- opts.on('-p', '--password PWD', 'specify a password') {|pwd| options[:pwd] = pwd}
40
- opts.on('-n', '--username USR', 'specify a username') {|usr| options[:usr] = usr}
41
- opts.on('-l', '--list', 'list all entries') {options[:lst] = true}
42
- opts.on('-c', '--to-clipboard', 'copy password to clipboard') {options[:clip] = true}
35
+ opts.on('-a', '--add ENTRY', 'create an entry') {|id| options[:add] = id}
36
+ opts.on('-g', '--get ENTRY', 'read an entry') {|id| options[:get] = id}
37
+ opts.on('-u', '--update ENTRY', 'update an entry') {|id| options[:upd] = id}
38
+ opts.on('-d', '--delete ENTRY', 'delete an entry') {|id| options[:del] = id}
39
+ opts.on('-p', '--password PWD', 'specify a password') {|pwd| options[:pwd] = pwd}
40
+ opts.on('-n', '--username USR', 'specify a username') {|usr| options[:usr] = usr}
41
+ opts.on('-l', '--list', 'list all entries') {options[:lst] = true}
42
+ opts.on('-c', '--to-clipboard', 'copy password to clipboard') {options[:clip] = true}
43
+ opts.on('-e', '--export FILE', 'export all entries to file') {|f| options[:exp] = f}
44
+ opts.on('-i', '--import FILE', 'import all entries from file') {|f| options[:imp] = f}
43
45
  end.parse!
44
46
 
45
- unless (options.keys & [:add, :del, :upd, :get, :lst]).size == 1
46
- puts "please specifiy either --add, --delete, --update, --get or --list"
47
+ unless (options.keys & [:add, :del, :upd, :get, :lst, :exp, :imp]).size == 1
48
+ puts 'ERR zero or too many actions specified'
47
49
  exit
48
50
  end
49
51
 
@@ -62,7 +64,7 @@ cfg = YAML.load_file(CONFIG)
62
64
 
63
65
  print cfg['ui']['prompt']
64
66
  master = STDIN.noecho(&:gets).chomp!; puts "\n"
65
- redis = Redis.new(
67
+ redis = Redis.new(
66
68
  url: "redis://" +
67
69
  (cfg['redis']['protected'] ? ":#{master}@" : "") +
68
70
  "#{cfg['redis']['ip']}:" +
@@ -79,9 +81,9 @@ end
79
81
  if id = options[:add]
80
82
  pwd = options[:pwd] ? options[:pwd] : 32.times.map {PASSWORD_CHARS.sample}.join
81
83
  unless @vault.add id, pwd, options[:usr]
82
- puts "entry already exists"
84
+ puts 'ERR entry already exists'
83
85
  else
84
- puts "+".green + " #{id}"
86
+ puts '+'.green + " #{id}"
85
87
  Clipboard.copy pwd if options[:clip]
86
88
  end
87
89
  exit
@@ -89,16 +91,16 @@ end
89
91
 
90
92
  if id = options[:del]
91
93
  unless @vault.remove id
92
- puts "no such entry"
94
+ puts 'ERR no such entry'
93
95
  else
94
- puts "-".red + " #{id}"
96
+ puts '-'.red + " #{id}"
95
97
  end
96
98
  exit
97
99
  end
98
100
 
99
101
  if id = options[:upd]
100
102
  unless options[:pwd] || options[:usr]
101
- puts "oops! did you forget something?"
103
+ puts 'ERR no new data'
102
104
  exit
103
105
  end
104
106
  pwd_updated = false
@@ -110,9 +112,9 @@ if id = options[:upd]
110
112
  usr_updated = @vault.update id, :usr, usr
111
113
  end
112
114
  unless pwd_updated || usr_updated
113
- puts "no such entry"
115
+ puts 'ERR no such entry'
114
116
  else
115
- puts "*".yellow + " #{id}"
117
+ puts '*'.yellow + " #{id}"
116
118
  Clipboard.copy pwd if options[:clip]
117
119
  end
118
120
  exit
@@ -122,7 +124,7 @@ if id = options[:get]
122
124
  password = @vault.get id, :pwd
123
125
  username = @vault.get id, :usr
124
126
  unless password || username
125
- puts "no such entry"
127
+ puts 'ERR no such entry'
126
128
  else
127
129
  puts username if username
128
130
  unless options[:clip]
@@ -138,3 +140,23 @@ if options[:lst]
138
140
  puts @vault.list.sort
139
141
  exit
140
142
  end
143
+
144
+ if export_file = options[:exp]
145
+ begin
146
+ @vault.export export_file
147
+ puts '>'.red + " #{export_file}"
148
+ rescue Exception => e
149
+ puts e.message
150
+ exit
151
+ end
152
+ end
153
+
154
+ if import_file = options[:imp]
155
+ begin
156
+ @vault.import import_file
157
+ puts '<'.green + " #{import_file}"
158
+ rescue Exception => e
159
+ puts e.message
160
+ exit
161
+ end
162
+ end
data/lib/yamp.rb CHANGED
@@ -1,6 +1,7 @@
1
1
 
2
2
  require 'redis'
3
3
  require 'openssl'
4
+ require 'yaml'
4
5
 
5
6
  module YAMP
6
7
 
@@ -11,7 +12,7 @@ module YAMP
11
12
  master_hash = redis.get "__mstr_h"
12
13
  master_salt = redis.get "__mstr_s"
13
14
  if master_hash && master_salt
14
- @master_key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(master, master_salt, 10000, 32)
15
+ @master_key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(master, hex_to_bytes(master_salt), 10000, 32)
15
16
  unless OpenSSL::Digest::SHA512.hexdigest(@master_key) == master_hash
16
17
  raise ArgumentError, 'ERR invalid password'
17
18
  end
@@ -19,7 +20,7 @@ module YAMP
19
20
  salt = OpenSSL::Random.random_bytes(32)
20
21
  @master_key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(master, salt, 10000, 32)
21
22
  redis.set "__mstr_h", OpenSSL::Digest::SHA512.hexdigest(@master_key)
22
- redis.set "__mstr_s", salt
23
+ redis.set "__mstr_s", bytes_to_hex(salt)
23
24
  end
24
25
  end
25
26
 
@@ -52,28 +53,61 @@ module YAMP
52
53
  @redis.keys - %w{__mstr_h __mstr_s}
53
54
  end
54
55
 
56
+ def export file
57
+ begin
58
+ File.open file, 'w' do |f|
59
+ f.write "__mstr_h: #{@redis.get '__mstr_h'}\n"
60
+ f.write "__mstr_s: #{@redis.get '__mstr_s'}\n"
61
+ @redis.keys.select {|k| @redis.type(k) == "hash"} .each do |k|
62
+ f.write(({k => @redis.hgetall(k)}).to_yaml[3..-1])
63
+ end
64
+ end
65
+ rescue Errno::ENOENT
66
+ raise ArgumentError, 'ERR invalid path'
67
+ end
68
+ end
69
+
70
+ def import file
71
+ begin
72
+ data = YAML.load_file file
73
+ @redis.flushdb
74
+ data.each do |key, val|
75
+ if key.match "^__mstr_[h|s]"
76
+ @redis.set key, val
77
+ next
78
+ end
79
+ data[key].each {|k, v| @redis.hset key, k, v}
80
+ end
81
+ rescue Errno::ENOENT
82
+ raise ArgumentError, 'ERR file not found'
83
+ end
84
+ end
85
+
55
86
  private
56
87
 
88
+ def bytes_to_hex s; s.unpack('H*').first end
89
+ def hex_to_bytes s; s.scan(/../).map {|x| x.hex}.pack('c*') end
90
+
57
91
  def encrypt id, data
58
92
  cipher = OpenSSL::Cipher.new 'chacha20'
59
93
  cipher.encrypt
60
94
  iv = @redis.hget id, "__iv"
61
95
  unless iv
62
96
  iv = cipher.random_iv
63
- @redis.hset id, "__iv", iv
97
+ @redis.hset id, "__iv", bytes_to_hex(iv)
64
98
  else
65
- cipher.iv = iv
99
+ cipher.iv = hex_to_bytes(iv)
66
100
  end
67
101
  cipher.key = @master_key
68
- cipher.update(data) + cipher.final
102
+ bytes_to_hex(cipher.update(data) + cipher.final)
69
103
  end
70
104
 
71
105
  def decrypt id, data
72
106
  cipher = OpenSSL::Cipher.new 'chacha20'
73
107
  cipher.decrypt
74
- cipher.iv = @redis.hget id, "__iv"
108
+ cipher.iv = hex_to_bytes(@redis.hget id, "__iv")
75
109
  cipher.key = @master_key
76
- cipher.update(data) + cipher.final
110
+ cipher.update(hex_to_bytes(data)) + cipher.final
77
111
  end
78
112
 
79
113
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yamp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Heilig
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-16 00:00:00.000000000 Z
11
+ date: 2017-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -57,8 +57,10 @@ email: holy708145@gmail.com
57
57
  executables:
58
58
  - yamp
59
59
  extensions: []
60
- extra_rdoc_files: []
60
+ extra_rdoc_files:
61
+ - README.md
61
62
  files:
63
+ - README.md
62
64
  - bin/yamp
63
65
  - lib/yamp.rb
64
66
  homepage: https://github.com/HIGHphen/yamp