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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 12c65083b751c2460cbc017507793dd3202f27c701b58a1c6b7d7d0612a683ab
4
- data.tar.gz: 8c7d30ff3442a5d137058e00ccf0f1d3ee619c55214ba98feea29ed2a9caa66e
3
+ metadata.gz: 47190df18bbcffa68fd20d16d9ffb048f6f7b0b521104727f3e849a40aa29b48
4
+ data.tar.gz: 6acad9247d871f293b81f33976ce37b6465e043afacf5849fa31edf8640475ce
5
5
  SHA512:
6
- metadata.gz: 88eefbbf38673ff319d324bf51d101a2a6d0ba87a3f614bcdf8bbf1804bbcfd969ac70c7420bb82d0f17917d181f792bfca6a9d5ec6a0cc325c9da00d24972f6
7
- data.tar.gz: d2ae10748dc73c4f9d83984ca6e38d279b9a182893f311cb41f675b0c35e6ac290451292a3b15817f254a0ebb373d3f5b7f86cdc9073afb061b7ec899d993b6a
6
+ metadata.gz: 36b1e5ac28a539c7a2e5461bcfa4b0d0c512ae572ff8e8b2b4faf56b52a9212e0d2566e89c963b7347782d03a43cfdde3179289a2f0b73a3dd582e7855a5be51
7
+ data.tar.gz: 7a46bbccbf113303a4ed2f6db562ecd4c59f4fbdf8abcaff582e9c7e0425869b02f4b1a3a778f356ce22f22196ffabff31cdb40234029e95f783584c538cbb74
data/bin/adamantite CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
- require "adamantite"
2
+ # frozen_string_literal: true
3
3
 
4
- AdamantiteApp.launch
4
+ require 'adamantite'
5
+
6
+ Adamantite::AdamantiteApp.launch
data/lib/adamantite.rb CHANGED
@@ -1,134 +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 "gui/screen/login_screen"
12
- require "gui/screen/copy_screen"
13
- require "gui/screen/show_screen"
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
- include Adamantite::FileUtils
22
- 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'
23
24
 
24
- class AdamantiteApp
25
- include Glimmer::LibUI::Application
25
+ module Adamantite
26
+ class AdamantiteApp
27
+ include Glimmer::LibUI::Application
28
+ include Adamantite::AdamantiteFileUtils
26
29
 
27
- attr_accessor :add_password_request, :stored_passwords
30
+ attr_accessor :add_password_request, :stored_passwords
28
31
 
29
- before_body do
30
- if !pw_file_exists?('master')
31
- set_master_password_request = Adamantite::GUI::Request::SetMasterPasswordRequest.new
32
- set_master_password_screen(set_master_password_request: set_master_password_request).show
33
- 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
34
37
 
35
- login_request = Adamantite::GUI::Request::LoginRequest.new
36
- login_screen(login_request: login_request).show
38
+ login_request = GUI::Request::LoginRequest.new
39
+ login_screen(login_request: login_request).show
37
40
 
38
- if !login_request.authenticated
39
- exit(0)
40
- end
41
+ unless login_request.authenticated
42
+ exit(0)
43
+ end
41
44
 
42
- @stored_passwords = get_stored_pws.map do |title|
43
- pw_info = get_pw_file(title)
44
- [title, pw_info["username"], '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)
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
- body {
54
- window('Adamantite', 600, 400) {
55
- margined true
54
+ body do
55
+ window('Adamantite', 800, 400) do
56
+ margined true
56
57
 
57
- vertical_box {
58
- table {
59
- text_column('Title')
60
- text_column('Username')
61
- button_column('Copy') {
62
- on_clicked do |row|
63
- password_title = @stored_passwords[row].first
64
- pw_info = get_pw_file(password_title)
65
- stored_pw_selection = decrypt_pw(pw_info["iv"], pw_info["password"], @master_password, @master_password_salt)
66
- IO.popen('pbcopy', 'w') { |f| f << stored_pw_selection }
67
- copy_screen(password_title: password_title).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
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
- button_column('Show') {
71
- on_clicked do |row|
72
- pw_info = get_pw_file(@stored_passwords[row].first)
73
- stored_pw_selection = decrypt_pw(pw_info["iv"], pw_info["password"], @master_password, @master_password_salt)
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
- button_column('Delete') {
78
- on_clicked do |row|
79
- delete_pw_file(@stored_passwords[row].first)
80
- @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
81
108
  end
82
- }
83
-
84
- cell_rows <=> [self, :stored_passwords]
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
- @add_password_request.confirm_and_add_password!
110
- if @add_password_request.password_saved
111
- new_stored_password = [@add_password_request.website_title, @add_password_request.username]
112
- new_stored_password << 'Copy'
113
- new_stored_password << 'Show'
114
- new_stored_password << 'Delete'
115
- @stored_passwords << new_stored_password
116
- @add_password_request.website_title = ''
117
- @add_password_request.username = ''
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 = Adamantite::GUI::Request::UpdateMasterPasswordRequest.new(@adamantite_object)
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
@@ -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
@@ -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
- if @password == @password_confirmation
16
- @password_saved = true
17
- pw_info_for_file = make_pw_info(@username, @password, @master_password, @master_password_salt)
18
- write_pw_to_file(@website_title, **pw_info_for_file)
19
- end
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 :master_password, :master_password_salt, :authenticated
9
+ attr_accessor :adamantite, :master_password, :authenticated
7
10
 
8
11
  def authenticate!
9
- user_master_pw_info = get_master_pw_info
10
- master_pw_hash = user_master_pw_info['password']
11
- master_pw_salt = user_master_pw_info['salt']
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
- if @new_master_pw == @new_master_pw_confirmation
11
- master_pw_info = generate_master_pw_hash(@new_master_pw)
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
- attr_accessor :new_master_pw, :new_master_pw_confirmation, :adamantite_object
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("#{password}")
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
- success = update_master_password_request.adamantite_object.update_master_password!(new_master_pw, new_master_pw_confirmation)
28
- if success
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.2
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-09 00:00:00.000000000 Z
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/file_utils/file_utils.rb
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: '0'
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
@@ -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