]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
unetd: cli: add support for changing network password
authorFelix Fietkau <nbd@nbd.name>
Sat, 15 Mar 2025 20:55:10 +0000 (21:55 +0100)
committerFelix Fietkau <nbd@nbd.name>
Mon, 17 Mar 2025 12:17:14 +0000 (13:17 +0100)
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 <nbd@nbd.name>
package/network/services/unetd/files/unet.uc

index 30ad452c04cf58512b4658d418ce9d1b83c649c9..ad45d4b839826f4ca86d7e621d2b5fd249a4ea6f 100644 (file)
@@ -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;