From 1be7036f696e2a92cdbd1eb8014174678addf16f Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 15 Mar 2025 21:55:10 +0100 Subject: [PATCH] unetd: cli: add support for changing network password This does not actually create a new private key. Instead, the salt is replaced, and a xor key is generated which when merged with the key derived from the new password transforms into the original private key. Signed-off-by: Felix Fietkau --- package/network/services/unetd/files/unet.uc | 147 ++++++++++++++++--- 1 file changed, 124 insertions(+), 23 deletions(-) diff --git a/package/network/services/unetd/files/unet.uc b/package/network/services/unetd/files/unet.uc index 30ad452c04c..ad45d4b8398 100644 --- a/package/network/services/unetd/files/unet.uc +++ b/package/network/services/unetd/files/unet.uc @@ -39,6 +39,7 @@ function network_get_string_file(str) let f = mkstemp(); f.write(str); f.flush(); + f.seek(); return f; } @@ -50,24 +51,52 @@ function network_get_file_string(f) return str; } -function __network_get_pubkey(pw_file, salt, rounds) +function network_keygen(pw_file, args, config, out_file, extra_args) { + let rounds = config.rounds; + let salt = config.salt; + let out, output, xorkey; + + if (!out_file) { + output = mkstemp(); + out_file = "/dev/fd/" + output.fileno(); + } + + if (extra_args) + extra_args = '"' + extra_args + '"'; + else + extra_args = ""; + args += ` -s ${rounds},${salt} -o ${out_file}`; + + if (config.xorkey) { + xorkey = network_get_string_file(config.xorkey); + args += " -x /dev/fd/" + xorkey.fileno(); + } + pw_file.seek(); + args += " <&" + pw_file.fileno() + " " + extra_args; + let rc = system("unet-tool " + args); - let pubkey_file = mkstemp(); - if (system(`unet-tool -P -s ${rounds},${salt} <&${pw_file.fileno()} >&${pubkey_file.fileno()}`)) - return ctx.command_failed("Failed to generate public key"); + if (xorkey) + xorkey.close(); - pubkey_file.seek(); - let pubkey = trim(pubkey_file.read("all")); - pubkey_file.close(); + if (output) + out = network_get_file_string(output); + else + out = true; - return pubkey; + if (rc != 0) + return; + + return out; } function network_get_pubkey(pw_file, network) { - return __network_get_pubkey(pw_file, network.config.salt, network.config.rounds); + let key = network_keygen(pw_file, '-P', network.config); + if (!key) + return ctx.command_failed("Failed to generate public key"); + return key; } function __network_fetch_password(ctx, named, confirm) @@ -81,7 +110,7 @@ function __network_fetch_password(ctx, named, confirm) return; } - let pw = model.cb.getpass("Network config password: "); + let pw = model.cb.getpass((confirm ? "Set new" : "Network") + " config password: "); if (length(pw) < 12) { if (ctx.invalid_argument) ctx.invalid_argument("Password must be at least 12 characters long"); @@ -115,6 +144,16 @@ function network_fetch_password(ctx, named, confirm) return pw_file; } +function network_generate_salt() +{ + let salt = readfile("/dev/urandom", 16); + if (length(salt) != 16) + return; + salt = map(split(salt, ""), (v) => ord(v)); + salt = join("", map(salt, (v) => sprintf("%02x", v))); + return salt; +} + function network_sign_data(ctx, name, network, pw_file, upload) { let rounds = network.config.rounds; @@ -125,12 +164,11 @@ function network_sign_data(ctx, name, network, pw_file, upload) let bin_file = "/etc/unetd/" + name + ".bin"; if (upload) bin_file += "." + time(); - writefile(json_file, sprintf("%.J\n", network)); - pw_file.seek(); - let ret = system(`unet-tool -S -s ${rounds},${salt} -o "${bin_file}" "${json_file}" <&${pw_file.fileno()}`); + writefile(json_file, sprintf("%.J\n", network)); + let ret = network_keygen(pw_file, '-S', network.config, bin_file, json_file); unlink(json_file); - if (ret) { + if (!ret) { if (ctx.command_failed) ctx.command_failed("Failed to sign network configuration"); return false; @@ -301,17 +339,19 @@ function network_create(ctx, argv, named) { if (!pw_file) return; - let salt = readfile("/dev/urandom", 16); - if (length(salt) != 16) + let salt = network_generate_salt(); + if (!salt) return ctx.unknown_error(); - salt = map(split(salt, ""), (v) => ord(v)); - salt = join("", map(salt, (v) => sprintf("%02x", v))); let rounds = 10000; + let xorkey_file = mkstemp(); + system(`unet-tool -G >&${xorkey_file.fileno()}`); + let xorkey = network_get_file_string(xorkey_file); + let network = { config: { - salt, rounds, + salt, rounds, xorkey, }, hosts: {}, }; @@ -702,6 +742,48 @@ function network_edit_exit_hook() return true; } + +function network_set_password(ctx, argv, named) +{ + let netdata = ctx.data.netdata; + let network = netdata.json; + + let pw_file = network_fetch_password(ctx, named); + if (!pw_file) + return; + + let salt = network_generate_salt(); + if (!salt) + return ctx.unknown_error(); + + let rounds = 10000; + let config = { ...network.config, salt }; + + let key = network_keygen(pw_file, '-G', network.config); + pw_file.close(); + + named.password = named["new-password"]; + pw_file = network_fetch_password(ctx, named, true); + if (!pw_file) + return; + + let key_file = network_get_string_file(key); + delete config.xorkey; + config.xorkey = network_keygen(pw_file, '-G -x /dev/fd/' + key_file.fileno(), config); + key_file.close(); + + if (!config.xorkey) { + delete named.password; + return ctx.unknown_error("Error generating key"); + } + + network.config = config; + netdata.changed = true; + netdata.password = named.password; + + return ctx.ok(); +} + function network_edit(ctx, argv) { let network = argv[0]; if (!network) { @@ -766,7 +848,7 @@ const network_status_args = [ } ]; -const network_sign_args = { +const network_password_arg = { password: { help: "Network configuration password", no_complete: true, @@ -777,10 +859,21 @@ const network_sign_args = { }, }; +const network_new_password_arg = { + "new-password": { + help: "New network configuration password", + no_complete: true, + args: { + type: "string", + min: 12, + } + }, +}; + const network_config_args = editor.object_create_params(UnetConfigEdit); const network_create_args = { - ...network_sign_args, + ...network_password_arg, ...network_config_args, ...network_local_args, host: { @@ -834,7 +927,7 @@ const network_join_args = { const network_invite_args = { ...network_enroll_args, - ...network_sign_args, + ...network_password_arg, }; const host_editor = { @@ -1081,6 +1174,14 @@ let UnetEdit = { return ctx.json("Network data", ctx.data.netdata.json); } }, + password: { + help: "Edit network password", + call: network_set_password, + named_args: { + ...network_password_arg, + ...network_new_password_arg + } + }, save: { help: "Save network data to json file", args: [ @@ -1137,7 +1238,7 @@ let UnetEdit = { }, apply: { help: "Apply changes", - named_args: network_sign_args, + named_args: network_password_arg, call: function(ctx, argv, named) { let netdata = ctx.data.netdata;