From cbfacf1e9c7ab0ece067b14ce61c0de6407ad145 Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Wed, 26 Oct 2022 18:49:10 +0200 Subject: [PATCH 01/31] Net: Initial integration of log message delivery --- Makefile | 1 + .../integrity_timer/p_integrity_timer.c | 2 + src/modules/net/hydrogen/hydrogen.c | 18 + src/modules/net/hydrogen/hydrogen.h | 337 +++++++++++ src/modules/net/hydrogen/impl/common.h | 330 +++++++++++ src/modules/net/hydrogen/impl/core.h | 221 ++++++++ src/modules/net/hydrogen/impl/gimli-core.h | 25 + .../net/hydrogen/impl/gimli-core/portable.h | 39 ++ .../net/hydrogen/impl/gimli-core/sse2.h | 100 ++++ src/modules/net/hydrogen/impl/hash.h | 140 +++++ src/modules/net/hydrogen/impl/hydrogen_p.h | 83 +++ src/modules/net/hydrogen/impl/kdf.h | 20 + src/modules/net/hydrogen/impl/kx.h | 535 ++++++++++++++++++ src/modules/net/hydrogen/impl/random.h | 149 +++++ .../net/hydrogen/impl/random/linux_kernel.h | 9 + src/modules/net/hydrogen/impl/secretbox.h | 236 ++++++++ src/modules/net/hydrogen/impl/sign.h | 207 +++++++ src/modules/net/hydrogen/impl/x25519.h | 386 +++++++++++++ src/modules/net/net.c | 262 +++++++++ src/modules/net/net.h | 9 + src/modules/print_log/p_lkrg_print_log.h | 4 + src/p_lkrg_main.c | 9 + 22 files changed, 3122 insertions(+) create mode 100644 src/modules/net/hydrogen/hydrogen.c create mode 100644 src/modules/net/hydrogen/hydrogen.h create mode 100644 src/modules/net/hydrogen/impl/common.h create mode 100644 src/modules/net/hydrogen/impl/core.h create mode 100644 src/modules/net/hydrogen/impl/gimli-core.h create mode 100644 src/modules/net/hydrogen/impl/gimli-core/portable.h create mode 100644 src/modules/net/hydrogen/impl/gimli-core/sse2.h create mode 100644 src/modules/net/hydrogen/impl/hash.h create mode 100644 src/modules/net/hydrogen/impl/hydrogen_p.h create mode 100644 src/modules/net/hydrogen/impl/kdf.h create mode 100644 src/modules/net/hydrogen/impl/kx.h create mode 100644 src/modules/net/hydrogen/impl/random.h create mode 100644 src/modules/net/hydrogen/impl/random/linux_kernel.h create mode 100644 src/modules/net/hydrogen/impl/secretbox.h create mode 100644 src/modules/net/hydrogen/impl/sign.h create mode 100644 src/modules/net/hydrogen/impl/x25519.h create mode 100644 src/modules/net/net.c create mode 100644 src/modules/net/net.h diff --git a/Makefile b/Makefile index ce8b35e8..70347997 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,7 @@ $(TARGET)-objs += src/modules/ksyms/p_resolve_ksym.o \ src/modules/integrity_timer/p_integrity_timer.o \ src/modules/integrity_timer/verify_kprobes/p_verify_kprobes.o \ src/modules/kmod/p_kmod.o \ + src/modules/net/net.o \ src/modules/database/CPU.o \ src/modules/database/arch/x86/p_x86_metadata.o \ src/modules/database/arch/x86/p_switch_idt/p_switch_idt.o \ diff --git a/src/modules/integrity_timer/p_integrity_timer.c b/src/modules/integrity_timer/p_integrity_timer.c index 344c38e2..7f5001e9 100644 --- a/src/modules/integrity_timer/p_integrity_timer.c +++ b/src/modules/integrity_timer/p_integrity_timer.c @@ -16,6 +16,7 @@ */ #include "../../p_lkrg_main.h" +#include "../net/net.h" /* * Local timer for integrity checks... @@ -92,6 +93,7 @@ void p_offload_work(struct timer_list *p_timer) { INIT_WORK(p_worker, p_check_integrity); /* schedule for execution */ queue_work(system_unbound_wq, p_worker); + lkrg_queue_net(); if (p_timer) p_integrity_timer(); } diff --git a/src/modules/net/hydrogen/hydrogen.c b/src/modules/net/hydrogen/hydrogen.c new file mode 100644 index 00000000..af6f5dec --- /dev/null +++ b/src/modules/net/hydrogen/hydrogen.c @@ -0,0 +1,18 @@ +#include "hydrogen.h" + +#include "impl/common.h" +#include "impl/hydrogen_p.h" + +#include "impl/random.h" + +#include "impl/core.h" +#include "impl/gimli-core.h" + +#include "impl/hash.h" +#include "impl/kdf.h" +#include "impl/secretbox.h" + +#include "impl/x25519.h" + +#include "impl/kx.h" +#include "impl/sign.h" diff --git a/src/modules/net/hydrogen/hydrogen.h b/src/modules/net/hydrogen/hydrogen.h new file mode 100644 index 00000000..76831a11 --- /dev/null +++ b/src/modules/net/hydrogen/hydrogen.h @@ -0,0 +1,337 @@ +#ifndef hydrogen_H +#define hydrogen_H + +#if !(defined(__linux__) && defined(__KERNEL__)) +#include +#include +#include +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wdeclaration-after-statement" +#endif + +#ifdef __cplusplus +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wlong-long" +#endif +extern "C" { +#endif + +#if defined(__clang__) || defined(__GNUC__) +#define _hydro_attr_(X) __attribute__(X) +#else +#define _hydro_attr_(X) +#endif +#define _hydro_attr_deprecated_ _hydro_attr_((deprecated)) +#define _hydro_attr_malloc_ _hydro_attr_((malloc)) +#define _hydro_attr_noinline_ _hydro_attr_((noinline)) +#define _hydro_attr_noreturn_ _hydro_attr_((noreturn)) +#define _hydro_attr_warn_unused_result_ _hydro_attr_((warn_unused_result)) +#define _hydro_attr_weak_ _hydro_attr_((weak)) + +#if defined(__INTEL_COMPILER) || defined(_MSC_VER) +#define _hydro_attr_aligned_(X) __declspec(align(X)) +#elif defined(__clang__) || defined(__GNUC__) +#define _hydro_attr_aligned_(X) _hydro_attr_((aligned(X))) +#else +#define _hydro_attr_aligned_(X) +#endif + +#define HYDRO_VERSION_MAJOR 1 +#define HYDRO_VERSION_MINOR 0 + +int hydro_init(void); + +/* ---------------- */ + +#define hydro_random_SEEDBYTES 32 + +uint32_t hydro_random_u32(void); + +uint32_t hydro_random_uniform(const uint32_t upper_bound); + +void hydro_random_buf(void *out, size_t out_len); + +void hydro_random_buf_deterministic(void *out, size_t out_len, + const uint8_t seed[hydro_random_SEEDBYTES]); + +void hydro_random_ratchet(void); + +void hydro_random_reseed(void); + +/* ---------------- */ + +#define hydro_hash_BYTES 32 +#define hydro_hash_BYTES_MAX 65535 +#define hydro_hash_BYTES_MIN 16 +#define hydro_hash_CONTEXTBYTES 8 +#define hydro_hash_KEYBYTES 32 + +typedef struct hydro_hash_state { + uint32_t state[12]; + uint8_t buf_off; + uint8_t align[3]; +} hydro_hash_state; + +void hydro_hash_keygen(uint8_t key[hydro_hash_KEYBYTES]); + +int hydro_hash_init(hydro_hash_state *state, const char ctx[hydro_hash_CONTEXTBYTES], + const uint8_t key[hydro_hash_KEYBYTES]); + +int hydro_hash_update(hydro_hash_state *state, const void *in_, size_t in_len); + +int hydro_hash_final(hydro_hash_state *state, uint8_t *out, size_t out_len); + +int hydro_hash_hash(uint8_t *out, size_t out_len, const void *in_, size_t in_len, + const char ctx[hydro_hash_CONTEXTBYTES], + const uint8_t key[hydro_hash_KEYBYTES]); + +/* ---------------- */ + +#define hydro_secretbox_CONTEXTBYTES 8 +#define hydro_secretbox_HEADERBYTES (20 + 16) +#define hydro_secretbox_KEYBYTES 32 +#define hydro_secretbox_PROBEBYTES 16 + +void hydro_secretbox_keygen(uint8_t key[hydro_secretbox_KEYBYTES]); + +int hydro_secretbox_encrypt(uint8_t *c, const void *m_, size_t mlen, uint64_t msg_id, + const char ctx[hydro_secretbox_CONTEXTBYTES], + const uint8_t key[hydro_secretbox_KEYBYTES]); + +int hydro_secretbox_decrypt(void *m_, const uint8_t *c, size_t clen, uint64_t msg_id, + const char ctx[hydro_secretbox_CONTEXTBYTES], + const uint8_t key[hydro_secretbox_KEYBYTES]) + _hydro_attr_warn_unused_result_; + +void hydro_secretbox_probe_create(uint8_t probe[hydro_secretbox_PROBEBYTES], const uint8_t *c, + size_t c_len, const char ctx[hydro_secretbox_CONTEXTBYTES], + const uint8_t key[hydro_secretbox_KEYBYTES]); + +int hydro_secretbox_probe_verify(const uint8_t probe[hydro_secretbox_PROBEBYTES], const uint8_t *c, + size_t c_len, const char ctx[hydro_secretbox_CONTEXTBYTES], + const uint8_t key[hydro_secretbox_KEYBYTES]) + _hydro_attr_warn_unused_result_; + +/* ---------------- */ + +#define hydro_kdf_CONTEXTBYTES 8 +#define hydro_kdf_KEYBYTES 32 +#define hydro_kdf_BYTES_MAX 65535 +#define hydro_kdf_BYTES_MIN 16 + +void hydro_kdf_keygen(uint8_t key[hydro_kdf_KEYBYTES]); + +int hydro_kdf_derive_from_key(uint8_t *subkey, size_t subkey_len, uint64_t subkey_id, + const char ctx[hydro_kdf_CONTEXTBYTES], + const uint8_t key[hydro_kdf_KEYBYTES]); + +/* ---------------- */ + +#define hydro_sign_BYTES 64 +#define hydro_sign_CONTEXTBYTES 8 +#define hydro_sign_PUBLICKEYBYTES 32 +#define hydro_sign_SECRETKEYBYTES 64 +#define hydro_sign_SEEDBYTES 32 + +typedef struct hydro_sign_state { + hydro_hash_state hash_st; +} hydro_sign_state; + +typedef struct hydro_sign_keypair { + uint8_t pk[hydro_sign_PUBLICKEYBYTES]; + uint8_t sk[hydro_sign_SECRETKEYBYTES]; +} hydro_sign_keypair; + +void hydro_sign_keygen(hydro_sign_keypair *kp); + +void hydro_sign_keygen_deterministic(hydro_sign_keypair *kp, + const uint8_t seed[hydro_sign_SEEDBYTES]); + +int hydro_sign_init(hydro_sign_state *state, const char ctx[hydro_sign_CONTEXTBYTES]); + +int hydro_sign_update(hydro_sign_state *state, const void *m_, size_t mlen); + +int hydro_sign_final_create(hydro_sign_state *state, uint8_t csig[hydro_sign_BYTES], + const uint8_t sk[hydro_sign_SECRETKEYBYTES]); + +int hydro_sign_final_verify(hydro_sign_state *state, const uint8_t csig[hydro_sign_BYTES], + const uint8_t pk[hydro_sign_PUBLICKEYBYTES]) + _hydro_attr_warn_unused_result_; + +int hydro_sign_create(uint8_t csig[hydro_sign_BYTES], const void *m_, size_t mlen, + const char ctx[hydro_sign_CONTEXTBYTES], + const uint8_t sk[hydro_sign_SECRETKEYBYTES]); + +int hydro_sign_verify(const uint8_t csig[hydro_sign_BYTES], const void *m_, size_t mlen, + const char ctx[hydro_sign_CONTEXTBYTES], + const uint8_t pk[hydro_sign_PUBLICKEYBYTES]) _hydro_attr_warn_unused_result_; + +/* ---------------- */ + +#define hydro_kx_SESSIONKEYBYTES 32 +#define hydro_kx_PUBLICKEYBYTES 32 +#define hydro_kx_SECRETKEYBYTES 32 +#define hydro_kx_PSKBYTES 32 +#define hydro_kx_SEEDBYTES 32 + +typedef struct hydro_kx_keypair { + uint8_t pk[hydro_kx_PUBLICKEYBYTES]; + uint8_t sk[hydro_kx_SECRETKEYBYTES]; +} hydro_kx_keypair; + +typedef struct hydro_kx_session_keypair { + uint8_t rx[hydro_kx_SESSIONKEYBYTES]; + uint8_t tx[hydro_kx_SESSIONKEYBYTES]; +} hydro_kx_session_keypair; + +typedef struct hydro_kx_state { + hydro_kx_keypair eph_kp; + hydro_hash_state h_st; +} hydro_kx_state; + +void hydro_kx_keygen(hydro_kx_keypair *static_kp); + +void hydro_kx_keygen_deterministic(hydro_kx_keypair *static_kp, + const uint8_t seed[hydro_kx_SEEDBYTES]); + +/* NOISE_N */ + +#define hydro_kx_N_PACKET1BYTES (32 + 16) + +int hydro_kx_n_1(hydro_kx_session_keypair *kp, uint8_t packet1[hydro_kx_N_PACKET1BYTES], + const uint8_t psk[hydro_kx_PSKBYTES], + const uint8_t peer_static_pk[hydro_kx_PUBLICKEYBYTES]); + +int hydro_kx_n_2(hydro_kx_session_keypair *kp, const uint8_t packet1[hydro_kx_N_PACKET1BYTES], + const uint8_t psk[hydro_kx_PSKBYTES], const hydro_kx_keypair *static_kp); + +/* NOISE_KK */ + +#define hydro_kx_KK_PACKET1BYTES (32 + 16) +#define hydro_kx_KK_PACKET2BYTES (32 + 16) + +int hydro_kx_kk_1(hydro_kx_state *state, uint8_t packet1[hydro_kx_KK_PACKET1BYTES], + const uint8_t peer_static_pk[hydro_kx_PUBLICKEYBYTES], + const hydro_kx_keypair *static_kp); + +int hydro_kx_kk_2(hydro_kx_session_keypair *kp, uint8_t packet2[hydro_kx_KK_PACKET2BYTES], + const uint8_t packet1[hydro_kx_KK_PACKET1BYTES], + const uint8_t peer_static_pk[hydro_kx_PUBLICKEYBYTES], + const hydro_kx_keypair *static_kp); + +int hydro_kx_kk_3(hydro_kx_state *state, hydro_kx_session_keypair *kp, + const uint8_t packet2[hydro_kx_KK_PACKET2BYTES], + const hydro_kx_keypair *static_kp); + +/* NOISE_XX */ + +#define hydro_kx_XX_PACKET1BYTES (32 + 16) +#define hydro_kx_XX_PACKET2BYTES (32 + 32 + 16 + 16) +#define hydro_kx_XX_PACKET3BYTES (32 + 16 + 16) + +int hydro_kx_xx_1(hydro_kx_state *state, uint8_t packet1[hydro_kx_XX_PACKET1BYTES], + const uint8_t psk[hydro_kx_PSKBYTES]); + +int hydro_kx_xx_2(hydro_kx_state *state, uint8_t packet2[hydro_kx_XX_PACKET2BYTES], + const uint8_t packet1[hydro_kx_XX_PACKET1BYTES], + const uint8_t psk[hydro_kx_PSKBYTES], const hydro_kx_keypair *static_kp); + +int hydro_kx_xx_3(hydro_kx_state *state, hydro_kx_session_keypair *kp, + uint8_t packet3[hydro_kx_XX_PACKET3BYTES], + uint8_t peer_static_pk[hydro_kx_PUBLICKEYBYTES], + const uint8_t packet2[hydro_kx_XX_PACKET2BYTES], + const uint8_t psk[hydro_kx_PSKBYTES], const hydro_kx_keypair *static_kp); + +int hydro_kx_xx_4(hydro_kx_state *state, hydro_kx_session_keypair *kp, + uint8_t peer_static_pk[hydro_kx_PUBLICKEYBYTES], + const uint8_t packet3[hydro_kx_XX_PACKET3BYTES], + const uint8_t psk[hydro_kx_PSKBYTES]); + +/* NOISE_NK */ + +#define hydro_kx_NK_PACKET1BYTES (32 + 16) +#define hydro_kx_NK_PACKET2BYTES (32 + 16) + +int hydro_kx_nk_1(hydro_kx_state *state, uint8_t packet1[hydro_kx_NK_PACKET1BYTES], + const uint8_t psk[hydro_kx_PSKBYTES], + const uint8_t peer_static_pk[hydro_kx_PUBLICKEYBYTES]); + +int hydro_kx_nk_2(hydro_kx_session_keypair *kp, uint8_t packet2[hydro_kx_NK_PACKET2BYTES], + const uint8_t packet1[hydro_kx_NK_PACKET1BYTES], + const uint8_t psk[hydro_kx_PSKBYTES], const hydro_kx_keypair *static_kp); + +int hydro_kx_nk_3(hydro_kx_state *state, hydro_kx_session_keypair *kp, + const uint8_t packet2[hydro_kx_NK_PACKET2BYTES]); + +/* ---------------- */ + +#define hydro_pwhash_CONTEXTBYTES 8 +#define hydro_pwhash_MASTERKEYBYTES 32 +#define hydro_pwhash_STOREDBYTES 128 + +void hydro_pwhash_keygen(uint8_t master_key[hydro_pwhash_MASTERKEYBYTES]); + +int hydro_pwhash_deterministic(uint8_t *h, size_t h_len, const char *passwd, size_t passwd_len, + const char ctx[hydro_pwhash_CONTEXTBYTES], + const uint8_t master_key[hydro_pwhash_MASTERKEYBYTES], + uint64_t opslimit, size_t memlimit, uint8_t threads); + +int hydro_pwhash_create(uint8_t stored[hydro_pwhash_STOREDBYTES], const char *passwd, + size_t passwd_len, const uint8_t master_key[hydro_pwhash_MASTERKEYBYTES], + uint64_t opslimit, size_t memlimit, uint8_t threads); + +int hydro_pwhash_verify(const uint8_t stored[hydro_pwhash_STOREDBYTES], const char *passwd, + size_t passwd_len, const uint8_t master_key[hydro_pwhash_MASTERKEYBYTES], + uint64_t opslimit_max, size_t memlimit_max, uint8_t threads_max); + +int hydro_pwhash_derive_static_key(uint8_t *static_key, size_t static_key_len, + const uint8_t stored[hydro_pwhash_STOREDBYTES], + const char *passwd, size_t passwd_len, + const char ctx[hydro_pwhash_CONTEXTBYTES], + const uint8_t master_key[hydro_pwhash_MASTERKEYBYTES], + uint64_t opslimit_max, size_t memlimit_max, uint8_t threads_max); + +int hydro_pwhash_reencrypt(uint8_t stored[hydro_pwhash_STOREDBYTES], + const uint8_t master_key[hydro_pwhash_MASTERKEYBYTES], + const uint8_t new_master_key[hydro_pwhash_MASTERKEYBYTES]); + +int hydro_pwhash_upgrade(uint8_t stored[hydro_pwhash_STOREDBYTES], + const uint8_t master_key[hydro_pwhash_MASTERKEYBYTES], uint64_t opslimit, + size_t memlimit, uint8_t threads); + +/* ---------------- */ + +void hydro_memzero(void *pnt, size_t len); + +void hydro_increment(uint8_t *n, size_t len); + +bool hydro_equal(const void *b1_, const void *b2_, size_t len); + +int hydro_compare(const uint8_t *b1_, const uint8_t *b2_, size_t len); + +char *hydro_bin2hex(char *hex, size_t hex_maxlen, const uint8_t *bin, size_t bin_len); + +int hydro_hex2bin(uint8_t *bin, size_t bin_maxlen, const char *hex, size_t hex_len, + const char *ignore, const char **hex_end_p); + +int hydro_pad(unsigned char *buf, size_t unpadded_buflen, size_t blocksize, size_t max_buflen); + +int hydro_unpad(const unsigned char *buf, size_t padded_buflen, size_t blocksize); + +/* ---------------- */ + +#define HYDRO_HWTYPE_ATMEGA328 1 + +#ifndef HYDRO_HWTYPE +#ifdef __AVR__ +#define HYDRO_HWTYPE HYDRO_HWTYPE_ATMEGA328 +#endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/modules/net/hydrogen/impl/common.h b/src/modules/net/hydrogen/impl/common.h new file mode 100644 index 00000000..77676ac5 --- /dev/null +++ b/src/modules/net/hydrogen/impl/common.h @@ -0,0 +1,330 @@ +#if defined(__linux__) && defined(__KERNEL__) +#define TLS /* Danger: at most one call into hydro_*() at a time */ +#define CHAR_BIT 8 +#define abort BUG +#define uint_fast16_t uint16_t +#define errno hydro_errno +static int errno; +#else +#include +#include +#include +#include +#include +#include +#endif + +#if !defined(__unix__) && (defined(__APPLE__) || defined(__linux__)) +#define __unix__ 1 +#endif +#ifndef __GNUC__ +#define __restrict__ +#endif + +#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && \ + __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define NATIVE_BIG_ENDIAN +#endif +#ifndef NATIVE_BIG_ENDIAN +#ifndef NATIVE_LITTLE_ENDIAN +#define NATIVE_LITTLE_ENDIAN +#endif +#endif + +#ifndef TLS +#if defined(_WIN32) && !defined(__GNUC__) +#define TLS __declspec(thread) +#elif (defined(__clang__) || defined(__GNUC__)) && defined(__unix__) && !defined(__TINYC__) +#define TLS __thread +#else +#define TLS +#endif +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX ((size_t) -1) +#endif + +#ifdef __OpenBSD__ +#define HAVE_EXPLICIT_BZERO 1 +#elif defined(__GLIBC__) && defined(__GLIBC_PREREQ) && defined(_GNU_SOURCE) +#if __GLIBC_PREREQ(2, 25) +#define HAVE_EXPLICIT_BZERO 1 +#endif +#endif + +#define COMPILER_ASSERT(X) (void) sizeof(char[(X) ? 1 : -1]) + +#define ROTL32(x, b) (uint32_t)(((x) << (b)) | ((x) >> (32 - (b)))) +#define ROTL64(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) +#define ROTR32(x, b) (uint32_t)(((x) >> (b)) | ((x) << (32 - (b)))) +#define ROTR64(x, b) (uint64_t)(((x) >> (b)) | ((x) << (64 - (b)))) + +#define LOAD64_LE(SRC) load64_le(SRC) +static inline uint64_t +load64_le(const uint8_t src[8]) +{ +#ifdef NATIVE_LITTLE_ENDIAN + uint64_t w; + memcpy(&w, src, sizeof w); + return w; +#else + uint64_t w = (uint64_t) src[0]; + w |= (uint64_t) src[1] << 8; + w |= (uint64_t) src[2] << 16; + w |= (uint64_t) src[3] << 24; + w |= (uint64_t) src[4] << 32; + w |= (uint64_t) src[5] << 40; + w |= (uint64_t) src[6] << 48; + w |= (uint64_t) src[7] << 56; + return w; +#endif +} + +#define STORE64_LE(DST, W) store64_le((DST), (W)) +static inline void +store64_le(uint8_t dst[8], uint64_t w) +{ +#ifdef NATIVE_LITTLE_ENDIAN + memcpy(dst, &w, sizeof w); +#else + dst[0] = (uint8_t) w; + w >>= 8; + dst[1] = (uint8_t) w; + w >>= 8; + dst[2] = (uint8_t) w; + w >>= 8; + dst[3] = (uint8_t) w; + w >>= 8; + dst[4] = (uint8_t) w; + w >>= 8; + dst[5] = (uint8_t) w; + w >>= 8; + dst[6] = (uint8_t) w; + w >>= 8; + dst[7] = (uint8_t) w; +#endif +} + +#define LOAD32_LE(SRC) load32_le(SRC) +static inline uint32_t +load32_le(const uint8_t src[4]) +{ +#ifdef NATIVE_LITTLE_ENDIAN + uint32_t w; + memcpy(&w, src, sizeof w); + return w; +#else + uint32_t w = (uint32_t) src[0]; + w |= (uint32_t) src[1] << 8; + w |= (uint32_t) src[2] << 16; + w |= (uint32_t) src[3] << 24; + return w; +#endif +} + +#define STORE32_LE(DST, W) store32_le((DST), (W)) +static inline void +store32_le(uint8_t dst[4], uint32_t w) +{ +#ifdef NATIVE_LITTLE_ENDIAN + memcpy(dst, &w, sizeof w); +#else + dst[0] = (uint8_t) w; + w >>= 8; + dst[1] = (uint8_t) w; + w >>= 8; + dst[2] = (uint8_t) w; + w >>= 8; + dst[3] = (uint8_t) w; +#endif +} + +#define LOAD16_LE(SRC) load16_le(SRC) +static inline uint16_t +load16_le(const uint8_t src[2]) +{ +#ifdef NATIVE_LITTLE_ENDIAN + uint16_t w; + memcpy(&w, src, sizeof w); + return w; +#else + uint16_t w = (uint16_t) src[0]; + w |= (uint16_t) src[1] << 8; + return w; +#endif +} + +#define STORE16_LE(DST, W) store16_le((DST), (W)) +static inline void +store16_le(uint8_t dst[2], uint16_t w) +{ +#ifdef NATIVE_LITTLE_ENDIAN + memcpy(dst, &w, sizeof w); +#else + dst[0] = (uint8_t) w; + w >>= 8; + dst[1] = (uint8_t) w; +#endif +} + +/* ----- */ + +#define LOAD64_BE(SRC) load64_be(SRC) +static inline uint64_t +load64_be(const uint8_t src[8]) +{ +#ifdef NATIVE_BIG_ENDIAN + uint64_t w; + memcpy(&w, src, sizeof w); + return w; +#else + uint64_t w = (uint64_t) src[7]; + w |= (uint64_t) src[6] << 8; + w |= (uint64_t) src[5] << 16; + w |= (uint64_t) src[4] << 24; + w |= (uint64_t) src[3] << 32; + w |= (uint64_t) src[2] << 40; + w |= (uint64_t) src[1] << 48; + w |= (uint64_t) src[0] << 56; + return w; +#endif +} + +#define STORE64_BE(DST, W) store64_be((DST), (W)) +static inline void +store64_be(uint8_t dst[8], uint64_t w) +{ +#ifdef NATIVE_BIG_ENDIAN + memcpy(dst, &w, sizeof w); +#else + dst[7] = (uint8_t) w; + w >>= 8; + dst[6] = (uint8_t) w; + w >>= 8; + dst[5] = (uint8_t) w; + w >>= 8; + dst[4] = (uint8_t) w; + w >>= 8; + dst[3] = (uint8_t) w; + w >>= 8; + dst[2] = (uint8_t) w; + w >>= 8; + dst[1] = (uint8_t) w; + w >>= 8; + dst[0] = (uint8_t) w; +#endif +} + +#define LOAD32_BE(SRC) load32_be(SRC) +static inline uint32_t +load32_be(const uint8_t src[4]) +{ +#ifdef NATIVE_BIG_ENDIAN + uint32_t w; + memcpy(&w, src, sizeof w); + return w; +#else + uint32_t w = (uint32_t) src[3]; + w |= (uint32_t) src[2] << 8; + w |= (uint32_t) src[1] << 16; + w |= (uint32_t) src[0] << 24; + return w; +#endif +} + +#define STORE32_BE(DST, W) store32_be((DST), (W)) +static inline void +store32_be(uint8_t dst[4], uint32_t w) +{ +#ifdef NATIVE_BIG_ENDIAN + memcpy(dst, &w, sizeof w); +#else + dst[3] = (uint8_t) w; + w >>= 8; + dst[2] = (uint8_t) w; + w >>= 8; + dst[1] = (uint8_t) w; + w >>= 8; + dst[0] = (uint8_t) w; +#endif +} + +#define LOAD16_BE(SRC) load16_be(SRC) +static inline uint16_t +load16_be(const uint8_t src[2]) +{ +#ifdef NATIVE_BIG_ENDIAN + uint16_t w; + memcpy(&w, src, sizeof w); + return w; +#else + uint16_t w = (uint16_t) src[1]; + w |= (uint16_t) src[0] << 8; + return w; +#endif +} + +#define STORE16_BE(DST, W) store16_be((DST), (W)) +static inline void +store16_be(uint8_t dst[2], uint16_t w) +{ +#ifdef NATIVE_BIG_ENDIAN + memcpy(dst, &w, sizeof w); +#else + dst[1] = (uint8_t) w; + w >>= 8; + dst[0] = (uint8_t) w; +#endif +} + +static inline void +mem_cpy(void *__restrict__ dst_, const void *__restrict__ src_, size_t n) +{ + unsigned char * dst = (unsigned char *) dst_; + const unsigned char *src = (const unsigned char *) src_; + size_t i; + + for (i = 0; i < n; i++) { + dst[i] = src[i]; + } +} + +static inline void +mem_zero(void *dst_, size_t n) +{ + unsigned char *dst = (unsigned char *) dst_; + size_t i; + + for (i = 0; i < n; i++) { + dst[i] = 0; + } +} + +static inline void +mem_xor(void *__restrict__ dst_, const void *__restrict__ src_, size_t n) +{ + unsigned char * dst = (unsigned char *) dst_; + const unsigned char *src = (const unsigned char *) src_; + size_t i; + + for (i = 0; i < n; i++) { + dst[i] ^= src[i]; + } +} + +static inline void +mem_xor2(void *__restrict__ dst_, const void *__restrict__ src1_, const void *__restrict__ src2_, + size_t n) +{ + unsigned char * dst = (unsigned char *) dst_; + const unsigned char *src1 = (const unsigned char *) src1_; + const unsigned char *src2 = (const unsigned char *) src2_; + size_t i; + + for (i = 0; i < n; i++) { + dst[i] = src1[i] ^ src2[i]; + } +} + +static const uint8_t zero[64] = { 0 }; diff --git a/src/modules/net/hydrogen/impl/core.h b/src/modules/net/hydrogen/impl/core.h new file mode 100644 index 00000000..0498ee5a --- /dev/null +++ b/src/modules/net/hydrogen/impl/core.h @@ -0,0 +1,221 @@ +int +hydro_init(void) +{ + hydro_random_ensure_initialized(); + return 0; +} + +void +hydro_memzero(void *pnt, size_t len) +{ +#ifdef HAVE_EXPLICIT_BZERO + explicit_bzero(pnt, len); +#else + volatile unsigned char *volatile pnt_ = (volatile unsigned char *volatile) pnt; + size_t i = (size_t) 0U; + + while (i < len) { + pnt_[i++] = 0U; + } +#endif +} + +void +hydro_increment(uint8_t *n, size_t len) +{ + size_t i; + uint_fast16_t c = 1U; + + for (i = 0; i < len; i++) { + c += (uint_fast16_t) n[i]; + n[i] = (uint8_t) c; + c >>= 8; + } +} + +char * +hydro_bin2hex(char *hex, size_t hex_maxlen, const uint8_t *bin, size_t bin_len) +{ + size_t i = (size_t) 0U; + unsigned int x; + int b; + int c; + + if (bin_len >= SIZE_MAX / 2 || hex_maxlen <= bin_len * 2U) { + abort(); + } + while (i < bin_len) { + c = bin[i] & 0xf; + b = bin[i] >> 4; + x = (unsigned char) (87U + c + (((c - 10U) >> 8) & ~38U)) << 8 | + (unsigned char) (87U + b + (((b - 10U) >> 8) & ~38U)); + hex[i * 2U] = (char) x; + x >>= 8; + hex[i * 2U + 1U] = (char) x; + i++; + } + hex[i * 2U] = 0U; + + return hex; +} + +int +hydro_hex2bin(uint8_t *bin, size_t bin_maxlen, const char *hex, size_t hex_len, const char *ignore, + const char **hex_end_p) +{ + size_t bin_pos = (size_t) 0U; + size_t hex_pos = (size_t) 0U; + int ret = 0; + unsigned char c; + unsigned char c_alpha0, c_alpha; + unsigned char c_num0, c_num; + uint8_t c_acc = 0U; + uint8_t c_val; + unsigned char state = 0U; + + while (hex_pos < hex_len) { + c = (unsigned char) hex[hex_pos]; + c_num = c ^ 48U; + c_num0 = (c_num - 10U) >> 8; + c_alpha = (c & ~32U) - 55U; + c_alpha0 = ((c_alpha - 10U) ^ (c_alpha - 16U)) >> 8; + if ((c_num0 | c_alpha0) == 0U) { + if (ignore != NULL && state == 0U && strchr(ignore, c) != NULL) { + hex_pos++; + continue; + } + break; + } + c_val = (uint8_t) ((c_num0 & c_num) | (c_alpha0 & c_alpha)); + if (bin_pos >= bin_maxlen) { + ret = -1; + errno = ERANGE; + break; + } + if (state == 0U) { + c_acc = c_val * 16U; + } else { + bin[bin_pos++] = c_acc | c_val; + } + state = ~state; + hex_pos++; + } + if (state != 0U) { + hex_pos--; + errno = EINVAL; + ret = -1; + } + if (ret != 0) { + bin_pos = (size_t) 0U; + } + if (hex_end_p != NULL) { + *hex_end_p = &hex[hex_pos]; + } else if (hex_pos != hex_len) { + errno = EINVAL; + ret = -1; + } + if (ret != 0) { + return ret; + } + return (int) bin_pos; +} + +bool +hydro_equal(const void *b1_, const void *b2_, size_t len) +{ + const volatile uint8_t *volatile b1 = (const volatile uint8_t *volatile) b1_; + const uint8_t *b2 = (const uint8_t *) b2_; + size_t i; + uint8_t d = (uint8_t) 0U; + + if (b1 == b2) { + d = ~d; + } + for (i = 0U; i < len; i++) { + d |= b1[i] ^ b2[i]; + } + return (bool) (1 & ((d - 1) >> 8)); +} + +int +hydro_compare(const uint8_t *b1_, const uint8_t *b2_, size_t len) +{ + const volatile uint8_t *volatile b1 = (const volatile uint8_t *volatile) b1_; + const uint8_t *b2 = (const uint8_t *) b2_; + uint8_t gt = 0U; + uint8_t eq = 1U; + size_t i; + + i = len; + while (i != 0U) { + i--; + gt |= ((b2[i] - b1[i]) >> 8) & eq; + eq &= ((b2[i] ^ b1[i]) - 1) >> 8; + } + return (int) (gt + gt + eq) - 1; +} + +int +hydro_pad(unsigned char *buf, size_t unpadded_buflen, size_t blocksize, size_t max_buflen) +{ + unsigned char *tail; + size_t i; + size_t xpadlen; + size_t xpadded_len; + volatile unsigned char mask; + unsigned char barrier_mask; + + if (blocksize <= 0U || max_buflen > INT_MAX) { + return -1; + } + xpadlen = blocksize - 1U; + if ((blocksize & (blocksize - 1U)) == 0U) { + xpadlen -= unpadded_buflen & (blocksize - 1U); + } else { + xpadlen -= unpadded_buflen % blocksize; + } + if ((size_t) SIZE_MAX - unpadded_buflen <= xpadlen) { + return -1; + } + xpadded_len = unpadded_buflen + xpadlen; + if (xpadded_len >= max_buflen) { + return -1; + } + tail = &buf[xpadded_len]; + mask = 0U; + for (i = 0; i < blocksize; i++) { + barrier_mask = (unsigned char) (((i ^ xpadlen) - 1U) >> ((sizeof(size_t) - 1U) * CHAR_BIT)); + *(tail - i) = ((*(tail - i)) & mask) | (0x80 & barrier_mask); + mask |= barrier_mask; + } + return (int) (xpadded_len + 1); +} + +int +hydro_unpad(const unsigned char *buf, size_t padded_buflen, size_t blocksize) +{ + const unsigned char *tail; + unsigned char acc = 0U; + unsigned char c; + unsigned char valid = 0U; + volatile size_t pad_len = 0U; + size_t i; + size_t is_barrier; + + if (padded_buflen < blocksize || blocksize <= 0U) { + return -1; + } + tail = &buf[padded_buflen - 1U]; + + for (i = 0U; i < blocksize; i++) { + c = *(tail - i); + is_barrier = (((acc - 1U) & (pad_len - 1U) & ((c ^ 0x80) - 1U)) >> 8) & 1U; + acc |= c; + pad_len |= i & (1U + ~is_barrier); + valid |= (unsigned char) is_barrier; + } + if (valid == 0) { + return -1; + } + return (int) (padded_buflen - 1 - pad_len); +} diff --git a/src/modules/net/hydrogen/impl/gimli-core.h b/src/modules/net/hydrogen/impl/gimli-core.h new file mode 100644 index 00000000..57628bb1 --- /dev/null +++ b/src/modules/net/hydrogen/impl/gimli-core.h @@ -0,0 +1,25 @@ +#ifdef __SSE2__ +#include "gimli-core/sse2.h" +#else +#include "gimli-core/portable.h" +#endif + +static void +gimli_core_u8(uint8_t state_u8[gimli_BLOCKBYTES], uint8_t tag) +{ + state_u8[gimli_BLOCKBYTES - 1] ^= tag; +#ifndef NATIVE_LITTLE_ENDIAN + uint32_t state_u32[12]; + int i; + + for (i = 0; i < 12; i++) { + state_u32[i] = LOAD32_LE(&state_u8[i * 4]); + } + gimli_core(state_u32); + for (i = 0; i < 12; i++) { + STORE32_LE(&state_u8[i * 4], state_u32[i]); + } +#else + gimli_core((uint32_t *) (void *) state_u8); /* state_u8 must be properly aligned */ +#endif +} diff --git a/src/modules/net/hydrogen/impl/gimli-core/portable.h b/src/modules/net/hydrogen/impl/gimli-core/portable.h new file mode 100644 index 00000000..db6b84ce --- /dev/null +++ b/src/modules/net/hydrogen/impl/gimli-core/portable.h @@ -0,0 +1,39 @@ +static void +gimli_core(uint32_t state[gimli_BLOCKBYTES / 4]) +{ + unsigned int round; + unsigned int column; + uint32_t x; + uint32_t y; + uint32_t z; + + for (round = 24; round > 0; round--) { + for (column = 0; column < 4; column++) { + x = ROTL32(state[column], 24); + y = ROTL32(state[4 + column], 9); + z = state[8 + column]; + + state[8 + column] = x ^ (z << 1) ^ ((y & z) << 2); + state[4 + column] = y ^ x ^ ((x | z) << 1); + state[column] = z ^ y ^ ((x & y) << 3); + } + switch (round & 3) { + case 0: + x = state[0]; + state[0] = state[1]; + state[1] = x; + x = state[2]; + state[2] = state[3]; + state[3] = x; + state[0] ^= ((uint32_t) 0x9e377900 | round); + break; + case 2: + x = state[0]; + state[0] = state[2]; + state[2] = x; + x = state[1]; + state[1] = state[3]; + state[3] = x; + } + } +} diff --git a/src/modules/net/hydrogen/impl/gimli-core/sse2.h b/src/modules/net/hydrogen/impl/gimli-core/sse2.h new file mode 100644 index 00000000..f58d01ac --- /dev/null +++ b/src/modules/net/hydrogen/impl/gimli-core/sse2.h @@ -0,0 +1,100 @@ +#include +#ifdef __SSSE3__ +# include +#endif + +#define S 9 + +static inline __m128i +shift(__m128i x, int bits) +{ + return _mm_slli_epi32(x, bits); +} + +static inline __m128i +rotate(__m128i x, int bits) +{ + return _mm_slli_epi32(x, bits) | _mm_srli_epi32(x, 32 - bits); +} + +#ifdef __SSSE3__ +static inline __m128i +rotate24(__m128i x) +{ + return _mm_shuffle_epi8(x, _mm_set_epi8(12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1)); +} +#else +static inline __m128i +rotate24(__m128i x) +{ + uint8_t _hydro_attr_aligned_(16) x8[16], y8[16]; + + _mm_storeu_si128((__m128i *) (void *) x8, x); + + y8[ 0] = x8[ 1]; y8[ 1] = x8[ 2]; y8[ 2] = x8[ 3]; y8[ 3] = x8[ 0]; + y8[ 4] = x8[ 5]; y8[ 5] = x8[ 6]; y8[ 6] = x8[ 7]; y8[ 7] = x8[ 4]; + y8[ 8] = x8[ 9]; y8[ 9] = x8[10]; y8[10] = x8[11]; y8[11] = x8[ 8]; + y8[12] = x8[13]; y8[13] = x8[14]; y8[14] = x8[15]; y8[15] = x8[12]; + + return _mm_loadu_si128((const __m128i *) (const void *) y8); +} +#endif + +static const uint32_t _hydro_attr_aligned_(16) coeffs[24] = { + 0x9e377904, 0, 0, 0, 0x9e377908, 0, 0, 0, 0x9e37790c, 0, 0, 0, + 0x9e377910, 0, 0, 0, 0x9e377914, 0, 0, 0, 0x9e377918, 0, 0, 0, +}; + +static void +gimli_core(uint32_t state[gimli_BLOCKBYTES / 4]) +{ + __m128i x = _mm_loadu_si128((const __m128i *) (const void *) &state[0]); + __m128i y = _mm_loadu_si128((const __m128i *) (const void *) &state[4]); + __m128i z = _mm_loadu_si128((const __m128i *) (const void *) &state[8]); + __m128i newy; + __m128i newz; + int round; + + for (round = 5; round >= 0; round--) { + x = rotate24(x); + y = rotate(y, S); + newz = x ^ shift(z, 1) ^ shift(y & z, 2); + newy = y ^ x ^ shift(x | z, 1); + x = z ^ y ^ shift(x & y, 3); + y = newy; + z = newz; + + x = _mm_shuffle_epi32(x, _MM_SHUFFLE(2, 3, 0, 1)); + x ^= ((const __m128i *) (const void *) coeffs)[round]; + + x = rotate24(x); + y = rotate(y, S); + newz = x ^ shift(z, 1) ^ shift(y & z, 2); + newy = y ^ x ^ shift(x | z, 1); + x = z ^ y ^ shift(x & y, 3); + y = newy; + z = newz; + + x = rotate24(x); + y = rotate(y, S); + newz = x ^ shift(z, 1) ^ shift(y & z, 2); + newy = y ^ x ^ shift(x | z, 1); + x = z ^ y ^ shift(x & y, 3); + y = newy; + z = newz; + + x = _mm_shuffle_epi32(x, _MM_SHUFFLE(1, 0, 3, 2)); + + x = rotate24(x); + y = rotate(y, S); + newz = x ^ shift(z, 1) ^ shift(y & z, 2); + newy = y ^ x ^ shift(x | z, 1); + x = z ^ y ^ shift(x & y, 3); + y = newy; + z = newz; + } + + _mm_storeu_si128((__m128i *) (void *) &state[0], x); + _mm_storeu_si128((__m128i *) (void *) &state[4], y); + _mm_storeu_si128((__m128i *) (void *) &state[8], z); +} diff --git a/src/modules/net/hydrogen/impl/hash.h b/src/modules/net/hydrogen/impl/hash.h new file mode 100644 index 00000000..29ce02ee --- /dev/null +++ b/src/modules/net/hydrogen/impl/hash.h @@ -0,0 +1,140 @@ +int +hydro_hash_update(hydro_hash_state *state, const void *in_, size_t in_len) +{ + const uint8_t *in = (const uint8_t *) in_; + uint8_t * buf = (uint8_t *) (void *) state->state; + size_t left; + size_t ps; + size_t i; + + while (in_len > 0) { + left = gimli_RATE - state->buf_off; + if ((ps = in_len) > left) { + ps = left; + } + for (i = 0; i < ps; i++) { + buf[state->buf_off + i] ^= in[i]; + } + in += ps; + in_len -= ps; + state->buf_off += (uint8_t) ps; + if (state->buf_off == gimli_RATE) { + gimli_core_u8(buf, 0); + state->buf_off = 0; + } + } + return 0; +} + +/* pad(str_enc("kmac") || str_enc(context)) || pad(str_enc(k)) || + msg || right_enc(msg_len) || 0x00 */ + +int +hydro_hash_init(hydro_hash_state *state, const char ctx[hydro_hash_CONTEXTBYTES], + const uint8_t key[hydro_hash_KEYBYTES]) +{ + uint8_t block[64] = { 4, 'k', 'm', 'a', 'c', 8 }; + size_t p; + + COMPILER_ASSERT(hydro_hash_KEYBYTES <= sizeof block - gimli_RATE - 1); + COMPILER_ASSERT(hydro_hash_CONTEXTBYTES == 8); + mem_zero(block + 14, sizeof block - 14); + memcpy(block + 6, ctx, 8); + if (key != NULL) { + block[gimli_RATE] = (uint8_t) hydro_hash_KEYBYTES; + memcpy(block + gimli_RATE + 1, key, hydro_hash_KEYBYTES); + p = (gimli_RATE + 1 + hydro_hash_KEYBYTES + (gimli_RATE - 1)) & ~(size_t) (gimli_RATE - 1); + } else { + block[gimli_RATE] = (uint8_t) 0; + p = (gimli_RATE + 1 + 0 + (gimli_RATE - 1)) & ~(size_t) (gimli_RATE - 1); + } + mem_zero(state, sizeof *state); + hydro_hash_update(state, block, p); + + return 0; +} + +/* pad(str_enc("tmac") || str_enc(context)) || pad(str_enc(k)) || + pad(right_enc(tweak)) || msg || right_enc(msg_len) || 0x00 */ + +static int +hydro_hash_init_with_tweak(hydro_hash_state *state, const char ctx[hydro_hash_CONTEXTBYTES], + uint64_t tweak, const uint8_t key[hydro_hash_KEYBYTES]) +{ + uint8_t block[80] = { 4, 't', 'm', 'a', 'c', 8 }; + size_t p; + + COMPILER_ASSERT(hydro_hash_KEYBYTES <= sizeof block - 2 * gimli_RATE - 1); + COMPILER_ASSERT(hydro_hash_CONTEXTBYTES == 8); + mem_zero(block + 14, sizeof block - 14); + memcpy(block + 6, ctx, 8); + if (key != NULL) { + block[gimli_RATE] = (uint8_t) hydro_hash_KEYBYTES; + memcpy(block + gimli_RATE + 1, key, hydro_hash_KEYBYTES); + p = (gimli_RATE + 1 + hydro_hash_KEYBYTES + (gimli_RATE - 1)) & ~(size_t) (gimli_RATE - 1); + } else { + block[gimli_RATE] = (uint8_t) 0; + p = (gimli_RATE + 1 + 0 + (gimli_RATE - 1)) & ~(size_t) (gimli_RATE - 1); + } + block[p] = (uint8_t) sizeof tweak; + STORE64_LE(&block[p + 1], tweak); + p += gimli_RATE; + mem_zero(state, sizeof *state); + hydro_hash_update(state, block, p); + + return 0; +} + +int +hydro_hash_final(hydro_hash_state *state, uint8_t *out, size_t out_len) +{ + uint8_t lc[4]; + uint8_t *buf = (uint8_t *) (void *) state->state; + size_t i; + size_t lc_len; + size_t leftover; + + if (out_len < hydro_hash_BYTES_MIN || out_len > hydro_hash_BYTES_MAX) { + return -1; + } + COMPILER_ASSERT(hydro_hash_BYTES_MAX <= 0xffff); + lc[1] = (uint8_t) out_len; + lc[2] = (uint8_t) (out_len >> 8); + lc[3] = 0; + lc_len = (size_t) (1 + (lc[2] != 0)); + lc[0] = (uint8_t) lc_len; + hydro_hash_update(state, lc, 1 + lc_len + 1); + gimli_pad_u8(buf, state->buf_off, gimli_DOMAIN_XOF); + for (i = 0; i < out_len / gimli_RATE; i++) { + gimli_core_u8(buf, 0); + memcpy(out + i * gimli_RATE, buf, gimli_RATE); + } + leftover = out_len % gimli_RATE; + if (leftover != 0) { + gimli_core_u8(buf, 0); + mem_cpy(out + i * gimli_RATE, buf, leftover); + } + state->buf_off = gimli_RATE; + + return 0; +} + +int +hydro_hash_hash(uint8_t *out, size_t out_len, const void *in_, size_t in_len, + const char ctx[hydro_hash_CONTEXTBYTES], const uint8_t key[hydro_hash_KEYBYTES]) +{ + hydro_hash_state st; + const uint8_t * in = (const uint8_t *) in_; + + if (hydro_hash_init(&st, ctx, key) != 0 || hydro_hash_update(&st, in, in_len) != 0 || + hydro_hash_final(&st, out, out_len) != 0) { + return -1; + } + return 0; +} + +void +hydro_hash_keygen(uint8_t key[hydro_hash_KEYBYTES]) +{ + hydro_random_buf(key, hydro_hash_KEYBYTES); +} diff --git a/src/modules/net/hydrogen/impl/hydrogen_p.h b/src/modules/net/hydrogen/impl/hydrogen_p.h new file mode 100644 index 00000000..1bdaf741 --- /dev/null +++ b/src/modules/net/hydrogen/impl/hydrogen_p.h @@ -0,0 +1,83 @@ +static int hydro_random_init(void); + +/* ---------------- */ + +#define gimli_BLOCKBYTES 48 +#define gimli_CAPACITY 32 +#define gimli_RATE 16 + +#define gimli_TAG_HEADER 0x01 +#define gimli_TAG_PAYLOAD 0x02 +#define gimli_TAG_FINAL 0x08 +#define gimli_TAG_FINAL0 0xf8 +#define gimli_TAG_KEY0 0xfe +#define gimli_TAG_KEY 0xff + +#define gimli_DOMAIN_AEAD 0x0 +#define gimli_DOMAIN_XOF 0xf + +static void gimli_core_u8(uint8_t state_u8[gimli_BLOCKBYTES], uint8_t tag); + +static inline void +gimli_pad_u8(uint8_t buf[gimli_BLOCKBYTES], size_t pos, uint8_t domain) +{ + buf[pos] ^= (domain << 1) | 1; + buf[gimli_RATE - 1] ^= 0x80; +} + +static inline void +hydro_mem_ct_zero_u32(uint32_t *dst_, size_t n) +{ + volatile uint32_t *volatile dst = (volatile uint32_t *volatile) (void *) dst_; + size_t i; + + for (i = 0; i < n; i++) { + dst[i] = 0; + } +} + +static inline uint32_t hydro_mem_ct_cmp_u32(const uint32_t *b1_, const uint32_t *b2, + size_t n) _hydro_attr_warn_unused_result_; + +static inline uint32_t +hydro_mem_ct_cmp_u32(const uint32_t *b1_, const uint32_t *b2, size_t n) +{ + const volatile uint32_t *volatile b1 = (const volatile uint32_t *volatile) (const void *) b1_; + size_t i; + uint32_t cv = 0; + + for (i = 0; i < n; i++) { + cv |= b1[i] ^ b2[i]; + } + return cv; +} + +/* ---------------- */ + +static int hydro_hash_init_with_tweak(hydro_hash_state *state, + const char ctx[hydro_hash_CONTEXTBYTES], uint64_t tweak, + const uint8_t key[hydro_hash_KEYBYTES]); + +/* ---------------- */ + +#define hydro_secretbox_NONCEBYTES 20 +#define hydro_secretbox_MACBYTES 16 + +/* ---------------- */ + +#define hydro_x25519_BYTES 32 +#define hydro_x25519_PUBLICKEYBYTES 32 +#define hydro_x25519_SECRETKEYBYTES 32 + +static int hydro_x25519_scalarmult(uint8_t out[hydro_x25519_BYTES], + const uint8_t scalar[hydro_x25519_SECRETKEYBYTES], + const uint8_t x1[hydro_x25519_PUBLICKEYBYTES], + bool clamp) _hydro_attr_warn_unused_result_; + +static inline int hydro_x25519_scalarmult_base(uint8_t pk[hydro_x25519_PUBLICKEYBYTES], + const uint8_t sk[hydro_x25519_SECRETKEYBYTES]) + _hydro_attr_warn_unused_result_; + +static inline void +hydro_x25519_scalarmult_base_uniform(uint8_t pk[hydro_x25519_PUBLICKEYBYTES], + const uint8_t sk[hydro_x25519_SECRETKEYBYTES]); diff --git a/src/modules/net/hydrogen/impl/kdf.h b/src/modules/net/hydrogen/impl/kdf.h new file mode 100644 index 00000000..22a222cd --- /dev/null +++ b/src/modules/net/hydrogen/impl/kdf.h @@ -0,0 +1,20 @@ +int +hydro_kdf_derive_from_key(uint8_t *subkey, size_t subkey_len, uint64_t subkey_id, + const char ctx[hydro_kdf_CONTEXTBYTES], + const uint8_t key[hydro_kdf_KEYBYTES]) +{ + hydro_hash_state st; + + COMPILER_ASSERT(hydro_kdf_CONTEXTBYTES >= hydro_hash_CONTEXTBYTES); + COMPILER_ASSERT(hydro_kdf_KEYBYTES >= hydro_hash_KEYBYTES); + if (hydro_hash_init_with_tweak(&st, ctx, subkey_id, key) != 0) { + return -1; + } + return hydro_hash_final(&st, subkey, subkey_len); +} + +void +hydro_kdf_keygen(uint8_t key[hydro_kdf_KEYBYTES]) +{ + hydro_random_buf(key, hydro_kdf_KEYBYTES); +} diff --git a/src/modules/net/hydrogen/impl/kx.h b/src/modules/net/hydrogen/impl/kx.h new file mode 100644 index 00000000..ee34a41a --- /dev/null +++ b/src/modules/net/hydrogen/impl/kx.h @@ -0,0 +1,535 @@ +#define hydro_kx_AEAD_KEYBYTES hydro_hash_KEYBYTES +#define hydro_kx_AEAD_MACBYTES 16 + +#define hydro_kx_CONTEXT "hydro_kx" + +static void +hydro_kx_aead_init(uint8_t aead_state[gimli_BLOCKBYTES], uint8_t k[hydro_kx_AEAD_KEYBYTES], + hydro_kx_state *state) +{ + static const uint8_t prefix[] = { 6, 'k', 'x', 'x', '2', '5', '6', 0 }; + + hydro_hash_final(&state->h_st, k, hydro_kx_AEAD_KEYBYTES); + + mem_zero(aead_state + sizeof prefix, gimli_BLOCKBYTES - sizeof prefix); + memcpy(aead_state, prefix, sizeof prefix); + gimli_core_u8(aead_state, gimli_TAG_HEADER); + + COMPILER_ASSERT(hydro_kx_AEAD_KEYBYTES == 2 * gimli_RATE); + mem_xor(aead_state, k, gimli_RATE); + gimli_core_u8(aead_state, gimli_TAG_KEY); + mem_xor(aead_state, k + gimli_RATE, gimli_RATE); + gimli_core_u8(aead_state, gimli_TAG_KEY); +} + +static void +hydro_kx_aead_final(uint8_t *aead_state, const uint8_t key[hydro_kx_AEAD_KEYBYTES]) +{ + COMPILER_ASSERT(hydro_kx_AEAD_KEYBYTES == gimli_CAPACITY); + mem_xor(aead_state + gimli_RATE, key, hydro_kx_AEAD_KEYBYTES); + gimli_core_u8(aead_state, gimli_TAG_FINAL); + mem_xor(aead_state + gimli_RATE, key, hydro_kx_AEAD_KEYBYTES); + gimli_core_u8(aead_state, gimli_TAG_FINAL); +} + +static void +hydro_kx_aead_xor_enc(uint8_t aead_state[gimli_BLOCKBYTES], uint8_t *out, const uint8_t *in, + size_t inlen) +{ + size_t i; + size_t leftover; + + for (i = 0; i < inlen / gimli_RATE; i++) { + mem_xor2(&out[i * gimli_RATE], &in[i * gimli_RATE], aead_state, gimli_RATE); + memcpy(aead_state, &out[i * gimli_RATE], gimli_RATE); + gimli_core_u8(aead_state, gimli_TAG_PAYLOAD); + } + leftover = inlen % gimli_RATE; + if (leftover != 0) { + mem_xor2(&out[i * gimli_RATE], &in[i * gimli_RATE], aead_state, leftover); + mem_cpy(aead_state, &out[i * gimli_RATE], leftover); + } + gimli_pad_u8(aead_state, leftover, gimli_DOMAIN_AEAD); + gimli_core_u8(aead_state, gimli_TAG_PAYLOAD); +} + +static void +hydro_kx_aead_xor_dec(uint8_t aead_state[gimli_BLOCKBYTES], uint8_t *out, const uint8_t *in, + size_t inlen) +{ + size_t i; + size_t leftover; + + for (i = 0; i < inlen / gimli_RATE; i++) { + mem_xor2(&out[i * gimli_RATE], &in[i * gimli_RATE], aead_state, gimli_RATE); + memcpy(aead_state, &in[i * gimli_RATE], gimli_RATE); + gimli_core_u8(aead_state, gimli_TAG_PAYLOAD); + } + leftover = inlen % gimli_RATE; + if (leftover != 0) { + mem_xor2(&out[i * gimli_RATE], &in[i * gimli_RATE], aead_state, leftover); + mem_cpy(aead_state, &in[i * gimli_RATE], leftover); + } + gimli_pad_u8(aead_state, leftover, gimli_DOMAIN_AEAD); + gimli_core_u8(aead_state, gimli_TAG_PAYLOAD); +} + +static void +hydro_kx_aead_encrypt(hydro_kx_state *state, uint8_t *c, const uint8_t *m, size_t mlen) +{ + _hydro_attr_aligned_(16) uint8_t aead_state[gimli_BLOCKBYTES]; + uint8_t k[hydro_kx_AEAD_KEYBYTES]; + uint8_t * mac = &c[0]; + uint8_t * ct = &c[hydro_kx_AEAD_MACBYTES]; + + hydro_kx_aead_init(aead_state, k, state); + hydro_kx_aead_xor_enc(aead_state, ct, m, mlen); + hydro_kx_aead_final(aead_state, k); + COMPILER_ASSERT(hydro_kx_AEAD_MACBYTES <= gimli_CAPACITY); + memcpy(mac, aead_state + gimli_RATE, hydro_kx_AEAD_MACBYTES); + hydro_hash_update(&state->h_st, c, mlen + hydro_kx_AEAD_MACBYTES); +} + +static int hydro_kx_aead_decrypt(hydro_kx_state *state, uint8_t *m, const uint8_t *c, + size_t clen) _hydro_attr_warn_unused_result_; + +static int +hydro_kx_aead_decrypt(hydro_kx_state *state, uint8_t *m, const uint8_t *c, size_t clen) +{ + _hydro_attr_aligned_(16) uint32_t int_state[gimli_BLOCKBYTES / 4]; + uint32_t pub_mac[hydro_kx_AEAD_MACBYTES / 4]; + uint8_t k[hydro_kx_AEAD_KEYBYTES]; + uint8_t * aead_state = (uint8_t *) (void *) int_state; + const uint8_t * mac; + const uint8_t * ct; + size_t mlen; + uint32_t cv; + + if (clen < hydro_kx_AEAD_MACBYTES) { + return -1; + } + mac = &c[0]; + ct = &c[hydro_kx_AEAD_MACBYTES]; + mlen = clen - hydro_kx_AEAD_MACBYTES; + memcpy(pub_mac, mac, sizeof pub_mac); + hydro_kx_aead_init(aead_state, k, state); + hydro_hash_update(&state->h_st, c, clen); + hydro_kx_aead_xor_dec(aead_state, m, ct, mlen); + hydro_kx_aead_final(aead_state, k); + COMPILER_ASSERT(hydro_kx_AEAD_MACBYTES <= gimli_CAPACITY); + COMPILER_ASSERT(gimli_RATE % 4 == 0); + cv = hydro_mem_ct_cmp_u32(int_state + gimli_RATE / 4, pub_mac, hydro_kx_AEAD_MACBYTES / 4); + hydro_mem_ct_zero_u32(int_state, gimli_BLOCKBYTES / 4); + if (cv != 0) { + mem_zero(m, mlen); + return -1; + } + return 0; +} + +/* -- */ + +void +hydro_kx_keygen(hydro_kx_keypair *static_kp) +{ + hydro_random_buf(static_kp->sk, hydro_kx_SECRETKEYBYTES); + if (hydro_x25519_scalarmult_base(static_kp->pk, static_kp->sk) != 0) { + abort(); + } +} + +void +hydro_kx_keygen_deterministic(hydro_kx_keypair *static_kp, const uint8_t seed[hydro_kx_SEEDBYTES]) +{ + COMPILER_ASSERT(hydro_kx_SEEDBYTES >= hydro_random_SEEDBYTES); + hydro_random_buf_deterministic(static_kp->sk, hydro_kx_SECRETKEYBYTES, seed); + if (hydro_x25519_scalarmult_base(static_kp->pk, static_kp->sk) != 0) { + abort(); + } +} + +static void +hydro_kx_init_state(hydro_kx_state *state, const char *name) +{ + mem_zero(state, sizeof *state); + hydro_hash_init(&state->h_st, hydro_kx_CONTEXT, NULL); + hydro_hash_update(&state->h_st, name, strlen(name)); + hydro_hash_final(&state->h_st, NULL, 0); +} + +static void +hydro_kx_final(hydro_kx_state *state, uint8_t session_k1[hydro_kx_SESSIONKEYBYTES], + uint8_t session_k2[hydro_kx_SESSIONKEYBYTES]) +{ + uint8_t kdf_key[hydro_kdf_KEYBYTES]; + + hydro_hash_final(&state->h_st, kdf_key, sizeof kdf_key); + hydro_kdf_derive_from_key(session_k1, hydro_kx_SESSIONKEYBYTES, 0, hydro_kx_CONTEXT, kdf_key); + hydro_kdf_derive_from_key(session_k2, hydro_kx_SESSIONKEYBYTES, 1, hydro_kx_CONTEXT, kdf_key); +} + +static int +hydro_kx_dh(hydro_kx_state *state, const uint8_t sk[hydro_x25519_SECRETKEYBYTES], + const uint8_t pk[hydro_x25519_PUBLICKEYBYTES]) +{ + uint8_t dh_result[hydro_x25519_BYTES]; + + if (hydro_x25519_scalarmult(dh_result, sk, pk, 1) != 0) { + return -1; + } + hydro_hash_update(&state->h_st, dh_result, hydro_x25519_BYTES); + + return 0; +} + +static void +hydro_kx_eph_keygen(hydro_kx_state *state, hydro_kx_keypair *kp) +{ + hydro_kx_keygen(kp); + hydro_hash_update(&state->h_st, kp->pk, sizeof kp->pk); +} + +/* NOISE_N */ + +int +hydro_kx_n_1(hydro_kx_session_keypair *kp, uint8_t packet1[hydro_kx_N_PACKET1BYTES], + const uint8_t psk[hydro_kx_PSKBYTES], + const uint8_t peer_static_pk[hydro_kx_PUBLICKEYBYTES]) +{ + hydro_kx_state state; + uint8_t * packet1_eph_pk = &packet1[0]; + uint8_t * packet1_mac = &packet1[hydro_kx_PUBLICKEYBYTES]; + + if (psk == NULL) { + psk = zero; + } + hydro_kx_init_state(&state, "Noise_Npsk0_hydro1"); + hydro_hash_update(&state.h_st, peer_static_pk, hydro_x25519_PUBLICKEYBYTES); + + hydro_hash_update(&state.h_st, psk, hydro_kx_PSKBYTES); + hydro_kx_eph_keygen(&state, &state.eph_kp); + if (hydro_kx_dh(&state, state.eph_kp.sk, peer_static_pk) != 0) { + return -1; + } + hydro_kx_aead_encrypt(&state, packet1_mac, NULL, 0); + memcpy(packet1_eph_pk, state.eph_kp.pk, sizeof state.eph_kp.pk); + + hydro_kx_final(&state, kp->rx, kp->tx); + + return 0; +} + +int +hydro_kx_n_2(hydro_kx_session_keypair *kp, const uint8_t packet1[hydro_kx_N_PACKET1BYTES], + const uint8_t psk[hydro_kx_PSKBYTES], const hydro_kx_keypair *static_kp) +{ + hydro_kx_state state; + const uint8_t *peer_eph_pk = &packet1[0]; + const uint8_t *packet1_mac = &packet1[hydro_kx_PUBLICKEYBYTES]; + + if (psk == NULL) { + psk = zero; + } + hydro_kx_init_state(&state, "Noise_Npsk0_hydro1"); + hydro_hash_update(&state.h_st, static_kp->pk, hydro_kx_PUBLICKEYBYTES); + + hydro_hash_update(&state.h_st, psk, hydro_kx_PSKBYTES); + hydro_hash_update(&state.h_st, peer_eph_pk, hydro_x25519_PUBLICKEYBYTES); + if (hydro_kx_dh(&state, static_kp->sk, peer_eph_pk) != 0 || + hydro_kx_aead_decrypt(&state, NULL, packet1_mac, hydro_kx_AEAD_MACBYTES) != 0) { + return -1; + } + hydro_kx_final(&state, kp->tx, kp->rx); + + return 0; +} + +/* NOISE_KK */ + +int +hydro_kx_kk_1(hydro_kx_state *state, uint8_t packet1[hydro_kx_KK_PACKET1BYTES], + const uint8_t peer_static_pk[hydro_kx_PUBLICKEYBYTES], + const hydro_kx_keypair *static_kp) +{ + uint8_t *packet1_eph_pk = &packet1[0]; + uint8_t *packet1_mac = &packet1[hydro_kx_PUBLICKEYBYTES]; + + hydro_kx_init_state(state, "Noise_KK_hydro1"); + hydro_hash_update(&state->h_st, static_kp->pk, hydro_kx_PUBLICKEYBYTES); + hydro_hash_update(&state->h_st, peer_static_pk, hydro_kx_PUBLICKEYBYTES); + + hydro_kx_eph_keygen(state, &state->eph_kp); + if (hydro_kx_dh(state, state->eph_kp.sk, peer_static_pk) != 0 || + hydro_kx_dh(state, static_kp->sk, peer_static_pk) != 0) { + return -1; + } + hydro_kx_aead_encrypt(state, packet1_mac, NULL, 0); + memcpy(packet1_eph_pk, state->eph_kp.pk, sizeof state->eph_kp.pk); + + return 0; +} + +int +hydro_kx_kk_2(hydro_kx_session_keypair *kp, uint8_t packet2[hydro_kx_KK_PACKET2BYTES], + const uint8_t packet1[hydro_kx_KK_PACKET1BYTES], + const uint8_t peer_static_pk[hydro_kx_PUBLICKEYBYTES], + const hydro_kx_keypair *static_kp) +{ + hydro_kx_state state; + const uint8_t *peer_eph_pk = &packet1[0]; + const uint8_t *packet1_mac = &packet1[hydro_kx_PUBLICKEYBYTES]; + uint8_t * packet2_eph_pk = &packet2[0]; + uint8_t * packet2_mac = &packet2[hydro_kx_PUBLICKEYBYTES]; + + hydro_kx_init_state(&state, "Noise_KK_hydro1"); + hydro_hash_update(&state.h_st, peer_static_pk, hydro_kx_PUBLICKEYBYTES); + hydro_hash_update(&state.h_st, static_kp->pk, hydro_kx_PUBLICKEYBYTES); + + hydro_hash_update(&state.h_st, peer_eph_pk, hydro_kx_PUBLICKEYBYTES); + if (hydro_kx_dh(&state, static_kp->sk, peer_eph_pk) != 0 || + hydro_kx_dh(&state, static_kp->sk, peer_static_pk) != 0 || + hydro_kx_aead_decrypt(&state, NULL, packet1_mac, hydro_kx_AEAD_MACBYTES) != 0) { + return -1; + } + + hydro_kx_eph_keygen(&state, &state.eph_kp); + if (hydro_kx_dh(&state, state.eph_kp.sk, peer_eph_pk) != 0 || + hydro_kx_dh(&state, state.eph_kp.sk, peer_static_pk) != 0) { + return -1; + } + hydro_kx_aead_encrypt(&state, packet2_mac, NULL, 0); + hydro_kx_final(&state, kp->tx, kp->rx); + memcpy(packet2_eph_pk, state.eph_kp.pk, sizeof state.eph_kp.pk); + + return 0; +} + +int +hydro_kx_kk_3(hydro_kx_state *state, hydro_kx_session_keypair *kp, + const uint8_t packet2[hydro_kx_KK_PACKET2BYTES], const hydro_kx_keypair *static_kp) +{ + const uint8_t *peer_eph_pk = packet2; + const uint8_t *packet2_mac = &packet2[hydro_kx_PUBLICKEYBYTES]; + + hydro_hash_update(&state->h_st, peer_eph_pk, hydro_kx_PUBLICKEYBYTES); + if (hydro_kx_dh(state, state->eph_kp.sk, peer_eph_pk) != 0 || + hydro_kx_dh(state, static_kp->sk, peer_eph_pk) != 0) { + return -1; + } + + if (hydro_kx_aead_decrypt(state, NULL, packet2_mac, hydro_kx_AEAD_MACBYTES) != 0) { + return -1; + } + hydro_kx_final(state, kp->rx, kp->tx); + + return 0; +} + +/* NOISE_XX */ + +int +hydro_kx_xx_1(hydro_kx_state *state, uint8_t packet1[hydro_kx_XX_PACKET1BYTES], + const uint8_t psk[hydro_kx_PSKBYTES]) +{ + uint8_t *packet1_eph_pk = &packet1[0]; + uint8_t *packet1_mac = &packet1[hydro_kx_PUBLICKEYBYTES]; + + if (psk == NULL) { + psk = zero; + } + hydro_kx_init_state(state, "Noise_XXpsk0+psk3_hydro1"); + + hydro_kx_eph_keygen(state, &state->eph_kp); + hydro_hash_update(&state->h_st, psk, hydro_kx_PSKBYTES); + memcpy(packet1_eph_pk, state->eph_kp.pk, sizeof state->eph_kp.pk); + hydro_kx_aead_encrypt(state, packet1_mac, NULL, 0); + + return 0; +} + +int +hydro_kx_xx_2(hydro_kx_state *state, uint8_t packet2[hydro_kx_XX_PACKET2BYTES], + const uint8_t packet1[hydro_kx_XX_PACKET1BYTES], const uint8_t psk[hydro_kx_PSKBYTES], + const hydro_kx_keypair *static_kp) +{ + const uint8_t *peer_eph_pk = &packet1[0]; + const uint8_t *packet1_mac = &packet1[hydro_kx_PUBLICKEYBYTES]; + uint8_t * packet2_eph_pk = &packet2[0]; + uint8_t * packet2_enc_static_pk = &packet2[hydro_kx_PUBLICKEYBYTES]; + uint8_t * packet2_mac = + &packet2[hydro_kx_PUBLICKEYBYTES + hydro_kx_PUBLICKEYBYTES + hydro_kx_AEAD_MACBYTES]; + + if (psk == NULL) { + psk = zero; + } + hydro_kx_init_state(state, "Noise_XXpsk0+psk3_hydro1"); + + hydro_hash_update(&state->h_st, peer_eph_pk, hydro_kx_PUBLICKEYBYTES); + hydro_hash_update(&state->h_st, psk, hydro_kx_PSKBYTES); + if (hydro_kx_aead_decrypt(state, NULL, packet1_mac, hydro_kx_AEAD_MACBYTES) != 0) { + return -1; + } + + hydro_kx_eph_keygen(state, &state->eph_kp); + if (hydro_kx_dh(state, state->eph_kp.sk, peer_eph_pk) != 0) { + return -1; + } + hydro_kx_aead_encrypt(state, packet2_enc_static_pk, static_kp->pk, sizeof static_kp->pk); + if (hydro_kx_dh(state, static_kp->sk, peer_eph_pk) != 0) { + return -1; + } + hydro_kx_aead_encrypt(state, packet2_mac, NULL, 0); + + memcpy(packet2_eph_pk, state->eph_kp.pk, sizeof state->eph_kp.pk); + + return 0; +} + +int +hydro_kx_xx_3(hydro_kx_state *state, hydro_kx_session_keypair *kp, + uint8_t packet3[hydro_kx_XX_PACKET3BYTES], + uint8_t peer_static_pk[hydro_kx_PUBLICKEYBYTES], + const uint8_t packet2[hydro_kx_XX_PACKET2BYTES], const uint8_t psk[hydro_kx_PSKBYTES], + const hydro_kx_keypair *static_kp) +{ + uint8_t peer_static_pk_[hydro_kx_PUBLICKEYBYTES]; + const uint8_t *peer_eph_pk = &packet2[0]; + const uint8_t *peer_enc_static_pk = &packet2[hydro_kx_PUBLICKEYBYTES]; + const uint8_t *packet2_mac = + &packet2[hydro_kx_PUBLICKEYBYTES + hydro_kx_PUBLICKEYBYTES + hydro_kx_AEAD_MACBYTES]; + uint8_t *packet3_enc_static_pk = &packet3[0]; + uint8_t *packet3_mac = &packet3[hydro_kx_PUBLICKEYBYTES + hydro_kx_AEAD_MACBYTES]; + + if (psk == NULL) { + psk = zero; + } + if (peer_static_pk == NULL) { + peer_static_pk = peer_static_pk_; + } + hydro_hash_update(&state->h_st, peer_eph_pk, hydro_kx_PUBLICKEYBYTES); + if (hydro_kx_dh(state, state->eph_kp.sk, peer_eph_pk) != 0 || + hydro_kx_aead_decrypt(state, peer_static_pk, peer_enc_static_pk, + hydro_kx_PUBLICKEYBYTES + hydro_kx_AEAD_MACBYTES) != 0 || + hydro_kx_dh(state, state->eph_kp.sk, peer_static_pk) != 0 || + hydro_kx_aead_decrypt(state, NULL, packet2_mac, hydro_kx_AEAD_MACBYTES) != 0) { + return -1; + } + + hydro_kx_aead_encrypt(state, packet3_enc_static_pk, static_kp->pk, sizeof static_kp->pk); + if (hydro_kx_dh(state, static_kp->sk, peer_eph_pk) != 0) { + return -1; + } + hydro_hash_update(&state->h_st, psk, hydro_kx_PSKBYTES); + hydro_kx_aead_encrypt(state, packet3_mac, NULL, 0); + hydro_kx_final(state, kp->rx, kp->tx); + + return 0; +} + +int +hydro_kx_xx_4(hydro_kx_state *state, hydro_kx_session_keypair *kp, + uint8_t peer_static_pk[hydro_kx_PUBLICKEYBYTES], + const uint8_t packet3[hydro_kx_XX_PACKET3BYTES], const uint8_t psk[hydro_kx_PSKBYTES]) +{ + uint8_t peer_static_pk_[hydro_kx_PUBLICKEYBYTES]; + const uint8_t *peer_enc_static_pk = &packet3[0]; + const uint8_t *packet3_mac = &packet3[hydro_kx_PUBLICKEYBYTES + hydro_kx_AEAD_MACBYTES]; + + if (psk == NULL) { + psk = zero; + } + if (peer_static_pk == NULL) { + peer_static_pk = peer_static_pk_; + } + if (hydro_kx_aead_decrypt(state, peer_static_pk, peer_enc_static_pk, + hydro_kx_PUBLICKEYBYTES + hydro_kx_AEAD_MACBYTES) != 0 || + hydro_kx_dh(state, state->eph_kp.sk, peer_static_pk) != 0) { + return -1; + } + hydro_hash_update(&state->h_st, psk, hydro_kx_PSKBYTES); + if (hydro_kx_aead_decrypt(state, NULL, packet3_mac, hydro_kx_AEAD_MACBYTES) != 0) { + return -1; + } + hydro_kx_final(state, kp->tx, kp->rx); + + return 0; +} + +/* NOISE_NK */ + +int +hydro_kx_nk_1(hydro_kx_state *state, uint8_t packet1[hydro_kx_NK_PACKET1BYTES], + const uint8_t psk[hydro_kx_PSKBYTES], + const uint8_t peer_static_pk[hydro_kx_PUBLICKEYBYTES]) +{ + uint8_t *packet1_eph_pk = &packet1[0]; + uint8_t *packet1_mac = &packet1[hydro_kx_PUBLICKEYBYTES]; + + if (psk == NULL) { + psk = zero; + } + hydro_kx_init_state(state, "Noise_NKpsk0_hydro1"); + hydro_hash_update(&state->h_st, peer_static_pk, hydro_x25519_PUBLICKEYBYTES); + + hydro_hash_update(&state->h_st, psk, hydro_kx_PSKBYTES); + hydro_kx_eph_keygen(state, &state->eph_kp); + if (hydro_kx_dh(state, state->eph_kp.sk, peer_static_pk) != 0) { + return -1; + } + hydro_kx_aead_encrypt(state, packet1_mac, NULL, 0); + memcpy(packet1_eph_pk, state->eph_kp.pk, sizeof state->eph_kp.pk); + + return 0; +} + +int +hydro_kx_nk_2(hydro_kx_session_keypair *kp, uint8_t packet2[hydro_kx_NK_PACKET2BYTES], + const uint8_t packet1[hydro_kx_NK_PACKET1BYTES], const uint8_t psk[hydro_kx_PSKBYTES], + const hydro_kx_keypair *static_kp) +{ + hydro_kx_state state; + const uint8_t *peer_eph_pk = &packet1[0]; + const uint8_t *packet1_mac = &packet1[hydro_kx_PUBLICKEYBYTES]; + uint8_t * packet2_eph_pk = &packet2[0]; + uint8_t * packet2_mac = &packet2[hydro_kx_PUBLICKEYBYTES]; + + if (psk == NULL) { + psk = zero; + } + hydro_kx_init_state(&state, "Noise_NKpsk0_hydro1"); + hydro_hash_update(&state.h_st, static_kp->pk, hydro_kx_PUBLICKEYBYTES); + + hydro_hash_update(&state.h_st, psk, hydro_kx_PSKBYTES); + hydro_hash_update(&state.h_st, peer_eph_pk, hydro_x25519_PUBLICKEYBYTES); + if (hydro_kx_dh(&state, static_kp->sk, peer_eph_pk) != 0 || + hydro_kx_aead_decrypt(&state, NULL, packet1_mac, hydro_kx_AEAD_MACBYTES) != 0) { + return -1; + } + + hydro_kx_eph_keygen(&state, &state.eph_kp); + if (hydro_kx_dh(&state, state.eph_kp.sk, peer_eph_pk) != 0) { + return -1; + } + hydro_kx_aead_encrypt(&state, packet2_mac, NULL, 0); + hydro_kx_final(&state, kp->tx, kp->rx); + memcpy(packet2_eph_pk, state.eph_kp.pk, sizeof state.eph_kp.pk); + + return 0; +} + +int +hydro_kx_nk_3(hydro_kx_state *state, hydro_kx_session_keypair *kp, + const uint8_t packet2[hydro_kx_NK_PACKET2BYTES]) +{ + const uint8_t *peer_eph_pk = &packet2[0]; + const uint8_t *packet2_mac = &packet2[hydro_kx_PUBLICKEYBYTES]; + + hydro_hash_update(&state->h_st, peer_eph_pk, hydro_x25519_PUBLICKEYBYTES); + if (hydro_kx_dh(state, state->eph_kp.sk, peer_eph_pk) != 0 || + hydro_kx_aead_decrypt(state, NULL, packet2_mac, hydro_kx_AEAD_MACBYTES) != 0) { + return -1; + } + hydro_kx_final(state, kp->rx, kp->tx); + + return 0; +} diff --git a/src/modules/net/hydrogen/impl/random.h b/src/modules/net/hydrogen/impl/random.h new file mode 100644 index 00000000..2593d31f --- /dev/null +++ b/src/modules/net/hydrogen/impl/random.h @@ -0,0 +1,149 @@ +static TLS struct { + _hydro_attr_aligned_(16) uint8_t state[gimli_BLOCKBYTES]; + uint64_t counter; + uint8_t initialized; + uint8_t available; +} hydro_random_context; + +#if defined(AVR) && !defined(__unix__) +# include "random/avr.h" +#elif (defined(ESP32) || defined(ESP8266)) && !defined(__unix__) +# include "random/esp32.h" +#elif defined(PARTICLE) && defined(PLATFORM_ID) && PLATFORM_ID > 2 && !defined(__unix__) +# include "random/particle.h" +#elif (defined(NRF52832_XXAA) || defined(NRF52832_XXAB)) && !defined(__unix__) +# include "random/nrf52832.h" +#elif defined(_WIN32) +# include "random/windows.h" +#elif defined(__wasi__) +# include "random/wasi.h" +#elif defined(__linux__) && defined(__KERNEL__) +# include "random/linux_kernel.h" +#elif defined(__unix__) +# include "random/unix.h" +#elif defined(TARGET_LIKE_MBED) +# include "random/mbed.h" +#elif defined(RIOT_VERSION) +# include "random/riot.h" +#elif defined(STM32F4) +# include "random/stm32.h" +#elif defined(__RTTHREAD__) +# include "random/rtthread.h" +#else +# error Unsupported platform +#endif + +static void +hydro_random_ensure_initialized(void) +{ + if (hydro_random_context.initialized == 0) { + if (hydro_random_init() != 0) { + abort(); + } + gimli_core_u8(hydro_random_context.state, 0); + hydro_random_ratchet(); + hydro_random_context.initialized = 1; + } +} + +void +hydro_random_ratchet(void) +{ + mem_zero(hydro_random_context.state, gimli_RATE); + STORE64_LE(hydro_random_context.state, hydro_random_context.counter); + hydro_random_context.counter++; + gimli_core_u8(hydro_random_context.state, 0); + hydro_random_context.available = gimli_RATE; +} + +uint32_t +hydro_random_u32(void) +{ + uint32_t v; + + hydro_random_ensure_initialized(); + if (hydro_random_context.available < 4) { + hydro_random_ratchet(); + } + memcpy(&v, &hydro_random_context.state[gimli_RATE - hydro_random_context.available], 4); + hydro_random_context.available -= 4; + + return v; +} + +uint32_t +hydro_random_uniform(const uint32_t upper_bound) +{ + uint32_t min; + uint32_t r; + + if (upper_bound < 2U) { + return 0; + } + min = (1U + ~upper_bound) % upper_bound; /* = 2**32 mod upper_bound */ + do { + r = hydro_random_u32(); + } while (r < min); + /* r is now clamped to a set whose size mod upper_bound == 0 + * the worst case (2**31+1) requires 2 attempts on average */ + + return r % upper_bound; +} + +void +hydro_random_buf(void *out, size_t out_len) +{ + uint8_t *p = (uint8_t *) out; + size_t i; + size_t leftover; + + hydro_random_ensure_initialized(); + for (i = 0; i < out_len / gimli_RATE; i++) { + gimli_core_u8(hydro_random_context.state, 0); + memcpy(p + i * gimli_RATE, hydro_random_context.state, gimli_RATE); + } + leftover = out_len % gimli_RATE; + if (leftover != 0) { + gimli_core_u8(hydro_random_context.state, 0); + mem_cpy(p + i * gimli_RATE, hydro_random_context.state, leftover); + } + hydro_random_ratchet(); +} + +void +hydro_random_buf_deterministic(void *out, size_t out_len, + const uint8_t seed[hydro_random_SEEDBYTES]) +{ + static const uint8_t prefix[] = { 7, 'd', 'r', 'b', 'g', '2', '5', '6' }; + _hydro_attr_aligned_(16) uint8_t state[gimli_BLOCKBYTES]; + uint8_t * p = (uint8_t *) out; + size_t i; + size_t leftover; + + mem_zero(state, gimli_BLOCKBYTES); + COMPILER_ASSERT(sizeof prefix + 8 <= gimli_RATE); + memcpy(state, prefix, sizeof prefix); + STORE64_LE(state + sizeof prefix, (uint64_t) out_len); + gimli_core_u8(state, 1); + COMPILER_ASSERT(hydro_random_SEEDBYTES == gimli_RATE * 2); + mem_xor(state, seed, gimli_RATE); + gimli_core_u8(state, 2); + mem_xor(state, seed + gimli_RATE, gimli_RATE); + gimli_core_u8(state, 2); + for (i = 0; i < out_len / gimli_RATE; i++) { + gimli_core_u8(state, 0); + memcpy(p + i * gimli_RATE, state, gimli_RATE); + } + leftover = out_len % gimli_RATE; + if (leftover != 0) { + gimli_core_u8(state, 0); + mem_cpy(p + i * gimli_RATE, state, leftover); + } +} + +void +hydro_random_reseed(void) +{ + hydro_random_context.initialized = 0; + hydro_random_ensure_initialized(); +} diff --git a/src/modules/net/hydrogen/impl/random/linux_kernel.h b/src/modules/net/hydrogen/impl/random/linux_kernel.h new file mode 100644 index 00000000..340a7f01 --- /dev/null +++ b/src/modules/net/hydrogen/impl/random/linux_kernel.h @@ -0,0 +1,9 @@ +static int +hydro_random_init(void) +{ + get_random_bytes(&hydro_random_context.state, + sizeof hydro_random_context.state); + hydro_random_context.counter = ~LOAD64_LE(hydro_random_context.state); + + return 0; +} diff --git a/src/modules/net/hydrogen/impl/secretbox.h b/src/modules/net/hydrogen/impl/secretbox.h new file mode 100644 index 00000000..9cb3832c --- /dev/null +++ b/src/modules/net/hydrogen/impl/secretbox.h @@ -0,0 +1,236 @@ +#define hydro_secretbox_IVBYTES 20 +#define hydro_secretbox_SIVBYTES 20 +#define hydro_secretbox_MACBYTES 16 + +void +hydro_secretbox_keygen(uint8_t key[hydro_secretbox_KEYBYTES]) +{ + hydro_random_buf(key, hydro_secretbox_KEYBYTES); +} + +static void +hydro_secretbox_xor_enc(uint8_t buf[gimli_BLOCKBYTES], uint8_t *out, const uint8_t *in, + size_t inlen) +{ + size_t i; + size_t leftover; + + for (i = 0; i < inlen / gimli_RATE; i++) { + mem_xor2(&out[i * gimli_RATE], &in[i * gimli_RATE], buf, gimli_RATE); + memcpy(buf, &out[i * gimli_RATE], gimli_RATE); + gimli_core_u8(buf, gimli_TAG_PAYLOAD); + } + leftover = inlen % gimli_RATE; + if (leftover != 0) { + mem_xor2(&out[i * gimli_RATE], &in[i * gimli_RATE], buf, leftover); + mem_cpy(buf, &out[i * gimli_RATE], leftover); + } + gimli_pad_u8(buf, leftover, gimli_DOMAIN_AEAD); + gimli_core_u8(buf, gimli_TAG_PAYLOAD); +} + +static void +hydro_secretbox_xor_dec(uint8_t buf[gimli_BLOCKBYTES], uint8_t *out, const uint8_t *in, + size_t inlen) +{ + size_t i; + size_t leftover; + + for (i = 0; i < inlen / gimli_RATE; i++) { + mem_xor2(&out[i * gimli_RATE], &in[i * gimli_RATE], buf, gimli_RATE); + memcpy(buf, &in[i * gimli_RATE], gimli_RATE); + gimli_core_u8(buf, gimli_TAG_PAYLOAD); + } + leftover = inlen % gimli_RATE; + if (leftover != 0) { + mem_xor2(&out[i * gimli_RATE], &in[i * gimli_RATE], buf, leftover); + mem_cpy(buf, &in[i * gimli_RATE], leftover); + } + gimli_pad_u8(buf, leftover, gimli_DOMAIN_AEAD); + gimli_core_u8(buf, gimli_TAG_PAYLOAD); +} + +static void +hydro_secretbox_setup(uint8_t buf[gimli_BLOCKBYTES], uint64_t msg_id, + const char ctx[hydro_secretbox_CONTEXTBYTES], + const uint8_t key[hydro_secretbox_KEYBYTES], + const uint8_t iv[hydro_secretbox_IVBYTES], uint8_t key_tag) +{ + static const uint8_t prefix[] = { 6, 's', 'b', 'x', '2', '5', '6', 8 }; + uint8_t msg_id_le[8]; + + mem_zero(buf, gimli_BLOCKBYTES); + COMPILER_ASSERT(hydro_secretbox_CONTEXTBYTES == 8); + COMPILER_ASSERT(sizeof prefix + hydro_secretbox_CONTEXTBYTES <= gimli_RATE); + memcpy(buf, prefix, sizeof prefix); + memcpy(buf + sizeof prefix, ctx, hydro_secretbox_CONTEXTBYTES); + COMPILER_ASSERT(sizeof prefix + hydro_secretbox_CONTEXTBYTES == gimli_RATE); + gimli_core_u8(buf, gimli_TAG_HEADER); + + COMPILER_ASSERT(hydro_secretbox_KEYBYTES == 2 * gimli_RATE); + mem_xor(buf, key, gimli_RATE); + gimli_core_u8(buf, key_tag); + mem_xor(buf, key + gimli_RATE, gimli_RATE); + gimli_core_u8(buf, key_tag); + + COMPILER_ASSERT(hydro_secretbox_IVBYTES < gimli_RATE * 2); + buf[0] ^= hydro_secretbox_IVBYTES; + mem_xor(&buf[1], iv, gimli_RATE - 1); + gimli_core_u8(buf, gimli_TAG_HEADER); + mem_xor(buf, iv + gimli_RATE - 1, hydro_secretbox_IVBYTES - (gimli_RATE - 1)); + STORE64_LE(msg_id_le, msg_id); + COMPILER_ASSERT(hydro_secretbox_IVBYTES - gimli_RATE + 8 <= gimli_RATE); + mem_xor(buf + hydro_secretbox_IVBYTES - gimli_RATE, msg_id_le, 8); + gimli_core_u8(buf, gimli_TAG_HEADER); +} + +static void +hydro_secretbox_final(uint8_t *buf, const uint8_t key[hydro_secretbox_KEYBYTES], uint8_t tag) +{ + COMPILER_ASSERT(hydro_secretbox_KEYBYTES == gimli_CAPACITY); + mem_xor(buf + gimli_RATE, key, hydro_secretbox_KEYBYTES); + gimli_core_u8(buf, tag); + mem_xor(buf + gimli_RATE, key, hydro_secretbox_KEYBYTES); + gimli_core_u8(buf, tag); +} + +static int +hydro_secretbox_encrypt_iv(uint8_t *c, const void *m_, size_t mlen, uint64_t msg_id, + const char ctx[hydro_secretbox_CONTEXTBYTES], + const uint8_t key[hydro_secretbox_KEYBYTES], + const uint8_t iv[hydro_secretbox_IVBYTES]) +{ + _hydro_attr_aligned_(16) uint32_t state[gimli_BLOCKBYTES / 4]; + uint8_t * buf = (uint8_t *) (void *) state; + const uint8_t * m = (const uint8_t *) m_; + uint8_t * siv = &c[0]; + uint8_t * mac = &c[hydro_secretbox_SIVBYTES]; + uint8_t * ct = &c[hydro_secretbox_SIVBYTES + hydro_secretbox_MACBYTES]; + size_t i; + size_t leftover; + + if (c == m) { + memmove(c + hydro_secretbox_HEADERBYTES, m, mlen); + m = c + hydro_secretbox_HEADERBYTES; + } + + /* first pass: compute the SIV */ + + hydro_secretbox_setup(buf, msg_id, ctx, key, iv, gimli_TAG_KEY0); + for (i = 0; i < mlen / gimli_RATE; i++) { + mem_xor(buf, &m[i * gimli_RATE], gimli_RATE); + gimli_core_u8(buf, gimli_TAG_PAYLOAD); + } + leftover = mlen % gimli_RATE; + if (leftover != 0) { + mem_xor(buf, &m[i * gimli_RATE], leftover); + } + gimli_pad_u8(buf, leftover, gimli_DOMAIN_XOF); + gimli_core_u8(buf, gimli_TAG_PAYLOAD); + + hydro_secretbox_final(buf, key, gimli_TAG_FINAL0); + COMPILER_ASSERT(hydro_secretbox_SIVBYTES <= gimli_CAPACITY); + memcpy(siv, buf + gimli_RATE, hydro_secretbox_SIVBYTES); + + /* second pass: encrypt the message, mix the key, squeeze an extra block for + * the MAC */ + + COMPILER_ASSERT(hydro_secretbox_SIVBYTES == hydro_secretbox_IVBYTES); + hydro_secretbox_setup(buf, msg_id, ctx, key, siv, gimli_TAG_KEY); + hydro_secretbox_xor_enc(buf, ct, m, mlen); + + hydro_secretbox_final(buf, key, gimli_TAG_FINAL); + COMPILER_ASSERT(hydro_secretbox_MACBYTES <= gimli_CAPACITY); + memcpy(mac, buf + gimli_RATE, hydro_secretbox_MACBYTES); + + return 0; +} + +void +hydro_secretbox_probe_create(uint8_t probe[hydro_secretbox_PROBEBYTES], const uint8_t *c, + size_t c_len, const char ctx[hydro_secretbox_CONTEXTBYTES], + const uint8_t key[hydro_secretbox_KEYBYTES]) +{ + const uint8_t *mac; + + if (c_len < hydro_secretbox_HEADERBYTES) { + abort(); + } + mac = &c[hydro_secretbox_SIVBYTES]; + COMPILER_ASSERT(hydro_secretbox_CONTEXTBYTES >= hydro_hash_CONTEXTBYTES); + COMPILER_ASSERT(hydro_secretbox_KEYBYTES >= hydro_hash_KEYBYTES); + hydro_hash_hash(probe, hydro_secretbox_PROBEBYTES, mac, hydro_secretbox_MACBYTES, ctx, key); +} + +int +hydro_secretbox_probe_verify(const uint8_t probe[hydro_secretbox_PROBEBYTES], const uint8_t *c, + size_t c_len, const char ctx[hydro_secretbox_CONTEXTBYTES], + const uint8_t key[hydro_secretbox_KEYBYTES]) +{ + uint8_t computed_probe[hydro_secretbox_PROBEBYTES]; + const uint8_t *mac; + + if (c_len < hydro_secretbox_HEADERBYTES) { + return -1; + } + mac = &c[hydro_secretbox_SIVBYTES]; + hydro_hash_hash(computed_probe, hydro_secretbox_PROBEBYTES, mac, hydro_secretbox_MACBYTES, ctx, + key); + if (hydro_equal(computed_probe, probe, hydro_secretbox_PROBEBYTES) == 1) { + return 0; + } + hydro_memzero(computed_probe, hydro_secretbox_PROBEBYTES); + return -1; +} + +int +hydro_secretbox_encrypt(uint8_t *c, const void *m_, size_t mlen, uint64_t msg_id, + const char ctx[hydro_secretbox_CONTEXTBYTES], + const uint8_t key[hydro_secretbox_KEYBYTES]) +{ + uint8_t iv[hydro_secretbox_IVBYTES]; + + hydro_random_buf(iv, sizeof iv); + + return hydro_secretbox_encrypt_iv(c, m_, mlen, msg_id, ctx, key, iv); +} + +int +hydro_secretbox_decrypt(void *m_, const uint8_t *c, size_t clen, uint64_t msg_id, + const char ctx[hydro_secretbox_CONTEXTBYTES], + const uint8_t key[hydro_secretbox_KEYBYTES]) +{ + _hydro_attr_aligned_(16) uint32_t state[gimli_BLOCKBYTES / 4]; + uint32_t pub_mac[hydro_secretbox_MACBYTES / 4]; + uint8_t * buf = (uint8_t *) (void *) state; + const uint8_t * siv; + const uint8_t * mac; + const uint8_t * ct; + uint8_t * m = (uint8_t *) m_; + size_t mlen; + uint32_t cv; + + if (clen < hydro_secretbox_HEADERBYTES) { + return -1; + } + siv = &c[0]; + mac = &c[hydro_secretbox_SIVBYTES]; + ct = &c[hydro_secretbox_SIVBYTES + hydro_secretbox_MACBYTES]; + + mlen = clen - hydro_secretbox_HEADERBYTES; + memcpy(pub_mac, mac, sizeof pub_mac); + COMPILER_ASSERT(hydro_secretbox_SIVBYTES == hydro_secretbox_IVBYTES); + hydro_secretbox_setup(buf, msg_id, ctx, key, siv, gimli_TAG_KEY); + hydro_secretbox_xor_dec(buf, m, ct, mlen); + + hydro_secretbox_final(buf, key, gimli_TAG_FINAL); + COMPILER_ASSERT(hydro_secretbox_MACBYTES <= gimli_CAPACITY); + COMPILER_ASSERT(gimli_RATE % 4 == 0); + cv = hydro_mem_ct_cmp_u32(state + gimli_RATE / 4, pub_mac, hydro_secretbox_MACBYTES / 4); + hydro_mem_ct_zero_u32(state, gimli_BLOCKBYTES / 4); + if (cv != 0) { + mem_zero(m, mlen); + return -1; + } + return 0; +} diff --git a/src/modules/net/hydrogen/impl/sign.h b/src/modules/net/hydrogen/impl/sign.h new file mode 100644 index 00000000..b276f1a0 --- /dev/null +++ b/src/modules/net/hydrogen/impl/sign.h @@ -0,0 +1,207 @@ +#define hydro_sign_CHALLENGEBYTES 32 +#define hydro_sign_NONCEBYTES 32 +#define hydro_sign_PREHASHBYTES 64 + +static void +hydro_sign_p2(uint8_t sig[hydro_x25519_BYTES], const uint8_t challenge[hydro_sign_CHALLENGEBYTES], + const uint8_t eph_sk[hydro_x25519_BYTES], const uint8_t sk[hydro_x25519_BYTES]) +{ + hydro_x25519_scalar_t scalar1, scalar2, scalar3; + + COMPILER_ASSERT(hydro_sign_CHALLENGEBYTES == hydro_x25519_BYTES); + hydro_x25519_swapin(scalar1, eph_sk); + hydro_x25519_swapin(scalar2, sk); + hydro_x25519_swapin(scalar3, challenge); + hydro_x25519_sc_montmul(scalar1, scalar2, scalar3); + mem_zero(scalar2, sizeof scalar2); + hydro_x25519_sc_montmul(scalar2, scalar1, hydro_x25519_sc_r2); + hydro_x25519_swapout(sig, scalar2); +} + +static void +hydro_sign_challenge(uint8_t challenge[hydro_sign_CHALLENGEBYTES], + const uint8_t nonce[hydro_sign_NONCEBYTES], + const uint8_t pk[hydro_sign_PUBLICKEYBYTES], + const uint8_t prehash[hydro_sign_PREHASHBYTES]) +{ + hydro_hash_state st; + + hydro_hash_init(&st, (const char *) zero, NULL); + hydro_hash_update(&st, nonce, hydro_sign_NONCEBYTES); + hydro_hash_update(&st, pk, hydro_sign_PUBLICKEYBYTES); + hydro_hash_update(&st, prehash, hydro_sign_PREHASHBYTES); + hydro_hash_final(&st, challenge, hydro_sign_CHALLENGEBYTES); +} + +static int +hydro_sign_prehash(uint8_t csig[hydro_sign_BYTES], const uint8_t prehash[hydro_sign_PREHASHBYTES], + const uint8_t sk[hydro_sign_SECRETKEYBYTES]) +{ + hydro_hash_state st; + uint8_t challenge[hydro_sign_CHALLENGEBYTES]; + const uint8_t * pk = &sk[hydro_x25519_SECRETKEYBYTES]; + uint8_t * nonce = &csig[0]; + uint8_t * sig = &csig[hydro_sign_NONCEBYTES]; + uint8_t * eph_sk = sig; + + hydro_random_buf(eph_sk, hydro_x25519_SECRETKEYBYTES); + COMPILER_ASSERT(hydro_x25519_SECRETKEYBYTES == hydro_hash_KEYBYTES); + hydro_hash_init(&st, (const char *) zero, sk); + hydro_hash_update(&st, eph_sk, hydro_x25519_SECRETKEYBYTES); + hydro_hash_update(&st, prehash, hydro_sign_PREHASHBYTES); + hydro_hash_final(&st, eph_sk, hydro_x25519_SECRETKEYBYTES); + + hydro_x25519_scalarmult_base_uniform(nonce, eph_sk); + hydro_sign_challenge(challenge, nonce, pk, prehash); + + COMPILER_ASSERT(hydro_sign_BYTES == hydro_sign_NONCEBYTES + hydro_x25519_SECRETKEYBYTES); + COMPILER_ASSERT(hydro_x25519_SECRETKEYBYTES <= hydro_sign_CHALLENGEBYTES); + hydro_sign_p2(sig, challenge, eph_sk, sk); + + return 0; +} + +static int +hydro_sign_verify_core(hydro_x25519_fe xs[5], const hydro_x25519_limb_t *other1, + const uint8_t other2[hydro_x25519_BYTES]) +{ + hydro_x25519_limb_t * z2 = xs[1], *x3 = xs[2], *z3 = xs[3]; + hydro_x25519_fe xo2; + const hydro_x25519_limb_t sixteen = 16; + + hydro_x25519_swapin(xo2, other2); + memcpy(x3, other1, 2 * sizeof(hydro_x25519_fe)); + hydro_x25519_ladder_part1(xs); + + /* Here z2 = t2^2 */ + hydro_x25519_mul1(z2, other1); + hydro_x25519_mul1(z2, other1 + hydro_x25519_NLIMBS); + hydro_x25519_mul1(z2, xo2); + + hydro_x25519_mul(z2, z2, &sixteen, 1); + + hydro_x25519_mul1(z3, xo2); + hydro_x25519_sub(z3, z3, x3); + hydro_x25519_sqr1(z3); + + /* check equality */ + hydro_x25519_sub(z3, z3, z2); + + /* canon(z2): both sides are zero. canon(z3): the two sides are equal. */ + /* Reject sigs where both sides are zero. */ + return hydro_x25519_canon(z2) | ~hydro_x25519_canon(z3); +} + +static int +hydro_sign_verify_p2(const uint8_t sig[hydro_x25519_BYTES], + const uint8_t challenge[hydro_sign_CHALLENGEBYTES], + const uint8_t nonce[hydro_sign_NONCEBYTES], + const uint8_t pk[hydro_x25519_BYTES]) +{ + hydro_x25519_fe xs[7]; + + hydro_x25519_core(xs, challenge, pk, 0); + hydro_x25519_core(xs + 2, sig, hydro_x25519_BASE_POINT, 0); + + return hydro_sign_verify_core(xs + 2, xs[0], nonce); +} + +static int +hydro_sign_verify_challenge(const uint8_t csig[hydro_sign_BYTES], + const uint8_t challenge[hydro_sign_CHALLENGEBYTES], + const uint8_t pk[hydro_sign_PUBLICKEYBYTES]) +{ + const uint8_t *nonce = &csig[0]; + const uint8_t *sig = &csig[hydro_sign_NONCEBYTES]; + + return hydro_sign_verify_p2(sig, challenge, nonce, pk); +} + +void +hydro_sign_keygen(hydro_sign_keypair *kp) +{ + uint8_t *pk_copy = &kp->sk[hydro_x25519_SECRETKEYBYTES]; + + COMPILER_ASSERT(hydro_sign_SECRETKEYBYTES == + hydro_x25519_SECRETKEYBYTES + hydro_x25519_PUBLICKEYBYTES); + COMPILER_ASSERT(hydro_sign_PUBLICKEYBYTES == hydro_x25519_PUBLICKEYBYTES); + hydro_random_buf(kp->sk, hydro_x25519_SECRETKEYBYTES); + hydro_x25519_scalarmult_base_uniform(kp->pk, kp->sk); + memcpy(pk_copy, kp->pk, hydro_x25519_PUBLICKEYBYTES); +} + +void +hydro_sign_keygen_deterministic(hydro_sign_keypair *kp, const uint8_t seed[hydro_sign_SEEDBYTES]) +{ + uint8_t *pk_copy = &kp->sk[hydro_x25519_SECRETKEYBYTES]; + + COMPILER_ASSERT(hydro_sign_SEEDBYTES >= hydro_random_SEEDBYTES); + hydro_random_buf_deterministic(kp->sk, hydro_x25519_SECRETKEYBYTES, seed); + hydro_x25519_scalarmult_base_uniform(kp->pk, kp->sk); + memcpy(pk_copy, kp->pk, hydro_x25519_PUBLICKEYBYTES); +} + +int +hydro_sign_init(hydro_sign_state *state, const char ctx[hydro_sign_CONTEXTBYTES]) +{ + return hydro_hash_init(&state->hash_st, ctx, NULL); +} + +int +hydro_sign_update(hydro_sign_state *state, const void *m_, size_t mlen) +{ + return hydro_hash_update(&state->hash_st, m_, mlen); +} + +int +hydro_sign_final_create(hydro_sign_state *state, uint8_t csig[hydro_sign_BYTES], + const uint8_t sk[hydro_sign_SECRETKEYBYTES]) +{ + uint8_t prehash[hydro_sign_PREHASHBYTES]; + + hydro_hash_final(&state->hash_st, prehash, sizeof prehash); + + return hydro_sign_prehash(csig, prehash, sk); +} + +int +hydro_sign_final_verify(hydro_sign_state *state, const uint8_t csig[hydro_sign_BYTES], + const uint8_t pk[hydro_sign_PUBLICKEYBYTES]) +{ + uint8_t challenge[hydro_sign_CHALLENGEBYTES]; + uint8_t prehash[hydro_sign_PREHASHBYTES]; + const uint8_t *nonce = &csig[0]; + + hydro_hash_final(&state->hash_st, prehash, sizeof prehash); + hydro_sign_challenge(challenge, nonce, pk, prehash); + + return hydro_sign_verify_challenge(csig, challenge, pk); +} + +int +hydro_sign_create(uint8_t csig[hydro_sign_BYTES], const void *m_, size_t mlen, + const char ctx[hydro_sign_CONTEXTBYTES], + const uint8_t sk[hydro_sign_SECRETKEYBYTES]) +{ + hydro_sign_state st; + + if (hydro_sign_init(&st, ctx) != 0 || hydro_sign_update(&st, m_, mlen) != 0 || + hydro_sign_final_create(&st, csig, sk) != 0) { + return -1; + } + return 0; +} + +int +hydro_sign_verify(const uint8_t csig[hydro_sign_BYTES], const void *m_, size_t mlen, + const char ctx[hydro_sign_CONTEXTBYTES], + const uint8_t pk[hydro_sign_PUBLICKEYBYTES]) +{ + hydro_sign_state st; + + if (hydro_sign_init(&st, ctx) != 0 || hydro_sign_update(&st, m_, mlen) != 0 || + hydro_sign_final_verify(&st, csig, pk) != 0) { + return -1; + } + return 0; +} diff --git a/src/modules/net/hydrogen/impl/x25519.h b/src/modules/net/hydrogen/impl/x25519.h new file mode 100644 index 00000000..2c77ce1c --- /dev/null +++ b/src/modules/net/hydrogen/impl/x25519.h @@ -0,0 +1,386 @@ +/* + * Based on Michael Hamburg's STROBE reference implementation. + * Copyright (c) 2015-2016 Cryptography Research, Inc. + * MIT License (MIT) + */ + +#if defined(__GNUC__) && defined(__SIZEOF_INT128__) +#define hydro_x25519_WBITS 64 +#else +#define hydro_x25519_WBITS 32 +#endif + +#if hydro_x25519_WBITS == 64 +typedef uint64_t hydro_x25519_limb_t; +typedef __uint128_t hydro_x25519_dlimb_t; +typedef __int128_t hydro_x25519_sdlimb_t; +#define hydro_x25519_eswap_limb(X) LOAD64_LE((const uint8_t *) &(X)) +#define hydro_x25519_LIMB(x) x##ull +#elif hydro_x25519_WBITS == 32 +typedef uint32_t hydro_x25519_limb_t; +typedef uint64_t hydro_x25519_dlimb_t; +typedef int64_t hydro_x25519_sdlimb_t; +#define hydro_x25519_eswap_limb(X) LOAD32_LE((const uint8_t *) &(X)) +#define hydro_x25519_LIMB(x) (uint32_t)(x##ull), (uint32_t) ((x##ull) >> 32) +#else +#error "Need to know hydro_x25519_WBITS" +#endif + +#define hydro_x25519_NLIMBS (256 / hydro_x25519_WBITS) +typedef hydro_x25519_limb_t hydro_x25519_fe[hydro_x25519_NLIMBS]; + +typedef hydro_x25519_limb_t hydro_x25519_scalar_t[hydro_x25519_NLIMBS]; + +static const hydro_x25519_limb_t hydro_x25519_MONTGOMERY_FACTOR = + (hydro_x25519_limb_t) 0xd2b51da312547e1bull; + +static const hydro_x25519_scalar_t hydro_x25519_sc_p = { hydro_x25519_LIMB(0x5812631a5cf5d3ed), + hydro_x25519_LIMB(0x14def9dea2f79cd6), + hydro_x25519_LIMB(0x0000000000000000), + hydro_x25519_LIMB(0x1000000000000000) }; + +static const hydro_x25519_scalar_t hydro_x25519_sc_r2 = { hydro_x25519_LIMB(0xa40611e3449c0f01), + hydro_x25519_LIMB(0xd00e1ba768859347), + hydro_x25519_LIMB(0xceec73d217f5be65), + hydro_x25519_LIMB(0x0399411b7c309a3d) }; + +static const uint8_t hydro_x25519_BASE_POINT[hydro_x25519_BYTES] = { 9 }; + +static const hydro_x25519_limb_t hydro_x25519_a24[1] = { 121665 }; + +static inline hydro_x25519_limb_t +hydro_x25519_umaal(hydro_x25519_limb_t *carry, hydro_x25519_limb_t acc, hydro_x25519_limb_t mand, + hydro_x25519_limb_t mier) +{ + hydro_x25519_dlimb_t tmp = (hydro_x25519_dlimb_t) mand * mier + acc + *carry; + + *carry = tmp >> hydro_x25519_WBITS; + return (hydro_x25519_limb_t) tmp; +} + +static inline hydro_x25519_limb_t +hydro_x25519_adc(hydro_x25519_limb_t *carry, hydro_x25519_limb_t acc, hydro_x25519_limb_t mand) +{ + hydro_x25519_dlimb_t total = (hydro_x25519_dlimb_t) *carry + acc + mand; + + *carry = total >> hydro_x25519_WBITS; + return (hydro_x25519_limb_t) total; +} + +static inline hydro_x25519_limb_t +hydro_x25519_adc0(hydro_x25519_limb_t *carry, hydro_x25519_limb_t acc) +{ + hydro_x25519_dlimb_t total = (hydro_x25519_dlimb_t) *carry + acc; + + *carry = total >> hydro_x25519_WBITS; + return (hydro_x25519_limb_t) total; +} + +static void +hydro_x25519_propagate(hydro_x25519_fe x, hydro_x25519_limb_t over) +{ + hydro_x25519_limb_t carry; + int i; + + over = x[hydro_x25519_NLIMBS - 1] >> (hydro_x25519_WBITS - 1) | over << 1; + x[hydro_x25519_NLIMBS - 1] &= ~((hydro_x25519_limb_t) 1 << (hydro_x25519_WBITS - 1)); + carry = over * 19; + for (i = 0; i < hydro_x25519_NLIMBS; i++) { + x[i] = hydro_x25519_adc0(&carry, x[i]); + } +} + +static void +hydro_x25519_add(hydro_x25519_fe out, const hydro_x25519_fe a, const hydro_x25519_fe b) +{ + hydro_x25519_limb_t carry = 0; + int i; + + for (i = 0; i < hydro_x25519_NLIMBS; i++) { + out[i] = hydro_x25519_adc(&carry, a[i], b[i]); + } + hydro_x25519_propagate(out, carry); +} + +static void +hydro_x25519_sub(hydro_x25519_fe out, const hydro_x25519_fe a, const hydro_x25519_fe b) +{ + hydro_x25519_sdlimb_t carry = -76; + int i; + + for (i = 0; i < hydro_x25519_NLIMBS; i++) { + out[i] = (hydro_x25519_limb_t) (carry = carry + a[i] - b[i]); + carry >>= hydro_x25519_WBITS; + } + hydro_x25519_propagate(out, (hydro_x25519_limb_t) (2 + carry)); +} + +static void +hydro_x25519_swapin(hydro_x25519_limb_t *x, const uint8_t *in) +{ + int i; + + memcpy(x, in, sizeof(hydro_x25519_fe)); + for (i = 0; i < hydro_x25519_NLIMBS; i++) { + x[i] = hydro_x25519_eswap_limb(x[i]); + } +} + +static void +hydro_x25519_swapout(uint8_t *out, hydro_x25519_limb_t *x) +{ + int i; + + for (i = 0; i < hydro_x25519_NLIMBS; i++) { + x[i] = hydro_x25519_eswap_limb(x[i]); + } + memcpy(out, x, sizeof(hydro_x25519_fe)); +} + +static void +hydro_x25519_mul(hydro_x25519_fe out, const hydro_x25519_fe a, const hydro_x25519_limb_t b[], + const int nb) +{ + hydro_x25519_limb_t accum[2 * hydro_x25519_NLIMBS] = { 0 }; + hydro_x25519_limb_t carry2; + int i, j; + + for (i = 0; i < nb; i++) { + hydro_x25519_limb_t mand = b[i]; + carry2 = 0; + + for (j = 0; j < hydro_x25519_NLIMBS; j++) { + accum[i + j] = hydro_x25519_umaal(&carry2, accum[i + j], mand, a[j]); + } + accum[i + j] = carry2; + } + carry2 = 0; + for (j = 0; j < hydro_x25519_NLIMBS; j++) { + const hydro_x25519_limb_t mand = 38; + + out[j] = hydro_x25519_umaal(&carry2, accum[j], mand, accum[j + hydro_x25519_NLIMBS]); + } + hydro_x25519_propagate(out, carry2); +} + +static void +hydro_x25519_sqr(hydro_x25519_fe out, const hydro_x25519_fe a) +{ + hydro_x25519_mul(out, a, a, hydro_x25519_NLIMBS); +} + +static void +hydro_x25519_mul1(hydro_x25519_fe out, const hydro_x25519_fe a) +{ + hydro_x25519_mul(out, a, out, hydro_x25519_NLIMBS); +} + +static void +hydro_x25519_sqr1(hydro_x25519_fe a) +{ + hydro_x25519_mul1(a, a); +} + +static void +hydro_x25519_condswap(hydro_x25519_limb_t a[2 * hydro_x25519_NLIMBS], + hydro_x25519_limb_t b[2 * hydro_x25519_NLIMBS], hydro_x25519_limb_t doswap) +{ + int i; + + for (i = 0; i < 2 * hydro_x25519_NLIMBS; i++) { + hydro_x25519_limb_t xorv = (a[i] ^ b[i]) & doswap; + a[i] ^= xorv; + b[i] ^= xorv; + } +} + +static int +hydro_x25519_canon(hydro_x25519_fe x) +{ + hydro_x25519_sdlimb_t carry; + hydro_x25519_limb_t carry0 = 19; + hydro_x25519_limb_t res; + int i; + + for (i = 0; i < hydro_x25519_NLIMBS; i++) { + x[i] = hydro_x25519_adc0(&carry0, x[i]); + } + hydro_x25519_propagate(x, carry0); + carry = -19; + res = 0; + for (i = 0; i < hydro_x25519_NLIMBS; i++) { + res |= x[i] = (hydro_x25519_limb_t) (carry += x[i]); + carry >>= hydro_x25519_WBITS; + } + return ((hydro_x25519_dlimb_t) res - 1) >> hydro_x25519_WBITS; +} + +static void +hydro_x25519_ladder_part1(hydro_x25519_fe xs[5]) +{ + hydro_x25519_limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1 = xs[4]; + + hydro_x25519_add(t1, x2, z2); // t1 = A + hydro_x25519_sub(z2, x2, z2); // z2 = B + hydro_x25519_add(x2, x3, z3); // x2 = C + hydro_x25519_sub(z3, x3, z3); // z3 = D + hydro_x25519_mul1(z3, t1); // z3 = DA + hydro_x25519_mul1(x2, z2); // x3 = BC + hydro_x25519_add(x3, z3, x2); // x3 = DA+CB + hydro_x25519_sub(z3, z3, x2); // z3 = DA-CB + hydro_x25519_sqr1(t1); // t1 = AA + hydro_x25519_sqr1(z2); // z2 = BB + hydro_x25519_sub(x2, t1, z2); // x2 = E = AA-BB + hydro_x25519_mul(z2, x2, hydro_x25519_a24, // z2 = E*a24 + sizeof(hydro_x25519_a24) / sizeof(hydro_x25519_a24[0])); + hydro_x25519_add(z2, z2, t1); // z2 = E*a24 + AA +} + +static void +hydro_x25519_ladder_part2(hydro_x25519_fe xs[5], const hydro_x25519_fe x1) +{ + hydro_x25519_limb_t *x2 = xs[0], *z2 = xs[1], *x3 = xs[2], *z3 = xs[3], *t1 = xs[4]; + + hydro_x25519_sqr1(z3); // z3 = (DA-CB)^2 + hydro_x25519_mul1(z3, x1); // z3 = x1 * (DA-CB)^2 + hydro_x25519_sqr1(x3); // x3 = (DA+CB)^2 + hydro_x25519_mul1(z2, x2); // z2 = AA*(E*a24+AA) + hydro_x25519_sub(x2, t1, x2); // x2 = BB again + hydro_x25519_mul1(x2, t1); // x2 = AA*BB +} + +static void +hydro_x25519_core(hydro_x25519_fe xs[5], const uint8_t scalar[hydro_x25519_BYTES], + const uint8_t *x1, bool clamp) +{ + hydro_x25519_limb_t swap; + hydro_x25519_limb_t *x2 = xs[0], *x3 = xs[2], *z3 = xs[3]; + hydro_x25519_fe x1i; + int i; + + hydro_x25519_swapin(x1i, x1); + x1 = (const uint8_t *) x1i; + swap = 0; + mem_zero(xs, 4 * sizeof(hydro_x25519_fe)); + x2[0] = z3[0] = 1; + memcpy(x3, x1, sizeof(hydro_x25519_fe)); + for (i = 255; i >= 0; i--) { + uint8_t bytei = scalar[i / 8]; + hydro_x25519_limb_t doswap; + hydro_x25519_fe x1_dup; + + if (clamp) { + if (i / 8 == 0) { + bytei &= ~7; + } else if (i / 8 == hydro_x25519_BYTES - 1) { + bytei &= 0x7F; + bytei |= 0x40; + } + } + doswap = 1U + ~(hydro_x25519_limb_t) ((bytei >> (i % 8)) & 1); + hydro_x25519_condswap(x2, x3, swap ^ doswap); + swap = doswap; + hydro_x25519_ladder_part1(xs); + memcpy(x1_dup, x1, sizeof x1_dup); + hydro_x25519_ladder_part2(xs, x1_dup); + } + hydro_x25519_condswap(x2, x3, swap); +} + +static int +hydro_x25519_scalarmult(uint8_t out[hydro_x25519_BYTES], + const uint8_t scalar[hydro_x25519_SECRETKEYBYTES], + const uint8_t x1[hydro_x25519_PUBLICKEYBYTES], bool clamp) +{ + hydro_x25519_fe xs[5]; + hydro_x25519_limb_t *x2, *z2, *z3; + hydro_x25519_limb_t *prev; + int i; + int ret; + + hydro_x25519_core(xs, scalar, x1, clamp); + + /* Precomputed inversion chain */ + x2 = xs[0]; + z2 = xs[1]; + z3 = xs[3]; + prev = z2; + + /* Raise to the p-2 = 0x7f..ffeb */ + for (i = 253; i >= 0; i--) { + hydro_x25519_sqr(z3, prev); + prev = z3; + if (i >= 8 || (0xeb >> i & 1)) { + hydro_x25519_mul1(z3, z2); + } + } + + /* Here prev = z3 */ + /* x2 /= z2 */ + hydro_x25519_mul1(x2, z3); + ret = hydro_x25519_canon(x2); + hydro_x25519_swapout(out, x2); + + if (clamp == 0) { + return 0; + } + return ret; +} + +static inline int +hydro_x25519_scalarmult_base(uint8_t pk[hydro_x25519_PUBLICKEYBYTES], + const uint8_t sk[hydro_x25519_SECRETKEYBYTES]) +{ + return hydro_x25519_scalarmult(pk, sk, hydro_x25519_BASE_POINT, 1); +} + +static inline void +hydro_x25519_scalarmult_base_uniform(uint8_t pk[hydro_x25519_PUBLICKEYBYTES], + const uint8_t sk[hydro_x25519_SECRETKEYBYTES]) +{ + if (hydro_x25519_scalarmult(pk, sk, hydro_x25519_BASE_POINT, 0) != 0) { + abort(); + } +} + +static void +hydro_x25519_sc_montmul(hydro_x25519_scalar_t out, const hydro_x25519_scalar_t a, + const hydro_x25519_scalar_t b) +{ + hydro_x25519_limb_t hic = 0; + int i, j; + + for (i = 0; i < hydro_x25519_NLIMBS; i++) { + hydro_x25519_limb_t carry = 0, carry2 = 0, mand = a[i], + mand2 = hydro_x25519_MONTGOMERY_FACTOR; + + for (j = 0; j < hydro_x25519_NLIMBS; j++) { + hydro_x25519_limb_t acc = out[j]; + + acc = hydro_x25519_umaal(&carry, acc, mand, b[j]); + if (j == 0) { + mand2 *= acc; + } + acc = hydro_x25519_umaal(&carry2, acc, mand2, hydro_x25519_sc_p[j]); + if (j > 0) { + out[j - 1] = acc; + } + } + + /* Add two carry registers and high carry */ + out[hydro_x25519_NLIMBS - 1] = hydro_x25519_adc(&hic, carry, carry2); + } + + /* Reduce */ + hydro_x25519_sdlimb_t scarry = 0; + for (i = 0; i < hydro_x25519_NLIMBS; i++) { + out[i] = (hydro_x25519_limb_t) (scarry = scarry + out[i] - hydro_x25519_sc_p[i]); + scarry >>= hydro_x25519_WBITS; + } + hydro_x25519_limb_t need_add = (hydro_x25519_limb_t) - (scarry + hic); + + hydro_x25519_limb_t carry = 0; + for (i = 0; i < hydro_x25519_NLIMBS; i++) { + out[i] = hydro_x25519_umaal(&carry, out[i], need_add, hydro_x25519_sc_p[i]); + } +} diff --git a/src/modules/net/net.c b/src/modules/net/net.c new file mode 100644 index 00000000..e22d5c3c --- /dev/null +++ b/src/modules/net/net.c @@ -0,0 +1,262 @@ +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "hydrogen/hydrogen.c" + +static char net_server_addr[16]; +module_param_string(net_server_addr, net_server_addr, sizeof(net_server_addr), 0); +MODULE_PARM_DESC(net_server_addr, "log server IPv4 address"); + +static ushort net_server_port = 1515; +module_param(net_server_port, ushort, 0); +MODULE_PARM_DESC(net_server_port, "log server TCP port number [1515 is default]"); + +static char net_server_pk[65]; +module_param_string(net_server_pk, net_server_pk, sizeof(net_server_pk), 0); +MODULE_PARM_DESC(net_server_pk, "log server public key"); + +static __be32 net_server_addr_n; + +static struct socket *sk; + +static struct file *kmsg_file; +static loff_t kmsg_pos; + +static uint8_t server_static_pk[hydro_kx_PUBLICKEYBYTES]; +static uint8_t packet1[hydro_kx_N_PACKET1BYTES]; +static hydro_kx_session_keypair kp_client; + +#ifdef CONSOLE_EXT_LOG_MAX +static char buf[CONSOLE_EXT_LOG_MAX]; +#else +static char buf[0x2000]; +#endif +static uint8_t ciphertext[4 + sizeof(buf) + hydro_secretbox_HEADERBYTES]; + +static void disconnect(void) +{ + if (sk) { + sock_release(sk); + sk = NULL; + } +} + +static bool try_send_raw(void *buf, size_t count); + +static void maybe_reconnect(void) +{ + struct sockaddr_in saddr = {}; + + if (sk) + return; + + if (sock_create(PF_INET, SOCK_STREAM, IPPROTO_TCP, &sk) < 0) { + sk = NULL; + return; + } + + sk->sk->sk_sndtimeo = HZ; + + saddr.sin_family = AF_INET; + saddr.sin_port = htons(net_server_port); + saddr.sin_addr.s_addr = net_server_addr_n; + switch (sk->ops->connect(sk, (struct sockaddr *)&saddr, sizeof(saddr), O_WRONLY)) { + case 0: + case -EINPROGRESS: + break; + default: + disconnect(); + return; + } + + hydro_kx_n_1(&kp_client, packet1, NULL, server_static_pk); + try_send_raw(packet1, sizeof(packet1)); +} + +static bool try_send_raw(void *buf, size_t count) +{ + struct msghdr msg = {}; + struct kvec vec; + + if (!sk) + return false; + + vec.iov_base = buf; + vec.iov_len = count; + do { + int sent = kernel_sendmsg(sk, &msg, &vec, 1, vec.iov_len); + if (sent <= 0) { + disconnect(); + return false; + } +/* + * We have to either send the whole message or close the connection. Sending a + * partial message and then proceeding to send the next message isn't an option + * as it'd get the receiver out of sync with us. + */ + vec.iov_base += sent; + vec.iov_len -= sent; + } while (vec.iov_len > 0); + + return true; +} + +static bool try_send(void *buf, size_t count) +{ + if (!sk) + return false; + + if (count > sizeof(ciphertext) - 4 - hydro_secretbox_HEADERBYTES) + count = sizeof(ciphertext) - 4 - hydro_secretbox_HEADERBYTES; + + hydro_secretbox_encrypt(&ciphertext[4], buf, count, 0, "lkrg-net", kp_client.tx); + count += hydro_secretbox_HEADERBYTES; + ciphertext[0] = (count >> 24) & 0xff; + ciphertext[1] = (count >> 16) & 0xff; + ciphertext[2] = (count >> 8) & 0xff; + ciphertext[3] = count & 0xff; + + return try_send_raw(ciphertext, 4 + count); +} + +static bool try_send_reconnect(void *buf, size_t count) +{ + bool sent = try_send(buf, count); + if (!sent) { + maybe_reconnect(); + sent = try_send(buf, count); + } + return sent; +} + +static void work_do(struct work_struct *work); + +static DECLARE_WORK(work, work_do); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) +struct devkmsg_user { + atomic64_t seq; + struct ratelimit_state rs; + struct mutex lock; + char buf[CONSOLE_EXT_LOG_MAX]; +}; +#endif + +static void work_do(struct work_struct *work) +{ + static DEFINE_MUTEX(lock); + char *pbuf; + ssize_t n; + bool sent; + unsigned int retry = 0; + +/* + * If we cannot acquire the mutex right away, then most likely we're still in + * the loop below and thus would also process any extra messages that our work + * may have been re-queued to process. There's, however, a slight chance that + * we're already exiting the loop and thus wouldn't notice some new messages. + * To avoid or deal with the latter case, the work should only be invoked once + * at a time or it should be re-queued once in a while. For safety, we need to + * exit from here rather than stall a concurrent worker thread or access shared + * variables without locking. + */ + if (!mutex_trylock(&lock)) + return; + + do { +/* + * Assume we're running in a kthread, so with addr_limit = KERNEL_DS on < 5.10 + * and thus don't need to use kernel_read(). If we're on 5.10+, kernel_read() + * wouldn't have helped anyway (would have triggered a runtime warning), so we + * expect a -EFAULT, which we then handle. + */ + n = kmsg_file->f_op->read(kmsg_file, buf, sizeof(buf), &kmsg_pos); + pbuf = buf; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) + if (n == -EFAULT) { + struct devkmsg_user *user = kmsg_file->private_data; + char *end = memchr(user->buf, '\n', sizeof(user->buf)); + if (end) { + n = end - user->buf + 1; + pbuf = user->buf; + } + } +#endif +/* + * -EPIPE indicates "our last seen message is gone, return error and reset" as + * the comment in devkmsg_read() says. + */ + if (n == -EPIPE) + retry++; + else + retry = 0; + sent = false; + if (n > 0) + sent = try_send_reconnect(pbuf, n); + } while (sent || (retry > 0 && retry < 10)); + + mutex_unlock(&lock); +} + +void lkrg_queue_net(void) +{ + if (kmsg_file) + queue_work(system_unbound_wq, &work); +} + +static void write_msg(struct console *con, const char *str, unsigned int len) +{ + lkrg_queue_net(); +} + +static struct console lkrg_console = { + .name = "lkrg", + .flags = CON_ENABLED, + .write = write_msg +}; + +void lkrg_register_net(void) +{ + if (!net_server_addr[0] || !net_server_pk[0] || + hydro_init() || + hydro_hex2bin(server_static_pk, sizeof(server_static_pk), net_server_pk, 64, NULL, NULL) != 32) + return; + + kmsg_file = filp_open("/dev/kmsg", O_RDONLY | O_NONBLOCK, 0); + + if (!kmsg_file) + return; + + kmsg_file->f_op->llseek(kmsg_file, 0, SEEK_END); + + net_server_addr_n = in_aton(net_server_addr); + + /* Optional, could also connect on first message */ + maybe_reconnect(); + + register_console(&lkrg_console); +} + +void lkrg_deregister_net(void) +{ + unregister_console(&lkrg_console); + flush_work(&work); + cancel_work_sync(&work); /* Redundant unless the work re-queues */ + if (kmsg_file) + filp_close(kmsg_file, NULL); + disconnect(); +} diff --git a/src/modules/net/net.h b/src/modules/net/net.h new file mode 100644 index 00000000..b65ff79e --- /dev/null +++ b/src/modules/net/net.h @@ -0,0 +1,9 @@ +#ifndef LKRG_NET_H +#define LKRG_NET_H + +extern void lkrg_queue_net(void); + +extern void lkrg_register_net(void); +extern void lkrg_deregister_net(void); + +#endif diff --git a/src/modules/print_log/p_lkrg_print_log.h b/src/modules/print_log/p_lkrg_print_log.h index 3a41c3d9..65039cbf 100644 --- a/src/modules/print_log/p_lkrg_print_log.h +++ b/src/modules/print_log/p_lkrg_print_log.h @@ -18,6 +18,8 @@ #ifndef P_LKRG_PRINT_LOG_H #define P_LKRG_PRINT_LOG_H +#include "../net/net.h" + /* * Debugging settings */ @@ -139,6 +141,8 @@ break; \ } \ \ + lkrg_queue_net(); \ + \ p_print_ret; \ }) diff --git a/src/p_lkrg_main.c b/src/p_lkrg_main.c index 9ed9d37a..bb4b6517 100644 --- a/src/p_lkrg_main.c +++ b/src/p_lkrg_main.c @@ -16,6 +16,7 @@ */ #include "p_lkrg_main.h" +#include "modules/net/net.h" unsigned int log_level = 3; unsigned int heartbeat = 0; @@ -380,6 +381,8 @@ static int __init p_lkrg_register(void) { char p_cpu = 0; char p_freeze = 0; + lkrg_register_net(); + P_SYM(p_state_init) = 0; if (get_kallsyms_address() != P_LKRG_SUCCESS) { @@ -395,6 +398,7 @@ static int __init p_lkrg_register(void) { */ if (p_verify_boot_params()) { p_print_log(P_LOG_DYING, "Not loading LKRG ('" P_BOOT_DISABLE_LKRG "' kernel boot parameter detected)"); + lkrg_deregister_net(); return P_LKRG_BOOT_DISABLE_LKRG; } @@ -588,6 +592,9 @@ static int __init p_lkrg_register(void) { p_freeze = 0; } + if (p_ret != P_LKRG_SUCCESS) + lkrg_deregister_net(); + return p_ret; p_sym_error: @@ -646,6 +653,8 @@ static void __exit p_lkrg_deregister(void) { P_SYM(p_thaw_processes)(); p_print_log(P_LOG_DYING, "LKRG unloaded"); + + lkrg_deregister_net(); } From 04b329a9edee31b89519b9bccebde5afc9a98367 Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Sat, 5 Nov 2022 19:02:54 +0100 Subject: [PATCH 02/31] Net: Add message sending timestamps --- src/modules/net/net.c | 48 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/src/modules/net/net.c b/src/modules/net/net.c index e22d5c3c..4edcd07a 100644 --- a/src/modules/net/net.c +++ b/src/modules/net/net.c @@ -10,6 +10,8 @@ #include #include +#include + #include #include @@ -40,10 +42,11 @@ static uint8_t server_static_pk[hydro_kx_PUBLICKEYBYTES]; static uint8_t packet1[hydro_kx_N_PACKET1BYTES]; static hydro_kx_session_keypair kp_client; +#define TIMESTAMPS_MAX (20 + 1 + 20 + 1) #ifdef CONSOLE_EXT_LOG_MAX -static char buf[CONSOLE_EXT_LOG_MAX]; +static char buf[TIMESTAMPS_MAX + CONSOLE_EXT_LOG_MAX]; #else -static char buf[0x2000]; +static char buf[TIMESTAMPS_MAX + 0x2000]; #endif static uint8_t ciphertext[4 + sizeof(buf) + hydro_secretbox_HEADERBYTES]; @@ -159,8 +162,6 @@ struct devkmsg_user { static void work_do(struct work_struct *work) { static DEFINE_MUTEX(lock); - char *pbuf; - ssize_t n; bool sent; unsigned int retry = 0; @@ -178,21 +179,50 @@ static void work_do(struct work_struct *work) return; do { + u64 usec_since_epoch, usec_since_boot; + ssize_t m, n; + char *pbuf; + +#if defined(TIME_T_MAX) && !defined(TIME64_MAX) + struct timespec ts; + ktime_get_real_ts(&ts); +#else + struct timespec64 ts; + ktime_get_real_ts64(&ts); +#endif + + usec_since_epoch = ts.tv_nsec; + do_div(usec_since_epoch, 1000); + usec_since_epoch += ts.tv_sec * (u64)1000000; + +/* + * local_clock() is the function used by printk(). We use the same one to have + * comparable timestamps. We might not be on the same CPU and some clock drift + * between CPUs is possible, but the same issue applies to timestamps seen on + * different kmsg records anyway. + */ + usec_since_boot = local_clock(); + do_div(usec_since_boot, 1000); + + m = snprintf(buf, TIMESTAMPS_MAX + 1, "%llu,%llu,", usec_since_epoch, usec_since_boot); + if (m < 0 || m > TIMESTAMPS_MAX) + break; /* Shouldn't happen */ + pbuf = buf + m; + /* * Assume we're running in a kthread, so with addr_limit = KERNEL_DS on < 5.10 * and thus don't need to use kernel_read(). If we're on 5.10+, kernel_read() * wouldn't have helped anyway (would have triggered a runtime warning), so we * expect a -EFAULT, which we then handle. */ - n = kmsg_file->f_op->read(kmsg_file, buf, sizeof(buf), &kmsg_pos); - pbuf = buf; + n = kmsg_file->f_op->read(kmsg_file, pbuf, sizeof(buf) - TIMESTAMPS_MAX, &kmsg_pos); #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) if (n == -EFAULT) { struct devkmsg_user *user = kmsg_file->private_data; char *end = memchr(user->buf, '\n', sizeof(user->buf)); - if (end) { + if (end && end > user->buf && end - user->buf + 1 <= sizeof(buf) - TIMESTAMPS_MAX) { n = end - user->buf + 1; - pbuf = user->buf; + memcpy(pbuf, user->buf, n); } } #endif @@ -206,7 +236,7 @@ static void work_do(struct work_struct *work) retry = 0; sent = false; if (n > 0) - sent = try_send_reconnect(pbuf, n); + sent = try_send_reconnect(buf, m + n); } while (sent || (retry > 0 && retry < 10)); mutex_unlock(&lock); From 57a8cf8beafcbd4eba8c974671046f1a4f4237e4 Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Thu, 10 Nov 2022 18:11:30 +0100 Subject: [PATCH 03/31] Net: Implement replay protection within TCP streams Use hydrogen's message numbers for this. It will protect against pieces within TCP sessions from being replayed. It will not protect from replay of TCP sessions (entire or partial) from their beginnings. --- src/modules/net/net.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modules/net/net.c b/src/modules/net/net.c index 4edcd07a..e5cdb887 100644 --- a/src/modules/net/net.c +++ b/src/modules/net/net.c @@ -34,6 +34,7 @@ MODULE_PARM_DESC(net_server_pk, "log server public key"); static __be32 net_server_addr_n; static struct socket *sk; +static uint64_t msg_id; static struct file *kmsg_file; static loff_t kmsg_pos; @@ -88,6 +89,8 @@ static void maybe_reconnect(void) hydro_kx_n_1(&kp_client, packet1, NULL, server_static_pk); try_send_raw(packet1, sizeof(packet1)); + + msg_id = 0; } static bool try_send_raw(void *buf, size_t count) @@ -126,7 +129,7 @@ static bool try_send(void *buf, size_t count) if (count > sizeof(ciphertext) - 4 - hydro_secretbox_HEADERBYTES) count = sizeof(ciphertext) - 4 - hydro_secretbox_HEADERBYTES; - hydro_secretbox_encrypt(&ciphertext[4], buf, count, 0, "lkrg-net", kp_client.tx); + hydro_secretbox_encrypt(&ciphertext[4], buf, count, ++msg_id, "lkrg-net", kp_client.tx); count += hydro_secretbox_HEADERBYTES; ciphertext[0] = (count >> 24) & 0xff; ciphertext[1] = (count >> 16) & 0xff; From f6d52c9b6487a54fabe82e3aa8f506e9d72b5035 Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Thu, 10 Nov 2022 18:52:20 +0100 Subject: [PATCH 04/31] Net: Resend the unsent message upon reconnect --- src/modules/net/net.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/modules/net/net.c b/src/modules/net/net.c index e5cdb887..fb6cbbfd 100644 --- a/src/modules/net/net.c +++ b/src/modules/net/net.c @@ -50,6 +50,7 @@ static char buf[TIMESTAMPS_MAX + CONSOLE_EXT_LOG_MAX]; static char buf[TIMESTAMPS_MAX + 0x2000]; #endif static uint8_t ciphertext[4 + sizeof(buf) + hydro_secretbox_HEADERBYTES]; +static size_t buf_resend_size; static void disconnect(void) { @@ -165,7 +166,7 @@ struct devkmsg_user { static void work_do(struct work_struct *work) { static DEFINE_MUTEX(lock); - bool sent; + bool sent = true; unsigned int retry = 0; /* @@ -181,7 +182,11 @@ static void work_do(struct work_struct *work) if (!mutex_trylock(&lock)) return; - do { + if (buf_resend_size) + if ((sent = try_send_reconnect(buf, buf_resend_size))) + buf_resend_size = 0; + + while (sent || (retry > 0 && retry < 10)) { u64 usec_since_epoch, usec_since_boot; ssize_t m, n; char *pbuf; @@ -239,8 +244,9 @@ static void work_do(struct work_struct *work) retry = 0; sent = false; if (n > 0) - sent = try_send_reconnect(buf, m + n); - } while (sent || (retry > 0 && retry < 10)); + if (!(sent = try_send_reconnect(buf, m + n))) + buf_resend_size = m + n; + } mutex_unlock(&lock); } From 1b2b7cf88dc4e96e7432f402d0b767cf93853af2 Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Mon, 28 Nov 2022 12:43:26 +0100 Subject: [PATCH 05/31] Net: Allow to exclude this functionality at compile time --- src/modules/net/net.c | 6 ++++++ src/modules/net/net.h | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/modules/net/net.c b/src/modules/net/net.c index fb6cbbfd..34b738a3 100644 --- a/src/modules/net/net.c +++ b/src/modules/net/net.c @@ -1,3 +1,7 @@ +#include "net.h" + +#ifdef LKRG_WITH_NET + #include #include @@ -299,3 +303,5 @@ void lkrg_deregister_net(void) filp_close(kmsg_file, NULL); disconnect(); } + +#endif diff --git a/src/modules/net/net.h b/src/modules/net/net.h index b65ff79e..a0af3c25 100644 --- a/src/modules/net/net.h +++ b/src/modules/net/net.h @@ -1,9 +1,21 @@ #ifndef LKRG_NET_H #define LKRG_NET_H +#define LKRG_WITH_NET + +#ifdef LKRG_WITH_NET + extern void lkrg_queue_net(void); extern void lkrg_register_net(void); extern void lkrg_deregister_net(void); +#else + +#define lkrg_queue_net() +#define lkrg_register_net() +#define lkrg_deregister_net() + +#endif + #endif From cce44d9cdfdcaca8250e02316f7bc310d7d976f8 Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Mon, 28 Nov 2022 18:00:25 +0100 Subject: [PATCH 06/31] Net: Log configuration errors --- src/modules/net/net.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/modules/net/net.c b/src/modules/net/net.c index 34b738a3..f46ac0a8 100644 --- a/src/modules/net/net.c +++ b/src/modules/net/net.c @@ -23,6 +23,8 @@ #include "hydrogen/hydrogen.c" +#include "../../p_lkrg_main.h" + static char net_server_addr[16]; module_param_string(net_server_addr, net_server_addr, sizeof(net_server_addr), 0); MODULE_PARM_DESC(net_server_addr, "log server IPv4 address"); @@ -274,19 +276,30 @@ static struct console lkrg_console = { void lkrg_register_net(void) { - if (!net_server_addr[0] || !net_server_pk[0] || - hydro_init() || - hydro_hex2bin(server_static_pk, sizeof(server_static_pk), net_server_pk, 64, NULL, NULL) != 32) + const char *end; + + if (!net_server_addr[0]) return; - kmsg_file = filp_open("/dev/kmsg", O_RDONLY | O_NONBLOCK, 0); + if (!in4_pton(net_server_addr, -1, (u8 *)&net_server_addr_n, -1, &end) || *end) { + p_print_log(P_LOG_FAULT, "Net: Can't parse 'net_server_addr'"); + return; + } - if (!kmsg_file) + if (!net_server_pk[0] || + hydro_init() /* Currently can't fail */ || + hydro_hex2bin(server_static_pk, sizeof(server_static_pk), net_server_pk, 64, NULL, NULL) != 32) { + p_print_log(P_LOG_FAULT, "Net: Can't parse 'net_server_pk'"); return; + } - kmsg_file->f_op->llseek(kmsg_file, 0, SEEK_END); + kmsg_file = filp_open("/dev/kmsg", O_RDONLY | O_NONBLOCK, 0); + if (!kmsg_file) { + p_print_log(P_LOG_FAULT, "Net: Can't open '/dev/kmsg'"); + return; + } - net_server_addr_n = in_aton(net_server_addr); + kmsg_file->f_op->llseek(kmsg_file, 0, SEEK_END); /* Optional, could also connect on first message */ maybe_reconnect(); From 2daca741a3b654fbed0c0922aac2ea21c4c50892 Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Mon, 28 Nov 2022 19:26:19 +0100 Subject: [PATCH 07/31] Net: Log connection errors (rate-limited) --- src/modules/net/net.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/modules/net/net.c b/src/modules/net/net.c index f46ac0a8..87895556 100644 --- a/src/modules/net/net.c +++ b/src/modules/net/net.c @@ -58,6 +58,13 @@ static char buf[TIMESTAMPS_MAX + 0x2000]; static uint8_t ciphertext[4 + sizeof(buf) + hydro_secretbox_HEADERBYTES]; static size_t buf_resend_size; +static void log_issue(int err) +{ + static DEFINE_RATELIMIT_STATE(state, 180 * HZ, 3); + if (___ratelimit(&state, "LKRG: ISSUE: Net: Rate limit")) + p_print_log(P_LOG_ISSUE, "Net: Connection error %d", err); +} + static void disconnect(void) { if (sk) { @@ -71,12 +78,14 @@ static bool try_send_raw(void *buf, size_t count); static void maybe_reconnect(void) { struct sockaddr_in saddr = {}; + int err; if (sk) return; - if (sock_create(PF_INET, SOCK_STREAM, IPPROTO_TCP, &sk) < 0) { + if ((err = sock_create(PF_INET, SOCK_STREAM, IPPROTO_TCP, &sk)) < 0) { sk = NULL; + log_issue(err); return; } @@ -85,12 +94,13 @@ static void maybe_reconnect(void) saddr.sin_family = AF_INET; saddr.sin_port = htons(net_server_port); saddr.sin_addr.s_addr = net_server_addr_n; - switch (sk->ops->connect(sk, (struct sockaddr *)&saddr, sizeof(saddr), O_WRONLY)) { + switch ((err = sk->ops->connect(sk, (struct sockaddr *)&saddr, sizeof(saddr), O_WRONLY))) { case 0: case -EINPROGRESS: break; default: disconnect(); + log_issue(err); return; } @@ -114,6 +124,7 @@ static bool try_send_raw(void *buf, size_t count) int sent = kernel_sendmsg(sk, &msg, &vec, 1, vec.iov_len); if (sent <= 0) { disconnect(); + log_issue(sent); return false; } /* From 972f1958d7b1ff8f1bcc9df83a1549f6c5f48f3a Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Mon, 28 Nov 2022 19:45:55 +0100 Subject: [PATCH 08/31] Net: Rate-limit reconnect attempts --- src/modules/net/net.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/modules/net/net.c b/src/modules/net/net.c index 87895556..c22e48bb 100644 --- a/src/modules/net/net.c +++ b/src/modules/net/net.c @@ -77,12 +77,17 @@ static bool try_send_raw(void *buf, size_t count); static void maybe_reconnect(void) { + static unsigned long last_time; struct sockaddr_in saddr = {}; int err; if (sk) return; + if (last_time && jiffies - last_time < 10 * HZ) + return; + last_time = jiffies; + if ((err = sock_create(PF_INET, SOCK_STREAM, IPPROTO_TCP, &sk)) < 0) { sk = NULL; log_issue(err); From e55841c1b9ba4d972bd85ed5c2853a88c8b40f75 Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Sat, 17 Feb 2024 18:56:28 +0100 Subject: [PATCH 09/31] Net: Linux 6.3+ support --- src/modules/net/net.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/modules/net/net.c b/src/modules/net/net.c index c22e48bb..d7cb8a35 100644 --- a/src/modules/net/net.c +++ b/src/modules/net/net.c @@ -15,6 +15,7 @@ #include #include +#include /* for local_clock() */ #include #include @@ -49,12 +50,26 @@ static uint8_t server_static_pk[hydro_kx_PUBLICKEYBYTES]; static uint8_t packet1[hydro_kx_N_PACKET1BYTES]; static hydro_kx_session_keypair kp_client; -#define TIMESTAMPS_MAX (20 + 1 + 20 + 1) -#ifdef CONSOLE_EXT_LOG_MAX -static char buf[TIMESTAMPS_MAX + CONSOLE_EXT_LOG_MAX]; +/* + * There are three cases: + * - Below 5.10: we don't need exact value, but it's 8192; + * - 5.10 to 6.2.x: we need exact value and it's 8192 and CONSOLE_EXT_LOG_MAX + * is defined by the kernel (so we don't override); + * - 6.3+: we need exact value and it's actually PRINTK_MESSAGE_MAX 2048. + * We check for 5.10+ rather than 6.3+ in order to pick the right value on + * intermediary unreleased kernels between 6.2.x and 6.3 as well as with + * possible backports of the 6.3+ change to older-numbered kernel branches. + */ +#ifndef CONSOLE_EXT_LOG_MAX +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) +#define CONSOLE_EXT_LOG_MAX 2048 #else -static char buf[TIMESTAMPS_MAX + 0x2000]; +#define CONSOLE_EXT_LOG_MAX 8192 #endif +#endif + +#define TIMESTAMPS_MAX (20 + 1 + 20 + 1) +static char buf[TIMESTAMPS_MAX + CONSOLE_EXT_LOG_MAX]; static uint8_t ciphertext[4 + sizeof(buf) + hydro_secretbox_HEADERBYTES]; static size_t buf_resend_size; From 0c5ba4e5ea86ab998eb1f6f4dad4b4f5402faa0b Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Sat, 17 Feb 2024 20:00:58 +0100 Subject: [PATCH 10/31] Net: Clean up hydrogen/impl/random.h --- src/modules/net/hydrogen/impl/random.h | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/src/modules/net/hydrogen/impl/random.h b/src/modules/net/hydrogen/impl/random.h index 2593d31f..7293c368 100644 --- a/src/modules/net/hydrogen/impl/random.h +++ b/src/modules/net/hydrogen/impl/random.h @@ -5,30 +5,8 @@ static TLS struct { uint8_t available; } hydro_random_context; -#if defined(AVR) && !defined(__unix__) -# include "random/avr.h" -#elif (defined(ESP32) || defined(ESP8266)) && !defined(__unix__) -# include "random/esp32.h" -#elif defined(PARTICLE) && defined(PLATFORM_ID) && PLATFORM_ID > 2 && !defined(__unix__) -# include "random/particle.h" -#elif (defined(NRF52832_XXAA) || defined(NRF52832_XXAB)) && !defined(__unix__) -# include "random/nrf52832.h" -#elif defined(_WIN32) -# include "random/windows.h" -#elif defined(__wasi__) -# include "random/wasi.h" -#elif defined(__linux__) && defined(__KERNEL__) +#if defined(__linux__) && defined(__KERNEL__) # include "random/linux_kernel.h" -#elif defined(__unix__) -# include "random/unix.h" -#elif defined(TARGET_LIKE_MBED) -# include "random/mbed.h" -#elif defined(RIOT_VERSION) -# include "random/riot.h" -#elif defined(STM32F4) -# include "random/stm32.h" -#elif defined(__RTTHREAD__) -# include "random/rtthread.h" #else # error Unsupported platform #endif From 4536c680ec531c3371bab59c91c41be004e5d8f6 Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Sun, 18 Feb 2024 14:35:40 +0100 Subject: [PATCH 11/31] Net: Add authorship, copyright, and license statements --- LICENSE | 9 ++++++++- debian/copyright | 11 ++++++++++- src/modules/net/hydrogen/LICENSE | 18 ++++++++++++++++++ src/modules/net/net.c | 18 ++++++++++++++++++ src/modules/net/net.h | 18 ++++++++++++++++++ 5 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 src/modules/net/hydrogen/LICENSE diff --git a/LICENSE b/LICENSE index 3fa4e073..f39b0a08 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,14 @@ Linux Kernel Runtime Guard (LKRG) Copyright (c) 2015-2023 Adam 'pi3' Zabrocki Copyright (c) 2020-2022 Mariusz Zaborski -Copyright (c) 2020-2023 Solar Designer +Copyright (c) 2020-2024 Solar Designer + +src/modules/net/net.* +Copyright (c) 2022 Binarly +Copyright (c) 2024 Solar Designer + +src/modules/net/hydrogen/* +Copyright (c) 2017-2022 Frank Denis scripts/copy-builtin.sh, scripts/add-exports.sh Copyright (c) 2021-2022 RageLtMan diff --git a/debian/copyright b/debian/copyright index a13ad953..6aa234d2 100644 --- a/debian/copyright +++ b/debian/copyright @@ -6,9 +6,18 @@ Source: https://lkrg.org Files: * Copyright: 2015-2023 Adam 'pi3' Zabrocki 2020-2022 Mariusz Zaborski - 2020-2023 Solar Designer + 2020-2024 Solar Designer License: GPL-2.0 +Files: src/modules/net/net.* +Copyright: 2022 Binarly + 2024 Solar Designer +License: GPL-2.0 + +Files: src/modules/net/hydrogen/* +Copyright: 2017-2022 Frank Denis +License: ISC or GPL-2.0 + Files: debian/* Copyright: 2020-2021 Mikhail Morfikov License: GPL-2.0 diff --git a/src/modules/net/hydrogen/LICENSE b/src/modules/net/hydrogen/LICENSE new file mode 100644 index 00000000..0ff14cb6 --- /dev/null +++ b/src/modules/net/hydrogen/LICENSE @@ -0,0 +1,18 @@ +/* + * ISC License + * + * Copyright (c) 2017-2022 + * Frank Denis + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ diff --git a/src/modules/net/net.c b/src/modules/net/net.c index d7cb8a35..f5fc4aef 100644 --- a/src/modules/net/net.c +++ b/src/modules/net/net.c @@ -1,3 +1,21 @@ +/* + * pi3's Linux kernel Runtime Guard + * + * Component: + * - Remote logging module + * + * Timeline: + * - Initial implementation: October - November 2022 + * - Forward-port and publication: February 2024 + * + * Author: + * - Solar Designer + * + * Copyright: + * - 2022, Binarly + * - 2024, Solar Designer + */ + #include "net.h" #ifdef LKRG_WITH_NET diff --git a/src/modules/net/net.h b/src/modules/net/net.h index a0af3c25..e8a8f7ac 100644 --- a/src/modules/net/net.h +++ b/src/modules/net/net.h @@ -1,3 +1,21 @@ +/* + * pi3's Linux kernel Runtime Guard + * + * Component: + * - Remote logging module + * + * Timeline: + * - Initial implementation: October - November 2022 + * - Forward-port and publication: February 2024 + * + * Author: + * - Solar Designer + * + * Copyright: + * - 2022, Binarly + * - 2024, Solar Designer + */ + #ifndef LKRG_NET_H #define LKRG_NET_H From 457f9a1e57bb004a03aef9f23c9d75f9f95f1761 Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Sun, 18 Feb 2024 17:20:59 +0100 Subject: [PATCH 12/31] Hydrogen: Add random/unix.h --- src/modules/net/hydrogen/impl/random.h | 2 + src/modules/net/hydrogen/impl/random/unix.h | 85 +++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/modules/net/hydrogen/impl/random/unix.h diff --git a/src/modules/net/hydrogen/impl/random.h b/src/modules/net/hydrogen/impl/random.h index 7293c368..67b7861e 100644 --- a/src/modules/net/hydrogen/impl/random.h +++ b/src/modules/net/hydrogen/impl/random.h @@ -7,6 +7,8 @@ static TLS struct { #if defined(__linux__) && defined(__KERNEL__) # include "random/linux_kernel.h" +#elif defined(__unix__) +# include "random/unix.h" #else # error Unsupported platform #endif diff --git a/src/modules/net/hydrogen/impl/random/unix.h b/src/modules/net/hydrogen/impl/random/unix.h new file mode 100644 index 00000000..e372e8f9 --- /dev/null +++ b/src/modules/net/hydrogen/impl/random/unix.h @@ -0,0 +1,85 @@ +#include +#include +#ifdef __linux__ +#include +#endif +#include +#include + +#ifdef __linux__ +static int +hydro_random_block_on_dev_random(void) +{ + struct pollfd pfd; + int fd; + int pret; + + fd = open("/dev/random", O_RDONLY); + if (fd == -1) { + return 0; + } + pfd.fd = fd; + pfd.events = POLLIN; + pfd.revents = 0; + do { + pret = poll(&pfd, 1, -1); + } while (pret < 0 && (errno == EINTR || errno == EAGAIN)); + if (pret != 1) { + (void) close(fd); + errno = EIO; + return -1; + } + return close(fd); +} +#endif + +static ssize_t +hydro_random_safe_read(const int fd, void *const buf_, size_t len) +{ + unsigned char *buf = (unsigned char *) buf_; + ssize_t readnb; + + do { + while ((readnb = read(fd, buf, len)) < (ssize_t) 0 && (errno == EINTR || errno == EAGAIN)) { + } + if (readnb < (ssize_t) 0) { + return readnb; + } + if (readnb == (ssize_t) 0) { + break; + } + len -= (size_t) readnb; + buf += readnb; + } while (len > (ssize_t) 0); + + return (ssize_t) (buf - (unsigned char *) buf_); +} + +static int +hydro_random_init(void) +{ + uint8_t tmp[gimli_BLOCKBYTES + 8]; + int fd; + int ret = -1; + +#ifdef __linux__ + if (hydro_random_block_on_dev_random() != 0) { + return -1; + } +#endif + do { + fd = open("/dev/urandom", O_RDONLY); + if (fd == -1 && errno != EINTR) { + return -1; + } + } while (fd == -1); + if (hydro_random_safe_read(fd, tmp, sizeof tmp) == (ssize_t) sizeof tmp) { + memcpy(hydro_random_context.state, tmp, gimli_BLOCKBYTES); + memcpy(&hydro_random_context.counter, tmp + gimli_BLOCKBYTES, 8); + hydro_memzero(tmp, sizeof tmp); + ret = 0; + } + ret |= close(fd); + + return ret; +} From 9c48cdda55627e0e8e825c4cc8b6005c39f6d4d9 Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Thu, 10 Nov 2022 17:40:31 +0100 Subject: [PATCH 13/31] Logger: Initial commit --- logger/Makefile | 24 +++++ logger/hydrogen | 1 + logger/keygen.c | 16 ++++ logger/logger.c | 235 +++++++++++++++++++++++++++++++++++++++++++++++ logger/misc.c | 63 +++++++++++++ logger/misc.h | 3 + logger/params.h | 28 ++++++ logger/session.c | 83 +++++++++++++++++ logger/session.h | 2 + 9 files changed, 455 insertions(+) create mode 100644 logger/Makefile create mode 120000 logger/hydrogen create mode 100644 logger/keygen.c create mode 100644 logger/logger.c create mode 100644 logger/misc.c create mode 100644 logger/misc.h create mode 100644 logger/params.h create mode 100644 logger/session.c create mode 100644 logger/session.h diff --git a/logger/Makefile b/logger/Makefile new file mode 100644 index 00000000..0a72f818 --- /dev/null +++ b/logger/Makefile @@ -0,0 +1,24 @@ +CC = gcc +LD = $(CC) +RM = rm -f +CFLAGS = -Wall -W -O2 -fomit-frame-pointer +LDFLAGS = -s + +PROJ = lkrg-logger lkrg-keygen +LOGGER_OBJS = logger.o misc.o session.o +KEYGEN_OBJS = keygen.o +OBJS = $(LOGGER_OBJS) $(KEYGEN_OBJS) + +all: $(PROJ) + +lkrg-logger: $(LOGGER_OBJS) + $(LD) $(LDFLAGS) $(LOGGER_OBJS) -o $@ + +lkrg-keygen: $(KEYGEN_OBJS) + $(LD) $(LDFLAGS) $(KEYGEN_OBJS) -o $@ + +.c.o: + $(CC) $(CFLAGS) -c $*.c + +clean: + $(RM) $(PROJ) $(OBJS) diff --git a/logger/hydrogen b/logger/hydrogen new file mode 120000 index 00000000..c10517c8 --- /dev/null +++ b/logger/hydrogen @@ -0,0 +1 @@ +../src/modules/net/hydrogen \ No newline at end of file diff --git a/logger/keygen.c b/logger/keygen.c new file mode 100644 index 00000000..1133a35b --- /dev/null +++ b/logger/keygen.c @@ -0,0 +1,16 @@ +#include + +#include "hydrogen/hydrogen.c" + +int main(void) +{ + hydro_kx_keypair server_static_kp; + char hex[(hydro_kx_PUBLICKEYBYTES | hydro_kx_SECRETKEYBYTES) * 2 + 1]; + + hydro_kx_keygen(&server_static_kp); + + printf("LKRG_LOGGER_PK=%s\n", hydro_bin2hex(hex, sizeof(hex), server_static_kp.pk, hydro_kx_PUBLICKEYBYTES)); + printf("LKRG_LOGGER_SK=%s\n", hydro_bin2hex(hex, sizeof(hex), server_static_kp.sk, hydro_kx_SECRETKEYBYTES)); + + return 0; +} diff --git a/logger/logger.c b/logger/logger.c new file mode 100644 index 00000000..4d5e10a3 --- /dev/null +++ b/logger/logger.c @@ -0,0 +1,235 @@ +/* + * Standalone TCP server: accepts connections, checks the anti-flood limits, + * logs and starts the actual sessions. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "misc.h" +#include "params.h" +#include "session.h" + +typedef volatile sig_atomic_t va_int; + +/* + * Active sessions. Those that were started within the last MIN_DELAY + * seconds are also considered active (regardless of their actual state), + * to allow for limiting the logging rate without throwing away critical + * information about sessions that we could have allowed to proceed. + */ +static struct { + struct in_addr addr; /* Source IP address */ + volatile int pid; /* PID of the server, or 0 for none */ + clock_t start; /* When the server was started */ + clock_t log; /* When we've last logged a failure */ +} sessions[MAX_SESSIONS]; + +static va_int child_blocked; /* We use blocking to avoid races */ +static va_int child_pending; /* Are any dead children waiting? */ + +/* + * SIGCHLD handler. + */ +static void handle_child(int signum) +{ + int saved_errno; + int pid; + int i; + + (void)signum; + + saved_errno = errno; + + if (child_blocked) { + child_pending = 1; + } else { + child_pending = 0; + + while ((pid = waitpid(0, NULL, WNOHANG)) > 0) + for (i = 0; i < MAX_SESSIONS; i++) + if (sessions[i].pid == pid) { + sessions[i].pid = 0; + break; + } + } + + signal(SIGCHLD, handle_child); + + errno = saved_errno; +} + +int main(void) +{ + int true = 1; + int sock, new; + struct sockaddr_in addr; + socklen_t addrlen; + int pid; + struct tms buf; + clock_t min_delay, now, log; + int i, j, n; + + openlog(SYSLOG_IDENT, SYSLOG_OPTIONS | LOG_PERROR, SYSLOG_FACILITY); + + if (session_prepare()) + return 1; + + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) + return log_error("socket"); + + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + (void *)&true, sizeof(true))) + return log_error("setsockopt"); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(DAEMON_ADDR); + addr.sin_port = htons(DAEMON_PORT); + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr))) + return log_error("bind"); + + if (listen(sock, MAX_BACKLOG)) + return log_error("listen"); + +#if 0 + chdir("/"); + setsid(); + + switch (fork()) { + case -1: + return log_error("fork"); + + case 0: + break; + + default: + return 0; + } + + setsid(); +#endif + +/* Drop LOG_PERROR */ + closelog(); + openlog(SYSLOG_IDENT, SYSLOG_OPTIONS, SYSLOG_FACILITY); + +#if defined(_SC_CLK_TCK) || !defined(CLK_TCK) + min_delay = MIN_DELAY * sysconf(_SC_CLK_TCK); +#else + min_delay = MIN_DELAY * CLK_TCK; +#endif + + child_blocked = 1; + child_pending = 0; + signal(SIGCHLD, handle_child); + + memset((void *)sessions, 0, sizeof(sessions)); + log = 0; + + new = 0; + + while (1) { + child_blocked = 0; + if (child_pending) + raise(SIGCHLD); + + if (new > 0 && close(new)) + return log_error("close"); + + addrlen = sizeof(addr); + new = accept(sock, (struct sockaddr *)&addr, &addrlen); + +/* + * I wish there were a portable way to classify errno's... In this case, + * it appears to be better to risk eating up the CPU on a fatal error + * rather than risk terminating the entire service because of a minor + * temporary error having to do with one particular connection attempt. + */ + if (new < 0) + continue; + + now = times(&buf); + if (!now) + now = 1; + + child_blocked = 1; + + j = -1; n = 0; + for (i = 0; i < MAX_SESSIONS; i++) { + if (sessions[i].start > now) + sessions[i].start = 0; + if (sessions[i].pid || + (sessions[i].start && + now - sessions[i].start < min_delay)) { + if (sessions[i].addr.s_addr == + addr.sin_addr.s_addr) + if (++n >= MAX_SESSIONS_PER_SOURCE) + break; + } else if (j < 0) + j = i; + } + + if (n >= MAX_SESSIONS_PER_SOURCE) { + if (!sessions[i].log || + now < sessions[i].log || + now - sessions[i].log >= min_delay) { + syslog(SYSLOG_PRI_HI, + "%s: per source limit reached", + inet_ntoa(addr.sin_addr)); + sessions[i].log = now; + } + continue; + } + + if (j < 0) { + if (!log || + now < log || now - log >= min_delay) { + syslog(SYSLOG_PRI_HI, + "%s: sessions limit reached", + inet_ntoa(addr.sin_addr)); + log = now; + } + continue; + } + + switch ((pid = fork())) { + case -1: + syslog(SYSLOG_PRI_ERROR, "%s: fork: %m", + inet_ntoa(addr.sin_addr)); + break; + + case 0: { + const char *from = inet_ntoa(addr.sin_addr); + if (close(sock)) + return log_error("close"); + syslog(SYSLOG_PRI_LO, "Session from %s", from); + if (dup2(new, 0) < 0 || + dup2(new, 1) < 0 || + dup2(new, 2) < 0) + return log_error("dup2"); + if (close(new)) + return log_error("close"); + return session_process(from); + } + + default: + sessions[j].addr = addr.sin_addr; + sessions[j].pid = pid; + sessions[j].start = now; + sessions[j].log = 0; + } + } +} diff --git a/logger/misc.c b/logger/misc.c new file mode 100644 index 00000000..87d94100 --- /dev/null +++ b/logger/misc.c @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include + +#include "misc.h" +#include "params.h" + +ssize_t read_loop(int fd, void *buffer, size_t count) +{ + ssize_t offset, block; + + errno = 0; + + offset = 0; + while (count > 0 && count <= SSIZE_MAX) { + block = read(fd, (char *)buffer + offset, count); + + if (block < 0) + return block; + if (!block) + return offset; + + offset += block; + count -= block; + } + + return offset; +} + +ssize_t write_loop(int fd, const void *buffer, size_t count) +{ + ssize_t offset, block; + + errno = 0; + + offset = 0; + while (count > 0 && count <= SSIZE_MAX) { + block = write(fd, (char *)buffer + offset, count); + + if (block < 0) + return block; + if (!block) + return offset; + + offset += block; + count -= block; + } + + return offset; +} + +int log_error(const char *s) +{ + if (errno) + syslog(SYSLOG_PRI_ERROR, "%s: %m", s); + else if (!strcmp(s, "read") || !strcmp(s, "write")) + syslog(SYSLOG_PRI_ERROR, "%s: Partial data", s); + else + syslog(SYSLOG_PRI_ERROR, "%s", s); + return 1; +} diff --git a/logger/misc.h b/logger/misc.h new file mode 100644 index 00000000..4067257d --- /dev/null +++ b/logger/misc.h @@ -0,0 +1,3 @@ +extern ssize_t read_loop(int fd, void *buffer, size_t count); +extern ssize_t write_loop(int fd, const void *buffer, size_t count); +extern int log_error(const char *s); diff --git a/logger/params.h b/logger/params.h new file mode 100644 index 00000000..e7363e86 --- /dev/null +++ b/logger/params.h @@ -0,0 +1,28 @@ +/* + * Global daemon parameters. + */ + +/* + * The address and port to listen on. + */ +#define DAEMON_ADDR "0.0.0.0" /* INADDR_ANY */ +#define DAEMON_PORT 1515 + +/* + * Limit the number of sessions we can handle at a time to reduce the impact of + * connection flood DoS attacks. + */ +#define MAX_SESSIONS 1000 +#define MAX_SESSIONS_PER_SOURCE 10 +#define MAX_BACKLOG 5 +#define MIN_DELAY 10 + +/* + * How do we talk to syslogd? These should be fine for most systems. + */ +#define SYSLOG_IDENT "lkrg-logger" +#define SYSLOG_OPTIONS LOG_PID +#define SYSLOG_FACILITY LOG_DAEMON +#define SYSLOG_PRI_LO LOG_INFO +#define SYSLOG_PRI_HI LOG_NOTICE +#define SYSLOG_PRI_ERROR LOG_CRIT diff --git a/logger/session.c b/logger/session.c new file mode 100644 index 00000000..583ef829 --- /dev/null +++ b/logger/session.c @@ -0,0 +1,83 @@ +#include /* for getenv() */ +#include +#include +#include +#include + +#include "hydrogen/hydrogen.c" + +#include "misc.h" +#include "session.h" + +static hydro_kx_keypair server_static_kp; +static uint8_t packet1[hydro_kx_N_PACKET1BYTES]; +static hydro_kx_session_keypair kp_server; + +int session_prepare(void) +{ + const char *pk = getenv("LKRG_LOGGER_PK"); + const char *sk = getenv("LKRG_LOGGER_SK"); + if (!pk || !sk || + hydro_init() /* Currently can't fail */ || + hydro_hex2bin(server_static_kp.pk, sizeof(server_static_kp.pk), pk, 64, NULL, NULL) != 32 || + hydro_hex2bin(server_static_kp.sk, sizeof(server_static_kp.sk), sk, 64, NULL, NULL) != 32) { + errno = 0; + return log_error("Invalid LKRG_LOGGER_PK and/or LKRG_LOGGER_SK"); + } + return 0; +} + +int session_process(const char *from) +{ + int fd; + ssize_t n; + + if (read_loop(0, packet1, sizeof(packet1)) != sizeof(packet1)) + return log_error("read"); + + if (hydro_kx_n_2(&kp_server, packet1, NULL, &server_static_kp)) { + errno = 0; + return log_error("Received bad handshake packet"); + } + + { + char fn[24]; + snprintf(fn, sizeof(fn), "log/%s", from); + fd = open(fn, O_CREAT | O_WRONLY | O_APPEND, 0640); + if (fd < 0) + return log_error("open"); + } + + while (1) { + uint8_t buf[0x2100 + hydro_secretbox_HEADERBYTES]; + uint32_t len; + n = read_loop(0, &len, sizeof(len)); + if (n != sizeof(len)) { + if (n) + log_error("read"); + break; + } + len = ntohl(len); + if (len <= hydro_secretbox_HEADERBYTES || len > sizeof(buf)) + goto fail_data; + n = read_loop(0, buf, len); + if (n != len) { + n = -1; + log_error("read"); + break; + } + if (hydro_secretbox_decrypt(buf, buf, n, 0, "lkrg-net", kp_server.rx)) + goto fail_data; + n -= hydro_secretbox_HEADERBYTES; + if (write_loop(fd, buf, n) != n) + log_error("write"); + } + + close(fd); + return !!n; + +fail_data: + close(fd); + errno = 0; + return log_error("Received bad data packet"); +} diff --git a/logger/session.h b/logger/session.h new file mode 100644 index 00000000..64f73882 --- /dev/null +++ b/logger/session.h @@ -0,0 +1,2 @@ +extern int session_prepare(void); +extern int session_process(const char *from); From 8e6087cd41f99c5d07c6c4fb342946c6a5b1665b Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Thu, 10 Nov 2022 18:09:04 +0100 Subject: [PATCH 14/31] Logger: Implement replay protection within TCP streams Use hydrogen's message numbers for this. It will protect against pieces within TCP sessions from being replayed. It will not protect from replay of TCP sessions (entire or partial) from their beginnings. --- logger/session.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/logger/session.c b/logger/session.c index 583ef829..aa4ad17d 100644 --- a/logger/session.c +++ b/logger/session.c @@ -31,6 +31,7 @@ int session_process(const char *from) { int fd; ssize_t n; + uint64_t msg_id = 0; if (read_loop(0, packet1, sizeof(packet1)) != sizeof(packet1)) return log_error("read"); @@ -66,7 +67,7 @@ int session_process(const char *from) log_error("read"); break; } - if (hydro_secretbox_decrypt(buf, buf, n, 0, "lkrg-net", kp_server.rx)) + if (hydro_secretbox_decrypt(buf, buf, n, ++msg_id, "lkrg-net", kp_server.rx)) goto fail_data; n -= hydro_secretbox_HEADERBYTES; if (write_loop(fd, buf, n) != n) From c0c3ec7d76f567a0b069cb726d8c306d06a1b4fc Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Thu, 17 Nov 2022 19:50:27 +0100 Subject: [PATCH 15/31] Logger: Add receive timestamps --- logger/session.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/logger/session.c b/logger/session.c index aa4ad17d..e01f451c 100644 --- a/logger/session.c +++ b/logger/session.c @@ -4,6 +4,8 @@ #include #include +#include + #include "hydrogen/hydrogen.c" #include "misc.h" @@ -50,6 +52,7 @@ int session_process(const char *from) } while (1) { +/* Receive */ uint8_t buf[0x2100 + hydro_secretbox_HEADERBYTES]; uint32_t len; n = read_loop(0, &len, sizeof(len)); @@ -67,9 +70,25 @@ int session_process(const char *from) log_error("read"); break; } + +/* Timestamp */ + struct timeval tv; + if (gettimeofday(&tv, NULL)) { + n = -1; + log_error("gettimeofday"); + break; + } + +/* Decrypt */ if (hydro_secretbox_decrypt(buf, buf, n, ++msg_id, "lkrg-net", kp_server.rx)) goto fail_data; n -= hydro_secretbox_HEADERBYTES; + +/* Store */ + char tvbuf[22]; + snprintf(tvbuf, sizeof(tvbuf), "%llu,", (unsigned long long)tv.tv_sec * 1000000 + tv.tv_usec); + if (write_loop(fd, tvbuf, strlen(tvbuf)) != (ssize_t)strlen(tvbuf)) + log_error("write"); if (write_loop(fd, buf, n) != n) log_error("write"); } From 7bd8825a0ae4299110b3b4d6edf9d0145b6fc92b Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Thu, 24 Nov 2022 18:06:30 +0100 Subject: [PATCH 16/31] Logger: Include receive timestamps into the same write(2) with message --- logger/session.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/logger/session.c b/logger/session.c index e01f451c..a88343dd 100644 --- a/logger/session.c +++ b/logger/session.c @@ -53,7 +53,9 @@ int session_process(const char *from) while (1) { /* Receive */ - uint8_t buf[0x2100 + hydro_secretbox_HEADERBYTES]; +#define TIMESTAMP_SIZE 0x20 + uint8_t buf[TIMESTAMP_SIZE + 0x2100 + hydro_secretbox_HEADERBYTES]; + uint8_t *pbuf = &buf[TIMESTAMP_SIZE]; uint32_t len; n = read_loop(0, &len, sizeof(len)); if (n != sizeof(len)) { @@ -62,9 +64,9 @@ int session_process(const char *from) break; } len = ntohl(len); - if (len <= hydro_secretbox_HEADERBYTES || len > sizeof(buf)) + if (len <= hydro_secretbox_HEADERBYTES || len > sizeof(buf) - TIMESTAMP_SIZE) goto fail_data; - n = read_loop(0, buf, len); + n = read_loop(0, pbuf, len); if (n != len) { n = -1; log_error("read"); @@ -80,16 +82,17 @@ int session_process(const char *from) } /* Decrypt */ - if (hydro_secretbox_decrypt(buf, buf, n, ++msg_id, "lkrg-net", kp_server.rx)) + if (hydro_secretbox_decrypt(pbuf, pbuf, n, ++msg_id, "lkrg-net", kp_server.rx)) goto fail_data; n -= hydro_secretbox_HEADERBYTES; /* Store */ - char tvbuf[22]; - snprintf(tvbuf, sizeof(tvbuf), "%llu,", (unsigned long long)tv.tv_sec * 1000000 + tv.tv_usec); - if (write_loop(fd, tvbuf, strlen(tvbuf)) != (ssize_t)strlen(tvbuf)) - log_error("write"); - if (write_loop(fd, buf, n) != n) + int m = snprintf((char *)buf, TIMESTAMP_SIZE, "%llu,", (unsigned long long)tv.tv_sec * 1000000 + tv.tv_usec); + if (m < 0 || m >= TIMESTAMP_SIZE) + continue; /* Shouldn't happen */ + pbuf = &buf[TIMESTAMP_SIZE - m]; + memmove(pbuf, buf, m); + if (write_loop(fd, pbuf, m + n) != m + n) log_error("write"); } From 613f90ce8f41f4fb1b958fa05f68e940c879c775 Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Thu, 24 Nov 2022 18:44:00 +0100 Subject: [PATCH 17/31] Logger: Add basic syntax checking of decrypted received messages --- logger/session.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/logger/session.c b/logger/session.c index a88343dd..758a3714 100644 --- a/logger/session.c +++ b/logger/session.c @@ -86,6 +86,12 @@ int session_process(const char *from) goto fail_data; n -= hydro_secretbox_HEADERBYTES; +/* Sanitize */ + pbuf[n] = 0; + uint8_t *p = &pbuf[strspn((char *)pbuf, "0123456789,")]; + if ((*p != '-' && *p != 'c') || p[1] != ';' || memchr(pbuf, '\n', n) != &pbuf[n - 1]) + goto fail_data; /* Assumes no CONFIG_PRINTK_CALLER */ + /* Store */ int m = snprintf((char *)buf, TIMESTAMP_SIZE, "%llu,", (unsigned long long)tv.tv_sec * 1000000 + tv.tv_usec); if (m < 0 || m >= TIMESTAMP_SIZE) From b0402ddb591158f39782adae17a9a9ac928638c8 Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Thu, 24 Nov 2022 16:51:56 +0100 Subject: [PATCH 18/31] Logger: Add log file parser/reporter --- logger/Makefile | 8 ++++-- logger/logctl.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 logger/logctl.c diff --git a/logger/Makefile b/logger/Makefile index 0a72f818..1d7c0af9 100644 --- a/logger/Makefile +++ b/logger/Makefile @@ -4,16 +4,20 @@ RM = rm -f CFLAGS = -Wall -W -O2 -fomit-frame-pointer LDFLAGS = -s -PROJ = lkrg-logger lkrg-keygen +PROJ = lkrg-logger lkrg-logctl lkrg-keygen LOGGER_OBJS = logger.o misc.o session.o +LOGCTL_OBJS = logctl.o KEYGEN_OBJS = keygen.o -OBJS = $(LOGGER_OBJS) $(KEYGEN_OBJS) +OBJS = $(LOGGER_OBJS) $(LOGCTL_OBJS) $(KEYGEN_OBJS) all: $(PROJ) lkrg-logger: $(LOGGER_OBJS) $(LD) $(LDFLAGS) $(LOGGER_OBJS) -o $@ +lkrg-logctl: $(LOGCTL_OBJS) + $(LD) $(LDFLAGS) $(LOGCTL_OBJS) -o $@ + lkrg-keygen: $(KEYGEN_OBJS) $(LD) $(LDFLAGS) $(KEYGEN_OBJS) -o $@ diff --git a/logger/logctl.c b/logger/logctl.c new file mode 100644 index 00000000..454b04c1 --- /dev/null +++ b/logger/logctl.c @@ -0,0 +1,72 @@ +#include +#include + +static const char *format_time(char *s, size_t max, unsigned long long t) +{ + time_t tu = t / 1000000; + struct tm *tm = gmtime(&tu); + if (strftime(s, max, "%Y-%m-%dT%H:%M:%SZ", tm)) + return s; + return ""; +} + +static int process_log(const char *pathname) +{ + int retval = 0; + + FILE *f = fopen(pathname, "r"); + if (!f) { + perror("fopen"); + return 1; + } + + char buf[0x2100]; + while (fgets(buf, sizeof(buf), f)) { + unsigned long long tr, ts, tsu, seq, teu; + unsigned int sev; + int msgofs = 0; + int n = sscanf(buf, "%llu,%llu,%llu,%u,%llu,%llu,-;%n", &tr, &ts, &tsu, &sev, &seq, &teu, &msgofs); + if (n < 6 || !msgofs) { + if (!(retval & 2)) { + fputs("Warning: Skipping misformatted line(s)\n", stderr); + retval |= 2; + } + continue; + } + const char *msg = buf + msgofs; + + unsigned long long te = ts; + if (tsu > teu) { +/* + * Infer more accurate event time by subtracting the delay between send uptime + * and event uptime from the send time. + */ + te -= tsu - teu; + } else if (teu - tsu >= 500000 && !(retval & 4)) { + fputs("Warning: Major system uptime clock drift between CPUs\n", stderr); + retval |= 4; + } + + char ste[21]; + printf("%s %s", format_time(ste, sizeof(ste), te), msg); + } + + if (ferror(f)) { + perror("fgets"); + retval |= 1; + } + + fclose(f); + + return retval; +} + +int main(int argc, const char * const *argv) +{ + if (argc != 2) { + fputs("Usage: lkrg-logctl PATHNAME\n", stderr); + return 1; + } + + return process_log(argv[1]); +} From fb644c7db641f2934fe07cbb31dea3a61554114b Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Sun, 18 Feb 2024 18:36:04 +0100 Subject: [PATCH 19/31] Logger: Add authorship, copyright, and license statements --- LICENSE | 2 +- logger/keygen.c | 7 +++++++ logger/logctl.c | 8 ++++++++ logger/logger.c | 3 +++ logger/misc.c | 7 +++++++ logger/session.c | 7 +++++++ 6 files changed, 33 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index f39b0a08..0c56a9e7 100644 --- a/LICENSE +++ b/LICENSE @@ -3,7 +3,7 @@ Copyright (c) 2015-2023 Adam 'pi3' Zabrocki Copyright (c) 2020-2022 Mariusz Zaborski Copyright (c) 2020-2024 Solar Designer -src/modules/net/net.* +src/modules/net/net.*, logger Copyright (c) 2022 Binarly Copyright (c) 2024 Solar Designer diff --git a/logger/keygen.c b/logger/keygen.c index 1133a35b..d7b47175 100644 --- a/logger/keygen.c +++ b/logger/keygen.c @@ -1,3 +1,10 @@ +/* + * Generate a public/secret keypair. + * + * Written in 2022 by Solar Designer + * Copyright (c) 2022 Binarly + */ + #include #include "hydrogen/hydrogen.c" diff --git a/logger/logctl.c b/logger/logctl.c index 454b04c1..b90d3702 100644 --- a/logger/logctl.c +++ b/logger/logctl.c @@ -1,3 +1,11 @@ +/* + * Process an LKRG logger output file. This program currently derives each + * event's ISO timestamp from several recorded relative timestamps. + * + * Written in 2022 by Solar Designer + * Copyright (c) 2022 Binarly + */ + #include #include diff --git a/logger/logger.c b/logger/logger.c index 4d5e10a3..b31934c0 100644 --- a/logger/logger.c +++ b/logger/logger.c @@ -1,6 +1,9 @@ /* * Standalone TCP server: accepts connections, checks the anti-flood limits, * logs and starts the actual sessions. + * + * Initially written for popa3d, reused for LKRG logger with minor changes + * Copyright (c) 1999-2022 Solar Designer */ #include diff --git a/logger/misc.c b/logger/misc.c index 87d94100..a78e214a 100644 --- a/logger/misc.c +++ b/logger/misc.c @@ -1,3 +1,10 @@ +/* + * Miscellaneous system and library call wrappers. + * + * Initially written for popa3d, reused for LKRG logger with various changes + * Copyright (c) 1998-2022 Solar Designer + */ + #include #include #include diff --git a/logger/session.c b/logger/session.c index 758a3714..f2293224 100644 --- a/logger/session.c +++ b/logger/session.c @@ -1,3 +1,10 @@ +/* + * Process one client session (one TCP connection). + * + * Written in 2022 by Solar Designer + * Copyright (c) 2022 Binarly + */ + #include /* for getenv() */ #include #include From 57b4ae44efaf4cfb01e5807b5845ba46a40b6fc2 Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Wed, 21 Feb 2024 18:38:29 +0100 Subject: [PATCH 20/31] Logger: Derive PK from SK --- logger/session.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/logger/session.c b/logger/session.c index f2293224..1fac2ec5 100644 --- a/logger/session.c +++ b/logger/session.c @@ -24,14 +24,13 @@ static hydro_kx_session_keypair kp_server; int session_prepare(void) { - const char *pk = getenv("LKRG_LOGGER_PK"); const char *sk = getenv("LKRG_LOGGER_SK"); - if (!pk || !sk || + if (!sk || hydro_init() /* Currently can't fail */ || - hydro_hex2bin(server_static_kp.pk, sizeof(server_static_kp.pk), pk, 64, NULL, NULL) != 32 || - hydro_hex2bin(server_static_kp.sk, sizeof(server_static_kp.sk), sk, 64, NULL, NULL) != 32) { + hydro_hex2bin(server_static_kp.sk, sizeof(server_static_kp.sk), sk, 64, NULL, NULL) != 32 || + hydro_x25519_scalarmult_base(server_static_kp.pk, server_static_kp.sk)) { errno = 0; - return log_error("Invalid LKRG_LOGGER_PK and/or LKRG_LOGGER_SK"); + return log_error("Invalid LKRG_LOGGER_SK"); } return 0; } From 265964b4f72839dee781c2a65caac86810c61f88 Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Sun, 25 Feb 2024 20:30:29 +0100 Subject: [PATCH 21/31] Net, Logger: Add documentation --- CHANGES | 4 +++ LOGGING | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ README | 24 +++++++++++++-- 3 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 LOGGING diff --git a/CHANGES b/CHANGES index 37e9663e..b4d379a4 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,9 @@ The following major changes have been made since LKRG 0.9.7: + *) Add optional remote kernel message logging, including the sending component + in LKRG itself and the receiving/logging counterpart in a userspace daemon, + as well as additional utilities to generate a public/secret keypair and to + process the logs, and documentation in LOGGING *) Add support for RHEL 8.8+ *) RPM spec file improvements to make it work or properly fail in a wider set of circumstances, and to use the "weak-modules" script if available so that diff --git a/LOGGING b/LOGGING new file mode 100644 index 00000000..b28f20de --- /dev/null +++ b/LOGGING @@ -0,0 +1,96 @@ +LKRG remote logging +=================== + +LKRG supports optional remote kernel message logging. Once enabled, this logs +not only messages generated by LKRG, but also all other kernel messages. + +The sending component is in the LKRG kernel module itself, whereas the +receiving and logging counterpart is in a userspace daemon. There are also +additional userspace utilities. + + +Transport security +------------------ + +We currently use a one-way TCP stream, with transport security provided by our +usage of libhydrogen (bundled in here). libhydrogen is "built using just two +cryptographic building blocks: the Curve25519 elliptic curve, and the Gimli +permutation." It implements a number of Noise protocol handshake patterns, out +of which we currently use the most trivial one, "N". + +As long as the server's secret key is not compromised, this protects +confidentiality and integrity of messages. While our usage of a one-way TCP +stream (a write-only socket) is a great way to limit LKRG's remote attack +surface, it precludes implementation of forward secrecy, so a compromised +secret key can unfortunately be used to decrypt past sessions. + +Replay protection is partial - messages from the middle of a (current or past) +TCP connection cannot be replayed on their own (won't be accepted by the +receiver even if the TCP layer is tricked to accept them), but an entire TCP +connection or its starting portion can be. + +There's no explicit server authentication since the communication on top of TCP +is one-way, however security against a spoofed/MITM server is achieved through +the client only encrypting to the correct server's pre-configured public key. + +There's currently no explicit client authentication (a major shortcoming to be +addressed), but only clients with knowledge of the server's public key can send +messages that would be accepted by the server. + + +How to use +---------- + +Please refer to the section "Remote logging configuration" in README for how to +enable remote logging in LKRG. + +Please read below about the userspace tools to support LKRG remote logging. + + +Build requirements and building +------------------------------- + +The build requirements for the userspace tools are a subset of what's needed to +build LKRG itself. Currently, it's just GNU make and GCC. You can build the +tools by simply running "make" in the "logger" directory, e.g. like this: + + make -C logger -j8 + +This will build three programs: + + +lkrg-keygen +----------- + +Run this program once to obtain a public/secret keypair, which it prints as: + + LKRG_LOGGER_PK=64hexdigitshere + LKRG_LOGGER_SK=64hexdigitshere + +Use the 64 digits from LKRG_LOGGER_PK as value for LKRG's net_server_pk module +parameter. Use the line LKRG_LOGGER_SK= including the 64 digits to set an +environment variable of this name for lkrg-logger. + + +lkrg-logger +----------- + +This is the receiving and logging userspace daemon. Currently, it is to be run +e.g. as follows: + + cd logger + mkdir log + LKRG_LOGGER_SK=64hexdigitshere ./lkrg-logger + +It does not need to be run as root and does not actually daemonize itself yet. + +Per-client log files will appear in the "log" directory once client connections +encrypting to the right public key arrive. The filenames correspond to each +client's IPv4 address as seen by the server. + + +lkrg-logctl +----------- + +A tool to process one lkrg-logger output file. This program currently derives +each event's ISO timestamp from several recorded relative timestamps. diff --git a/README b/README index 720b8013..01c78abc 100644 --- a/README +++ b/README @@ -275,11 +275,29 @@ Parameters can be specified on command-lines of "insmod", "modprobe", or in a file in the /etc/modprobe.d directory. For descriptions of the parameters and their default and possible values, -please refer to the following section. +please refer to the following two sections. -Runtime configuration ---------------------- +Remote logging configuration (load-time only) +--------------------------------------------- + +LKRG supports the following module parameters (with default values or lack +thereof specified in braces) to enable its optional remote logging. + +- net_server_addr (no default) + Log server IPv4 address (e.g., 127.0.0.1) + +- net_server_port (1515) + Log server TCP port number + +- net_server_pk (no default) + Log server public key (64 hexadecimal digits) + +Please refer to LOGGING on how to use the corresponding userspace components. + + +Load-time and runtime configuration +----------------------------------- Besides the parameters optionally specified when loading the module into the kernel, LKRG also supports a number of sysctl's, which can be used to adjust From 2fdd45aa161aa69d7d47ab340a69717e6276732c Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Mon, 26 Feb 2024 19:15:27 +0100 Subject: [PATCH 22/31] Logger: Daemonize itself --- LOGGING | 2 +- logger/logger.c | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/LOGGING b/LOGGING index b28f20de..aac4a440 100644 --- a/LOGGING +++ b/LOGGING @@ -82,7 +82,7 @@ e.g. as follows: mkdir log LKRG_LOGGER_SK=64hexdigitshere ./lkrg-logger -It does not need to be run as root and does not actually daemonize itself yet. +It does not need to be run as root. Per-client log files will appear in the "log" directory once client connections encrypting to the right public key arrive. The filenames correspond to each diff --git a/logger/logger.c b/logger/logger.c index b31934c0..45eebee4 100644 --- a/logger/logger.c +++ b/logger/logger.c @@ -107,7 +107,6 @@ int main(void) if (listen(sock, MAX_BACKLOG)) return log_error("listen"); -#if 0 chdir("/"); setsid(); @@ -123,7 +122,6 @@ int main(void) } setsid(); -#endif /* Drop LOG_PERROR */ closelog(); From 6ce7e3e42f314e4ea18f0c65c1bb13c31c58a665 Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Mon, 26 Feb 2024 19:26:59 +0100 Subject: [PATCH 23/31] Logger: Store logs under /var/log/lkrg-logger --- LOGGING | 7 ++++--- logger/params.h | 12 +++++++++++- logger/session.c | 5 +++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/LOGGING b/LOGGING index aac4a440..8832b013 100644 --- a/LOGGING +++ b/LOGGING @@ -78,13 +78,14 @@ lkrg-logger This is the receiving and logging userspace daemon. Currently, it is to be run e.g. as follows: + mkdir /var/log/lkrg-logger cd logger - mkdir log LKRG_LOGGER_SK=64hexdigitshere ./lkrg-logger -It does not need to be run as root. +It does not need to be run as root, but it does need write permissions to +/var/log/lkrg-logger. -Per-client log files will appear in the "log" directory once client connections +Per-client log files will appear in the log directory once client connections encrypting to the right public key arrive. The filenames correspond to each client's IPv4 address as seen by the server. diff --git a/logger/params.h b/logger/params.h index e7363e86..38a1050c 100644 --- a/logger/params.h +++ b/logger/params.h @@ -2,6 +2,11 @@ * Global daemon parameters. */ +/* + * Our name to use when talking to various interfaces. + */ +#define DAEMON_NAME "lkrg-logger" + /* * The address and port to listen on. */ @@ -17,10 +22,15 @@ #define MAX_BACKLOG 5 #define MIN_DELAY 10 +/* + * Directory where to store the received logs. + */ +#define LOG_PATH "/var/log/" DAEMON_NAME + /* * How do we talk to syslogd? These should be fine for most systems. */ -#define SYSLOG_IDENT "lkrg-logger" +#define SYSLOG_IDENT DAEMON_NAME #define SYSLOG_OPTIONS LOG_PID #define SYSLOG_FACILITY LOG_DAEMON #define SYSLOG_PRI_LO LOG_INFO diff --git a/logger/session.c b/logger/session.c index 1fac2ec5..feb720ec 100644 --- a/logger/session.c +++ b/logger/session.c @@ -16,6 +16,7 @@ #include "hydrogen/hydrogen.c" #include "misc.h" +#include "params.h" #include "session.h" static hydro_kx_keypair server_static_kp; @@ -50,8 +51,8 @@ int session_process(const char *from) } { - char fn[24]; - snprintf(fn, sizeof(fn), "log/%s", from); + char fn[sizeof(LOG_PATH) + 16]; + snprintf(fn, sizeof(fn), LOG_PATH "/%s", from); fd = open(fn, O_CREAT | O_WRONLY | O_APPEND, 0640); if (fd < 0) return log_error("open"); From 091f0bf1729bf12ba57ecf8ca5d47d528d3beb4a Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Mon, 26 Feb 2024 19:46:19 +0100 Subject: [PATCH 24/31] Net, Logger: Use port 514/tcp by default --- LOGGING | 6 +----- README | 2 +- logger/params.h | 2 +- src/modules/net/net.c | 4 ++-- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/LOGGING b/LOGGING index 8832b013..a42c0f7e 100644 --- a/LOGGING +++ b/LOGGING @@ -76,15 +76,11 @@ lkrg-logger ----------- This is the receiving and logging userspace daemon. Currently, it is to be run -e.g. as follows: +as root like this: mkdir /var/log/lkrg-logger - cd logger LKRG_LOGGER_SK=64hexdigitshere ./lkrg-logger -It does not need to be run as root, but it does need write permissions to -/var/log/lkrg-logger. - Per-client log files will appear in the log directory once client connections encrypting to the right public key arrive. The filenames correspond to each client's IPv4 address as seen by the server. diff --git a/README b/README index 01c78abc..3a1ea7c7 100644 --- a/README +++ b/README @@ -287,7 +287,7 @@ thereof specified in braces) to enable its optional remote logging. - net_server_addr (no default) Log server IPv4 address (e.g., 127.0.0.1) -- net_server_port (1515) +- net_server_port (514) Log server TCP port number - net_server_pk (no default) diff --git a/logger/params.h b/logger/params.h index 38a1050c..ef7d51a0 100644 --- a/logger/params.h +++ b/logger/params.h @@ -11,7 +11,7 @@ * The address and port to listen on. */ #define DAEMON_ADDR "0.0.0.0" /* INADDR_ANY */ -#define DAEMON_PORT 1515 +#define DAEMON_PORT 514 /* * Limit the number of sessions we can handle at a time to reduce the impact of diff --git a/src/modules/net/net.c b/src/modules/net/net.c index f5fc4aef..1d9d076d 100644 --- a/src/modules/net/net.c +++ b/src/modules/net/net.c @@ -48,9 +48,9 @@ static char net_server_addr[16]; module_param_string(net_server_addr, net_server_addr, sizeof(net_server_addr), 0); MODULE_PARM_DESC(net_server_addr, "log server IPv4 address"); -static ushort net_server_port = 1515; +static ushort net_server_port = 514; module_param(net_server_port, ushort, 0); -MODULE_PARM_DESC(net_server_port, "log server TCP port number [1515 is default]"); +MODULE_PARM_DESC(net_server_port, "log server TCP port number [514 is default]"); static char net_server_pk[65]; module_param_string(net_server_pk, net_server_pk, sizeof(net_server_pk), 0); From f7f2a93b1ad537d52c71c54583dc79215b65175e Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Mon, 26 Feb 2024 20:06:06 +0100 Subject: [PATCH 25/31] Logger: Drop root --- LOGGING | 2 ++ logger/logger.c | 38 ++++++++++++++++++++++++++++++++++---- logger/params.h | 1 + 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/LOGGING b/LOGGING index a42c0f7e..3843be47 100644 --- a/LOGGING +++ b/LOGGING @@ -78,7 +78,9 @@ lkrg-logger This is the receiving and logging userspace daemon. Currently, it is to be run as root like this: + useradd -r lkrg-logger -d / -s /sbin/nologin mkdir /var/log/lkrg-logger + chown lkrg-logger: /var/log/lkrg-logger LKRG_LOGGER_SK=64hexdigitshere ./lkrg-logger Per-client log files will appear in the log directory once client connections diff --git a/logger/logger.c b/logger/logger.c index 45eebee4..fbf4fc9d 100644 --- a/logger/logger.c +++ b/logger/logger.c @@ -3,7 +3,7 @@ * logs and starts the actual sessions. * * Initially written for popa3d, reused for LKRG logger with minor changes - * Copyright (c) 1999-2022 Solar Designer + * Copyright (c) 1999-2024 Solar Designer */ #include @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include #include @@ -74,6 +76,34 @@ static void handle_child(int signum) errno = saved_errno; } +static int drop_root(void) +{ + struct passwd *pw; + + errno = 0; + if (!(pw = getpwnam(DAEMON_USER))) { + syslog(SYSLOG_PRI_ERROR, "getpwnam(\"" DAEMON_USER "\"): %s", + errno ? strerror(errno) : "No such user"); + return 1; + } + memset(pw->pw_passwd, 0, strlen(pw->pw_passwd)); + endpwent(); + + if (!pw->pw_uid) { + syslog(SYSLOG_PRI_ERROR, "getpwnam(\"" DAEMON_USER "\"): Invalid user"); + return 1; + } + + if (setgroups(1, &pw->pw_gid)) + return log_error("setgroups"); + if (setgid(pw->pw_gid)) + return log_error("setgid"); + if (setuid(pw->pw_uid)) + return log_error("setuid"); + + return 0; +} + int main(void) { int true = 1; @@ -87,9 +117,6 @@ int main(void) openlog(SYSLOG_IDENT, SYSLOG_OPTIONS | LOG_PERROR, SYSLOG_FACILITY); - if (session_prepare()) - return 1; - if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) return log_error("socket"); @@ -104,6 +131,9 @@ int main(void) if (bind(sock, (struct sockaddr *)&addr, sizeof(addr))) return log_error("bind"); + if (drop_root() || session_prepare()) + return 1; + if (listen(sock, MAX_BACKLOG)) return log_error("listen"); diff --git a/logger/params.h b/logger/params.h index ef7d51a0..7b786eff 100644 --- a/logger/params.h +++ b/logger/params.h @@ -12,6 +12,7 @@ */ #define DAEMON_ADDR "0.0.0.0" /* INADDR_ANY */ #define DAEMON_PORT 514 +#define DAEMON_USER DAEMON_NAME /* * Limit the number of sessions we can handle at a time to reduce the impact of From 02e38fe2adcb8f006f11d63fc572de45f4f111dc Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Mon, 26 Feb 2024 20:40:09 +0100 Subject: [PATCH 26/31] Logger: Run with umask 077 --- logger/logger.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/logger/logger.c b/logger/logger.c index fbf4fc9d..b2a0e3bd 100644 --- a/logger/logger.c +++ b/logger/logger.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -115,6 +116,8 @@ int main(void) clock_t min_delay, now, log; int i, j, n; + umask(077); + openlog(SYSLOG_IDENT, SYSLOG_OPTIONS | LOG_PERROR, SYSLOG_FACILITY); if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) From 011e12df8a7a6cd3ed0001303490e7fbcdef2d43 Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Mon, 26 Feb 2024 20:24:44 +0100 Subject: [PATCH 27/31] Logger: Add "make install" and "make uninstall" --- LOGGING | 20 ++++++++++++++++++-- logger/Makefile | 15 +++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/LOGGING b/LOGGING index 3843be47..a56438da 100644 --- a/LOGGING +++ b/LOGGING @@ -56,7 +56,23 @@ tools by simply running "make" in the "logger" directory, e.g. like this: make -C logger -j8 -This will build three programs: +This will build three programs described below. + + +Installation +------------ + +To install the programs on the system globally, run: + + sudo make install + + +Uninstalling +------------ + +Similarly to installation, you can uninstall these programs with: + + sudo make uninstall lkrg-keygen @@ -81,7 +97,7 @@ as root like this: useradd -r lkrg-logger -d / -s /sbin/nologin mkdir /var/log/lkrg-logger chown lkrg-logger: /var/log/lkrg-logger - LKRG_LOGGER_SK=64hexdigitshere ./lkrg-logger + LKRG_LOGGER_SK=64hexdigitshere lkrg-logger Per-client log files will appear in the log directory once client connections encrypting to the right public key arrive. The filenames correspond to each diff --git a/logger/Makefile b/logger/Makefile index 1d7c0af9..1ba499ae 100644 --- a/logger/Makefile +++ b/logger/Makefile @@ -1,9 +1,15 @@ CC = gcc LD = $(CC) RM = rm -f +MKDIR = mkdir -p +INSTALL = install -c CFLAGS = -Wall -W -O2 -fomit-frame-pointer LDFLAGS = -s +DESTDIR = +PREFIX = /usr/local +SBINDIR = $(PREFIX)/sbin + PROJ = lkrg-logger lkrg-logctl lkrg-keygen LOGGER_OBJS = logger.o misc.o session.o LOGCTL_OBJS = logctl.o @@ -24,5 +30,14 @@ lkrg-keygen: $(KEYGEN_OBJS) .c.o: $(CC) $(CFLAGS) -c $*.c +install: $(PROJ) + $(MKDIR) -m 755 $(DESTDIR)$(SBINDIR) + $(INSTALL) -m 700 lkrg-logger $(DESTDIR)$(SBINDIR)/ + $(INSTALL) -m 700 lkrg-logctl $(DESTDIR)$(SBINDIR)/ + $(INSTALL) -m 755 lkrg-keygen $(DESTDIR)$(SBINDIR)/ + +uninstall: + $(RM) $(DESTDIR)$(SBINDIR)/lkrg-{logger,logctl,keygen} + clean: $(RM) $(PROJ) $(OBJS) From 11dee92f43f80ec9517e87342312b4aa8942199b Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Tue, 27 Feb 2024 00:55:49 +0100 Subject: [PATCH 28/31] Logger: Support the -D option to disable daemonization --- logger/logger.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/logger/logger.c b/logger/logger.c index b2a0e3bd..3e073209 100644 --- a/logger/logger.c +++ b/logger/logger.c @@ -105,7 +105,7 @@ static int drop_root(void) return 0; } -int main(void) +int main(int argc, const char * const *argv) { int true = 1; int sock, new; @@ -116,6 +116,13 @@ int main(void) clock_t min_delay, now, log; int i, j, n; +/* Must have either no options or -D, which disables daemonization */ + int daemonize = (argc != 2 || strcmp(argv[1], "-D")); + if (argc != 1 && daemonize) { + fputs("Usage: lkrg-logger [-D]\n", stderr); + return 1; + } + umask(077); openlog(SYSLOG_IDENT, SYSLOG_OPTIONS | LOG_PERROR, SYSLOG_FACILITY); @@ -143,19 +150,21 @@ int main(void) chdir("/"); setsid(); - switch (fork()) { - case -1: - return log_error("fork"); + if (daemonize) { + switch (fork()) { + case -1: + return log_error("fork"); + + case 0: + break; - case 0: - break; + default: + return 0; + } - default: - return 0; + setsid(); } - setsid(); - /* Drop LOG_PERROR */ closelog(); openlog(SYSLOG_IDENT, SYSLOG_OPTIONS, SYSLOG_FACILITY); From 90ba51d891e89e24a48ed7533fed9104e1a011ea Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Tue, 27 Feb 2024 01:06:02 +0100 Subject: [PATCH 29/31] Logger: Add systemd service --- LOGGING | 18 +++++++++++++++--- logger/Makefile | 7 ++++++- logger/lkrg-logger.service | 16 ++++++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 logger/lkrg-logger.service diff --git a/LOGGING b/LOGGING index a56438da..45cd0fc6 100644 --- a/LOGGING +++ b/LOGGING @@ -66,11 +66,15 @@ To install the programs on the system globally, run: sudo make install +On systems with systemd, this also installs, but does not yet enable, a systemd +unit file for lkrg-logger. + Uninstalling ------------ -Similarly to installation, you can uninstall these programs with: +Similarly to installation, you can uninstall these programs and the systemd +unit file with: sudo make uninstall @@ -91,12 +95,20 @@ environment variable of this name for lkrg-logger. lkrg-logger ----------- -This is the receiving and logging userspace daemon. Currently, it is to be run -as root like this: +This is the receiving and logging userspace daemon. Currently, it is to be set +up and started via systemd like this: useradd -r lkrg-logger -d / -s /sbin/nologin mkdir /var/log/lkrg-logger chown lkrg-logger: /var/log/lkrg-logger + + umask 077 + lkrg-keygen > /etc/lkrg-logger.conf + systemctl enable lkrg-logger + systemctl start lkrg-logger + +or instead of the last 4 commands above, started manually as root like this: + LKRG_LOGGER_SK=64hexdigitshere lkrg-logger Per-client log files will appear in the log directory once client connections diff --git a/logger/Makefile b/logger/Makefile index 1ba499ae..20a1740a 100644 --- a/logger/Makefile +++ b/logger/Makefile @@ -9,6 +9,7 @@ LDFLAGS = -s DESTDIR = PREFIX = /usr/local SBINDIR = $(PREFIX)/sbin +UNITDIR = /etc/systemd/system PROJ = lkrg-logger lkrg-logctl lkrg-keygen LOGGER_OBJS = logger.o misc.o session.o @@ -35,9 +36,13 @@ install: $(PROJ) $(INSTALL) -m 700 lkrg-logger $(DESTDIR)$(SBINDIR)/ $(INSTALL) -m 700 lkrg-logctl $(DESTDIR)$(SBINDIR)/ $(INSTALL) -m 755 lkrg-keygen $(DESTDIR)$(SBINDIR)/ + if [ -d $(DESTDIR)$(UNITDIR) ]; then \ + umask 022; \ + sed "s,/usr,$(PREFIX)," lkrg-logger.service > $(DESTDIR)$(UNITDIR)/lkrg-logger.service; \ + fi uninstall: - $(RM) $(DESTDIR)$(SBINDIR)/lkrg-{logger,logctl,keygen} + $(RM) $(DESTDIR)$(SBINDIR)/lkrg-{logger,logctl,keygen} $(DESTDIR)$(UNITDIR)/lkrg-logger.service clean: $(RM) $(PROJ) $(OBJS) diff --git a/logger/lkrg-logger.service b/logger/lkrg-logger.service new file mode 100644 index 00000000..41436fd5 --- /dev/null +++ b/logger/lkrg-logger.service @@ -0,0 +1,16 @@ +[Unit] +Description=Linux Kernel Runtime Guard remote logging server +Documentation=https://lkrg.org +After=network.target + +[Service] +Type=simple +EnvironmentFile=/etc/lkrg-logger.conf +ExecStart=/usr/sbin/lkrg-logger -D +ExecReload=/bin/kill -HUP $MAINPID +KillMode=process +Restart=on-failure +RestartSec=42s + +[Install] +WantedBy=multi-user.target From 63156980cdc3341c8aa7956116b873c5004687af Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Tue, 27 Feb 2024 18:07:28 +0100 Subject: [PATCH 30/31] Net: README: Add example usage of /etc/modprobe.d/lkrg.conf --- README | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README b/README index 3a1ea7c7..5992d17c 100644 --- a/README +++ b/README @@ -271,8 +271,8 @@ With LKRG installed on the system, you can list them with: (Depending on system configuration, "modinfo" might not require root.) -Parameters can be specified on command-lines of "insmod", "modprobe", or in a -file in the /etc/modprobe.d directory. +Parameters can be specified on command-lines of "insmod", "modprobe", or after +"options lkrg " in a file in the /etc/modprobe.d directory. For descriptions of the parameters and their default and possible values, please refer to the following two sections. @@ -293,6 +293,12 @@ thereof specified in braces) to enable its optional remote logging. - net_server_pk (no default) Log server public key (64 hexadecimal digits) +If you're starting LKRG via a systemd unit or startup script (such as those +provided in here), our recommended way to specify the above parameters is by +creating the file /etc/modprobe.d/lkrg.conf with something like this in it: + +options lkrg net_server_addr=127.0.0.1 net_server_pk=64hexdigitshere + Please refer to LOGGING on how to use the corresponding userspace components. From addb229a4f69879c9c8baf6d9316207b3fec3b34 Mon Sep 17 00:00:00 2001 From: Solar Designer Date: Tue, 27 Feb 2024 19:38:14 +0100 Subject: [PATCH 31/31] RPM: Add logger sub-package, prepare for 0.9.8 --- lkrg.spec | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/lkrg.spec b/lkrg.spec index be5e4a94..15cad687 100644 --- a/lkrg.spec +++ b/lkrg.spec @@ -1,11 +1,11 @@ -%define kmod_headers_version %(rpm -qa kernel-devel | sed 's/^kernel-devel-//' | sort -r | head -1) +%define kmod_headers_version %(rpm -qa kernel-devel | sed 's/^kernel-devel-//' | sort -rV | head -1) %define module_dir /lib/modules/%kmod_headers_version/extra %global debug_package %nil Summary: Linux Kernel Runtime Guard (LKRG) Name: lkrg -Version: 0.9.7 -Release: 3%{?dist} +Version: 0.9.8 +Release: 1%{?dist} License: GPLv2 URL: https://lkrg.org Source: https://lkrg.org/download/%name-%version.tar.gz @@ -24,17 +24,27 @@ processes (exploit detection). For process credentials, LKRG attempts to detect the exploit and take action before the kernel would grant access (such as open a file) based on the unauthorized credentials. +%package logger +Summary: Linux Kernel Runtime Guard (LKRG) remote logging tools +Requires(pre): /usr/sbin/useradd + +%description logger +Userspace tools to support Linux Kernel Runtime Guard (LKRG) remote logging. + %prep %setup -q %build make %{?_smp_mflags} KERNELRELEASE=%kmod_headers_version +make -C logger %{?_smp_mflags} CFLAGS='%optflags' %install rm -rf %buildroot install -D -p -m 644 lkrg.ko %buildroot%module_dir/lkrg.ko install -D -p -m 644 scripts/bootup/systemd/lkrg.service %buildroot%_unitdir/lkrg.service install -D -p -m 644 scripts/bootup/lkrg.conf %buildroot%_sysconfdir/sysctl.d/01-lkrg.conf +make -C logger install DESTDIR=%buildroot PREFIX=/usr UNITDIR=%_unitdir +mkdir -p %buildroot/var/log/lkrg-logger %posttrans if [ -e %_sbindir/weak-modules ]; then @@ -54,14 +64,37 @@ if [ -e %_sbindir/weak-modules ]; then fi %systemd_postun_with_restart lkrg.service +%pre logger +# Ignore errors so that we don't fail if the user already exists +/usr/sbin/useradd -r lkrg-logger -d / -s /sbin/nologin || : +# Don't remove this user on package uninstall because the user may still own +# files under /var/log/lkrg-logger, which won't be removed if non-empty + %files %defattr(-,root,root) %doc CHANGES CONCEPTS LICENSE PATREONS PERFORMANCE README %module_dir/* -%_unitdir/* -%_sysconfdir/sysctl.d/* +%_unitdir/lkrg.service +%config(noreplace) %_sysconfdir/sysctl.d/* + +%files logger +%defattr(-,root,root) +%doc LOGGING +/usr/sbin/* +%_unitdir/lkrg-logger.service +%dir %attr(0750,lkrg-logger,lkrg-logger) /var/log/lkrg-logger %changelog +* Tue Feb 27 2024 Solar Designer 0.9.8-1 +- Update to 0.9.8 +- Add logger sub-package +- Mark the sysctl configuration file config(noreplace) +- Use "sort -V" to build against the latest installed version of kernel-devel + +* Wed Nov 8 2023 Solar Designer 0.9.7-4 +- Add a couple of upstream patches, most notably to fix kINT false positives on +EL 8.8. + * Tue Oct 24 2023 Solar Designer 0.9.7-3 - Use weak-modules if available so that on RHEL and its rebuilds the same LKRG package build works across different kABI-compatible kernel revisions/builds