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