]> jxnshi.xyz Git - mesange.git/commitdiff
Working on host
authorjxnshi <jxnshi@cock.li>
Sat, 4 Jan 2025 20:48:43 +0000 (21:48 +0100)
committerjxnshi <jxnshi@cock.li>
Sat, 4 Jan 2025 20:48:43 +0000 (21:48 +0100)
client-cli/client-cli
client-cli/command.odin
client-cli/main.odin
client-cli/profile.odin
client-cli/state.odin

index 21bf908f611d68637285e0929b77d85ddc53cc40..a0b1753148dae6dd130684d23223941d6a5d084b 100755 (executable)
Binary files a/client-cli/client-cli and b/client-cli/client-cli differ
index aa5bb2716765127741440fecc638160973a14102..23902c3c892c9ceb8bfabad462cdbc76b22e7257 100644 (file)
@@ -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
index eb4c0ced9fbd9e421e15fdad70727918b3c3d00f..038a31d074d7a4d7d00fc68a08be833c19bb0f03 100644 (file)
@@ -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)
index 2687697eb2c0daa389ecc0ab5ab3e8cd27d8a101..1abe74a50806a6a5a8855e4406bc050b1beb6ac7 100644 (file)
@@ -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
index 7234732e95ea259ebf4d78caa771c6d88c2f7115..771632c95abd30a7abf30af89264add634d38211 100644 (file)
@@ -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) {
     
 }