Various updates
@@ -2,4 +2,4 @@ # circe-vpn
Tiny, fast, and paranoid VPN. Use at your own risk. - +
@@ -1,51 +1,26 @@
-/* - * Circe configuration for mldsa-native. - * - * Selected instead of contrib/mldsa-native/mldsa/mldsa_native_config.h by - * building with -DMLD_CONFIG_FILE="circe_mldsa_config.h" (see - * contrib/meson.build). - * - * Circe only needs ML-DSA-44 with the randomized (randombytes) API. - */ - #ifndef MLD_CONFIG_H #define MLD_CONFIG_H -/* ML-DSA-44. */ #define MLD_CONFIG_PARAMETER_SET 44 -/* Namespace prefix for exported symbols (matches the upstream default for 44). - */ #define MLD_CONFIG_NAMESPACE_PREFIX PQCP_MLDSA_NATIVE_MLDSA44 -/* - * Randomized API. - * - * We deliberately leave MLD_CONFIG_NO_RANDOMIZED_API and - * MLD_CONFIG_CUSTOM_RANDOMBYTES *unset*: mldsa-native keeps the randomized - * signing API and expects the consumer to provide - * - * int randombytes(uint8_t *out, size_t outlen); // 0 on success - * - * No deterministic/derand-only build. - */ +#if defined(MLD_BUILD_INTERNAL) + +#define MLD_CONFIG_CUSTOM_RANDOMBYTES +#ifndef __ASSEMBLER__ +#include "core/platform.h" +#include "src/sys.h" +#include <stddef.h> +#include <stdint.h> -/* - * Implementation-only options. - * - * MLD_BUILD_INTERNAL is defined by the library's src/common.h before this file - * is included, so these only affect the library build, not consumers that - * merely include mldsa_native.h. - */ -#if defined(MLD_BUILD_INTERNAL) +static MLD_INLINE int +mld_randombytes (uint8_t *ptr, size_t len) +{ + return circe_platform_random_bytes_callback (ptr, len); +} +#endif -/* - * Use the optimized native backends on AArch64 and x86-64, and the portable C - * backends everywhere else. The architecture is detected from the compiler's - * predefined macros, matching mldsa-native's own src/sys.h; the actual backend - * (NEON / AVX2, and the SHA3/Keccak accelerator) is then selected - * automatically from the -march flags passed by contrib/meson.build. - */ #if defined(__aarch64__) || defined(__x86_64__) #define MLD_CONFIG_USE_NATIVE_BACKEND_ARITH #define MLD_CONFIG_ARITH_BACKEND_FILE "native/meta.h"@@ -53,17 +28,11 @@ #define MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202
#define MLD_CONFIG_FIPS202_BACKEND_FILE "fips202/native/auto.h" #endif -/* - * Freestanding build: no libc is linked, so route mldsa-native's memcpy / - * memset / stack-zeroization through Circe's portable implementations in - * common/memory.c. This drops the library's <string.h> includes and leaves no - * unresolved libc symbols. - */ #define MLD_CONFIG_CUSTOM_MEMCPY #define MLD_CONFIG_CUSTOM_MEMSET #define MLD_CONFIG_CUSTOM_ZEROIZE #if !defined(__ASSEMBLER__) -#include "src/sys.h" /* for MLD_INLINE */ +#include "src/sys.h" #include <stddef.h> #include <stdint.h>
@@ -1,52 +1,26 @@
-/* - * Circe configuration for mlkem-native. - * - * Selected instead of contrib/mlkem-native/mlkem/mlkem_native_config.h by - * building with -DMLK_CONFIG_FILE="circe_mlkem_config.h" (see - * contrib/meson.build). - * - * Circe only needs ML-KEM-768 with the randomized (randombytes) API. - */ - #ifndef MLK_CONFIG_H #define MLK_CONFIG_H -/* ML-KEM-768. */ #define MLK_CONFIG_PARAMETER_SET 768 -/* Namespace prefix for exported symbols (matches the upstream default for - * 768). */ #define MLK_CONFIG_NAMESPACE_PREFIX PQCP_MLKEM_NATIVE_MLKEM768 -/* - * Randomized API. - * - * We deliberately leave MLK_CONFIG_NO_RANDOMIZED_API and - * MLK_CONFIG_CUSTOM_RANDOMBYTES *unset*: mlkem-native keeps - * crypto_kem_keypair() and crypto_kem_enc() and expects the consumer to - * provide - * - * int randombytes(uint8_t *out, size_t outlen); // 0 on success - * - * No deterministic/derand-only build. - */ - -/* - * Implementation-only options. - * - * MLK_BUILD_INTERNAL is defined by the library's src/common.h before this file - * is included, so these only affect the library build, not consumers that - * merely include mlkem_native.h. - */ #if defined(MLK_BUILD_INTERNAL) -/* - * Use the optimized native backends on AArch64 and x86-64, and the portable C - * backends everywhere else. The architecture is detected from the compiler's - * predefined macros, matching mlkem-native's own src/sys.h; the actual backend - * (NEON / AVX2, and the SHA3/Keccak accelerator) is then selected - * automatically from the -march flags passed by contrib/meson.build. - */ +#define MLK_CONFIG_CUSTOM_RANDOMBYTES +#ifndef __ASSEMBLER__ +#include "core/platform.h" +#include "src/sys.h" +#include <stddef.h> +#include <stdint.h> + +static MLK_INLINE int +mlk_randombytes (uint8_t *ptr, size_t len) +{ + return circe_platform_random_bytes_callback (ptr, len); +} +#endif + #if defined(__aarch64__) || defined(__x86_64__) #define MLK_CONFIG_USE_NATIVE_BACKEND_ARITH #define MLK_CONFIG_ARITH_BACKEND_FILE "native/meta.h"@@ -54,12 +28,6 @@ #define MLK_CONFIG_USE_NATIVE_BACKEND_FIPS202
#define MLK_CONFIG_FIPS202_BACKEND_FILE "fips202/native/auto.h" #endif -/* - * Freestanding build: no libc is linked, so route mlkem-native's memcpy / - * memset / stack-zeroization through Circe's portable implementations in - * common/memory.c. This drops the library's <string.h> includes and leaves no - * unresolved libc symbols. - */ #define MLK_CONFIG_CUSTOM_MEMCPY #define MLK_CONFIG_CUSTOM_MEMSET #define MLK_CONFIG_CUSTOM_ZEROIZE
@@ -1,37 +1,15 @@
-# Builds the external post-quantum primitives Circe depends on: -# - mlkem-native -> ML-KEM-768 -# - mldsa-native -> ML-DSA-44 -# -# Both are compiled as single-compilation-unit (monolithic) builds: one C unit -# plus one assembly unit per library. The parameter set, namespace and native -# backend selection live in the config headers under contrib/config/, which are -# pulled in via -DML{K,D}_CONFIG_FILE. -# -# Each consumer of these libraries must still provide a randombytes() -# implementation: int randombytes(uint8_t *out, size_t outlen); - cc = meson.get_compiler('c') -# --------------------------------------------------------------------------- -# Native-backend architecture flags -# -# The config headers enable the native backends whenever the compiler targets -# AArch64 or x86-64. Here we hand the compiler the ISA flags those backends -# need; on any other architecture we add nothing and the portable C backends -# are used instead. -# --------------------------------------------------------------------------- native_arch_args = [] cpu = host_machine.cpu_family() if cpu == 'x86_64' - # AVX2 arithmetic backend + BMI2 for the Keccak/rejection-sampling paths. foreach arg : ['-mavx2', '-mbmi2'] if cc.has_argument(arg) native_arch_args += arg endif endforeach elif cpu == 'aarch64' - # NEON arithmetic backend + the Armv8.4 SHA3 extension for native Keccak. if cc.has_argument('-march=armv8.4-a+sha3') native_arch_args += '-march=armv8.4-a+sha3' endif@@ -39,9 +17,6 @@ endif
contrib_config_inc = include_directories('config') -# Freestanding, matching the rest of Circe: no libc is linked, and the config -# headers route the libraries' memcpy/memset/zeroize through common's -# circe_memcpy/circe_memset (hence the common_dep below). pqc_c_args = [ '-O3', '-ffreestanding',@@ -49,9 +24,6 @@ '-fno-stack-protector', # no libc: don't emit __stack_chk_fail references
'-fomit-frame-pointer', ] + native_arch_args -# --------------------------------------------------------------------------- -# mlkem-native -> ML-KEM-768 -# --------------------------------------------------------------------------- mlkem_inc = include_directories('mlkem-native') mlkem_config_arg = '-DMLK_CONFIG_FILE="circe_mlkem_config.h"'@@ -74,9 +46,6 @@ dependencies: [common_dep],
) set_variable(meson.project_name() + '_mlkem_dep', mlkem_dep) -# --------------------------------------------------------------------------- -# mldsa-native -> ML-DSA-44 -# --------------------------------------------------------------------------- mldsa_inc = include_directories('mldsa-native') mldsa_config_arg = '-DMLD_CONFIG_FILE="circe_mldsa_config.h"'
@@ -1,6 +1,7 @@
#include "core/core.h" #include "common/memory.h" #include "core/peer_table.h" +#include "core/platform.h" #include "mldsa/mldsa_native.h" #include "mlkem/mlkem_native.h" #include "proto_circe.h"@@ -109,34 +110,171 @@ return CIRCE_RESULT_HANDSHAKE_PENDING;
} static void -close_without_saying_goodbye (struct circe_context *ctx, - struct circe_peer *remote_pt_entry) +create_prk (const uint8_t ek_shared[32], + const uint8_t qk_shared[MLKEM768_BYTES], + uint8_t prk_out[TCT_TURBOSHAKE128_STATE_LEN]) +{ + tct_turboshake128_init (prk_out); + const uint8_t SALT[] = "circe/v1"; + tct_turboshake128_absorb (prk_out, SALT, sizeof (SALT) - 1, 0x0); + tct_turboshake128_absorb (prk_out, ek_shared, 32, 0x1); + tct_turboshake128_absorb (prk_out, qk_shared, MLKEM768_BYTES, 0x2); +} + +static void +derive_k_hs_e (const uint8_t prk[TCT_TURBOSHAKE128_STATE_LEN], + const uint8_t th2[64], uint8_t out[32]) { - size_t start_idx = remote_pt_entry->identity[0] % CIRCE_PEER_TABLE_LEN; - size_t peer_idx = CIRCE_PEER_TABLE_LEN; - for (size_t i = 0; i < CIRCE_PEER_TABLE_LEN; ++i) + uint8_t prk_to_expand[TCT_TURBOSHAKE128_STATE_LEN]; + circe_memory_copy (prk, prk_to_expand, TCT_TURBOSHAKE128_STATE_LEN); + const uint8_t EXPANSION_DATA[] = "hs eurylochus"; + tct_turboshake128_absorb (prk_to_expand, EXPANSION_DATA, + sizeof (EXPANSION_DATA) - 1, 0x3); + tct_turboshake128_absorb (prk_to_expand, th2, 64, 0x4); + tct_turboshake128_squeeze_destructive (prk_to_expand, out, 32); + circe_memory_set (prk_to_expand, 0x0, sizeof (prk_to_expand)); +} + +static void +derive_k_hs_p (const uint8_t prk[TCT_TURBOSHAKE128_STATE_LEN], + const uint8_t th2[64], uint8_t out[32]) +{ + uint8_t prk_to_expand[TCT_TURBOSHAKE128_STATE_LEN]; + circe_memory_copy (prk, prk_to_expand, TCT_TURBOSHAKE128_STATE_LEN); + const uint8_t EXPANSION_DATA[] = "hs polites"; + tct_turboshake128_absorb (prk_to_expand, EXPANSION_DATA, + sizeof (EXPANSION_DATA) - 1, 0x3); + tct_turboshake128_absorb (prk_to_expand, th2, 64, 0x4); + tct_turboshake128_squeeze_destructive (prk_to_expand, out, 32); + circe_memory_set (prk_to_expand, 0x0, sizeof (prk_to_expand)); +} + +static void +derive_k_d_p2e (const uint8_t prk[TCT_TURBOSHAKE128_STATE_LEN], + const uint8_t th3[64], uint8_t out[32]) +{ + uint8_t prk_to_expand[TCT_TURBOSHAKE128_STATE_LEN]; + circe_memory_copy (prk, prk_to_expand, TCT_TURBOSHAKE128_STATE_LEN); + const uint8_t EXPANSION_DATA[] = "data p2e"; + tct_turboshake128_absorb (prk_to_expand, EXPANSION_DATA, + sizeof (EXPANSION_DATA) - 1, 0x3); + tct_turboshake128_absorb (prk_to_expand, th3, 64, 0x4); + tct_turboshake128_squeeze_destructive (prk_to_expand, out, 32); + circe_memory_set (prk_to_expand, 0x0, sizeof (prk_to_expand)); +} + +static void +derive_k_d_e2p (const uint8_t prk[TCT_TURBOSHAKE128_STATE_LEN], + const uint8_t th3[64], uint8_t out[32]) +{ + uint8_t prk_to_expand[TCT_TURBOSHAKE128_STATE_LEN]; + circe_memory_copy (prk, prk_to_expand, TCT_TURBOSHAKE128_STATE_LEN); + const uint8_t EXPANSION_DATA[] = "data e2p"; + tct_turboshake128_absorb (prk_to_expand, EXPANSION_DATA, + sizeof (EXPANSION_DATA) - 1, 0x3); + tct_turboshake128_absorb (prk_to_expand, th3, 64, 0x4); + tct_turboshake128_squeeze_destructive (prk_to_expand, out, 32); + circe_memory_set (prk_to_expand, 0x0, sizeof (prk_to_expand)); +} + +/* END GENERAL HELPER FUNCTIONS */ + +/* BUSINESS LOGIC */ + +enum circe_result +dispatch_complete_packet (struct circe_context *ctx, struct circe_peer *remote) +{ + enum circe_result res = CIRCE_RESULT_SUCCESS; + uint8_t key[32]; + uint8_t signature[MLDSA44_BYTES]; + size_t siglen; + + struct proto_circe_v1_open_tunnel ot_contents; + uint8_t ek_shared[32]; + uint8_t qk_shared[MLKEM768_BYTES]; + uint8_t mlkem_encaps[MLKEM768_CIPHERTEXTBYTES]; + + struct proto_circe_v1_ack_open ao_contents; + + struct proto_circe_v1_identify id_contents; + + struct proto_circe_v1_data dt_contents; + + struct proto_circe_v1_close_tunnel ct_contents; + + struct proto_circe_v1_path_challenge pc_contents; + + switch (proto_circe_v1_rx_type (&(remote->dgram_rx_state))) { - if (circe_memory_equal ( - remote_pt_entry->identity, - ctx->peer_table[(i + start_idx) % CIRCE_PEER_TABLE_LEN].identity, - CIRCE_IDENTITY_LEN)) + case PROTO_CIRCE_V1_MSG_OPEN_TUNNEL: + if (proto_circe_v1_open_tunnel_decode ( + remote->dgram_rx_buffer, + proto_circe_v1_rx_len (&(remote->dgram_rx_state)), &ot_contents, + remote->my_th2) + != PBA_OK) + { + res = CIRCE_RESULT_INTERNAL_ERROR; + goto dispatch_complete_packet_cleanup; + } + circe_platform_random_bytes_callback (remote->my_ecdh_privkey, 32); + tct_x25519 (remote->my_ecdh_privkey, ot_contents.ecdh_fragment, + ek_shared); + if (PQCP_MLKEM_NATIVE_MLKEM768_enc (mlkem_encaps, qk_shared, + ot_contents.mlkem_pubkey) + != 0) + { + res = CIRCE_RESULT_INTERNAL_ERROR; + goto dispatch_complete_packet_cleanup; + } + create_prk (ek_shared, qk_shared, remote->prk); + derive_k_hs_e (remote->prk, remote->my_th2, key); + ao_contents.hdr.tunnel_id = remote->tunnel_id; + ao_contents.ecdh_fragment = remote->my_ecdh_privkey; + ao_contents.identity = ctx->my_identity; + ao_contents.mlkem_encaps = mlkem_encaps; + if (PQCP_MLDSA_NATIVE_MLDSA44_signature ( + signature, &siglen, ao_contents.identity, CIRCE_IDENTITY_LEN, + NULL, 0, ctx->my_mldsa_privkey) + != 0) + { + res = CIRCE_RESULT_INTERNAL_ERROR; + goto dispatch_complete_packet_cleanup; + } + proto_circe_v1_ack_open_encode (&ao_contents, key, + remote->dgram_tx_buffer, + sizeof (remote->dgram_tx_buffer), + &remote->dgram_tx_len, remote->my_th3); + proto_circe_v1_tx_init (&(remote->dgram_tx_state), + remote->dgram_tx_buffer, remote->dgram_tx_len, + key, remote->last_seqn_tx); + remote->last_seqn_tx++; + break; + case PROTO_CIRCE_V1_MSG_ACK_OPEN: + derive_k_hs_e (remote->prk, remote->my_th2, key); + if (proto_circe_v1_ack_open_decode ( + remote->dgram_rx_buffer, + proto_circe_v1_rx_len (&(remote->dgram_rx_state)), key, + &ao_contents, remote->my_th3) + != PBA_OK) { - peer_idx = start_idx; - break; + res = CIRCE_RESULT_INTERNAL_ERROR; + goto dispatch_complete_packet_cleanup; } + + case PROTO_CIRCE_V1_MSG_IDENTIFY: + case PROTO_CIRCE_V1_MSG_DATA: + case PROTO_CIRCE_V1_MSG_CLOSE_TUNNEL: + case PROTO_CIRCE_V1_MSG_PATH_CHALLENGE: } - if (peer_idx == CIRCE_PEER_TABLE_LEN) - { - // Double-free, perhaps; just ignore it - return; - } - ctx->peer_handshake_pending_mask &= ~(1ull << peer_idx); - ctx->peer_table_free_mask |= 1ull << peer_idx; - circe_memory_set ((uint8_t *)&(ctx->peer_table[peer_idx]), 0x0, - sizeof (struct circe_peer)); +dispatch_complete_packet_cleanup: + circe_memory_set (key, 0x0, sizeof (key)); + circe_memory_set (ek_shared, 0x0, sizeof (ek_shared)); + circe_memory_set (qk_shared, 0x0, sizeof (qk_shared)); + circe_memory_set (mlkem_encaps, 0x0, sizeof (mlkem_encaps)); + return res; } -/* END GENERAL HELPER FUNCTIONS */ +/* END BUSINESS LOGIC */ /* PUBLIC API */@@ -145,7 +283,6 @@ circe_initialize_core (
struct circe_context *ctx, uint8_t identity[CIRCE_IDENTITY_LEN], uint8_t mldsa_privkey[MLDSA_SECRETKEYBYTES (44)], uint64_t now_ms, void *ctx_handle, - void (*random_bytes_callback) (void *, uint8_t *, uint32_t), bool (*recall_peer_callback) (void *, const uint8_t[CIRCE_IDENTITY_LEN], struct circe_peer *)) {@@ -160,7 +297,6 @@ circe_memory_set ((uint8_t *)ctx->outgoing_packets, 0x0,
sizeof (ctx->outgoing_packets)); circe_memory_set ((uint8_t *)ctx->peer_table, 0x0, sizeof (ctx->peer_table)); ctx->ctx_handle = ctx_handle; - ctx->random_bytes_callback = random_bytes_callback; ctx->recall_peer_callback = recall_peer_callback; return CIRCE_RESULT_SUCCESS; }@@ -172,6 +308,8 @@ const uint8_t *restrict in, size_t len,
uint8_t *restrict out, size_t *out_len, uint64_t now_ms, struct circe_event *events, size_t *n_events) { + enum circe_result res = CIRCE_RESULT_SUCCESS; + proto_circe_v1_frag_t fragment; if (proto_circe_v1_peek (in, len, &fragment) != PBA_OK) {@@ -179,8 +317,57 @@ return CIRCE_RESULT_INVALID_PACKET;
} struct circe_peer *remote_pt_entry; peer_table_lookup (ctx, fragment.hdr.tunnel_id, &remote_pt_entry); + uint8_t frag_key_buf[32]; uint8_t *frag_key; - return CIRCE_RESULT_SUCCESS; + switch (fragment.type) + { + case PROTO_CIRCE_V1_MSG_OPEN_TUNNEL: + case PROTO_CIRCE_V1_MSG_DATA: + // Can't fragment + frag_key = NULL; + break; + case PROTO_CIRCE_V1_MSG_ACK_OPEN: + derive_k_hs_e (remote_pt_entry->prk, remote_pt_entry->my_th2, + frag_key_buf); + frag_key = frag_key_buf; + break; + case PROTO_CIRCE_V1_MSG_IDENTIFY: + derive_k_hs_p (remote_pt_entry->prk, remote_pt_entry->my_th2, + frag_key_buf); + frag_key = frag_key_buf; + break; + case PROTO_CIRCE_V1_MSG_CLOSE_TUNNEL: + case PROTO_CIRCE_V1_MSG_PATH_CHALLENGE: + if (remote_pt_entry->my_role == CIRCE_ROLE_EURYLOCHUS) + { + derive_k_d_p2e (remote_pt_entry->prk, remote_pt_entry->my_th3, + frag_key_buf); + } + else + { + derive_k_d_e2p (remote_pt_entry->prk, remote_pt_entry->my_th3, + frag_key_buf); + } + frag_key = frag_key_buf; + break; + } + switch (proto_circe_v1_rx_push (&(remote_pt_entry->dgram_rx_state), in, len, + frag_key)) + { + case PBA_OK_COMPLETE: + dispatch_complete_packet ( + proto_circe_v1_rx_buf (&(remote_pt_entry->dgram_rx_state)), + remote_pt_entry); + break; + case PBA_OK_PARTIAL: + break; + default: + res = CIRCE_RESULT_INVALID_PACKET; + goto circe_handle_packet_cleanup; + } +circe_handle_packet_cleanup: + circe_memory_set (frag_key_buf, 0x0, sizeof (frag_key_buf)); + return res; } enum circe_result
@@ -72,7 +72,6 @@ uint8_t my_identity[CIRCE_IDENTITY_LEN];
uint8_t my_mldsa_privkey[MLDSA_SECRETKEYBYTES (44)]; void *ctx_handle; - void (*random_bytes_callback) (void *ctx_handle, uint8_t *out, uint32_t len); bool (*recall_peer_callback) (void *ctx_handle, const uint8_t identity[CIRCE_IDENTITY_LEN], struct circe_peer *out);@@ -83,7 +82,6 @@ enum circe_result circe_initialize_core (
struct circe_context *ctx, uint8_t identity[CIRCE_IDENTITY_LEN], uint8_t mldsa_privkey[MLDSA_SECRETKEYBYTES (44)], uint64_t now_ms, void *ctx_handle, - void (*random_bytes_callback) (void *, uint8_t *, uint32_t), bool (*recall_peer_callback) (void *, const uint8_t[CIRCE_IDENTITY_LEN], struct circe_peer *));
@@ -24,7 +24,7 @@ } ip_addr;
}; #define CIRCE_PEER_TABLE_LEN 64 -#define CIRCE_IDENTITY_LEN 64 // SHA-512 hash of hostname || island name +#define CIRCE_IDENTITY_LEN 64 // SHA-512 hash of hostname || island name #define CIRCE_TUNNEL_ID_LEN 64 // Random bytes enum circe_handshake_stage@@ -73,6 +73,7 @@ enum circe_handshake_stage last_handshake_stage;
uint8_t dgram_rx_buffer[PROTO_CIRCE_V1_MAX_MSG_LEN]; uint8_t dgram_tx_buffer[PROTO_CIRCE_V1_MAX_MSG_LEN]; + size_t dgram_tx_len; proto_circe_v1_rx_t dgram_rx_state; proto_circe_v1_tx_t dgram_tx_state;@@ -82,6 +83,12 @@ uint8_t my_th3[64]; // SHA-512
uint8_t my_ecdh_privkey[32]; uint8_t my_mlkem_privkey[MLKEM_SECRETKEYBYTES (768)]; uint8_t prk[TCT_TURBOSHAKE128_STATE_LEN]; + + enum + { + CIRCE_ROLE_EURYLOCHUS, + CIRCE_ROLE_POLITES, + } my_role; }; #endif
@@ -0,0 +1,10 @@
+#ifndef CIRCE_CORE_PLATFORM_H +#define CIRCE_CORE_PLATFORM_H + +#include <stdint.h> +#include <stddef.h> + +/// Return 0 on success +int circe_platform_random_bytes_callback (uint8_t *out, size_t len); + +#endif