adamantite 0.0.3 → 0.0.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb2fee60dd29d1cf4e5d3ca9fe052322b5e824769c4fb673068c75ec75bdd700
4
- data.tar.gz: 77b65b3e70c79ea50d945566f9f7b4af841d49449fab4ba69ecde594a0093a0a
3
+ metadata.gz: b339af86674a560875455a4ddab002be33d4adcc3219a0ab0f6613cdecc5f4e4
4
+ data.tar.gz: b710b5092de2a53d49757ee95c2767e3c5a23f5769cc36359fade7d21892784c
5
5
  SHA512:
6
- metadata.gz: 8d512ad63718365e1076946093bbf180a2e48d98a74f1ca94ebcf7f3db0f8812a8d2a021b43c653762fecd29741e602ea5c6c92e5021658f550bb1d7bf0c6ede
7
- data.tar.gz: 2e68d7b07925cc5910fa6129371696191f0ca721c7676db9d61aad9daac4694682f3cebda5b7d3c237df3178b08064a6645e87225dbf6352d815e3c694f988d0
6
+ metadata.gz: a8611efaf00d7d6aa3627a6a6f2d0e768ddc3d87ac757391c2536c89fa0e213d41b0b138ee51e66d6239acba0c3b2dd484106e8514392bc4c0fccd5f49be5fd2
7
+ data.tar.gz: f8628c2fc29cac9e0f27c54f94a0ab4cdd4ef435e3be7225b6c22bd691ddb0b109938a184a0bd5430746796c00ae818f990cf516d210a54e7ebcba0ad98e700a
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