unet-tool: add support for generating keys from salt + seed passphrase
authorFelix Fietkau <nbd@nbd.name>
Fri, 13 Dec 2024 16:56:36 +0000 (17:56 +0100)
committerFelix Fietkau <nbd@nbd.name>
Sun, 15 Dec 2024 15:11:33 +0000 (16:11 +0100)
Uses PBKDF2-HMAC-SHA512 with configurable number of rounds to derive the key

Signed-off-by: Felix Fietkau <nbd@nbd.name>
cli.c
sha512.c
sha512.h

diff --git a/cli.c b/cli.c
index e3654f60bc7e5231a5e6258bc2b3d451fda30cb5..12d3bd5fc7be686fcae45bb37040929c71f3d8f2 100644 (file)
--- a/cli.c
+++ b/cli.c
@@ -13,6 +13,8 @@
 #include <stdlib.h>
 #include <fcntl.h>
 #include <errno.h>
+#include <pwd.h>
+#include <unistd.h>
 #include <libubox/utils.h>
 #include <libubox/uloop.h>
 #include <libubox/blobmsg.h>
@@ -35,6 +37,8 @@ static struct blob_buf b;
 static FILE *out_file;
 static bool quiet;
 static bool sync_done;
+static bool has_key;
+static bool password_prompt;
 static enum {
        CMD_UNKNOWN,
        CMD_GENERATE,
@@ -82,6 +86,9 @@ static int usage(const char *progname)
                "       -K <keyfile>|-:         Set secret key from file or stdin\n"
                "       -h <keyfile>|-          Set peer private key from file or stdin\n"
                "                               (for network data down-/upload)\n"
+               "       -s <n>,<salt>           Generating secret key from seed using <n> rounds and <salt>\n"
+               "                               (passphrase is read from stdin)\n"
+               "       -p                      Prompt for seed password\n"
                "\n", progname);
        return 1;
 }
@@ -429,6 +436,9 @@ static int cmd_generate(int argc, char **argv)
        FILE *f;
        int ret;
 
+       if (has_key)
+               goto out;
+
        f = fopen("/dev/urandom", "r");
        if (!f) {
                INFO("Can't open /dev/urandom\n");
@@ -444,6 +454,8 @@ static int cmd_generate(int argc, char **argv)
        }
 
        ed25519_prepare(seckey);
+
+out:
        print_key(seckey);
 
        return 0;
@@ -479,6 +491,58 @@ static bool parse_key(uint8_t *dest, const char *str)
        return true;
 }
 
+static void
+pbkdf2_hmac_sha512(uint8_t *dest, const void *key, size_t key_len,
+                  const void *salt, size_t salt_len,
+                  unsigned int rounds)
+{
+       uint8_t hash[SHA512_HASH_SIZE];
+
+       hmac_sha512(dest, key, key_len, salt, salt_len);
+
+       for (size_t i = 0; i < rounds - 1; i++) {
+               hmac_sha512(hash, key, key_len, dest, SHA512_HASH_SIZE);
+               for (size_t k = 0; k < SHA512_HASH_SIZE; k++)
+                       dest[k] ^= hash[k];
+       }
+}
+
+static bool parse_seed(uint8_t *dest, const char *salt)
+{
+       uint8_t hash[SHA512_HASH_SIZE];
+       char buf[256], *pw = buf;
+       unsigned long rounds;
+       size_t len = 0;
+       char *sep;
+
+       rounds = strtoul(salt, &sep, 0);
+       if (!rounds || *sep != ',') {
+               INFO("Invalid number of rounds\n");
+               return false;
+       }
+
+       if (password_prompt) {
+               pw = getpass("Password: ");
+               if (pw)
+                       len = strlen(pw);
+       } else {
+               len = fread(buf, 1, sizeof(buf), stdin);
+               if (!feof(stdin)) {
+                       INFO("Key data too long\n");
+                       return false;
+               }
+       }
+       if (len < 12) {
+               INFO("Key data too short\n");
+               return false;
+       }
+
+       pbkdf2_hmac_sha512(hash, pw, len, salt, strlen(salt), rounds);
+       memcpy(dest, hash, EDSIGN_PUBLIC_KEY_SIZE);
+
+       return true;
+}
+
 static bool cmd_needs_peerkey(void)
 {
        switch (cmd) {
@@ -530,11 +594,11 @@ int main(int argc, char **argv)
        const char *progname = argv[0];
        const char *out_filename = NULL;
        const char *cmd_arg = NULL;
-       bool has_key = false, has_pubkey = false;
+       bool has_pubkey = false;
        bool has_peerkey = false;
        int ret, ch;
 
-       while ((ch = getopt(argc, argv, "h:k:K:o:qD:GHPSU:V")) != -1) {
+       while ((ch = getopt(argc, argv, "h:k:K:o:qD:GHpPs:SU:V")) != -1) {
                switch (ch) {
                case 'D':
                case 'U':
@@ -567,6 +631,17 @@ int main(int argc, char **argv)
 
                        has_peerkey = true;
                        break;
+               case 's':
+                       if (has_pubkey)
+                               return usage(progname);
+
+                       if (!parse_seed(seckey, optarg))
+                               return 1;
+
+                       has_key = true;
+                       edsign_sec_to_pub(pubkey, seckey);
+                       has_pubkey = true;
+                       break;
                case 'k':
                        if (has_pubkey)
                                return usage(progname);
@@ -590,6 +665,9 @@ int main(int argc, char **argv)
                        edsign_sec_to_pub(pubkey, seckey);
                        has_pubkey = true;
                        break;
+               case 'p':
+                       password_prompt = true;
+                       break;
                case 'U':
                        cmd = CMD_UPLOAD;
                        cmd_arg = optarg;
index d06d65b653d4ebee110521eb7dc9d7d0ed3df5b0..57d3221a5bde1e7a98767c05585cc817fbedf476 100644 (file)
--- a/sha512.c
+++ b/sha512.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 Felix Fietkau <nbd@openwrt.org>
+ * Copyright (C) 2015-2024 Felix Fietkau <nbd@nbd.name>
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -256,3 +256,34 @@ void sha512_final(struct sha512_state *s, uint8_t *hash)
                memcpy(hash, tmp, len);
        }
 }
+
+void hmac_sha512(void *dest, const void *key, size_t key_len,
+                const void *data, size_t data_len)
+{
+       uint8_t k_pad[2 * SHA512_HASH_SIZE] = {};
+       struct sha512_state s;
+
+       if (key_len > 128) {
+               sha512_init(&s);
+               sha512_add(&s, key, key_len);
+               sha512_final(&s, k_pad);
+       } else {
+               memcpy(k_pad, key, key_len);
+       }
+
+       for (size_t i = 0; i < sizeof(k_pad); i++)
+               k_pad[i] ^= 0x36;
+
+       sha512_init(&s);
+       sha512_add(&s, k_pad, sizeof(k_pad));
+       sha512_add(&s, data, data_len);
+       sha512_final(&s, dest);
+
+       for (size_t i = 0; i < sizeof(k_pad); i++)
+               k_pad[i] ^= 0x36 ^ 0x5c;
+
+       sha512_init(&s);
+       sha512_add(&s, k_pad, sizeof(k_pad));
+       sha512_add(&s, dest, SHA512_HASH_SIZE);
+       sha512_final(&s, dest);
+}
index 7fb00545288ad8baccc83633f87722dd665d1c05..1909ae38c0974e2c8b70c0c89b5cc431225f49c6 100644 (file)
--- a/sha512.h
+++ b/sha512.h
@@ -60,4 +60,7 @@ sha512_final_get(struct sha512_state *s)
        return s->partial;
 }
 
+void hmac_sha512(void *dest, const void *key, size_t key_len,
+                const void *data, size_t data_len);
+
 #endif