info_bar_content: string,
config: Config,
- profile_password: Maybe(string),
+ profile_password: string,
profile: Profile,
seed_phrase_entropy: u128,
}
app_deinit :: proc(app: ^App) {
+ profile_deinit(app.profile)
+ delete(app.password)
config_deinit(app.config)
nc.delwin(app.input_window)
}
}
-app_get_profile_path :: proc(app: ^App, allocator := context.allocator) -> string {
- if _, ok := app.profile_password.?; ok {
- return fpath.join({ app.profiles_path, app.config.selected_profile.? }, allocator)
- } else {
- profile_filename := strings.concatenate({ app.config.selected_profile.?, ".json" }, allocator)
- return fpath.join({ app.profiles_path, profile_filename }, allocator)
- }
-}
-
app_reset_seed_phrase :: proc(app: ^App) {
+ sync.mutex_lock(&app.mutex)
+ defer sync.mutex_unlock(&app.mutex)
+
app.seed_phrase_entropy = rand.uint128()
sha_context: sha3.Context
case .Ask_Profile: state_ask_profile(app)
case .Ask_Profile_Seed_Phrase: state_ask_profile_seed_phrase(app)
case .Ask_Profile_Host: state_ask_profile_host(app)
- case .Ask_Profile_Password_Protection: state_ask_profile_password_protection(app)
case .Ask_Profile_Set_Password: state_ask_profile_set_password(app)
case .Ask_Profile_Confirm_Password: state_ask_profile_confirm_password(app)
case .Ask_Profile_Password: state_ask_profile_password(app)
package main
-import "core:crypto/aes"
import "core:crypto/ed25519"
import "core:crypto/sha3"
import "core:encoding/json"
import "core:mem"
import "core:os"
import "core:strings"
+import "core:sync"
+import chacha "core:crypto/chacha20poly1305"
import fpath "core:path/filepath"
Profile :: struct {
profile_load_from_name :: proc(name: string, app: ^App) -> (Profile, Maybe(Profile_Load_Error)) {
buffer: [4096]u8
- profile_content: string
- if profile_password, ok := app.profile_password.?; ok {
- encrypted_profile_path := fpath.join({ app.profiles_path, name }, context.temp_allocator)
- encrypted_profile_content, profile_read := os.read_entire_file_from_filename(encrypted_profile_path, context.temp_allocator)
+ profile_filename := strings.concatenate({ name, ".json" }, context.temp_allocator)
+ profile_path := fpath.join({ app.profiles_path, profile_filename }, context.temp_allocator)
- if !profile_read {
- return {}, .No_File_Present
- }
+ profile_content, profile_read := os.read_entire_file_from_filename(profile_path, context.temp_allocator)
- aes_context: aes.Context_GCM
- aes.init_gcm(&aes_context, transmute([]u8)app.profile_password.?)
+ if !profile_read {
+ return {}, .No_File_Present
+ }
- iv := encrypted_profile_content[:aes.GCM_IV_SIZE]
- tag := encrypted_profile_content[len(iv):][:aes.GCM_TAG_SIZE]
- ciphertext := encrypted_profile_content[len(iv) + len(tag):]
+ sha_context: sha3.Context
+ sha3.init_256(&sha_context)
- if !aes.open_gcm(&aes_context, buffer[:], iv, {}, ciphertext, tag) {
- return {}, .Invalid_Password
- }
+ sha3.update(&sha_context, transmute([]u8)app.profile_password)
- profile_content = string(buffer[:len(ciphertext)])
- } else {
- profile_filename := strings.concatenate({ name, ".json" }, context.temp_allocator)
- profile_path := fpath.join({ app.profiles_path, profile_filename }, context.temp_allocator)
+ password_hash: [sha3.DIGEST_SIZE_256]u8
+ sha3.final(&sha_context, password_hash[:])
- profile_content, profile_read := os.read_entire_file_from_filename(profile_path, context.temp_allocator)
+ chacha_context: chacha.Context
+ chacha.init_xchacha(&chacha_context, password_hash[:])
- if !profile_read {
- return {}, .No_File_Present
- }
+ iv := buffer[:chacha.XIV_SIZE]
+ tag := buffer[len(iv):][:chacha.TAG_SIZE]
+ ciphertext := buffer[len(iv) + len(tag):]
+
+ if !chacha.open(&chacha_context, buffer[:], iv, {}, ciphertext, tag) {
+ return {}, .Invalid_Password
}
+ json_string := string(buffer[:len(ciphertext)])
+
parsed_profile: Profile_Parsed
- err := json.unmarshal(transmute([]u8)profile_content, &parsed_profile, allocator = context.temp_allocator)
+ err := json.unmarshal(transmute([]u8)json_string, &parsed_profile, allocator = context.temp_allocator)
if err != nil {
return {}, .Invalid_File
}
profile_update :: proc(profile: ^Profile, app: ^App) {
- profile_path := app_get_profile_path(app, context.temp_allocator)
+ profile_filename := strings.concatenate({ app.config.selected_profile.?, ".json" }, context.temp_allocator)
+ profile_path := fpath.join({ app.profiles_path, profile_filename }, context.temp_allocator)
+
profile_file, err1 := os.open(profile_path, os.O_CREATE | os.O_TRUNC, 0o664)
parsed_profile := profile_to_parsed(profile^)
return
}
- if profile_password, ok := app.profile_password.?; ok {
- json_string_builder: strings.Builder
- strings.builder_init_none(&json_string_builder, context.temp_allocator)
+ json_string_builder: strings.Builder
+ strings.builder_init_none(&json_string_builder, context.temp_allocator)
- err2 := json.marshal_to_builder(&json_string_builder, parsed_profile, &json_marshal_options)
+ err2 := json.marshal_to_builder(&json_string_builder, parsed_profile, &json_marshal_options)
- if err2 != nil {
- app_set_info_bar(app, "Failed to save profile.")
- return
- }
+ if err2 != nil {
+ app_set_info_bar(app, "Failed to save profile.")
+ return
+ }
+
+ json_string := strings.to_string(json_string_builder)
- json_string := strings.to_string(json_string_builder)
+ sha_context: sha3.Context
+ sha3.init_256(&sha_context)
- aes_context: aes.Context_GCM
- aes.init_gcm(&aes_context, transmute([]u8)profile_password)
+ sha3.update(&sha_context, transmute([]u8)app.profile_password)
- buffer: [4096]u8
- iv := buffer[:aes.GCM_IV_SIZE]
- tag := buffer[len(iv):][:aes.GCM_TAG_SIZE]
+ password_hash: [sha3.DIGEST_SIZE_256]u8
+ sha3.final(&sha_context, password_hash[:])
- if rand.read(iv[:]) < len(iv) {
- app_set_info_bar(app, "Failed to save profile.")
- return
- }
+ chacha_context: chacha.Context
+ chacha.init_xchacha(&chacha_context, password_hash[:])
- aes.seal_gcm(&aes_context, buffer[len(iv) + len(tag):], tag, iv, {}, transmute([]u8)json_string)
+ buffer: [4096]u8
+ iv := buffer[:chacha.XIV_SIZE]
+ tag := buffer[len(iv):][:chacha.TAG_SIZE]
+
+ if rand.read(iv[:]) < len(iv) {
+ app_set_info_bar(app, "Failed to save profile.")
+ return
+ }
- content := string(buffer[:len(iv) + len(tag) + len(json_string)])
+ chacha.seal(&chacha_context, buffer[len(iv) + len(tag):], tag, iv, {}, transmute([]u8)json_string)
- _, err3 := os.write_string(profile_file, content)
+ content := string(buffer[:len(iv) + len(tag) + len(json_string)])
- if err3 != nil {
- app_set_info_bar(app, "Failed to save profile.")
- return
- }
- } else {
- profile_file_stream := os.stream_from_handle(profile_file)
- err2 := json.marshal_to_writer(profile_file_stream, parsed_profile, &json_marshal_options)
+ _, err3 := os.write_string(profile_file, content)
- if err2 != nil {
- app_set_info_bar(app, "Failed to save profile.")
- return
- }
+ if err3 != nil {
+ app_set_info_bar(app, "Failed to save profile.")
+ return
}
}
-profile_set_private_key_from_seed_phrase :: proc(profile: ^Profile, seed_phrase: string) -> Maybe(Profile_Set_Seed_Phrase_Error) {
+profile_set_private_key_from_seed_phrase :: proc(profile: ^Profile, app: ^App, seed_phrase: string) -> Maybe(Profile_Set_Seed_Phrase_Error) {
seed_phrase := seed_phrase
bip_0039_words := BIP_0039_WORDS
private_key_bytes: [sha3.DIGEST_SIZE_256]u8
sha3.final(&sha_context, private_key_bytes[:])
- if !ed25519.private_key_set_bytes(&profile.private_key, private_key_bytes[:]) {
- log.errorf("Failed to set private key bytes.")
+ {
+ sync.mutex_lock(&app.mutex)
+ defer sync.mutex_unlock(&app.mutex)
+
+ if !ed25519.private_key_set_bytes(&profile.private_key, private_key_bytes[:]) {
+ log.errorf("Failed to set private key bytes.")
+ }
}
return nil
import "core:log"
import "core:math/rand"
import "core:mem"
+import "core:net"
import "core:os"
import "core:slice"
import "core:strings"
+import "core:sync"
import fpath "core:path/filepath"
Ask_Profile,
Ask_Profile_Seed_Phrase,
Ask_Profile_Host,
- Ask_Profile_Password_Protection,
Ask_Profile_Set_Password,
Ask_Profile_Confirm_Password,
Ask_Profile_Password,
return
}
- encrypted_profile_path := fpath.join({ app.storage_path, selected_profile }, context.temp_allocator)
-
- if os.exists(encrypted_profile_path) && app.profile_password == nil {
- app_set_state(app, .Ask_Profile_Password)
- return
- }
+ profile_filename := strings.concatenate({ name, ".json" }, context.temp_allocator)
+ profile_path := fpath.join({ app.storage_path, profile_filename }, context.temp_allocator)
profile, profile_error := profile_load_from_name(selected_profile, app)
switch err {
case .No_File_Present:
case .Invalid_Password:
- app_set_info_bar(app, "Invalid password.")
+ if len(app.profile_password) != 0 {
+ app_set_info_bar(app, "Invalid password.")
+ }
+
app_set_state(app, .Ask_Profile_Password)
+
return
case .Invalid_File:
}
app.profile = profile
+
+ app_set_state(app, .Main)
}
state_invalid_profile :: proc(app: ^App) {
input := app_get_input(app)
- if input[0] == ':' {
- if !handle_command(app, input) {
- app_set_info_bar(app, "Invalid command.")
- return
- }
- }
-
if err, ok := handle_command(app, input).?; ok {
if err != .Not_A_Command {
return
return
}
- app.config.selected_profile = input
+ {
+ sync.mutex_lock(&app.mutex)
+ defer sync.mutex_unlock(&app.mutex)
+
+ app.config.selected_profile = input
+ }
config_update(app.config, app)
+ app_set_state(app, .Ask_Profile_Password)
+}
+
+state_ask_profile_password :: proc(app: ^App) {
+ app_set_box_message(
+ app,
+ {
+ "Please enter profile password.",
+ },
+ )
+
+ app.profile_password = app_get_input(app)
+
app_set_state(app, .Load_Profile)
}
{
"Please enter either your seed phrase",
"or the one bellow.",
+ "Words are separated by spaces.",
"You can type :regen to generate",
"a new seed phrase.",
"",
return
}
- err2 := profile_set_private_key_from_seed_phrase(&app.profile, input)
+ err2 := profile_set_private_key_from_seed_phrase(&app.profile, app, input)
if err2 != nil {
log.errorf("Invalid seed phrase with error %v", err2)
} else {
return
}
-
-}
-
-state_ask_profile_password_protection :: proc(app: ^App) {
- app_set_box_message(
- app,
- {
- "Should this profile be password protected?",
- "Please type 'yes' or 'no'."
- },
- )
+ {
+ sync.mutex_lock(&app.mutex)
+ defer sync.mutex_unlock(&app.mutex)
- input := app_get_input(app)
-
- switch input {
- case "yes": app_set_state(app, .Ask_Profile_Set_Password)
- case "no": app_set_state(app, .Ask_Profile_Seed_Phrase)
- case: app_set_info_bar(app, "Invalid answer.")
+ app.profile.host = input
}
+
+ app_set_state(app, .Ask_Profile_Set_Password)
}
state_ask_profile_set_password :: proc(app: ^App) {
app,
{
"Please enter a password.",
+ "Leave empty for no password.",
},
)
+
+ input := app_get_input(app)
+
+ app_set_state(app, .Ask_Profile_Confirm_Password)
}
state_ask_profile_confirm_password :: proc(app: ^App) {
}
-state_ask_profile_password :: proc(app: ^App) {
- app_set_box_message(
- app,
- {
- "Profile is encrypted.",
- "Please enter profile password."
- },
- )
-
- app.profile_password = app_get_input(app)
-
- app_set_state(app, .Load_Profile)
-}
-
state_main :: proc(app: ^App) {
}