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 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