yamp 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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