From df328249e2d5d9075e27b8a986a780aa3ecbd270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20M=C3=B6rlein?= Date: Sat, 13 Feb 2021 19:40:49 +0100 Subject: [PATCH 1/3] respondd: implement ed25519 signature As of now, the private key is hardcoded into respondd. This will be changed later. --- net/respondd/Makefile | 2 +- net/respondd/src/CMakeLists.txt | 7 ++- net/respondd/src/respondd.c | 75 +++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/net/respondd/Makefile b/net/respondd/Makefile index 033dda73e..0c641ecf0 100644 --- a/net/respondd/Makefile +++ b/net/respondd/Makefile @@ -11,7 +11,7 @@ include $(INCLUDE_DIR)/cmake.mk define Package/respondd SECTION:=net CATEGORY:=Network - DEPENDS:=@IPV6 +libjson-c + DEPENDS:=@IPV6 +libjson-c +libecdsautil TITLE:=Responds to multicast queries with answers generated by Lua code endef diff --git a/net/respondd/src/CMakeLists.txt b/net/respondd/src/CMakeLists.txt index 21cb75641..a53af38ef 100644 --- a/net/respondd/src/CMakeLists.txt +++ b/net/respondd/src/CMakeLists.txt @@ -8,11 +8,16 @@ find_package(JSON_C REQUIRED) set_property(DIRECTORY PROPERTY COMPILE_DEFINITIONS _GNU_SOURCE) +# libecdsautil +find_package(PkgConfig REQUIRED QUIET) +pkg_check_modules(ECDSAUTIL REQUIRED ecdsautil) +include_directories(${ECDSAUTIL_INCLUDE_DIRS}) + add_executable(respondd respondd.c) set_property(TARGET respondd PROPERTY COMPILE_FLAGS "-Wall -std=c99 -fno-strict-aliasing ${JSON_C_CFLAGS_OTHER}") set_property(TARGET respondd PROPERTY LINK_FLAGS "${JSON_C_LDFLAGS_OTHER}") set_property(TARGET respondd APPEND PROPERTY INCLUDE_DIRECTORIES ${JSON_C_INCLUDE_DIR}) -target_link_libraries(respondd ${JSON_C_LIBRARIES} dl) +target_link_libraries(respondd ${JSON_C_LIBRARIES} ${ECDSAUTIL_LIBRARIES} dl) install(TARGETS respondd RUNTIME DESTINATION bin) diff --git a/net/respondd/src/respondd.c b/net/respondd/src/respondd.c index 70ea3477d..df13a59dd 100644 --- a/net/respondd/src/respondd.c +++ b/net/respondd/src/respondd.c @@ -53,6 +53,9 @@ #include #include +#include +#include + #define SCHEDULE_LEN 8 #define REQUEST_MAXLEN 256 #define MAX_MULTICAST_DELAY_DEFAULT 0 @@ -359,6 +362,68 @@ static struct json_object * eval_providers(struct provider_list *providers) { return ret; } + +static void public_from_secret(ecc_int256_t *pub, const ecc_int256_t *secret) { + ecc_25519_work_t work; + ecc_25519_scalarmult_base(&work, secret); + ecc_25519_store_packed_legacy(pub, &work); +} + +// str must be a char[2*(offset+len)+1] +static void sprintf_hex(char *str_buf, const uint8_t *buf, size_t len, size_t offset) { + str_buf += 2*offset; + for (size_t i = 0; i < len; i++) { + snprintf(str_buf, 3, "%02hhx", buf[i]); + str_buf += 2; + } +} + +// The string representation of obj is signed using the secret. After signing, +// a structure containing the public key and the signature is added into obj. +// The obj looks like this after calling sign_json(obj, ...): +// +// { +// ..., +// "auth": { +// "pub": "25077b1914533e94a60853678b8484531a5f63463de87786f042e3d88d0bbc27", +// "sig": "eca0455a99a6b79edc719c18aa46c7d8f960e041f77f836326e6eae08064606320daff6f11cb0d0a2fb51a346725e3dc01e9a85f7c064ec857200c302937409" +// } +// } +// +// To verify the signature, the substructure "auth" has to be removed before. +// The string representation of obj has to be densely packed. No whitespace and +// tabs " " between keys, no newlines and the order of keys must not be changed. +static void sign_json(struct json_object * obj, const ecc_int256_t *secret, const ecc_int256_t *pub) { + // TODO: This currently enables replay attacks, as the json does not contain + // any time value or so... However, we do not care much, as + // this is probably not an effective attack vector. + const char *str = json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PLAIN); + + // hash + ecc_int256_t hash; + ecdsa_sha256_context_t hash_ctx; + ecdsa_sha256_init(&hash_ctx); + ecdsa_sha256_update(&hash_ctx, str, strlen(str)); + ecdsa_sha256_final(&hash_ctx, hash.p); + + struct json_object *auth = json_object_new_object(); + + // generate signature + ecdsa_signature_t signature; + char signature_str[128+1]; + ecdsa_sign_legacy(&signature, &hash, secret); + sprintf_hex(signature_str, signature.r.p, 32, 0); + sprintf_hex(signature_str, signature.s.p, 32, 32); + json_object_object_add(auth, "signature", json_object_new_string(signature_str)); + + // append pubkey + char pub_str[64+1]; + sprintf_hex(pub_str, pub->p, 32, 0); + json_object_object_add(auth, "pubkey", json_object_new_string(pub_str)); + + json_object_object_add(obj, "auth", auth); +} + /** * Find all providers for the type and return the (eventually cached) result * @@ -385,6 +450,16 @@ static struct json_object * single_request(char *type) { struct json_object *ret = eval_providers(r->providers); + ecc_int256_t secret = { .p = { + 0x18, 0x70, 0x58, 0x06, 0x14, 0xb8, 0xa0, 0xc3, + 0xd2, 0x26, 0x97, 0x53, 0xeb, 0x59, 0x4a, 0xb8, + 0x84, 0x54, 0x21, 0xb6, 0x6c, 0x0d, 0xe5, 0x31, + 0xa2, 0xcb, 0x8e, 0x82, 0xc1, 0x65, 0x36, 0x45 + }}; + ecc_int256_t pub; + public_from_secret(&pub, &secret); + sign_json(ret, &secret, &pub); + if (r->cache_time) { if (r->cache) json_object_put(r->cache); From 84a7ab8074026b47dee75b6e18f9656e1f7136d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20M=C3=B6rlein?= Date: Sat, 13 Feb 2021 21:33:00 +0100 Subject: [PATCH 2/3] respondd: generate ed25519 key if not existing --- net/respondd/Makefile | 7 +- net/respondd/files/etc/config/respondd | 2 + net/respondd/src/CMakeLists.txt | 11 +- net/respondd/src/respondd.c | 138 ++++++++++++++++++++++--- 4 files changed, 143 insertions(+), 15 deletions(-) create mode 100644 net/respondd/files/etc/config/respondd diff --git a/net/respondd/Makefile b/net/respondd/Makefile index 0c641ecf0..c27fd257c 100644 --- a/net/respondd/Makefile +++ b/net/respondd/Makefile @@ -11,11 +11,16 @@ include $(INCLUDE_DIR)/cmake.mk define Package/respondd SECTION:=net CATEGORY:=Network - DEPENDS:=@IPV6 +libjson-c +libecdsautil + DEPENDS:=@IPV6 +libjson-c +libecdsautil +libuci TITLE:=Responds to multicast queries with answers generated by Lua code endef +define Package/respondd/conffiles +/etc/config/respondd +endef + define Package/respondd/install + $(CP) ./files/* $(1)/ $(INSTALL_DIR) $(1)/usr/bin $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/respondd $(1)/usr/bin/ endef diff --git a/net/respondd/files/etc/config/respondd b/net/respondd/files/etc/config/respondd new file mode 100644 index 000000000..2e9c20b40 --- /dev/null +++ b/net/respondd/files/etc/config/respondd @@ -0,0 +1,2 @@ + +config respondd settings diff --git a/net/respondd/src/CMakeLists.txt b/net/respondd/src/CMakeLists.txt index a53af38ef..598307136 100644 --- a/net/respondd/src/CMakeLists.txt +++ b/net/respondd/src/CMakeLists.txt @@ -8,6 +8,10 @@ find_package(JSON_C REQUIRED) set_property(DIRECTORY PROPERTY COMPILE_DEFINITIONS _GNU_SOURCE) +# libuci +find_library(UCI_LIBRARY NAMES uci) +include_directories(${UCI_INCLUDE_DIRS}) + # libecdsautil find_package(PkgConfig REQUIRED QUIET) pkg_check_modules(ECDSAUTIL REQUIRED ecdsautil) @@ -17,7 +21,12 @@ add_executable(respondd respondd.c) set_property(TARGET respondd PROPERTY COMPILE_FLAGS "-Wall -std=c99 -fno-strict-aliasing ${JSON_C_CFLAGS_OTHER}") set_property(TARGET respondd PROPERTY LINK_FLAGS "${JSON_C_LDFLAGS_OTHER}") set_property(TARGET respondd APPEND PROPERTY INCLUDE_DIRECTORIES ${JSON_C_INCLUDE_DIR}) -target_link_libraries(respondd ${JSON_C_LIBRARIES} ${ECDSAUTIL_LIBRARIES} dl) +target_link_libraries( + respondd + ${JSON_C_LIBRARIES} + ${ECDSAUTIL_LIBRARIES} + ${UCI_LIBRARY} + dl) install(TARGETS respondd RUNTIME DESTINATION bin) diff --git a/net/respondd/src/respondd.c b/net/respondd/src/respondd.c index df13a59dd..fafb242c1 100644 --- a/net/respondd/src/respondd.c +++ b/net/respondd/src/respondd.c @@ -55,11 +55,15 @@ #include #include +#include #define SCHEDULE_LEN 8 #define REQUEST_MAXLEN 256 #define MAX_MULTICAST_DELAY_DEFAULT 0 +ecc_int256_t ed25519_secret; +ecc_int256_t ed25519_public; + struct interface_info { struct interface_info *next; @@ -362,11 +366,37 @@ static struct json_object * eval_providers(struct provider_list *providers) { return ret; } +int random_bytes(unsigned char *buffer, size_t len) { + int fd; + size_t read_bytes = 0; -static void public_from_secret(ecc_int256_t *pub, const ecc_int256_t *secret) { - ecc_25519_work_t work; - ecc_25519_scalarmult_base(&work, secret); - ecc_25519_store_packed_legacy(pub, &work); + fd = open("/dev/random", O_RDONLY); + + if (fd < 0) { + fprintf(stderr, "Can't open /dev/random: %s\n", strerror(errno)); + goto out_error; + } + + while (read_bytes < len) { + ssize_t ret = read(fd, buffer + read_bytes, len - read_bytes); + + if (ret < 0) { + if (errno == EINTR) + continue; + + fprintf(stderr, "Unable to read random bytes: %s\n", strerror(errno)); + goto out_error; + } + + read_bytes += ret; + } + + close(fd); + return 1; + +out_error: + close(fd); + return 0; } // str must be a char[2*(offset+len)+1] @@ -378,6 +408,92 @@ static void sprintf_hex(char *str_buf, const uint8_t *buf, size_t len, size_t of } } +int parsehex(void *buffer, const char *string, size_t len) { + // number of digits must be even + if ((strlen(string) & 1) == 1) + return 0; + + // number of digits must be 2 * len + if (strlen(string) != 2 * len) + return 0; + + while (len--) { + int ret; + ret = sscanf(string, "%02hhx", (char*)(buffer++)); + string += 2; + + if (ret != 1) + break; + } + + if (len != -1) + return 0; + + return 1; +} + +ecc_int256_t read_or_generate_key() { + struct uci_context *ctx = uci_alloc_context(); + if (!ctx) { + fprintf(stderr, "respondd: error: failed to allocate UCI context\n"); + abort(); + } + + ctx->flags &= ~UCI_FLAG_STRICT; + + struct uci_package *p; + struct uci_section *s; + + if (uci_load(ctx, "respondd", &p) != UCI_OK) { + fputs("respondd: error: unable to load UCI package\n", stderr); + exit(1); + } + + s = uci_lookup_section(ctx, p, "settings"); + if (!s || strcmp(s->type, "respondd")) { + fputs("respondd: error: could not load UCI section respondd.settings\n", stderr); + exit(1); + } + + const char *secret_str = uci_lookup_option_string(ctx, s, "secret"); + ecc_int256_t secret; + + if (!secret_str || !parsehex(&secret, secret_str, 32)) { + fputs("respondd: no valid key found. generating new key.\n", stderr); + + // generate it + if (!random_bytes(secret.p, 32)) { + fputs("respondd: unable to read random bytes.\n", stderr); + exit(1); + } + ecc_25519_gf_sanitize_secret(&secret, &secret); + + // save it to uci + char secret_str_new[64+1]; + sprintf_hex(secret_str_new, secret.p, 32, 0); + struct uci_ptr ptr ={ + .package = "respondd", + .section = "settings", + .option = "secret", + .value = secret_str_new, + }; + uci_set(ctx, &ptr); + uci_commit(ctx, &ptr.p, false); + uci_unload(ctx, ptr.p); + fputs("respondd: key generated and saved.\n", stderr); + } + + uci_free_context(ctx); + + return secret; +} + +static void public_from_secret(ecc_int256_t *pub, const ecc_int256_t *secret) { + ecc_25519_work_t work; + ecc_25519_scalarmult_base(&work, secret); + ecc_25519_store_packed_legacy(pub, &work); +} + // The string representation of obj is signed using the secret. After signing, // a structure containing the public key and the signature is added into obj. // The obj looks like this after calling sign_json(obj, ...): @@ -450,15 +566,7 @@ static struct json_object * single_request(char *type) { struct json_object *ret = eval_providers(r->providers); - ecc_int256_t secret = { .p = { - 0x18, 0x70, 0x58, 0x06, 0x14, 0xb8, 0xa0, 0xc3, - 0xd2, 0x26, 0x97, 0x53, 0xeb, 0x59, 0x4a, 0xb8, - 0x84, 0x54, 0x21, 0xb6, 0x6c, 0x0d, 0xe5, 0x31, - 0xa2, 0xcb, 0x8e, 0x82, 0xc1, 0x65, 0x36, 0x45 - }}; - ecc_int256_t pub; - public_from_secret(&pub, &secret); - sign_json(ret, &secret, &pub); + sign_json(ret, &ed25519_secret, &ed25519_public); if (r->cache_time) { if (r->cache) @@ -821,6 +929,10 @@ int main(int argc, char **argv) { } } + // load keys for ed25519 signatures + ed25519_secret = read_or_generate_key(); + public_from_secret(&ed25519_public, &ed25519_secret); + if (bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("bind failed"); exit(EXIT_FAILURE); From 0b263bc76641aab8d812e2b121e0eaa6599c334c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20M=C3=B6rlein?= Date: Sat, 13 Feb 2021 22:01:20 +0100 Subject: [PATCH 3/3] respondd: rename pubkey to secure_nodeid --- net/respondd/src/respondd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/respondd/src/respondd.c b/net/respondd/src/respondd.c index fafb242c1..c481c84cf 100644 --- a/net/respondd/src/respondd.c +++ b/net/respondd/src/respondd.c @@ -501,7 +501,7 @@ static void public_from_secret(ecc_int256_t *pub, const ecc_int256_t *secret) { // { // ..., // "auth": { -// "pub": "25077b1914533e94a60853678b8484531a5f63463de87786f042e3d88d0bbc27", +// "secure_nodeid": "25077b1914533e94a60853678b8484531a5f63463de87786f042e3d88d0bbc27", // "sig": "eca0455a99a6b79edc719c18aa46c7d8f960e041f77f836326e6eae08064606320daff6f11cb0d0a2fb51a346725e3dc01e9a85f7c064ec857200c302937409" // } // } @@ -535,7 +535,7 @@ static void sign_json(struct json_object * obj, const ecc_int256_t *secret, cons // append pubkey char pub_str[64+1]; sprintf_hex(pub_str, pub->p, 32, 0); - json_object_object_add(auth, "pubkey", json_object_new_string(pub_str)); + json_object_object_add(auth, "secure_nodeid", json_object_new_string(pub_str)); json_object_object_add(obj, "auth", auth); }