adamantite 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb2fee60dd29d1cf4e5d3ca9fe052322b5e824769c4fb673068c75ec75bdd700
4
- data.tar.gz: 77b65b3e70c79ea50d945566f9f7b4af841d49449fab4ba69ecde594a0093a0a
3
+ metadata.gz: 47190df18bbcffa68fd20d16d9ffb048f6f7b0b521104727f3e849a40aa29b48
4
+ data.tar.gz: 6acad9247d871f293b81f33976ce37b6465e043afacf5849fa31edf8640475ce
5
5
  SHA512:
6
- metadata.gz: 8d512ad63718365e1076946093bbf180a2e48d98a74f1ca94ebcf7f3db0f8812a8d2a021b43c653762fecd29741e602ea5c6c92e5021658f550bb1d7bf0c6ede
7
- data.tar.gz: 2e68d7b07925cc5910fa6129371696191f0ca721c7676db9d61aad9daac4694682f3cebda5b7d3c237df3178b08064a6645e87225dbf6352d815e3c694f988d0
6
+ metadata.gz: 36b1e5ac28a539c7a2e5461bcfa4b0d0c512ae572ff8e8b2b4faf56b52a9212e0d2566e89c963b7347782d03a43cfdde3179289a2f0b73a3dd582e7855a5be51
7
+ data.tar.gz: 7a46bbccbf113303a4ed2f6db562ecd4c59f4fbdf8abcaff582e9c7e0425869b02f4b1a3a778f356ce22f22196ffabff31cdb40234029e95f783584c538cbb74
data/bin/adamantite CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
- require "adamantite"
2
+ # frozen_string_literal: true
3
3
 
4
- AdamantiteApp.launch
4
+ require 'adamantite'
5
+
6
+ Adamantite::AdamantiteApp.launch
data/lib/adamantite.rb CHANGED
@@ -1,133 +1,138 @@
1
- require "glimmer-dsl-libui"
2
- require "bcrypt"
3
- require "openssl"
4
- require "base64"
5
- require "json"
6
- require "io/console"
1
+ # frozen_string_literal: true
7
2
 
8
- require "file_utils/file_utils"
9
- require "pw_utils/pw_utils"
10
- require "base/adamantite"
11
- require "base/password_object"
12
- require "gui/screen/login_screen"
13
- require "gui/screen/copy_screen"
14
- require "gui/screen/show_screen"
15
- require "gui/screen/set_master_password_screen"
16
- require "gui/screen/update_master_password_screen"
17
- require "gui/request/login_request"
18
- require "gui/request/add_password_request"
19
- require "gui/request/update_master_password_request"
20
- require "gui/request/set_master_password_request"
21
- require "gui/form/password_object_form_window"
3
+ require 'glimmer-dsl-libui'
4
+ require 'bcrypt'
5
+ require 'openssl'
6
+ require 'base64'
7
+ require 'json'
8
+ require 'io/console'
22
9
 
23
- include Adamantite::FileUtils
24
- include Adamantite::PWUtils
10
+ require 'fileutils'
11
+ require 'file_utils/adamantite_file_utils'
12
+ require 'base/adamantite'
13
+ require 'base/password_object'
14
+ require 'gui/screen/login_screen'
15
+ require 'gui/screen/copy_screen'
16
+ require 'gui/screen/show_screen'
17
+ require 'gui/screen/set_master_password_screen'
18
+ require 'gui/screen/update_master_password_screen'
19
+ require 'gui/request/login_request'
20
+ require 'gui/request/add_password_request'
21
+ require 'gui/request/update_master_password_request'
22
+ require 'gui/request/set_master_password_request'
23
+ require 'gui/form/password_object_form_window'
25
24
 
26
- class AdamantiteApp
27
- include Glimmer::LibUI::Application
25
+ module Adamantite
26
+ class AdamantiteApp
27
+ include Glimmer::LibUI::Application
28
+ include Adamantite::AdamantiteFileUtils
28
29
 
29
- attr_accessor :add_password_request, :stored_passwords
30
+ attr_accessor :add_password_request, :stored_passwords
30
31
 
31
- before_body do
32
- if !pw_file_exists?('master')
33
- set_master_password_request = Adamantite::GUI::Request::SetMasterPasswordRequest.new
34
- set_master_password_screen(set_master_password_request: set_master_password_request).show
35
- end
32
+ before_body do
33
+ unless master_password_exists?
34
+ set_master_password_request = GUI::Request::SetMasterPasswordRequest.new
35
+ set_master_password_screen(set_master_password_request: set_master_password_request).show
36
+ end
36
37
 
37
- login_request = Adamantite::GUI::Request::LoginRequest.new
38
- login_screen(login_request: login_request).show
38
+ login_request = GUI::Request::LoginRequest.new
39
+ login_screen(login_request: login_request).show
39
40
 
40
- if !login_request.authenticated
41
- exit(0)
42
- end
41
+ unless login_request.authenticated
42
+ exit(0)
43
+ end
43
44
 
44
- @stored_passwords = get_stored_pws.map do |title|
45
- pw_info = get_pw_file(title)
46
- [title, pw_info["username"], 'Edit', 'Copy', 'Show', 'Delete']
45
+ @adamantite = login_request.adamantite
46
+ @stored_passwords = @adamantite.stored_passwords.map do |stored_password|
47
+ [stored_password[:website_title], stored_password[:username], 'Edit', 'Copy', 'Show', 'Delete']
48
+ end
49
+ @master_password = @adamantite.master_password
50
+ @master_password_salt = @adamantite.master_password_salt
51
+ @add_password_request = GUI::Request::AddPasswordRequest.new(@master_password, @master_password_salt)
47
52
  end
48
- @master_password = login_request.master_password
49
- @master_password_salt = login_request.master_password_salt
50
- @adamantite_object = Adamantite::Base::Adamantite.new(@master_password)
51
- @adamantite_object.authenticate!
52
- @add_password_request = Adamantite::GUI::Request::AddPasswordRequest.new(@master_password, @master_password_salt)
53
- end
54
53
 
55
- body {
56
- window('Adamantite', 800, 400) {
57
- margined true
54
+ body do
55
+ window('Adamantite', 800, 400) do
56
+ margined true
58
57
 
59
- vertical_box {
60
- table {
61
- text_column('Title')
62
- text_column('Username')
63
- button_column('Edit') {
64
- on_clicked do |row|
65
- on_save = lambda { |password_object|
66
- stored_password = []
67
- stored_password << password_object.website_title
68
- stored_password << password_object.username
69
- stored_password << 'Edit'
70
- stored_password << 'Copy'
71
- stored_password << 'Show'
72
- stored_password << 'Delete'
73
- @stored_passwords[password_object.row_index] = stored_password
74
- }
75
- password_title = @stored_passwords[row].first
76
- username = @stored_passwords[row][1]
77
- pw_info = get_pw_file(password_title)
78
- stored_pw_selection = decrypt_pw(pw_info["iv"], pw_info["password"], @master_password, @master_password_salt)
79
- password_object = Adamantite::Base::PasswordObject.new(password_title, username, stored_pw_selection, stored_pw_selection, row)
80
- password_object_form_window(master_pw: @master_password, master_pw_salt: @master_password_salt, on_save: on_save, password_object: password_object).show
58
+ vertical_box do
59
+ table do
60
+ text_column('Title')
61
+ text_column('Username')
62
+ button_column('Edit') do
63
+ on_clicked do |row|
64
+ on_save = lambda do |password_object|
65
+ stored_password = []
66
+ stored_password << password_object.website_title
67
+ stored_password << password_object.username
68
+ stored_password << 'Edit'
69
+ stored_password << 'Copy'
70
+ stored_password << 'Show'
71
+ stored_password << 'Delete'
72
+ @stored_passwords[password_object.row_index] = stored_password
73
+ adamantite_stored_password = {
74
+ 'dir_name': password_object.dir_name,
75
+ 'website_title': password_object.website_title,
76
+ 'username': @adamantite.retrieve_password_info(password_object.dir_name, 'username')
77
+ }
78
+ @adamantite.stored_passwords[password_object.row_index] = adamantite_stored_password
79
+ end
80
+ website_title = @stored_passwords[row][0]
81
+ username = @stored_passwords[row][1]
82
+ dir_name = @adamantite.stored_passwords[row][:dir_name]
83
+ password = @adamantite.retrieve_password_info(dir_name, 'password')
84
+ password_object = Base::PasswordObject.new(website_title, username, password, password, row, dir_name)
85
+ password_object_form_window(adamantite: @adamantite, on_save: on_save, password_object: password_object).show
86
+ end
81
87
  end
82
- }
83
- button_column('Copy') {
84
- on_clicked do |row|
85
- password_title = @stored_passwords[row].first
86
- pw_info = get_pw_file(password_title)
87
- stored_pw_selection = decrypt_pw(pw_info["iv"], pw_info["password"], @master_password, @master_password_salt)
88
- IO.popen('pbcopy', 'w') { |f| f << stored_pw_selection }
89
- copy_screen(password_title: password_title).show
88
+ button_column('Copy') do
89
+ on_clicked do |row|
90
+ IO.popen('pbcopy', 'w') do |f|
91
+ dir_name = @adamantite.stored_passwords[row][:dir_name]
92
+ f << @adamantite.retrieve_password_info(dir_name, 'password')
93
+ end
94
+ copy_screen(password_title: @stored_passwords[row].first).show
95
+ end
90
96
  end
91
- }
92
- button_column('Show') {
93
- on_clicked do |row|
94
- pw_info = get_pw_file(@stored_passwords[row].first)
95
- stored_pw_selection = decrypt_pw(pw_info["iv"], pw_info["password"], @master_password, @master_password_salt)
96
- show_screen(password: stored_pw_selection).show
97
+ button_column('Show') do
98
+ on_clicked do |row|
99
+ dir_name = @adamantite.stored_passwords[row][:dir_name]
100
+ show_screen(password: @adamantite.retrieve_password_info(dir_name, 'password')).show
101
+ end
97
102
  end
98
- }
99
- button_column('Delete') {
100
- on_clicked do |row|
101
- delete_pw_file(@stored_passwords[row].first)
102
- @stored_passwords.delete_at(row)
103
+ button_column('Delete') do
104
+ on_clicked do |row|
105
+ @adamantite.delete_password(@adamantite.stored_passwords[row][:dir_name])
106
+ @stored_passwords.delete_at(row)
107
+ end
103
108
  end
104
- }
105
- cell_rows <=> [self, :stored_passwords]
106
- }
107
- horizontal_box {
108
- button('Add Password') {
109
- on_clicked do
110
- on_save = lambda { |password_object|
111
- stored_password = []
112
- stored_password << password_object.website_title
113
- stored_password << password_object.username
114
- stored_password << 'Edit'
115
- stored_password << 'Copy'
116
- stored_password << 'Show'
117
- stored_password << 'Delete'
118
- @stored_passwords << stored_password
119
- }
120
- password_object_form_window(master_pw: @master_password, master_pw_salt: @master_password_salt, on_save: on_save).show
109
+ cell_rows <=> [self, :stored_passwords]
110
+ end
111
+ horizontal_box do
112
+ button('Add Password') do
113
+ on_clicked do
114
+ on_save = lambda do |password_object|
115
+ stored_password = []
116
+ stored_password << password_object.website_title
117
+ stored_password << password_object.username
118
+ stored_password << 'Edit'
119
+ stored_password << 'Copy'
120
+ stored_password << 'Show'
121
+ stored_password << 'Delete'
122
+ @stored_passwords << stored_password
123
+ end
124
+ password_object_form_window(adamantite: @adamantite, on_save: on_save).show
125
+ end
121
126
  end
122
- }
123
- button('Update Master Password') {
124
- on_clicked do
125
- update_master_password_request = Adamantite::GUI::Request::UpdateMasterPasswordRequest.new(@adamantite_object)
126
- update_master_password_screen(update_master_password_request: update_master_password_request).show
127
+ button('Update Master Password') do
128
+ on_clicked do
129
+ update_master_password_request = GUI::Request::UpdateMasterPasswordRequest.new(@adamantite)
130
+ update_master_password_screen(update_master_password_request: update_master_password_request).show
131
+ end
127
132
  end
128
- }
129
- }
130
- }
131
- }
132
- }
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
133
138
  end
@@ -1,59 +1,158 @@
1
- require "file_utils/file_utils"
2
- require "pw_utils/pw_utils"
1
+ # frozen_string_literal: true
3
2
 
4
- include Adamantite::FileUtils
5
- include Adamantite::PWUtils
3
+ require 'file_utils/adamantite_file_utils'
4
+ require 'rbnacl'
5
+ require 'base64'
6
6
 
7
7
  module Adamantite
8
8
  module Base
9
9
  class Adamantite
10
+ include AdamantiteFileUtils
10
11
 
11
- attr_accessor :authenticated
12
+ attr_reader :authenticated, :master_password, :master_password_salt, :stored_passwords
12
13
 
13
- def initialize(master_pw)
14
- @master_pw = master_pw
14
+ OPSLIMIT = 2**20
15
+ MEMLIMIT = 2**24
16
+ DIGEST_SIZE = 32
17
+
18
+ def initialize(master_password)
19
+ @master_password = master_password
15
20
  @authenticated = false
16
- @master_pw_exists = pw_file_exists?('master')
17
21
  end
18
22
 
19
23
  def authenticate!
20
- return false unless @master_pw_exists
21
- master_pw_info = get_master_pw_info
22
- master_pw_hash = master_pw_info['password']
23
- master_pw_salt = master_pw_info['salt']
24
- master_pw_comparator = generate_master_pw_comparator(master_pw_hash)
25
-
26
- if master_pw_comparator == @master_pw + master_pw_salt
27
- @authenticated = true
28
- @master_pw_hash = master_pw_hash
29
- @master_pw_salt = master_pw_salt
30
- @stored_passwords = get_stored_pws
24
+ if master_password_exists?
25
+ master_password_salt = get_master_password_salt
26
+ master_encrypted_vault_key = get_master_encrypted_vault_key
27
+ entered_master_password_hash = rbnacl_scrypt_hash(@master_password, master_password_salt)
28
+ vault = rbnacl_box(entered_master_password_hash)
29
+
30
+ begin
31
+ @master_vault_key = vault.decrypt(master_encrypted_vault_key)
32
+ @authenticated = true
33
+ @master_password_salt = master_password_salt
34
+ @vault = rbnacl_box(@master_vault_key)
35
+ update_stored_passwords!
36
+ true
37
+ rescue RbNaCl::CryptoError
38
+ false
39
+ end
40
+ else
41
+ false
42
+ end
43
+ end
44
+
45
+ def save_password(website_title, username, password, password_confirmation)
46
+ return unless password == password_confirmation && authenticated?
47
+
48
+ encrypted_file_name_ascii_8bit = @vault.encrypt(website_title)
49
+ dir_name = Base64.urlsafe_encode64(encrypted_file_name_ascii_8bit)
50
+ make_password_dir(dir_name)
51
+ write_to_file(password_file(dir_name, 'username'), @vault.encrypt(username), true)
52
+ write_to_file(password_file(dir_name, 'password'), @vault.encrypt(password), true)
53
+ update_stored_passwords!
54
+ dir_name
55
+ end
56
+
57
+ def delete_password(password_dir_name)
58
+ FileUtils.remove_entry_secure(password_file(password_dir_name))
59
+ update_stored_passwords!
60
+ end
61
+
62
+ def retrieve_password_info(website_title, info_name)
63
+ return unless authenticated?
64
+
65
+ @vault.decrypt(read_file(password_file(website_title, info_name), true))
66
+ end
67
+
68
+ def serialize_master_password(master_password, master_password_confirmation)
69
+ if master_password == master_password_confirmation
70
+ master_password_salt = rbnacl_random_bytes
71
+ master_password_hash = rbnacl_scrypt_hash(master_password, master_password_salt)
72
+ vault_key = rbnacl_random_bytes
73
+ vault = rbnacl_box(master_password_hash)
74
+ encrypted_vault_key = vault.encrypt(vault_key)
75
+ make_pwmanager_dir
76
+ write_master_info(master_password_salt, encrypted_vault_key)
31
77
  true
32
78
  else
33
79
  false
34
80
  end
35
81
  end
36
82
 
37
- def update_master_password!(new_master_pw, new_master_pw_confirmation)
38
- return false unless new_master_pw == new_master_pw_confirmation && @authenticated
83
+ def update_master_password!(new_master_password, new_master_password_confirmation)
84
+ if new_master_password == new_master_password_confirmation && authenticated?
85
+ new_master_password_salt = rbnacl_random_bytes
86
+ new_master_password_hash = rbnacl_scrypt_hash(new_master_password, new_master_password_salt)
87
+ vault_key = rbnacl_random_bytes
88
+ vault = rbnacl_box(new_master_password_hash)
89
+ encrypted_vault_key = vault.encrypt(vault_key)
39
90
 
40
- new_master_pw_info = generate_master_pw_hash(new_master_pw)
41
- new_master_pw_hash = new_master_pw_info[:master_pw_hash]
42
- new_master_pw_salt = new_master_pw_info[:salt]
91
+ new_password_data = @stored_passwords.map do |stored_password|
92
+ info = {}
93
+ info['website_title'] = stored_password[:website_title]
94
+ info['username'] = retrieve_password_info(stored_password[:dir_name], 'username')
95
+ info['password'] = retrieve_password_info(stored_password[:dir_name], 'password')
96
+ info
97
+ end
43
98
 
44
- @stored_passwords.each do |stored_password|
45
- pw_info = get_pw_file(stored_password)
46
- pw = decrypt_pw(pw_info['iv'], pw_info['password'], @master_pw, @master_pw_salt)
47
- pw_info_for_file = make_pw_info(pw_info['username'], pw, new_master_pw, new_master_pw_salt)
48
- write_pw_to_file(stored_password, **pw_info_for_file)
99
+ FileUtils.copy_entry(pwmanager_dir, pwmanager_tmp_dir)
100
+ FileUtils.remove_entry_secure(pwmanager_dir)
101
+ @vault = rbnacl_box(vault_key)
102
+ make_pwmanager_dir
103
+ new_password_data.each do |new_password|
104
+ website_title = new_password['website_title']
105
+ username = new_password['username']
106
+ password = new_password['password']
107
+ save_password(website_title, username, password, password)
108
+ end
109
+ FileUtils.remove_entry_secure(pwmanager_tmp_dir)
110
+ write_master_info(new_master_password_salt, encrypted_vault_key)
111
+ @master_password_salt = master_password_salt
112
+ @master_encrypted_vault_key = encrypted_vault_key
113
+ true
114
+ else
115
+ false
116
+ end
117
+ end
118
+
119
+ def authenticated?
120
+ @authenticated
121
+ end
122
+
123
+ def update_stored_passwords!
124
+ @stored_passwords = get_stored_pws.map do |stored_password|
125
+ {
126
+ 'dir_name': stored_password,
127
+ 'website_title': decode_encrypted_utf8_string(stored_password),
128
+ 'username': retrieve_password_info(stored_password, 'username')
129
+ }
49
130
  end
131
+ end
132
+
133
+ private
134
+
135
+ def rbnacl_box(key)
136
+ RbNaCl::SimpleBox.from_secret_key(key)
137
+ end
138
+
139
+ def rbnacl_random_bytes
140
+ RbNaCl::Random.random_bytes(RbNaCl::PasswordHash::SCrypt::SALTBYTES)
141
+ end
142
+
143
+ def rbnacl_scrypt_hash(password, salt)
144
+ RbNaCl::PasswordHash.scrypt(password, salt, OPSLIMIT, MEMLIMIT, DIGEST_SIZE)
145
+ end
146
+
147
+ def decode_encrypted_utf8_string(encrypted_string)
148
+ decoded_data = Base64.urlsafe_decode64(encrypted_string)
149
+ @vault.decrypt(decoded_data)
150
+ end
50
151
 
51
- write_pw_to_file('master', password: new_master_pw_hash, salt: new_master_pw_salt)
52
- @master_pw_hash = get_master_pw_info
53
- @master_pw = new_master_pw
54
- @master_pw_salt = new_master_pw_salt
55
- true
152
+ def write_master_info(master_password_salt, master_vault_key)
153
+ write_to_file(password_file('master_password_salt'), master_password_salt, true)
154
+ write_to_file(password_file('master_encrypted_vault_key'), master_vault_key, true)
56
155
  end
57
156
  end
58
157
  end
59
- end
158
+ end
@@ -1,12 +1,14 @@
1
- require "base/password_object"
2
- require "file_utils/file_utils"
3
- require "pw_utils/pw_utils"
1
+ # frozen_string_literal: true
2
+
3
+ require 'base/password_object'
4
+ require 'file_utils/adamantite_file_utils'
5
+ require 'pw_utils/pw_utils'
4
6
 
5
7
  module Adamantite
6
8
  module Base
7
9
  module Editor
8
10
  class PasswordObjectEditor
9
- include FileUtils
11
+ include AdamantiteFileUtils
10
12
  include PWUtils
11
13
 
12
14
  # editable_user provides the temporary user object for editing
@@ -14,10 +16,9 @@ module Adamantite
14
16
 
15
17
  # initializes a user editor with nil when creating a new user
16
18
  # or with an existing user when editing an existing user
17
- def initialize(master_pw, master_pw_salt, password_object = nil)
19
+ def initialize(adamantite, password_object = nil)
18
20
  @password_object = password_object || PasswordObject.new
19
- @master_pw = master_pw
20
- @master_pw_salt = master_pw_salt
21
+ @adamantite = adamantite
21
22
  reset_editable_password_object
22
23
  end
23
24
 
@@ -32,12 +33,17 @@ module Adamantite
32
33
  # saves editable user data and returns final user to add to DB/File/Array/etc...
33
34
  def save
34
35
  return false unless @password_object.password == @password_object.password_confirmation
36
+
35
37
  @password_object.website_title = @editable_password_object.website_title
36
38
  @password_object.username = @editable_password_object.username
37
39
  @password_object.password = @editable_password_object.password
38
40
  @password_object.password_confirmation = @editable_password_object.password_confirmation
39
- pw_info_for_file = make_pw_info(@password_object.username, @password_object.password, @master_pw, @master_pw_salt)
40
- write_pw_to_file(@password_object.website_title, **pw_info_for_file)
41
+ @password_object.dir_name = @adamantite.save_password(@password_object.website_title,
42
+ @password_object.username,
43
+ @password_object.password,
44
+ @password_object.password_confirmation)
45
+
46
+ @adamantite.delete_password(@password_object.initial_dir_name) if @password_object.initial_dir_name
41
47
  @password_object
42
48
  end
43
49
 
@@ -48,4 +54,4 @@ module Adamantite
48
54
  end
49
55
  end
50
56
  end
51
- end
57
+ end
@@ -1,15 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Adamantite
2
4
  module Base
3
5
  class PasswordObject
4
- attr_accessor :website_title, :username, :password, :password_confirmation, :row_index
6
+ attr_accessor :website_title, :username, :password, :password_confirmation,
7
+ :row_index, :dir_name, :initial_dir_name
5
8
 
6
- def initialize(website_title = nil, username = nil, password = nil, password_confirmation = nil, row_index = nil)
9
+ def initialize(website_title = nil, username = nil, password = nil,
10
+ password_confirmation = nil, row_index = nil, initial_dir_name = nil)
7
11
  @website_title = website_title
8
12
  @username = username
9
13
  @password = password
10
14
  @password_confirmation = password_confirmation
11
15
  @row_index = row_index
16
+ @initial_dir_name = initial_dir_name
12
17
  end
13
18
  end
14
19
  end
15
- end
20
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Adamantite
6
+ module AdamantiteFileUtils
7
+ def home_dir
8
+ ENV['HOME']
9
+ end
10
+
11
+ def pwmanager_dir
12
+ File.join(home_dir, '.adamantite')
13
+ end
14
+
15
+ def pwmanager_tmp_dir
16
+ File.join(home_dir, '.adamantite_tmp')
17
+ end
18
+
19
+ def pwmanager_dir_exists?
20
+ Dir.exist?(pwmanager_dir)
21
+ end
22
+
23
+ def make_pwmanager_dir
24
+ Dir.mkdir(pwmanager_dir)
25
+ end
26
+
27
+ def make_password_dir(password_dir_title)
28
+ Dir.mkdir(File.join(pwmanager_dir, password_dir_title))
29
+ end
30
+
31
+ def pw_file(title)
32
+ File.join(pwmanager_dir, title)
33
+ end
34
+
35
+ def password_file(*args)
36
+ File.join(pwmanager_dir, *args)
37
+ end
38
+
39
+ def pw_file_exists?(title)
40
+ File.exist?(pw_file(title))
41
+ end
42
+
43
+ def write_pw_to_file(title, **kwargs)
44
+ make_pwmanager_dir unless pwmanager_dir_exists?
45
+
46
+ File.open(pw_file(title), 'w') do |f|
47
+ JSON.dump(kwargs, f)
48
+ end
49
+ end
50
+
51
+ def write_to_file(file_name, file_contents, binary)
52
+ if binary
53
+ File.open(file_name, 'wb') do |f|
54
+ f.write(file_contents)
55
+ end
56
+ else
57
+ File.open(file_name, 'w') do |f|
58
+ f.write(file_contents)
59
+ end
60
+ end
61
+ end
62
+
63
+ def read_file(file_name, binary)
64
+ if binary
65
+ File.open(file_name, 'rb', &:read)
66
+ else
67
+ File.open(file_name, 'r', &:read)
68
+ end
69
+ end
70
+
71
+ def delete_pw_file(title)
72
+ File.delete(pw_file(title))
73
+ end
74
+
75
+ def get_pw_file(title)
76
+ JSON.load_file(pw_file(title))
77
+ end
78
+
79
+ def get_master_password_info
80
+ get_pw_file('master')
81
+ end
82
+
83
+ def get_master_password_hash
84
+ File.open(pw_file('master_password_hash'), 'rb', &:read)
85
+ end
86
+
87
+ def get_master_password_salt
88
+ File.open(pw_file('master_password_salt'), 'rb', &:read)
89
+ end
90
+
91
+ def get_master_encrypted_vault_key
92
+ File.open(pw_file('master_encrypted_vault_key'), 'rb', &:read)
93
+ end
94
+
95
+ def get_stored_pws
96
+ excluded_filenames = ['.', '..', 'master_password_hash', 'master_password_salt', 'master_encrypted_vault_key']
97
+ Dir.entries(pwmanager_dir).filter { |f| !excluded_filenames.include?(f) }
98
+ end
99
+
100
+ def master_password_exists?
101
+ pw_file_exists?('master_encrypted_vault_key') && pw_file_exists?('master_password_salt')
102
+ end
103
+ end
104
+ end