Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

respondd: sign responses #241

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion net/respondd/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@ include $(INCLUDE_DIR)/cmake.mk
define Package/respondd
SECTION:=net
CATEGORY:=Network
DEPENDS:=@IPV6 +libjson-c
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
Expand Down
2 changes: 2 additions & 0 deletions net/respondd/files/etc/config/respondd
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

config respondd settings
16 changes: 15 additions & 1 deletion net/respondd/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,25 @@ 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)
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}
${UCI_LIBRARY}
dl)

install(TARGETS respondd RUNTIME DESTINATION bin)

Expand Down
187 changes: 187 additions & 0 deletions net/respondd/src/respondd.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,17 @@
#include <sys/socket.h>
#include <sys/stat.h>

#include <ecdsautil/ecdsa.h>
#include <ecdsautil/sha256.h>
#include <uci.h>

#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;

Expand Down Expand Up @@ -359,6 +366,180 @@ 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;

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]
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;
}
}

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, ...):
//
// {
// ...,
// "auth": {
// "secure_nodeid": "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, "secure_nodeid", 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
*
Expand All @@ -385,6 +566,8 @@ static struct json_object * single_request(char *type) {

struct json_object *ret = eval_providers(r->providers);

sign_json(ret, &ed25519_secret, &ed25519_public);

if (r->cache_time) {
if (r->cache)
json_object_put(r->cache);
Expand Down Expand Up @@ -746,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);
Expand Down