add cli tool for signing network json files
authorFelix Fietkau <nbd@nbd.name>
Sun, 31 Jul 2022 19:56:36 +0000 (21:56 +0200)
committerFelix Fietkau <nbd@nbd.name>
Tue, 23 Aug 2022 11:48:18 +0000 (13:48 +0200)
Signed-off-by: Felix Fietkau <nbd@nbd.name>
.gitignore
CMakeLists.txt
auth-data.c [new file with mode: 0644]
auth-data.h [new file with mode: 0644]
cli.c [new file with mode: 0644]

index 78d50c7c6dab96ce0eac202a9324bad9334c8fde..3fc95a3d7eed900aeda38c1753065043345bd034 100644 (file)
@@ -6,3 +6,4 @@ CMakeFiles
 *.so
 *.dylib
 unetd
+unet-tool
index bd57aeb54882d6455749a6cfa8d1b74db2cada88..651ac804ad6714a9ed22b2232fc4691c2e37544b 100644 (file)
@@ -30,13 +30,16 @@ ELSE()
   SET(ubus "")
 ENDIF()
 
-ADD_LIBRARY(unet SHARED curve25519.c siphash.c sha512.c fprime.c f25519.c ed25519.c edsign.c)
+ADD_LIBRARY(unet SHARED curve25519.c siphash.c sha512.c fprime.c f25519.c ed25519.c edsign.c auth-data.c)
 TARGET_LINK_LIBRARIES(unet ubox)
 
 ADD_EXECUTABLE(unetd ${SOURCES})
 TARGET_LINK_LIBRARIES(unetd unet ubox ${ubus} blobmsg_json ${libjson} ${nl} ${bpf})
 
-INSTALL(TARGETS unetd unet
+ADD_EXECUTABLE(unet-tool cli.c)
+TARGET_LINK_LIBRARIES(unet-tool unet blobmsg_json ${libjson} ubox)
+
+INSTALL(TARGETS unetd unet unet-tool
        RUNTIME DESTINATION sbin
        LIBRARY DESTINATION lib
 )
diff --git a/auth-data.c b/auth-data.c
new file mode 100644 (file)
index 0000000..c262c54
--- /dev/null
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
+ */
+#include "edsign.h"
+#include "ed25519.h"
+#include "auth-data.h"
+
+int unet_auth_data_validate(const uint8_t *key, const void *buf, size_t len,
+                           const char **json_data)
+{
+       const struct unet_auth_hdr *hdr = buf;
+       const struct unet_auth_data *data = net_data_auth_data_hdr(buf);
+       struct edsign_verify_state vst;
+
+       if (len <= sizeof(*hdr) + sizeof(*data))
+               return -1;
+
+       len -= sizeof(*hdr);
+
+       if (hdr->magic != cpu_to_be32(UNET_AUTH_MAGIC) ||
+           hdr->version != 0 || data->flags != 0 ||
+           data->timestamp == 0)
+               return -1;
+
+       if (memcmp(data->pubkey, key, EDSIGN_PUBLIC_KEY_SIZE) != 0)
+               return -2;
+
+       edsign_verify_init(&vst, hdr->signature, data->pubkey);
+       edsign_verify_add(&vst, data, len);
+       if (!edsign_verify(&vst, hdr->signature, data->pubkey))
+               return -3;
+
+       if (*(char *)(data + len - 1) != 0)
+               return -2;
+
+       if (json_data)
+               *json_data = (const char *)(data + 1);
+
+       return 0;
+}
diff --git a/auth-data.h b/auth-data.h
new file mode 100644 (file)
index 0000000..7a5e832
--- /dev/null
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
+ */
+#ifndef __AUTH_DATA_H
+#define __AUTH_DATA_H
+
+#include <stdint.h>
+
+#include <libubox/utils.h>
+
+#include "edsign.h"
+#include "curve25519.h"
+
+#define UNET_AUTH_MAGIC 0x754e6574
+
+struct unet_auth_hdr {
+       uint32_t magic;
+
+       uint8_t version;
+       uint8_t _pad[3];
+
+       uint8_t signature[EDSIGN_SIGNATURE_SIZE];
+} __packed;
+
+struct unet_auth_data {
+       uint64_t timestamp;
+       uint8_t pubkey[CURVE25519_KEY_SIZE];
+       uint32_t flags;
+} __packed;
+
+int unet_auth_data_validate(const uint8_t *key, const void *buf, size_t len,
+                           const char **json_data);
+
+static inline const struct unet_auth_data *
+net_data_auth_data_hdr(const void *net_data)
+{
+       return net_data + sizeof(struct unet_auth_hdr);
+}
+
+#endif
diff --git a/cli.c b/cli.c
new file mode 100644 (file)
index 0000000..19215ae
--- /dev/null
+++ b/cli.c
@@ -0,0 +1,374 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
+ */
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <libubox/utils.h>
+#include "edsign.h"
+#include "ed25519.h"
+#include "curve25519.h"
+#include "auth-data.h"
+
+static uint8_t pubkey[EDSIGN_PUBLIC_KEY_SIZE];
+static uint8_t seckey[EDSIGN_PUBLIC_KEY_SIZE];
+static FILE *out_file;
+static enum {
+       CMD_UNKNOWN,
+       CMD_GENERATE,
+       CMD_PUBKEY,
+       CMD_HOST_PUBKEY,
+       CMD_VERIFY,
+       CMD_SIGN,
+} cmd;
+
+static void print_key(const uint8_t *key)
+{
+       char keystr[B64_ENCODE_LEN(EDSIGN_PUBLIC_KEY_SIZE)];
+
+       if (b64_encode(key, EDSIGN_PUBLIC_KEY_SIZE, keystr, sizeof(keystr)) < 0)
+               return;
+
+       fprintf(out_file, "%s\n", keystr);
+}
+
+static int usage(const char *progname)
+{
+       fprintf(stderr, "Usage: %s [command|options] [<file>]\n"
+               "Commands:\n"
+               "       -S                      Sign file\n"
+               "       -V                      Verify file\n"
+               "       -P                      Get pulic signing key from secret key\n"
+               "       -H                      Get pulic host key from secret key\n"
+               "       -G                      Generate new private key\n"
+               "\n"
+               "Options:\n"
+               "       -o <file>:              Set output file to <file> (defaults to stdout)\n"
+               "       -k <keyfile>|-:         Set public key from file or stdin\n"
+               "       -K <keyfile>|-:         Set secret key from file or stdin\n"
+               "\n", progname);
+       return 1;
+}
+
+static int cmd_sign(int argc, char **argv)
+{
+       struct unet_auth_hdr hdr = {
+               .magic = cpu_to_be32(UNET_AUTH_MAGIC),
+       };
+       struct unet_auth_data *data;
+       struct timeval tv;
+       struct stat st;
+       off_t len;
+       FILE *f;
+
+       if (argc != 1) {
+               fprintf(stderr, "Missing filename\n");
+               return 1;
+       }
+
+       if (gettimeofday(&tv, NULL)) {
+               perror("gettimeofday");
+               return 1;
+       }
+
+       if (stat(argv[0], &st) ||
+           (f = fopen(argv[0], "r")) == NULL) {
+               fprintf(stderr, "Input file not found\n");
+               return 1;
+       }
+
+       data = calloc(1, sizeof(*data) + st.st_size + 1);
+       data->timestamp = cpu_to_be64(tv.tv_sec);
+       len = fread(data + 1, 1, st.st_size, f);
+       fclose(f);
+
+       if (len != st.st_size) {
+               fprintf(stderr, "Error reading from input file\n");
+               return 1;
+       }
+
+       len += sizeof(*data);
+
+       memcpy(data->pubkey, pubkey, sizeof(pubkey));
+       edsign_sign(hdr.signature, pubkey, seckey, (const void *)data, len);
+
+       fwrite(&hdr, sizeof(hdr), 1, out_file);
+       fwrite(data, len, 1, out_file);
+
+       free(data);
+
+       return 0;
+}
+
+static int cmd_verify(int argc, char **argv)
+{
+       struct unet_auth_data *data;
+       struct unet_auth_hdr *hdr;
+       struct stat st;
+       off_t len;
+       FILE *f;
+       int ret = 1;
+
+       if (argc != 1) {
+               fprintf(stderr, "Missing filename\n");
+               return 1;
+       }
+
+       if (stat(argv[0], &st) ||
+           (f = fopen(argv[0], "r")) == NULL) {
+               fprintf(stderr, "Input file not found\n");
+               return 1;
+       }
+
+       if (st.st_size <= sizeof(*hdr) + sizeof(*data)) {
+               fprintf(stderr, "Input file too small\n");
+               fclose(f);
+               return 1;
+       }
+
+       hdr = calloc(1, st.st_size);
+       len = fread(hdr, 1, st.st_size, f);
+       fclose(f);
+
+       if (len != st.st_size) {
+               fprintf(stderr, "Error reading from input file\n");
+               return 1;
+       }
+
+       ret = unet_auth_data_validate(pubkey, hdr, len, NULL);
+       switch (ret) {
+       case -1:
+               fprintf(stderr, "Invalid input data\n");
+               break;
+       case -2:
+               fprintf(stderr, "Public key does not match\n");
+               break;
+       case -3:
+               fprintf(stderr, "Signature verification failed\n");
+               break;
+       }
+
+       free(hdr);
+       return ret;
+}
+
+static int cmd_host_pubkey(int argc, char **argv)
+{
+       curve25519_generate_public(pubkey, seckey);
+       print_key(pubkey);
+
+       return 0;
+}
+
+static int cmd_pubkey(int argc, char **argv)
+{
+       print_key(pubkey);
+
+       return 0;
+}
+
+static int cmd_generate(int argc, char **argv)
+{
+       FILE *f;
+       int ret;
+
+       f = fopen("/dev/urandom", "r");
+       if (!f) {
+               fprintf(stderr, "Can't open /dev/urandom\n");
+               return 1;
+       }
+
+       ret = fread(seckey, sizeof(seckey), 1, f);
+       fclose(f);
+
+       if (ret != 1) {
+               fprintf(stderr, "Can't read data from /dev/urandom\n");
+               return 1;
+       }
+
+       ed25519_prepare(seckey);
+       print_key(seckey);
+
+       return 0;
+}
+
+static bool parse_key(uint8_t *dest, const char *str)
+{
+       char keystr[B64_ENCODE_LEN(EDSIGN_PUBLIC_KEY_SIZE) + 2];
+       FILE *f;
+       int len;
+
+       if (!strcmp(str, "-"))
+               f = stdin;
+       else
+               f = fopen(str, "r");
+
+       if (!f) {
+               fprintf(stderr, "Can't open key file for reading\n");
+               return false;
+       }
+
+       len = fread(keystr, 1, sizeof(keystr) - 1, f);
+       if (f != stdin)
+               fclose(f);
+
+       keystr[len] = 0;
+
+       if (b64_decode(keystr, dest, EDSIGN_PUBLIC_KEY_SIZE) != EDSIGN_PUBLIC_KEY_SIZE) {
+               fprintf(stderr, "Failed to parse key data\n");
+               return false;
+       }
+
+       return true;
+}
+
+static bool cmd_needs_pubkey(void)
+{
+       switch (cmd) {
+       case CMD_VERIFY:
+               return true;
+       default:
+               return false;
+       }
+}
+
+static bool cmd_needs_key(void)
+{
+       switch (cmd) {
+       case CMD_SIGN:
+       case CMD_PUBKEY:
+       case CMD_HOST_PUBKEY:
+               return true;
+       default:
+               return false;
+       }
+}
+
+int main(int argc, char **argv)
+{
+       const char *progname = argv[0];
+       const char *out_filename = NULL;
+       bool has_key = false, has_pubkey = false;
+       int ret, ch;
+
+       while ((ch = getopt(argc, argv, "o:k:K:GHPSV")) != -1) {
+               switch (ch) {
+               case 'o':
+                       out_filename = optarg;
+                       break;
+               case 'k':
+                       if (has_pubkey)
+                               return usage(progname);
+
+                       if (!parse_key(pubkey, optarg)) {
+                               return 1;
+                       }
+
+                       has_pubkey = true;
+                       break;
+               case 'K':
+                       if (has_pubkey)
+                               return usage(progname);
+
+                       if (!parse_key(seckey, optarg)) {
+                               return 1;
+                       }
+
+                       has_key = true;
+
+                       edsign_sec_to_pub(pubkey, seckey);
+                       has_pubkey = true;
+                       break;
+               case 'G':
+                       if (cmd != CMD_UNKNOWN)
+                               return usage(progname);
+
+                       cmd = CMD_GENERATE;
+                       break;
+               case 'S':
+                       if (cmd != CMD_UNKNOWN)
+                               return usage(progname);
+
+                       cmd = CMD_SIGN;
+                       break;
+               case 'P':
+                       if (cmd != CMD_UNKNOWN)
+                               return usage(progname);
+
+                       cmd = CMD_PUBKEY;
+                       break;
+               case 'H':
+                       if (cmd != CMD_UNKNOWN)
+                               return usage(progname);
+
+                       cmd = CMD_HOST_PUBKEY;
+                       break;
+               case 'V':
+                       if (cmd != CMD_UNKNOWN)
+                               return usage(progname);
+
+                       cmd = CMD_VERIFY;
+                       break;
+               default:
+                       return usage(progname);
+               }
+       }
+
+       if (!has_key && cmd_needs_key()) {
+               fprintf(stderr, "Missing -K <key> argument\n");
+               return 1;
+       }
+
+       if (!has_pubkey && cmd_needs_pubkey()) {
+               fprintf(stderr, "Missing -k <key> argument\n");
+               return 1;
+       }
+
+       argc -= optind;
+       argv += optind;
+
+       if (out_filename) {
+               out_file = fopen(out_filename, "w");
+               if (!out_file) {
+                       fprintf(stderr, "Failed to open output file\n");
+                       return 1;
+               }
+       } else {
+               out_file = stdout;
+       }
+
+       ret = -1;
+       switch (cmd) {
+       case CMD_GENERATE:
+               ret = cmd_generate(argc, argv);
+               break;
+       case CMD_SIGN:
+               ret = cmd_sign(argc, argv);
+               break;
+       case CMD_PUBKEY:
+               ret = cmd_pubkey(argc, argv);
+               break;
+       case CMD_HOST_PUBKEY:
+               ret = cmd_host_pubkey(argc, argv);
+               break;
+       case CMD_VERIFY:
+               ret = cmd_verify(argc, argv);
+               break;
+       case CMD_UNKNOWN:
+               ret = usage(progname);
+               break;
+       }
+
+       if (out_file != stdout) {
+               fclose(out_file);
+               if (ret)
+                       unlink(out_filename);
+       }
+
+       return ret;
+}