From: jxnshi Date: Sat, 4 Jan 2025 20:48:43 +0000 (+0100) Subject: Working on host X-Git-Url: https://jxnshi.xyz/repos?a=commitdiff_plain;h=93c3503f6f8380678d2d0dd7143429ef0d925755;p=mesange.git Working on host --- diff --git a/client-cli/client-cli b/client-cli/client-cli index 21bf908..a0b1753 100755 Binary files a/client-cli/client-cli and b/client-cli/client-cli differ diff --git a/client-cli/command.odin b/client-cli/command.odin index aa5bb27..23902c3 100644 --- a/client-cli/command.odin +++ b/client-cli/command.odin @@ -1,6 +1,7 @@ package main import "core:os" +import "core:strings" import "core:sync" import fpath "core:path/filepath" @@ -38,7 +39,9 @@ handle_command :: proc(app: ^App, command: string) -> Maybe(Handle_Command_Error return nil case ":del", ":delete": - 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) + os.remove(profile_path) return nil diff --git a/client-cli/main.odin b/client-cli/main.odin index eb4c0ce..038a31d 100644 --- a/client-cli/main.odin +++ b/client-cli/main.odin @@ -48,7 +48,7 @@ App :: struct { info_bar_content: string, config: Config, - profile_password: Maybe(string), + profile_password: string, profile: Profile, seed_phrase_entropy: u128, @@ -100,6 +100,8 @@ app_init :: proc(storage_path: string) -> App { } app_deinit :: proc(app: ^App) { + profile_deinit(app.profile) + delete(app.password) config_deinit(app.config) nc.delwin(app.input_window) @@ -227,16 +229,10 @@ app_get_input :: proc(app: ^App, allocator := context.allocator) -> string { } } -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 @@ -263,7 +259,6 @@ handle_state :: proc(app: ^App) { 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) diff --git a/client-cli/profile.odin b/client-cli/profile.odin index 2687697..1abe74a 100644 --- a/client-cli/profile.odin +++ b/client-cli/profile.odin @@ -1,6 +1,5 @@ package main -import "core:crypto/aes" import "core:crypto/ed25519" import "core:crypto/sha3" import "core:encoding/json" @@ -9,7 +8,9 @@ import "core:math/rand" import "core:mem" import "core:os" import "core:strings" +import "core:sync" +import chacha "core:crypto/chacha20poly1305" import fpath "core:path/filepath" Profile :: struct { @@ -62,41 +63,39 @@ profile_to_parsed :: proc(profile: Profile) -> Profile_Parsed { 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 @@ -110,7 +109,9 @@ profile_deinit :: proc(profile: ^Profile) { } 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^) @@ -120,53 +121,51 @@ profile_update :: proc(profile: ^Profile, app: ^App) { 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 @@ -222,8 +221,13 @@ profile_set_private_key_from_seed_phrase :: proc(profile: ^Profile, seed_phrase: 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 diff --git a/client-cli/state.odin b/client-cli/state.odin index 7234732..771632c 100644 --- a/client-cli/state.odin +++ b/client-cli/state.odin @@ -7,9 +7,11 @@ import "core:fmt" 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" @@ -22,7 +24,6 @@ State :: enum { Ask_Profile, Ask_Profile_Seed_Phrase, Ask_Profile_Host, - Ask_Profile_Password_Protection, Ask_Profile_Set_Password, Ask_Profile_Confirm_Password, Ask_Profile_Password, @@ -81,12 +82,8 @@ state_load_profile :: proc(app: ^App) { 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) @@ -94,8 +91,12 @@ state_load_profile :: proc(app: ^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: @@ -108,6 +109,8 @@ state_load_profile :: proc(app: ^App) { } app.profile = profile + + app_set_state(app, .Main) } state_invalid_profile :: proc(app: ^App) { @@ -144,13 +147,6 @@ state_ask_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 @@ -164,10 +160,28 @@ state_ask_profile :: proc(app: ^App) { 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) } @@ -227,6 +241,7 @@ state_ask_profile_seed_phrase :: proc(app: ^App) { { "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.", "", @@ -248,7 +263,7 @@ state_ask_profile_seed_phrase :: proc(app: ^App) { 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) @@ -276,26 +291,15 @@ state_ask_profile_host :: proc(app: ^App) { } 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) { @@ -303,28 +307,19 @@ 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) { }