adamantite 0.0.2 → 0.0.5
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 +4 -4
- data/bin/adamantite +4 -2
- data/lib/adamantite.rb +117 -113
- data/lib/base/adamantite.rb +134 -35
- data/lib/base/editor/password_object_editor.rb +57 -0
- data/lib/base/password_object.rb +20 -0
- data/lib/file_utils/adamantite_file_utils.rb +104 -0
- data/lib/gui/form/password_object_form_window.rb +74 -0
- data/lib/gui/request/add_password_request.rb +8 -7
- data/lib/gui/request/login_request.rb +9 -13
- data/lib/gui/request/set_master_password_request.rb +7 -8
- data/lib/gui/request/update_master_password_request.rb +6 -5
- data/lib/gui/screen/copy_screen.rb +7 -5
- data/lib/gui/screen/login_screen.rb +14 -13
- data/lib/gui/screen/set_master_password_screen.rb +17 -15
- data/lib/gui/screen/show_screen.rb +8 -6
- data/lib/gui/screen/update_master_password_screen.rb +19 -16
- metadata +7 -6
- data/lib/adamantite_command_line.rb +0 -95
- data/lib/file_utils/file_utils.rb +0 -55
- data/lib/pw_utils/pw_utils.rb +0 -40
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 47190df18bbcffa68fd20d16d9ffb048f6f7b0b521104727f3e849a40aa29b48
|
4
|
+
data.tar.gz: 6acad9247d871f293b81f33976ce37b6465e043afacf5849fa31edf8640475ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36b1e5ac28a539c7a2e5461bcfa4b0d0c512ae572ff8e8b2b4faf56b52a9212e0d2566e89c963b7347782d03a43cfdde3179289a2f0b73a3dd582e7855a5be51
|
7
|
+
data.tar.gz: 7a46bbccbf113303a4ed2f6db562ecd4c59f4fbdf8abcaff582e9c7e0425869b02f4b1a3a778f356ce22f22196ffabff31cdb40234029e95f783584c538cbb74
|
data/bin/adamantite
CHANGED
data/lib/adamantite.rb
CHANGED
@@ -1,134 +1,138 @@
|
|
1
|
-
|
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
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require "gui/screen/set_master_password_screen"
|
15
|
-
require "gui/screen/update_master_password_screen"
|
16
|
-
require "gui/request/login_request"
|
17
|
-
require "gui/request/add_password_request"
|
18
|
-
require "gui/request/update_master_password_request"
|
19
|
-
require "gui/request/set_master_password_request"
|
3
|
+
require 'glimmer-dsl-libui'
|
4
|
+
require 'bcrypt'
|
5
|
+
require 'openssl'
|
6
|
+
require 'base64'
|
7
|
+
require 'json'
|
8
|
+
require 'io/console'
|
20
9
|
|
21
|
-
|
22
|
-
|
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'
|
23
24
|
|
24
|
-
|
25
|
-
|
25
|
+
module Adamantite
|
26
|
+
class AdamantiteApp
|
27
|
+
include Glimmer::LibUI::Application
|
28
|
+
include Adamantite::AdamantiteFileUtils
|
26
29
|
|
27
|
-
|
30
|
+
attr_accessor :add_password_request, :stored_passwords
|
28
31
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
34
37
|
|
35
|
-
|
36
|
-
|
38
|
+
login_request = GUI::Request::LoginRequest.new
|
39
|
+
login_screen(login_request: login_request).show
|
37
40
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
+
unless login_request.authenticated
|
42
|
+
exit(0)
|
43
|
+
end
|
41
44
|
|
42
|
-
|
43
|
-
|
44
|
-
|
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)
|
45
52
|
end
|
46
|
-
@master_password = login_request.master_password
|
47
|
-
@master_password_salt = login_request.master_password_salt
|
48
|
-
@adamantite_object = Adamantite::Base::Adamantite.new(@master_password)
|
49
|
-
@adamantite_object.authenticate!
|
50
|
-
@add_password_request = Adamantite::GUI::Request::AddPasswordRequest.new(@master_password, @master_password_salt)
|
51
|
-
end
|
52
53
|
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
body do
|
55
|
+
window('Adamantite', 800, 400) do
|
56
|
+
margined true
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
87
|
+
end
|
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
|
68
96
|
end
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
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
|
75
102
|
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
81
108
|
end
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
}
|
87
|
-
vertical_box {
|
88
|
-
form {
|
89
|
-
entry {
|
90
|
-
label 'Website Title'
|
91
|
-
text <=> [@add_password_request, :website_title]
|
92
|
-
}
|
93
|
-
entry {
|
94
|
-
label 'Username'
|
95
|
-
text <=> [@add_password_request, :username]
|
96
|
-
}
|
97
|
-
password_entry {
|
98
|
-
label 'Password'
|
99
|
-
text <=> [@add_password_request, :password]
|
100
|
-
}
|
101
|
-
password_entry {
|
102
|
-
label 'Confirm Password'
|
103
|
-
text <=> [@add_password_request, :password_confirmation]
|
104
|
-
}
|
105
|
-
}
|
106
|
-
horizontal_box {
|
107
|
-
button('Add Password') {
|
109
|
+
cell_rows <=> [self, :stored_passwords]
|
110
|
+
end
|
111
|
+
horizontal_box do
|
112
|
+
button('Add Password') do
|
108
113
|
on_clicked do
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
@
|
118
|
-
@add_password_request.password = ''
|
119
|
-
@add_password_request.password_confirmation = ''
|
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
|
120
123
|
end
|
124
|
+
password_object_form_window(adamantite: @adamantite, on_save: on_save).show
|
121
125
|
end
|
122
|
-
|
123
|
-
button('Update Master Password')
|
126
|
+
end
|
127
|
+
button('Update Master Password') do
|
124
128
|
on_clicked do
|
125
|
-
update_master_password_request =
|
129
|
+
update_master_password_request = GUI::Request::UpdateMasterPasswordRequest.new(@adamantite)
|
126
130
|
update_master_password_screen(update_master_password_request: update_master_password_request).show
|
127
131
|
end
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
134
138
|
end
|
data/lib/base/adamantite.rb
CHANGED
@@ -1,59 +1,158 @@
|
|
1
|
-
|
2
|
-
require "pw_utils/pw_utils"
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
5
|
-
|
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
|
-
|
12
|
+
attr_reader :authenticated, :master_password, :master_password_salt, :stored_passwords
|
12
13
|
|
13
|
-
|
14
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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!(
|
38
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
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
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base/password_object'
|
4
|
+
require 'file_utils/adamantite_file_utils'
|
5
|
+
require 'pw_utils/pw_utils'
|
6
|
+
|
7
|
+
module Adamantite
|
8
|
+
module Base
|
9
|
+
module Editor
|
10
|
+
class PasswordObjectEditor
|
11
|
+
include AdamantiteFileUtils
|
12
|
+
include PWUtils
|
13
|
+
|
14
|
+
# editable_user provides the temporary user object for editing
|
15
|
+
attr_reader :editable_password_object
|
16
|
+
|
17
|
+
# initializes a user editor with nil when creating a new user
|
18
|
+
# or with an existing user when editing an existing user
|
19
|
+
def initialize(adamantite, password_object = nil)
|
20
|
+
@password_object = password_object || PasswordObject.new
|
21
|
+
@adamantite = adamantite
|
22
|
+
reset_editable_password_object
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset_editable_password_object
|
26
|
+
@editable_password_object = PasswordObject.new
|
27
|
+
@editable_password_object.website_title = @password_object.website_title
|
28
|
+
@editable_password_object.username = @password_object.username
|
29
|
+
@editable_password_object.password = @password_object.password
|
30
|
+
@editable_password_object.password_confirmation = @password_object.password_confirmation
|
31
|
+
end
|
32
|
+
|
33
|
+
# saves editable user data and returns final user to add to DB/File/Array/etc...
|
34
|
+
def save
|
35
|
+
return false unless @password_object.password == @password_object.password_confirmation
|
36
|
+
|
37
|
+
@password_object.website_title = @editable_password_object.website_title
|
38
|
+
@password_object.username = @editable_password_object.username
|
39
|
+
@password_object.password = @editable_password_object.password
|
40
|
+
@password_object.password_confirmation = @editable_password_object.password_confirmation
|
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
|
47
|
+
@password_object
|
48
|
+
end
|
49
|
+
|
50
|
+
def cancel
|
51
|
+
reset_editable_password_object
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Adamantite
|
4
|
+
module Base
|
5
|
+
class PasswordObject
|
6
|
+
attr_accessor :website_title, :username, :password, :password_confirmation,
|
7
|
+
:row_index, :dir_name, :initial_dir_name
|
8
|
+
|
9
|
+
def initialize(website_title = nil, username = nil, password = nil,
|
10
|
+
password_confirmation = nil, row_index = nil, initial_dir_name = nil)
|
11
|
+
@website_title = website_title
|
12
|
+
@username = username
|
13
|
+
@password = password
|
14
|
+
@password_confirmation = password_confirmation
|
15
|
+
@row_index = row_index
|
16
|
+
@initial_dir_name = initial_dir_name
|
17
|
+
end
|
18
|
+
end
|
19
|
+
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
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base/editor/password_object_editor'
|
4
|
+
|
5
|
+
module Adamantite
|
6
|
+
module GUI
|
7
|
+
module Form
|
8
|
+
class PasswordObjectFormWindow
|
9
|
+
include Glimmer::LibUI::CustomWindow
|
10
|
+
|
11
|
+
# This holds the final user produced by the form
|
12
|
+
# And, a user can be optionally passed (e.g. `user_form(user: someuser)`) when editing an existing user
|
13
|
+
option :password_object, default: nil
|
14
|
+
option :on_save, default: ->(password_object) {}
|
15
|
+
option :adamantite
|
16
|
+
|
17
|
+
before_body do
|
18
|
+
@password_object_editor = Base::Editor::PasswordObjectEditor.new(adamantite, password_object)
|
19
|
+
end
|
20
|
+
|
21
|
+
body do
|
22
|
+
window('Password Form', 50, 50) do |password_object_form_editor|
|
23
|
+
margined true
|
24
|
+
|
25
|
+
vertical_box do
|
26
|
+
form do
|
27
|
+
entry do
|
28
|
+
label 'Website Title'
|
29
|
+
text <=> [@password_object_editor.editable_password_object, :website_title]
|
30
|
+
end
|
31
|
+
entry do
|
32
|
+
label 'Username'
|
33
|
+
text <=> [@password_object_editor.editable_password_object, :username]
|
34
|
+
end
|
35
|
+
|
36
|
+
password_entry do
|
37
|
+
label 'Password'
|
38
|
+
text <=> [@password_object_editor.editable_password_object, :password]
|
39
|
+
end
|
40
|
+
password_entry do
|
41
|
+
label 'Password Confirmation'
|
42
|
+
text <=> [@password_object_editor.editable_password_object, :password_confirmation]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
horizontal_box do
|
47
|
+
stretchy false
|
48
|
+
|
49
|
+
button('Save') do
|
50
|
+
on_clicked do
|
51
|
+
self.password_object = @password_object_editor.save
|
52
|
+
on_save.call(password_object)
|
53
|
+
password_object_form_editor.destroy
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
button('Cancel') do
|
58
|
+
on_clicked do
|
59
|
+
@password_object_editor.cancel
|
60
|
+
password_object_form_editor.destroy
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
on_closing do
|
67
|
+
@password_object_editor.cancel
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Adamantite
|
2
4
|
module GUI
|
3
5
|
module Request
|
4
6
|
class AddPasswordRequest
|
5
|
-
|
6
7
|
attr_accessor :website_title, :username, :password, :password_confirmation, :password_saved
|
7
8
|
|
8
9
|
def initialize(master_password, master_password_salt)
|
@@ -12,13 +13,13 @@ module Adamantite
|
|
12
13
|
end
|
13
14
|
|
14
15
|
def confirm_and_add_password!
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
return unless @password == @password_confirmation
|
17
|
+
|
18
|
+
@password_saved = true
|
19
|
+
pw_info_for_file = make_pw_info(@username, @password, @master_password, @master_password_salt)
|
20
|
+
write_pw_to_file(@website_title, **pw_info_for_file)
|
20
21
|
end
|
21
22
|
end
|
22
23
|
end
|
23
24
|
end
|
24
|
-
end
|
25
|
+
end
|
@@ -1,23 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base/adamantite'
|
4
|
+
|
1
5
|
module Adamantite
|
2
6
|
module GUI
|
3
7
|
module Request
|
4
|
-
class LoginRequest
|
5
8
|
|
6
|
-
attr_accessor :
|
9
|
+
attr_accessor :adamantite, :master_password, :authenticated
|
7
10
|
|
8
11
|
def authenticate!
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
master_pw_comparator = generate_master_pw_comparator(master_pw_hash)
|
13
|
-
|
14
|
-
if master_pw_comparator == master_password + master_pw_salt
|
15
|
-
@authenticated = true
|
16
|
-
@master_password = master_password
|
17
|
-
@master_password_salt = master_pw_salt
|
18
|
-
end
|
12
|
+
@adamantite = Base::Adamantite.new(master_password)
|
13
|
+
@adamantite.authenticate!
|
14
|
+
@authenticated = @adamantite.authenticated?
|
19
15
|
end
|
20
16
|
end
|
21
17
|
end
|
22
18
|
end
|
23
|
-
end
|
19
|
+
end
|
@@ -1,20 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base/adamantite'
|
4
|
+
|
1
5
|
module Adamantite
|
2
6
|
module GUI
|
3
7
|
module Request
|
4
8
|
class SetMasterPasswordRequest
|
5
|
-
|
6
9
|
attr_accessor :new_master_pw, :new_master_pw_confirmation, :success
|
7
10
|
|
8
11
|
def set_master_password!
|
9
12
|
@success = false
|
10
|
-
|
11
|
-
|
12
|
-
write_pw_to_file('master', password: master_pw_info[:master_pw_hash], salt: master_pw_info[:salt])
|
13
|
-
@success = true
|
14
|
-
end
|
15
|
-
@success
|
13
|
+
adamantite = Base::Adamantite.new(@new_master_pw)
|
14
|
+
@success = adamantite.serialize_master_password(@new_master_pw, @new_master_pw_confirmation)
|
16
15
|
end
|
17
16
|
end
|
18
17
|
end
|
19
18
|
end
|
20
|
-
end
|
19
|
+
end
|
@@ -1,14 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Adamantite
|
2
4
|
module GUI
|
3
5
|
module Request
|
4
6
|
class UpdateMasterPasswordRequest
|
7
|
+
attr_accessor :new_master_pw, :new_master_pw_confirmation, :adamantite
|
5
8
|
|
6
|
-
|
7
|
-
|
8
|
-
def initialize(adamantite_object)
|
9
|
-
@adamantite_object = adamantite_object
|
9
|
+
def initialize(adamantite)
|
10
|
+
@adamantite = adamantite
|
10
11
|
end
|
11
12
|
end
|
12
13
|
end
|
13
14
|
end
|
14
|
-
end
|
15
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Adamantite
|
2
4
|
module GUI
|
3
5
|
module Screen
|
@@ -6,13 +8,13 @@ module Adamantite
|
|
6
8
|
|
7
9
|
option :password_title
|
8
10
|
|
9
|
-
body
|
10
|
-
window('Copy', 400, 100)
|
11
|
+
body do
|
12
|
+
window('Adamantite - Copy Password', 400, 100) do
|
11
13
|
margined true
|
12
14
|
label("Copied password for #{password_title} to your clipboard.")
|
13
|
-
|
14
|
-
|
15
|
+
end
|
16
|
+
end
|
15
17
|
end
|
16
18
|
end
|
17
19
|
end
|
18
|
-
end
|
20
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
module PWManager
|
3
4
|
module GUI
|
@@ -7,19 +8,19 @@ module PWManager
|
|
7
8
|
|
8
9
|
option :login_request
|
9
10
|
|
10
|
-
body
|
11
|
-
window('Adamantite', 400, 100)
|
11
|
+
body do
|
12
|
+
window('Adamantite - Login', 400, 100) do
|
12
13
|
margined true
|
13
14
|
|
14
|
-
vertical_box
|
15
|
-
form
|
16
|
-
password_entry
|
15
|
+
vertical_box do
|
16
|
+
form do
|
17
|
+
password_entry do
|
17
18
|
label 'Master Password'
|
18
19
|
text <=> [login_request, :master_password]
|
19
|
-
|
20
|
-
|
20
|
+
end
|
21
|
+
end
|
21
22
|
|
22
|
-
button('Login')
|
23
|
+
button('Login') do
|
23
24
|
on_clicked do
|
24
25
|
login_request.authenticate!
|
25
26
|
# Destroy window if password is correct.
|
@@ -28,11 +29,11 @@ module PWManager
|
|
28
29
|
::LibUI.quit
|
29
30
|
end
|
30
31
|
end
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
35
36
|
end
|
36
37
|
end
|
37
38
|
end
|
38
|
-
end
|
39
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Adamantite
|
2
4
|
module GUI
|
3
5
|
module Screen
|
@@ -6,21 +8,21 @@ module Adamantite
|
|
6
8
|
|
7
9
|
option :set_master_password_request
|
8
10
|
|
9
|
-
body
|
10
|
-
window('Adamantite - Create Master Password', 450, 150)
|
11
|
+
body do
|
12
|
+
window('Adamantite - Create Master Password', 450, 150) do
|
11
13
|
margined true
|
12
|
-
vertical_box
|
13
|
-
form
|
14
|
-
password_entry
|
14
|
+
vertical_box do
|
15
|
+
form do
|
16
|
+
password_entry do
|
15
17
|
label 'Master Password'
|
16
18
|
text <=> [set_master_password_request, :new_master_pw]
|
17
|
-
|
18
|
-
password_entry
|
19
|
+
end
|
20
|
+
password_entry do
|
19
21
|
label 'Master Password Confirmation'
|
20
22
|
text <=> [set_master_password_request, :new_master_pw_confirmation]
|
21
|
-
|
22
|
-
|
23
|
-
button('Set Master Password')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
button('Set Master Password') do
|
24
26
|
on_clicked do
|
25
27
|
set_master_password_request.set_master_password!
|
26
28
|
if set_master_password_request.success
|
@@ -31,11 +33,11 @@ module Adamantite
|
|
31
33
|
set_master_password_request.new_master_pw_confirmation = ''
|
32
34
|
end
|
33
35
|
end
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
38
40
|
end
|
39
41
|
end
|
40
42
|
end
|
41
|
-
end
|
43
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Adamantite
|
2
4
|
module GUI
|
3
5
|
module Screen
|
@@ -6,14 +8,14 @@ module Adamantite
|
|
6
8
|
|
7
9
|
option :password
|
8
10
|
|
9
|
-
body
|
10
|
-
window('Show', 400, 100)
|
11
|
+
body do
|
12
|
+
window('Adamantite - Show Password', 400, 100) do
|
11
13
|
margined true
|
12
14
|
|
13
|
-
label(
|
14
|
-
|
15
|
-
|
15
|
+
label(password.to_s)
|
16
|
+
end
|
17
|
+
end
|
16
18
|
end
|
17
19
|
end
|
18
20
|
end
|
19
|
-
end
|
21
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Adamantite
|
2
4
|
module GUI
|
3
5
|
module Screen
|
@@ -6,26 +8,27 @@ module Adamantite
|
|
6
8
|
|
7
9
|
option :update_master_password_request
|
8
10
|
|
9
|
-
body
|
10
|
-
window('Adamantite - Update Master Password', 450, 150)
|
11
|
+
body do
|
12
|
+
window('Adamantite - Update Master Password', 450, 150) do
|
11
13
|
margined true
|
12
|
-
vertical_box
|
13
|
-
form
|
14
|
-
password_entry
|
14
|
+
vertical_box do
|
15
|
+
form do
|
16
|
+
password_entry do
|
15
17
|
label 'New Master Password'
|
16
18
|
text <=> [update_master_password_request, :new_master_pw]
|
17
|
-
|
18
|
-
password_entry
|
19
|
+
end
|
20
|
+
password_entry do
|
19
21
|
label 'New Master Password Confirmation'
|
20
22
|
text <=> [update_master_password_request, :new_master_pw_confirmation]
|
21
|
-
|
22
|
-
|
23
|
-
button('Update')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
button('Update') do
|
24
26
|
on_clicked do
|
25
27
|
new_master_pw = update_master_password_request.new_master_pw
|
26
28
|
new_master_pw_confirmation = update_master_password_request.new_master_pw_confirmation
|
27
|
-
|
28
|
-
|
29
|
+
pass = update_master_password_request.adamantite.update_master_password!(new_master_pw,
|
30
|
+
new_master_pw_confirmation)
|
31
|
+
if pass
|
29
32
|
body_root.destroy
|
30
33
|
::LibUI.quit
|
31
34
|
else
|
@@ -33,10 +36,10 @@ module Adamantite
|
|
33
36
|
update_master_password_request.new_master_pw_confirmation = ''
|
34
37
|
end
|
35
38
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
40
43
|
end
|
41
44
|
end
|
42
45
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: adamantite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jake Bruemmer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-11-
|
11
|
+
date: 2023-11-24 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A local password manager written in Ruby.
|
14
14
|
email: jakebruemmer@gmail.com
|
@@ -19,9 +19,11 @@ extra_rdoc_files: []
|
|
19
19
|
files:
|
20
20
|
- bin/adamantite
|
21
21
|
- lib/adamantite.rb
|
22
|
-
- lib/adamantite_command_line.rb
|
23
22
|
- lib/base/adamantite.rb
|
24
|
-
- lib/
|
23
|
+
- lib/base/editor/password_object_editor.rb
|
24
|
+
- lib/base/password_object.rb
|
25
|
+
- lib/file_utils/adamantite_file_utils.rb
|
26
|
+
- lib/gui/form/password_object_form_window.rb
|
25
27
|
- lib/gui/request/add_password_request.rb
|
26
28
|
- lib/gui/request/login_request.rb
|
27
29
|
- lib/gui/request/set_master_password_request.rb
|
@@ -31,7 +33,6 @@ files:
|
|
31
33
|
- lib/gui/screen/set_master_password_screen.rb
|
32
34
|
- lib/gui/screen/show_screen.rb
|
33
35
|
- lib/gui/screen/update_master_password_screen.rb
|
34
|
-
- lib/pw_utils/pw_utils.rb
|
35
36
|
homepage: https://github.com/jakebruemmer/adamantite
|
36
37
|
licenses:
|
37
38
|
- MIT
|
@@ -44,7 +45,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
44
45
|
requirements:
|
45
46
|
- - ">="
|
46
47
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
48
|
+
version: 3.1.0
|
48
49
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
50
|
requirements:
|
50
51
|
- - ">="
|
@@ -1,95 +0,0 @@
|
|
1
|
-
require "bcrypt"
|
2
|
-
require "openssl"
|
3
|
-
require "base64"
|
4
|
-
require "json"
|
5
|
-
require "io/console"
|
6
|
-
|
7
|
-
require "file_utils/file_utils"
|
8
|
-
require "pw_utils/pw_utils"
|
9
|
-
|
10
|
-
include Adamantite::FileUtils
|
11
|
-
include Adamantite::PWUtils
|
12
|
-
|
13
|
-
puts "Welcome to Adamantite."
|
14
|
-
|
15
|
-
if pw_file_exists?('master')
|
16
|
-
user_master_pw_info = get_master_pw_info
|
17
|
-
master_pw_hash = user_master_pw_info['password']
|
18
|
-
master_pw_salt = user_master_pw_info['salt']
|
19
|
-
|
20
|
-
master_pw = IO::console.getpass("Please enter your master password:")
|
21
|
-
master_pw_comparator = BCrypt::Password.new(master_pw_hash)
|
22
|
-
|
23
|
-
while master_pw_comparator != master_pw + master_pw_salt
|
24
|
-
puts "Entered password didn't match."
|
25
|
-
master_pw = IO::console.getpass("Please enter your master password:")
|
26
|
-
master_pw_hash = BCrypt::Password.create(master_pw + user_master_pw_info['salt'])
|
27
|
-
end
|
28
|
-
|
29
|
-
puts "Master password successfully entered."
|
30
|
-
puts "Here are your stored passwords:"
|
31
|
-
get_stored_pws.each_with_index do |pw, index|
|
32
|
-
puts "#{index + 1}. #{pw}"
|
33
|
-
end
|
34
|
-
|
35
|
-
puts "Would you like to enter another password? (Y/N)"
|
36
|
-
response = gets.chomp
|
37
|
-
while !["Y", "N"].include?(response)
|
38
|
-
puts "Please enter Y or N"
|
39
|
-
end
|
40
|
-
|
41
|
-
if response == "Y"
|
42
|
-
puts "What do you want to call this password?"
|
43
|
-
title = gets.chomp
|
44
|
-
puts "What is the username for #{title}?"
|
45
|
-
username = gets.chomp
|
46
|
-
pw = IO::console.getpass("Enter the password for this site.")
|
47
|
-
pw_confirmation = IO::console.getpass("Confirm the password for this site.")
|
48
|
-
|
49
|
-
while pw != pw_confirmation
|
50
|
-
puts "Those didn't match, please enter them again."
|
51
|
-
pw = IO::console.getpass("Enter the password for this site.")
|
52
|
-
pw_confirmation = IO::console.getpass("Confirm the password for this site.")
|
53
|
-
end
|
54
|
-
|
55
|
-
pw_info_for_file = make_pw_info(username, pw, master_pw, master_pw_salt)
|
56
|
-
write_pw_to_file(title, **pw_info_for_file)
|
57
|
-
puts "Successfully stored password for #{title}."
|
58
|
-
|
59
|
-
elsif response == "N"
|
60
|
-
puts "Exiting"
|
61
|
-
end
|
62
|
-
|
63
|
-
puts "Here are your stored passwords:"
|
64
|
-
stored_pws = get_stored_pws
|
65
|
-
stored_pws.each_with_index do |pw, index|
|
66
|
-
puts "#{index + 1}. #{pw}"
|
67
|
-
end
|
68
|
-
|
69
|
-
puts "Enter the number of the password that you would like to retrieve."
|
70
|
-
pw_entry = gets.chomp.to_i
|
71
|
-
|
72
|
-
pw_info = get_pw_file(stored_pws[pw_entry - 1])
|
73
|
-
stored_pw_selection = decrypt_pw(pw_info["iv"], pw_info['password'], master_pw, master_pw_salt)
|
74
|
-
|
75
|
-
IO.popen('pbcopy', 'w') { |f| f << stored_pw_selection }
|
76
|
-
|
77
|
-
puts "Your password has been copied to your clipboard."
|
78
|
-
|
79
|
-
else
|
80
|
-
puts "You don't have a master password. Please enter one now."
|
81
|
-
master_pw = IO::console.getpass("Enter your master password:")
|
82
|
-
master_pw_confirmation = IO::console.getpass("Confirm your master password:")
|
83
|
-
|
84
|
-
while master_pw != master_pw_confirmation
|
85
|
-
puts "Those didn't match, please enter them again."
|
86
|
-
master_pw = IO::console.getpass("Enter your master password:")
|
87
|
-
master_pw_confirmation = IO::console.getpass("Confirm your master password:")
|
88
|
-
end
|
89
|
-
|
90
|
-
master_pw_info = generate_master_pw_hash(master_pw)
|
91
|
-
|
92
|
-
write_pw_to_file('master', password: master_pw_info[:master_pw_hash], salt: master_pw_info[:salt])
|
93
|
-
|
94
|
-
puts "Wrote master pw to file."
|
95
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
require "json"
|
2
|
-
|
3
|
-
module Adamantite
|
4
|
-
module FileUtils
|
5
|
-
def home_dir
|
6
|
-
ENV['HOME']
|
7
|
-
end
|
8
|
-
|
9
|
-
def pwmanager_dir
|
10
|
-
File.join(home_dir, '.pwmanager')
|
11
|
-
end
|
12
|
-
|
13
|
-
def pwmanager_dir_exists?
|
14
|
-
Dir.exists?(pwmanager_dir)
|
15
|
-
end
|
16
|
-
|
17
|
-
def make_pwmanager_dir
|
18
|
-
Dir.mkdir(pwmanager_dir)
|
19
|
-
end
|
20
|
-
|
21
|
-
def pw_file(title)
|
22
|
-
File.join(pwmanager_dir, title)
|
23
|
-
end
|
24
|
-
|
25
|
-
def pw_file_exists?(title)
|
26
|
-
File.exists?(pw_file(title))
|
27
|
-
end
|
28
|
-
|
29
|
-
def write_pw_to_file(title, **kwargs)
|
30
|
-
if !pwmanager_dir_exists?
|
31
|
-
make_pwmanager_dir
|
32
|
-
end
|
33
|
-
|
34
|
-
File.open(pw_file(title), "w") do |f|
|
35
|
-
JSON.dump(kwargs, f)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def delete_pw_file(title)
|
40
|
-
File.delete(pw_file(title))
|
41
|
-
end
|
42
|
-
|
43
|
-
def get_pw_file(title)
|
44
|
-
JSON.load_file(pw_file(title))
|
45
|
-
end
|
46
|
-
|
47
|
-
def get_master_pw_info
|
48
|
-
get_pw_file('master')
|
49
|
-
end
|
50
|
-
|
51
|
-
def get_stored_pws
|
52
|
-
Dir.entries(pwmanager_dir).filter { |f| ![".", "..", "master"].include?(f) }
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
data/lib/pw_utils/pw_utils.rb
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
require "bcrypt"
|
2
|
-
require "openssl"
|
3
|
-
require "base64"
|
4
|
-
|
5
|
-
module Adamantite
|
6
|
-
module PWUtils
|
7
|
-
|
8
|
-
def make_pw_info(username, pw, master_pw, master_pw_salt)
|
9
|
-
cipher = OpenSSL::Cipher::AES256.new(:CBC)
|
10
|
-
cipher.encrypt
|
11
|
-
iv = cipher.random_iv
|
12
|
-
cipher.key = Digest::MD5.hexdigest(master_pw + master_pw_salt)
|
13
|
-
cipher_text = cipher.update(pw) + cipher.final
|
14
|
-
utf8_cipher_text = Base64.encode64(cipher_text).encode('utf-8')
|
15
|
-
utf8_iv = Base64.encode64(iv).encode('utf-8')
|
16
|
-
|
17
|
-
{username: username, password: utf8_cipher_text, iv: utf8_iv}
|
18
|
-
end
|
19
|
-
|
20
|
-
def decrypt_pw(iv, pw_hash, master_pw, master_pw_salt)
|
21
|
-
decrypt_cipher = OpenSSL::Cipher::AES256.new(:CBC)
|
22
|
-
decrypt_cipher.decrypt
|
23
|
-
iv = Base64.decode64(iv.encode('ascii-8bit'))
|
24
|
-
decrypt_cipher.iv = iv
|
25
|
-
decrypt_cipher.key = Digest::MD5.hexdigest(master_pw + master_pw_salt)
|
26
|
-
decrypt_text = Base64.decode64(pw_hash.encode('ascii-8bit'))
|
27
|
-
decrypt_cipher.update(decrypt_text) + decrypt_cipher.final
|
28
|
-
end
|
29
|
-
|
30
|
-
def generate_master_pw_hash(master_pw)
|
31
|
-
salt = BCrypt::Engine.generate_salt
|
32
|
-
master_pw_hash = BCrypt::Password.create(master_pw + salt)
|
33
|
-
{'salt': salt, 'master_pw_hash': master_pw_hash}
|
34
|
-
end
|
35
|
-
|
36
|
-
def generate_master_pw_comparator(master_pw_hash)
|
37
|
-
BCrypt::Password.new(master_pw_hash)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|