Continuing work on the core
@@ -1,11 +1,20 @@
#include "core.h" #include "cbor.h" #include "chacha20_poly1305.h" +#include "kangarootwelve128.h" #include "memory.h" +#include "mldsa_native.h" +#include "mlkem_native.h" #include "packet.h" #include "peer_table.h" +#include "platform.h" +#include "sha2.h" +#include "x25519.h" #include <stdbool.h> +#include <stddef.h> #include <stdint.h> + +#define CIRCE_P1305_MAC_LEN 16 #define CIRCE_CORE_ASSERT(x, err) \ do \@@ -17,13 +26,81 @@ } \
} \ while (false) +#define CIRCE_CORE_ASSERT_WITH_CLEANUP(x, err, errvar, label) \ + do \ + { \ + if (!(x)) \ + { \ + (errvar) = (err); \ + goto label; \ + } \ + } \ + while (false) + +/* CIRCE-CORE GLOBAL STATE */ + static struct circe_peer peer_table[CIRCE_PEER_TABLE_LEN]; +static uint64_t peer_handshake_pending_mask; + +static uint64_t peer_table_free_mask; // 1 if free, 0 if full + +static struct circe_outgoing_packet + outgoing_packets[CIRCE_MAX_OUTGOING_PACKETS]; + +static uint64_t outgoing_packet_table_free_mask; // 1 if free, 0 if full + +static uint64_t last_tick_ms; + +static uint8_t my_identity[CIRCE_IDENTITY_LEN]; + +static uint8_t my_mldsa_privkey[MLDSA_SECRETKEYBYTES (44)]; + +#if defined(__GNUC__) || defined(__clang__) +#define CIRCE_FIRST_FREE_OUTGOING_PACKET_ENTRY() \ + __builtin_ffsll (outgoing_packet_table_free_mask) +#define CIRCE_FIRST_TAKEN_OUTGOING_PACKET_ENTRY() \ + __builtin_ffsll (~outgoing_packet_table_free_mask) +#define CIRCE_FIRST_FREE_PEER_TABLE_ENTRY() \ + __builtin_ffsll (peer_table_free_mask) +#else +#error Circe can currently only be built with GCC or Clang. +#endif + +/* END CIRCE-CORE GLOBAL STATE */ + +/* GENERAL HELPER FUNCTIONS */ + static bool -peer_table_lookup (uint8_t identity[CIRCE_IDENTITY_LEN], - struct circe_peer **out) +locations_equal (const struct circe_location a, const struct circe_location b) +{ + if (a.ip_type != b.ip_type) + { + return false; + } + else if (a.port != b.port) + { + return false; + } + else if (a.ip_type == CIRCE_ADDRESS_IPV4) + { + return circe_memory_equal (a.ip_addr.ipv4, b.ip_addr.ipv4, + sizeof (a.ip_addr.ipv4)); + } + else if (a.ip_type == CIRCE_ADDRESS_IPV6) + { + return circe_memory_equal (a.ip_addr.ipv6, b.ip_addr.ipv6, + sizeof (a.ip_addr.ipv6)); + } + // What? + return false; +} + +static enum circe_result +peer_table_lookup (const uint8_t identity[CIRCE_IDENTITY_LEN], + struct circe_peer **const out) { - uint8_t start_idx = identity[0] % CIRCE_PEER_TABLE_LEN; + size_t start_idx = identity[0] % CIRCE_PEER_TABLE_LEN; for (size_t i = 0; i < CIRCE_PEER_TABLE_LEN; ++i) { if (circe_memory_equal (@@ -32,10 +109,23 @@ peer_table[(i + start_idx) % CIRCE_PEER_TABLE_LEN].identity,
CIRCE_IDENTITY_LEN)) { *out = &(peer_table[(i + start_idx) % CIRCE_PEER_TABLE_LEN]); - return true; + return CIRCE_RESULT_SUCCESS; } } - return false; + size_t out_idx = CIRCE_FIRST_FREE_PEER_TABLE_ENTRY (); + if (out_idx == 0) + { + return CIRCE_RESULT_PEER_TABLE_FULL; + } + if (!circe_platform_recall_peer (identity, &peer_table[out_idx - 1])) + { + return CIRCE_RESULT_UNKNOWN_REMOTE; + } + peer_table_free_mask &= ~(1ull << (out_idx - 1)); + peer_handshake_pending_mask |= (1ull << (out_idx - 1)); + *out = &(peer_table[out_idx - 1]); + (*out)->next_handshake_stage = CIRCE_HANDSHAKE_STAGE_OPEN; + return CIRCE_RESULT_HANDSHAKE_PENDING; } static enum circe_result@@ -61,13 +151,15 @@ CIRCE_RESULT_INVALID_PACKET);
CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (cd, &sequence_number), CIRCE_RESULT_INVALID_PACKET); + const uint8_t *end_of_aad = cd->cursor; + CIRCE_CORE_ASSERT (circe_cbor_get_major_type (cd, &major_type), CIRCE_RESULT_INVALID_PACKET); CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_UNSIGNED, CIRCE_RESULT_INVALID_PACKET); CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (cd, &field), CIRCE_RESULT_INVALID_PACKET); - CIRCE_CORE_ASSERT (field == CIRCE_PACKET_FIELD_PAYLOAD, + CIRCE_CORE_ASSERT (field == CIRCE_PACKET_FIELD_DATA_PAYLOAD, CIRCE_RESULT_INVALID_PACKET); CIRCE_CORE_ASSERT (circe_cbor_get_major_type (cd, &major_type),@@ -87,48 +179,698 @@ {
nonce[i] = (sequence_number >> (i * 8)) & 0xff; } if (!tct_aead_chacha20_poly1305_decrypt_and_verify ( - cd->buf_start, cd->cursor - cd->buf_start, peer->rx_key, nonce, + cd->buf_start, end_of_aad - cd->buf_start, peer->rx_key, nonce, sealed.buf_start, sealed.buf_len, out)) { return CIRCE_RESULT_INVALID_MAC; } - *out_len = sealed.buf_len - 16; // Don't include the MAC's length + *out_len + = sealed.buf_len - CIRCE_P1305_MAC_LEN; // Don't include the MAC's length return CIRCE_RESULT_SUCCESS; } -static bool -locations_equal (const struct circe_location a, const struct circe_location b) +static void +close_without_saying_goodbye (struct circe_peer *remote_pt_entry) { - if (a.ip_type != b.ip_type) + 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) { - return false; + if (circe_memory_equal ( + remote_pt_entry->identity, + peer_table[(i + start_idx) % CIRCE_PEER_TABLE_LEN].identity, + CIRCE_IDENTITY_LEN)) + { + peer_idx = start_idx; + break; + } } - else if (a.port != b.port) + if (peer_idx == CIRCE_PEER_TABLE_LEN) { - return false; + // What? + return; } - else if (a.ip_type == CIRCE_ADDRESS_IPV4) + peer_handshake_pending_mask &= ~(1ull << peer_idx); + peer_table_free_mask |= 1ull << peer_idx; + circe_memory_set ((uint8_t *)&peer_table[peer_idx], 0x0, + sizeof (struct circe_peer)); +} + +/* END GENERAL HELPER FUNCTIONS */ + +/* HANDSHAKE PROCESSING FUNCTIONS */ + +static enum circe_result +process_opentunnel (struct circe_cbor_decoder *cd, + struct circe_location remote, + struct circe_peer *remote_pt_entry) +{ + + enum circe_cbor_major_type major_type; + uint64_t field; + + if (remote_pt_entry->next_handshake_stage != CIRCE_HANDSHAKE_STAGE_OPEN) + { + return CIRCE_RESULT_HS_OUT_OF_ORDER; + } + + uint8_t remote_ecdh_frag[32]; + // ECDH fragment + CIRCE_CORE_ASSERT (circe_cbor_get_major_type (cd, &major_type), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_UNSIGNED, + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (cd, &field), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (field == CIRCE_PACKET_FIELD_ECDH_FRAGMENT, + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (circe_cbor_get_major_type (cd, &major_type), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_BYTESTR, + CIRCE_RESULT_INVALID_PACKET); + struct circe_cbor_buffer buf; + CIRCE_CORE_ASSERT (circe_cbor_extract_bytestr (cd, &buf), + CIRCE_RESULT_INVALID_PACKET); + // Size of a Curve25519 ECDH fragment + CIRCE_CORE_ASSERT (buf.buf_len == 32, CIRCE_RESULT_INVALID_PACKET); + circe_memory_copy (buf.buf_start, remote_ecdh_frag, 32); + + // MLKEM-768 public key + CIRCE_CORE_ASSERT (circe_cbor_get_major_type (cd, &major_type), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_UNSIGNED, + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (cd, &field), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (field == CIRCE_PACKET_FIELD_MLKEM_PUBKEY, + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (circe_cbor_get_major_type (cd, &major_type), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_BYTESTR, + CIRCE_RESULT_INVALID_PACKET); + struct circe_cbor_buffer mlkem_pubkey; + CIRCE_CORE_ASSERT (circe_cbor_extract_bytestr (cd, &mlkem_pubkey), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (mlkem_pubkey.buf_len == MLKEM_PUBLICKEYBYTES (768), + CIRCE_RESULT_INVALID_PACKET); + + size_t ack1_idx = CIRCE_FIRST_FREE_OUTGOING_PACKET_ENTRY (); + outgoing_packet_table_free_mask &= ~(1 << (ack1_idx - 1)); + if (ack1_idx == 0) { - return circe_memory_equal (a.ip_addr.ipv4, b.ip_addr.ipv4, - sizeof (a.ip_addr.ipv4)); + circe_memory_set (mlkem_pubkey.buf_start, 0x0, + MLKEM_PUBLICKEYBYTES (768)); + circe_memory_set (remote_ecdh_frag, 0x0, 32); + outgoing_packet_table_free_mask |= (1 << (ack1_idx - 1)); + return CIRCE_RESULT_PACKET_TABLE_FULL; } - else if (a.ip_type == CIRCE_ADDRESS_IPV6) + size_t ack2_idx = CIRCE_FIRST_FREE_OUTGOING_PACKET_ENTRY (); + outgoing_packet_table_free_mask &= ~(1 << (ack2_idx - 1)); + if (ack2_idx == 0) { - return circe_memory_equal (a.ip_addr.ipv6, b.ip_addr.ipv6, - sizeof (a.ip_addr.ipv6)); + circe_memory_set (mlkem_pubkey.buf_start, 0x0, + MLKEM_PUBLICKEYBYTES (768)); + circe_memory_set (remote_ecdh_frag, 0x0, 32); + outgoing_packet_table_free_mask |= (1 << (ack1_idx - 1)); + outgoing_packet_table_free_mask |= (1 << (ack2_idx - 1)); + return CIRCE_RESULT_PACKET_TABLE_FULL; } - // What? - return false; + size_t ack3_idx = CIRCE_FIRST_FREE_OUTGOING_PACKET_ENTRY (); + outgoing_packet_table_free_mask &= ~(1 << (ack3_idx - 1)); + if (ack3_idx == 0) + { + circe_memory_set (mlkem_pubkey.buf_start, 0x0, + MLKEM_PUBLICKEYBYTES (768)); + circe_memory_set (remote_ecdh_frag, 0x0, 32); + outgoing_packet_table_free_mask |= (1 << (ack1_idx - 1)); + outgoing_packet_table_free_mask |= (1 << (ack2_idx - 1)); + outgoing_packet_table_free_mask |= (1 << (ack3_idx - 1)); + return CIRCE_RESULT_PACKET_TABLE_FULL; + } + uint8_t ecdh_privkey[32]; + circe_platform_get_random_bytes (ecdh_privkey, sizeof (ecdh_privkey)); + const uint8_t u[32] + = { 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; + uint8_t my_ecdh_frag[32]; + tct_x25519 (ecdh_privkey, u, my_ecdh_frag); + + outgoing_packets[ack1_idx - 1].destination = remote; + outgoing_packets[ack2_idx - 1].destination = remote; + outgoing_packets[ack3_idx - 1].destination = remote; + struct circe_cbor_encoder ec = { + .buf_start = outgoing_packets[ack1_idx - 1].buffer, + .cursor = outgoing_packets[ack1_idx - 1].buffer, + .buf_len = CIRCE_MAX_UDP_PAYLOAD, + }; + enum circe_result err = CIRCE_RESULT_SUCCESS; + // Version, identity, type, ECDH fragment, MLKEM encapsulated, payload == 6 + // entries + CIRCE_CORE_ASSERT_WITH_CLEANUP (circe_cbor_emit_mapping_header (&ec, 6), + CIRCE_RESULT_INSUFFICIENT_MEM, err, + process_opentunnel_cleanup); + + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_VERSION), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_VERSION_V1), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_IDENTITY), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_bytestr ( + &ec, (struct circe_cbor_buffer){ .buf_start = my_identity, + .buf_len = CIRCE_IDENTITY_LEN }), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_TYPE), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_ACKOPEN1), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_ECDH_FRAGMENT), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_bytestr ( + &ec, (struct circe_cbor_buffer){ .buf_start = my_ecdh_frag, + .buf_len = sizeof (my_ecdh_frag) }), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + + uint8_t qk_shared[MLKEM_BYTES]; + uint8_t encapsulated[MLKEM_CIPHERTEXTBYTES (768)]; + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + crypto_kem_enc (encapsulated, qk_shared, mlkem_pubkey.buf_start), + CIRCE_RESULT_KEX_FAILED, err, process_opentunnel_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_MLKEM_ENCAPSULATED), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_bytestr ( + &ec, (struct circe_cbor_buffer){ .buf_start = encapsulated, + .buf_len = sizeof (encapsulated) }), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + outgoing_packets[ack1_idx - 1].length = ec.cursor - ec.buf_start; + + uint8_t ek_shared[32]; + tct_x25519 (ecdh_privkey, remote_ecdh_frag, ek_shared); + tct_turboshake128_init (remote_pt_entry->prk); + const uint8_t SALT[] = "circe/v1"; + tct_turboshake128_absorb (remote_pt_entry->prk, SALT, + (uint64_t)(sizeof (SALT) - 1), 0x0); + tct_turboshake128_absorb (remote_pt_entry->prk, ek_shared, + (uint64_t)sizeof (ek_shared), 0x1); + tct_turboshake128_absorb (remote_pt_entry->prk, qk_shared, + (uint64_t)sizeof (qk_shared), 0x2); + uint8_t prk_to_expand[TCT_TURBOSHAKE128_STATE_LEN]; + circe_memory_copy (remote_pt_entry->prk, prk_to_expand, + TCT_TURBOSHAKE128_STATE_LEN); + const uint8_t EXPANSION_DATA[] = "hs eurylochus"; + tct_turboshake128_absorb (prk_to_expand, EXPANSION_DATA, + (uint64_t)(sizeof (EXPANSION_DATA) - 1), 0x3); + uint8_t th2[64]; + tct_sha512 (cd->buf_start, cd->buf_len, th2); + tct_turboshake128_absorb (prk_to_expand, th2, sizeof (th2), 0x4); + uint8_t k_hs_e[32]; + tct_turboshake128_squeeze_destructive (prk_to_expand, k_hs_e, + sizeof (k_hs_e)); + + uint8_t payload_clear[CIRCE_IDENTITY_LEN + MLDSA_BYTES (44)]; + circe_memory_copy (my_identity, payload_clear, CIRCE_IDENTITY_LEN); + size_t _; + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + crypto_sign_signature (payload_clear + CIRCE_IDENTITY_LEN, &_, + payload_clear, CIRCE_IDENTITY_LEN, NULL, 0, + my_mldsa_privkey) + == 0, + CIRCE_RESULT_SIGNATURE_FAILED, err, process_opentunnel_cleanup); + uint8_t encrypted_buf[sizeof (payload_clear) + 16]; + uint8_t nonce[sizeof (uint64_t)] + = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; + + ec.buf_start = outgoing_packets[ack2_idx - 1].buffer; + ec.cursor = outgoing_packets[ack2_idx - 1].buffer; + // buf_len stays the same, which is good because it's const + + // Version, identity, type, ID payload 1 == 4 entries + CIRCE_CORE_ASSERT_WITH_CLEANUP (circe_cbor_emit_mapping_header (&ec, 4), + CIRCE_RESULT_INSUFFICIENT_MEM, err, + process_opentunnel_cleanup); + + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_VERSION), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_VERSION_V1), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_IDENTITY), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_bytestr ( + &ec, (struct circe_cbor_buffer){ .buf_start = my_identity, + .buf_len = CIRCE_IDENTITY_LEN }), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_TYPE), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_ACKOPEN2), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + + tct_aead_chacha20_poly1305_encrypt ( + ec.buf_start, ec.cursor - ec.buf_start, k_hs_e, nonce, payload_clear, + sizeof (payload_clear) / 2, encrypted_buf, + encrypted_buf + sizeof (payload_clear) / 2); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_ID_PAYLOAD1), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_bytestr ( + &ec, + (struct circe_cbor_buffer){ .buf_start = encrypted_buf, + .buf_len = sizeof (encrypted_buf) }), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + outgoing_packets[ack2_idx - 1].length = ec.cursor - ec.buf_start; + + ec.buf_start = outgoing_packets[ack3_idx - 1].buffer; + ec.cursor = outgoing_packets[ack3_idx - 1].buffer; + // buf_len once again stays the same + + // Version, identity, type, ID payload 1 == 4 entries + CIRCE_CORE_ASSERT_WITH_CLEANUP (circe_cbor_emit_mapping_header (&ec, 4), + CIRCE_RESULT_INSUFFICIENT_MEM, err, + process_opentunnel_cleanup); + + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_VERSION), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_VERSION_V1), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_IDENTITY), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_bytestr ( + &ec, (struct circe_cbor_buffer){ .buf_start = my_identity, + .buf_len = CIRCE_IDENTITY_LEN }), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_TYPE), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_ACKOPEN2), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + + nonce[0]++; + tct_aead_chacha20_poly1305_encrypt ( + ec.buf_start, ec.cursor - ec.buf_start, k_hs_e, nonce, + payload_clear + sizeof (payload_clear) / 2, sizeof (payload_clear) / 2, + encrypted_buf, encrypted_buf + sizeof (payload_clear) / 2); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_ID_PAYLOAD2), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_bytestr ( + &ec, + (struct circe_cbor_buffer){ .buf_start = encrypted_buf, + .buf_len = sizeof (encrypted_buf) }), + CIRCE_RESULT_INSUFFICIENT_MEM, err, process_opentunnel_cleanup); + outgoing_packets[ack3_idx - 1].length = ec.cursor - ec.buf_start; + +process_opentunnel_cleanup: + if (err != CIRCE_RESULT_SUCCESS) + { + outgoing_packet_table_free_mask |= (1 << (ack1_idx - 1)); + outgoing_packet_table_free_mask |= (1 << (ack2_idx - 1)); + outgoing_packet_table_free_mask |= (1 << (ack3_idx - 1)); + circe_memory_set ((uint8_t *)&outgoing_packets[ack1_idx - 1], 0x0, + sizeof (struct circe_outgoing_packet)); + circe_memory_set ((uint8_t *)&outgoing_packets[ack2_idx - 1], 0x0, + sizeof (struct circe_outgoing_packet)); + circe_memory_set ((uint8_t *)&outgoing_packets[ack3_idx - 1], 0x0, + sizeof (struct circe_outgoing_packet)); + } + circe_memory_set (ecdh_privkey, 0x0, 32); + circe_memory_set (remote_ecdh_frag, 0x0, 32); + circe_memory_set (mlkem_pubkey.buf_start, 0x0, MLKEM_PUBLICKEYBYTES (768)); + circe_memory_set (my_ecdh_frag, 0x0, 32); + circe_memory_set (qk_shared, 0x0, sizeof (qk_shared)); + circe_memory_set (ek_shared, 0x0, sizeof (ek_shared)); + circe_memory_set (encapsulated, 0x0, sizeof (encapsulated)); + return err; } +static enum circe_result +dispatch_identity_packets (struct circe_location remote, + struct circe_peer *remote_pt_entry, uint8_t th3[64]) +{ + enum circe_result err = CIRCE_RESULT_SUCCESS; + + size_t id1_idx = CIRCE_FIRST_FREE_OUTGOING_PACKET_ENTRY (); + outgoing_packet_table_free_mask &= ~(1 << (id1_idx - 1)); + if (id1_idx == 0) + { + outgoing_packet_table_free_mask |= (1 << (id1_idx - 1)); + return CIRCE_RESULT_PACKET_TABLE_FULL; + } + size_t id2_idx = CIRCE_FIRST_FREE_OUTGOING_PACKET_ENTRY (); + outgoing_packet_table_free_mask &= ~(1 << (id2_idx - 1)); + if (id2_idx == 0) + { + outgoing_packet_table_free_mask |= (1 << (id1_idx - 1)); + outgoing_packet_table_free_mask |= (1 << (id2_idx - 1)); + return CIRCE_RESULT_PACKET_TABLE_FULL; + } + + uint8_t k_hs_p[32]; + uint8_t EXPANSION_DATA[] = "hs polites"; + uint8_t prk_to_expand[TCT_TURBOSHAKE128_STATE_LEN]; + circe_memory_copy (remote_pt_entry->prk, prk_to_expand, + TCT_TURBOSHAKE128_STATE_LEN); + 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, k_hs_p, + sizeof (k_hs_p)); + circe_memory_set (prk_to_expand, 0x0, TCT_TURBOSHAKE128_STATE_LEN); + + struct circe_cbor_encoder ec = { + .buf_len = CIRCE_MAX_UDP_PAYLOAD, + }; + uint8_t id_payload[CIRCE_IDENTITY_LEN + MLDSA_BYTES (44)]; + circe_memory_copy (my_identity, id_payload, CIRCE_IDENTITY_LEN); + size_t _; + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + crypto_sign_signature (id_payload + CIRCE_IDENTITY_LEN, &_, id_payload, + CIRCE_IDENTITY_LEN, NULL, 0x0, my_mldsa_privkey) + == 0, + CIRCE_RESULT_SIGNATURE_FAILED, err, dispatch_identity_packets_cleanup); + for (size_t i = 0; i < 2; ++i) + { + ec.buf_start = ec.cursor = (i == 0) + ? outgoing_packets[id1_idx - 1].buffer + : outgoing_packets[id2_idx - 1].buffer; + // Version, identity, type, ID payload 1/2 == 4 entries + CIRCE_CORE_ASSERT_WITH_CLEANUP (circe_cbor_emit_mapping_header (&ec, 4), + CIRCE_RESULT_INSUFFICIENT_MEM, err, + dispatch_identity_packets_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_VERSION), + CIRCE_RESULT_INSUFFICIENT_MEM, err, + dispatch_identity_packets_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_VERSION_V1), + CIRCE_RESULT_INSUFFICIENT_MEM, err, + dispatch_identity_packets_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_IDENTITY), + CIRCE_RESULT_INSUFFICIENT_MEM, err, + dispatch_identity_packets_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_bytestr ( + &ec, (struct circe_cbor_buffer){ .buf_len = CIRCE_IDENTITY_LEN, + .buf_start = my_identity }), + CIRCE_RESULT_INSUFFICIENT_MEM, err, + dispatch_identity_packets_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_TYPE), + CIRCE_RESULT_INSUFFICIENT_MEM, err, + dispatch_identity_packets_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, (i == 0) ? CIRCE_PACKET_IDENTIFY1 + : CIRCE_PACKET_IDENTIFY2), + CIRCE_RESULT_INSUFFICIENT_MEM, err, + dispatch_identity_packets_cleanup); + uint8_t *old_cursor = ec.cursor; + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, (i == 0) + ? CIRCE_PACKET_FIELD_ID_PAYLOAD1 + : CIRCE_PACKET_FIELD_ID_PAYLOAD2), + CIRCE_RESULT_INSUFFICIENT_MEM, err, + dispatch_identity_packets_cleanup); + struct circe_cbor_buffer out_buf + = { .buf_len = (CIRCE_IDENTITY_LEN + MLDSA_BYTES (44)) / 2 + 16, + .buf_start = remote_pt_entry->id_payload_halves[i] }; + uint8_t nonce[sizeof (uint64_t)] + = { i, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; + tct_aead_chacha20_poly1305_encrypt ( + ec.buf_start, old_cursor - ec.buf_start, k_hs_p, nonce, + id_payload + i * (CIRCE_IDENTITY_LEN + MLDSA_BYTES (44)) / 2, + (CIRCE_IDENTITY_LEN + MLDSA_BYTES (44)) / 2, out_buf.buf_start, + out_buf.buf_start + (CIRCE_IDENTITY_LEN + MLDSA_BYTES (44)) / 2); + CIRCE_CORE_ASSERT_WITH_CLEANUP (circe_cbor_emit_bytestr (&ec, out_buf), + CIRCE_RESULT_INSUFFICIENT_MEM, err, + dispatch_identity_packets_cleanup); + outgoing_packets[(i == 0) ? (id1_idx - 1) : (id2_idx - 1)].destination + = remote; + outgoing_packets[(i == 0) ? (id1_idx - 1) : (id2_idx - 1)].length + = ec.cursor - ec.buf_start; + } +dispatch_identity_packets_cleanup: + if (err != CIRCE_RESULT_SUCCESS) + { + outgoing_packet_table_free_mask |= (1 << (id1_idx - 1)); + outgoing_packet_table_free_mask |= (1 << (id2_idx - 1)); + circe_memory_set ((uint8_t *)&outgoing_packets[id1_idx - 1], 0x0, + sizeof (struct circe_outgoing_packet)); + circe_memory_set ((uint8_t *)&outgoing_packets[id2_idx - 1], 0x0, + sizeof (struct circe_outgoing_packet)); + } + circe_memory_set (id_payload, 0x0, sizeof (id_payload)); + return err; +} + +static enum circe_result +process_openack (struct circe_cbor_decoder *cd, struct circe_location remote, + struct circe_peer *remote_pt_entry, size_t stage) +{ + enum circe_result err = CIRCE_RESULT_SUCCESS; + enum circe_cbor_major_type major_type; + struct circe_cbor_buffer buf; + uint64_t field; + size_t aad_len + = cd->cursor - cd->buf_start; // Will be the same for both stage 2 and 3 + + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + !(remote_pt_entry->packets_received_mask & (1 << (stage - 1))), + CIRCE_RESULT_DUPLICATE_HS_PACKET, err, process_openack_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + remote_pt_entry->next_handshake_stage == CIRCE_HANDSHAKE_STAGE_ACK, + CIRCE_RESULT_HS_OUT_OF_ORDER, err, process_openack_cleanup); + + uint8_t remote_ecdh_frag[32]; + struct circe_cbor_buffer mlkem_encapsulated; + uint8_t qk_shared[32]; + uint8_t ek_shared[32]; + + switch (stage) + { + case 1: + // ECDH fragment + CIRCE_CORE_ASSERT (circe_cbor_get_major_type (cd, &major_type), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_UNSIGNED, + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (cd, &field), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (field == CIRCE_PACKET_FIELD_ECDH_FRAGMENT, + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (circe_cbor_get_major_type (cd, &major_type), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_BYTESTR, + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (circe_cbor_extract_bytestr (cd, &buf), + CIRCE_RESULT_INVALID_PACKET); + // Size of a Curve25519 ECDH fragment + CIRCE_CORE_ASSERT (buf.buf_len == 32, CIRCE_RESULT_INVALID_PACKET); + circe_memory_copy (buf.buf_start, remote_ecdh_frag, 32); + + // MLKEM-768 public key + CIRCE_CORE_ASSERT (circe_cbor_get_major_type (cd, &major_type), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_UNSIGNED, + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (cd, &field), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (field == CIRCE_PACKET_FIELD_MLKEM_ENCAPSULATED, + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (circe_cbor_get_major_type (cd, &major_type), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_BYTESTR, + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (circe_cbor_extract_bytestr (cd, &mlkem_encapsulated), + CIRCE_RESULT_INVALID_PACKET); + CIRCE_CORE_ASSERT (mlkem_encapsulated.buf_len + == MLKEM_CIPHERTEXTBYTES (768), + CIRCE_RESULT_INVALID_PACKET); + + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + crypto_kem_dec (qk_shared, mlkem_encapsulated.buf_start, + remote_pt_entry->my_mlkem_privkey) + == 0, + CIRCE_RESULT_KEX_FAILED, err, process_openack_cleanup); + tct_x25519 (remote_pt_entry->my_ecdh_privkey, remote_ecdh_frag, + ek_shared); + tct_turboshake128_init (remote_pt_entry->prk); + const uint8_t SALT[] = "circe/v1"; + tct_turboshake128_absorb (remote_pt_entry->prk, SALT, + (uint64_t)(sizeof (SALT) - 1), 0x0); + tct_turboshake128_absorb (remote_pt_entry->prk, ek_shared, + (uint64_t)sizeof (ek_shared), 0x1); + tct_turboshake128_absorb (remote_pt_entry->prk, qk_shared, + (uint64_t)sizeof (qk_shared), 0x2); + break; + case 2: + case 3: + circe_memory_copy (cd->buf_start, + remote_pt_entry->id_payload_aads[stage - 2], + cd->cursor - cd->buf_start); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_get_major_type (cd, &major_type), + CIRCE_RESULT_INVALID_PACKET, err, process_openack_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP (circe_cbor_extract_unsigned (cd, &field), + CIRCE_RESULT_INVALID_PACKET, err, + process_openack_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + (stage == 2) ? (field == CIRCE_PACKET_FIELD_ID_PAYLOAD1) + : (field == CIRCE_PACKET_FIELD_ID_PAYLOAD2), + CIRCE_RESULT_INVALID_PACKET, err, process_openack_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_get_major_type (cd, &major_type), + CIRCE_RESULT_INVALID_PACKET, err, process_openack_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP (major_type == CIRCE_CBOR_MAJOR_BYTESTR, + CIRCE_RESULT_INVALID_PACKET, err, + process_openack_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP (circe_cbor_extract_bytestr (cd, &buf), + CIRCE_RESULT_INVALID_PACKET, err, + process_openack_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + buf.buf_len == (CIRCE_IDENTITY_LEN + MLDSA_BYTES (44)) / 2 + 16, + CIRCE_RESULT_INVALID_PACKET, err, process_openack_cleanup); + circe_memory_copy (buf.buf_start, + remote_pt_entry->id_payload_halves[stage - 1], + buf.buf_len); + break; + default: + CIRCE_CORE_ASSERT_WITH_CLEANUP (false, CIRCE_RESULT_INTERNAL_ERROR, err, + process_openack_cleanup); + break; + } +process_openack_cleanup: + circe_memory_set (remote_ecdh_frag, 0x0, sizeof (remote_ecdh_frag)); + circe_memory_set (ek_shared, 0x0, sizeof (ek_shared)); + circe_memory_set (qk_shared, 0x0, sizeof (qk_shared)); + if (err == CIRCE_RESULT_SUCCESS) + { + remote_pt_entry->packets_received_mask |= 1 << (stage - 1); + if (remote_pt_entry->packets_received_mask == 0b111) + { + remote_pt_entry->packets_received_mask = 0x0; + remote_pt_entry->next_handshake_stage = CIRCE_HANDSHAKE_STAGE_ID; + uint8_t prk_to_expand[TCT_TURBOSHAKE128_STATE_LEN]; + circe_memory_copy (remote_pt_entry->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, remote_pt_entry->my_th2, + sizeof (remote_pt_entry->my_th2), 0x4); + uint8_t k_hs_e[32]; + tct_turboshake128_squeeze_destructive (prk_to_expand, k_hs_e, + sizeof (k_hs_e)); + circe_memory_set (prk_to_expand, 0x0, TCT_TURBOSHAKE128_STATE_LEN); + uint8_t nonce[sizeof (uint64_t)] + = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; + uint8_t id_payload[CIRCE_IDENTITY_LEN + MLDSA_BYTES (44)]; + if (!tct_aead_chacha20_poly1305_decrypt_and_verify ( + remote_pt_entry->id_payload_aads[0], aad_len, k_hs_e, nonce, + remote_pt_entry->id_payload_halves[0], + (CIRCE_IDENTITY_LEN + MLDSA_BYTES (44)) / 2, id_payload)) + { + circe_memory_set (id_payload, 0x0, sizeof (id_payload)); + return CIRCE_RESULT_INVALID_MAC; + } + nonce[0]++; + if (!tct_aead_chacha20_poly1305_decrypt_and_verify ( + remote_pt_entry->id_payload_aads[1], aad_len, k_hs_e, nonce, + remote_pt_entry->id_payload_halves[1], + (CIRCE_IDENTITY_LEN + MLDSA_BYTES (44)) / 2, + id_payload + (CIRCE_IDENTITY_LEN + MLDSA_BYTES (44)) / 2)) + { + circe_memory_set (id_payload, 0x0, sizeof (id_payload)); + return CIRCE_RESULT_INVALID_MAC; + } + if (crypto_sign_verify (id_payload + CIRCE_IDENTITY_LEN, + MLDSA_BYTES (44), id_payload, + CIRCE_IDENTITY_LEN, NULL, 0, + remote_pt_entry->mldsa_pubkey) + != 0) + { + circe_memory_set (id_payload, 0x0, sizeof (id_payload)); + return CIRCE_RESULT_INVALID_SIGNATURE; + } + circe_memory_set (id_payload, 0x0, sizeof (id_payload)); + uint8_t th3[64]; + tct_sha512 (id_payload, sizeof (id_payload), th3); + return dispatch_identity_packets (remote, remote_pt_entry, th3); + } + } + return err; +} + +static enum circe_result +process_identify (struct circe_cbor_decoder *cd, struct circe_location remote, + struct circe_peer *remote_pt_entry, size_t stage) +{ +} + +static enum circe_result +dispatch_opentunnel (struct circe_location remote, + struct circe_peer *remote_pt_entry) +{ +} + +/* END HANDSHAKE PROCESSING FUNCTIONS */ + +/* PUBLIC API */ + enum circe_result -circe_handle_packet (const struct circe_location remote, const uint8_t *in, - size_t len, uint64_t now_ms, uint8_t *out, - size_t *out_len, struct circe_event *events, - size_t *n_events) +circe_initialize_core (uint8_t identity[CIRCE_IDENTITY_LEN], + uint8_t mldsa_privkey[MLDSA_SECRETKEYBYTES (44)], + uint64_t now_ms) +{ + last_tick_ms = now_ms; + circe_memory_copy (identity, my_identity, CIRCE_IDENTITY_LEN); + circe_memory_copy (mldsa_privkey, my_mldsa_privkey, + MLDSA_SECRETKEYBYTES (44)); + peer_handshake_pending_mask = 0x0ull; + peer_table_free_mask = ~0x0ull; + outgoing_packet_table_free_mask = ~0x0ull; + circe_memory_set ((uint8_t *)outgoing_packets, 0x0, + sizeof (outgoing_packets)); + circe_memory_set ((uint8_t *)peer_table, 0x0, sizeof (peer_table)); + return CIRCE_RESULT_SUCCESS; +} + +enum circe_result +circe_handle_packet (const struct circe_location remote, + 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) { *n_events = 0; - *out_len = 0; struct circe_cbor_decoder cd = { .buf_start = in, .buf_len = len,@@ -183,8 +925,12 @@ CIRCE_RESULT_INVALID_PACKET);
CIRCE_CORE_ASSERT (remote_id_buf.buf_len == CIRCE_IDENTITY_LEN, CIRCE_RESULT_INVALID_PACKET); struct circe_peer *remote_pt_entry; - bool peer_in_ram + enum circe_result peer_in_ram = peer_table_lookup (remote_id_buf.buf_start, &remote_pt_entry); + if (peer_in_ram == CIRCE_RESULT_UNKNOWN_REMOTE) + { + return CIRCE_RESULT_UNKNOWN_REMOTE; + } // Packet type CIRCE_CORE_ASSERT (circe_cbor_get_major_type (&cd, &major_type),@@ -203,8 +949,9 @@ uint64_t packet_type;
CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (&cd, &packet_type), CIRCE_RESULT_INVALID_PACKET); - if (packet_type != CIRCE_PACKET_ROUTE_CHALLENGE - && packet_type != CIRCE_PACKET_OPENTUNNEL && peer_in_ram) + if ((packet_type != CIRCE_PACKET_ROUTE_CHALLENGE1) + && (packet_type != CIRCE_PACKET_OPENTUNNEL) + && (peer_in_ram == CIRCE_RESULT_SUCCESS)) { // Verify that this came from our known remote location bool good_location;@@ -236,18 +983,229 @@ return CIRCE_RESULT_SUCCESS;
} } + enum circe_result err; switch (packet_type) { case CIRCE_PACKET_OPENTUNNEL: - case CIRCE_PACKET_ACKOPEN: - case CIRCE_PACKET_IDENTIFY: + if (peer_in_ram == CIRCE_RESULT_SUCCESS) + { + // We already have an open session, something is wrong + events[*n_events] = (struct circe_event){ + .event_type = CIRCE_EVENT_REJECTED_REMOTE, + }; + (*n_events)++; + return CIRCE_RESULT_SUCCESS; + } + else if (peer_in_ram == CIRCE_RESULT_HANDSHAKE_PENDING) + { + // This is the expected path + err = process_opentunnel (&cd, remote, remote_pt_entry); + if (err != CIRCE_RESULT_SUCCESS) + { + close_without_saying_goodbye (remote_pt_entry); + events[*n_events] = (struct circe_event){ + .event_type = CIRCE_EVENT_REJECTED_REMOTE, + }; + (*n_events)++; + return err; + } + events[*n_events] = (struct circe_event){ + .event_type = CIRCE_EVENT_NEW_REMOTE, + }; + (*n_events)++; + return CIRCE_RESULT_SUCCESS; + } + else + { + events[*n_events] = (struct circe_event){ + .event_type = CIRCE_EVENT_REJECTED_REMOTE, + }; + (*n_events)++; + return peer_in_ram; + } + case CIRCE_PACKET_ACKOPEN1: + case CIRCE_PACKET_ACKOPEN2: + case CIRCE_PACKET_ACKOPEN3: + err = process_openack (&cd, remote, remote_pt_entry, + (packet_type - CIRCE_PACKET_ACKOPEN1) + 1); + if (err != CIRCE_RESULT_SUCCESS) + { + close_without_saying_goodbye (remote_pt_entry); + events[*n_events] = (struct circe_event){ + .event_type = CIRCE_EVENT_REJECTED_REMOTE, + }; + (*n_events)++; + return err; + } + return CIRCE_RESULT_SUCCESS; case CIRCE_PACKET_DATA: - (*n_events)++; - events[0] = (struct circe_event){ + events[*n_events] = (struct circe_event){ .event_type = CIRCE_EVENT_DATA_RECEIVED, }; + (*n_events)++; return unseal_payload (remote_pt_entry, &cd, out, out_len); - case CIRCE_PACKET_COMMAND: + case CIRCE_PACKET_HEARTBEAT: + case CIRCE_PACKET_CLOSETUNNEL1: + case CIRCE_PACKET_CLOSETUNNEL2: + case CIRCE_PACKET_ROUTE_CHALLENGE1: + case CIRCE_PACKET_ROUTE_CHALLENGE2: + default: + return CIRCE_RESULT_INVALID_PACKET; } - return CIRCE_RESULT_INVALID_PACKET; -}+} + +enum circe_result +circe_send_outgoing_packet (const uint8_t remote_identity[CIRCE_IDENTITY_LEN], + const uint8_t *in, size_t len, uint64_t now_ms, + struct circe_event *events, size_t *n_events) +{ + if (len > CIRCE_MTU) + { + return CIRCE_RESULT_OVER_MTU; + } + struct circe_peer *remote_pt_entry; + enum circe_result peer_in_ram + = peer_table_lookup (remote_identity, &remote_pt_entry); + if (peer_in_ram != CIRCE_RESULT_SUCCESS) + { + // Might return if e.g. unknown peer or if we need to finish the + // handshake + return peer_in_ram; + } + + size_t next_entry_idx = CIRCE_FIRST_FREE_OUTGOING_PACKET_ENTRY (); + if (next_entry_idx == 0) + { + // No free entries + return CIRCE_RESULT_PACKET_TABLE_FULL; + } + outgoing_packet_table_free_mask &= ~(1ull << (next_entry_idx - 1)); + + struct circe_cbor_encoder ec = { + .buf_start = outgoing_packets[next_entry_idx].buffer, + .buf_len = sizeof (outgoing_packets[next_entry_idx].buffer), + .cursor = outgoing_packets[next_entry_idx].buffer, + }; + + remote_pt_entry->last_seqn_tx++; + + enum circe_result err = CIRCE_RESULT_SUCCESS; + + // Version, identity, type, sequence number, payload == 5 pairs + CIRCE_CORE_ASSERT_WITH_CLEANUP (circe_cbor_emit_mapping_header (&ec, 5), + CIRCE_RESULT_INSUFFICIENT_MEM, err, + circe_send_outgoing_packet_cleanup); + + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_VERSION), + CIRCE_RESULT_INSUFFICIENT_MEM, err, circe_send_outgoing_packet_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_VERSION_V1), + CIRCE_RESULT_INSUFFICIENT_MEM, err, circe_send_outgoing_packet_cleanup); + + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_IDENTITY), + CIRCE_RESULT_INSUFFICIENT_MEM, err, circe_send_outgoing_packet_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_bytestr ( + &ec, (struct circe_cbor_buffer){ .buf_len = CIRCE_IDENTITY_LEN, + .buf_start = my_identity }), + CIRCE_RESULT_INSUFFICIENT_MEM, err, circe_send_outgoing_packet_cleanup); + + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_TYPE), + CIRCE_RESULT_INSUFFICIENT_MEM, err, circe_send_outgoing_packet_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_DATA), + CIRCE_RESULT_INSUFFICIENT_MEM, err, circe_send_outgoing_packet_cleanup); + + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_SEQ_NUMBER), + CIRCE_RESULT_INSUFFICIENT_MEM, err, circe_send_outgoing_packet_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, remote_pt_entry->last_seqn_tx), + CIRCE_RESULT_INSUFFICIENT_MEM, err, circe_send_outgoing_packet_cleanup); + + uint8_t sealed[CIRCE_MTU + CIRCE_P1305_MAC_LEN]; // Encrypted with MAC + uint8_t nonce[sizeof (uint64_t)]; + for (size_t i = 0; i < sizeof (uint64_t); ++i) + { + nonce[i] = 0xff & (remote_pt_entry->last_seqn_tx >> (i * 8)); + } + tct_aead_chacha20_poly1305_encrypt (ec.buf_start, ec.cursor - ec.buf_start, + remote_pt_entry->tx_key, nonce, in, len, + sealed, sealed + len); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_unsigned (&ec, CIRCE_PACKET_FIELD_DATA_PAYLOAD), + CIRCE_RESULT_INSUFFICIENT_MEM, err, circe_send_outgoing_packet_cleanup); + CIRCE_CORE_ASSERT_WITH_CLEANUP ( + circe_cbor_emit_bytestr ( + &ec, + (struct circe_cbor_buffer){ .buf_len = len + CIRCE_P1305_MAC_LEN, + .buf_start = sealed }), + CIRCE_RESULT_INSUFFICIENT_MEM, err, circe_send_outgoing_packet_cleanup); + + enum circe_result relay_server_in_ram; + struct circe_peer *const relay_server_pt_entry = NULL; + switch (remote_pt_entry->current_route) + { + case CIRCE_ROUTE_LAN0: + outgoing_packets[next_entry_idx - 1].destination + = remote_pt_entry->lan0_loc; + break; + case CIRCE_ROUTE_LAN1: + outgoing_packets[next_entry_idx - 1].destination + = remote_pt_entry->lan1_loc; + break; + case CIRCE_ROUTE_WAN: + outgoing_packets[next_entry_idx - 1].destination + = remote_pt_entry->wan_loc; + break; + case CIRCE_ROUTE_RELAY: + relay_server_in_ram = peer_table_lookup ( + remote_pt_entry->relay_server_identity, + (struct circe_peer * *const)&relay_server_pt_entry); + CIRCE_CORE_ASSERT_WITH_CLEANUP (relay_server_in_ram + == CIRCE_RESULT_SUCCESS, + CIRCE_RESULT_RELAY_LOOKUP_FAILED, err, + circe_send_outgoing_packet_cleanup); + outgoing_packets[next_entry_idx - 1].destination + = relay_server_pt_entry->wan_loc; + break; + } + + outgoing_packets[next_entry_idx - 1].length = ec.cursor - ec.buf_start; + +circe_send_outgoing_packet_cleanup: + if (err != CIRCE_RESULT_SUCCESS) + { + remote_pt_entry->last_seqn_tx--; + outgoing_packet_table_free_mask |= (1ull << (next_entry_idx - 1)); + } + return err; +} + +enum circe_result +circe_tick (uint64_t now_ms, struct circe_outgoing_packet *out, + bool *new_packet_out) +{ + if (now_ms < last_tick_ms) + { + return CIRCE_RESULT_INVALID_TIMESTAMP; + } + last_tick_ms = now_ms; + size_t packet_idx = CIRCE_FIRST_TAKEN_OUTGOING_PACKET_ENTRY (); + if (packet_idx == 0) + { + *new_packet_out = false; + return CIRCE_RESULT_SUCCESS; + } + *new_packet_out = true; + *out = outgoing_packets[packet_idx - 1]; + circe_memory_set ((uint8_t *)(&outgoing_packets[packet_idx - 1]), 0x0, + CIRCE_MAX_UDP_PAYLOAD); + outgoing_packet_table_free_mask |= (1ull << (packet_idx - 1)); + return CIRCE_RESULT_SUCCESS; +} + +/* END PUBLIC API */
@@ -1,9 +1,14 @@
#ifndef CIRCE_CORE_H #define CIRCE_CORE_H +#include "mldsa_native.h" +#include "packet.h" #include "peer_table.h" #include <stddef.h> #include <stdint.h> + +#define CIRCE_MAX_OUTGOING_PACKETS 64 +#define CIRCE_HEARTBEAT_PERIOD_MS 100 enum circe_result {@@ -12,6 +17,18 @@ CIRCE_RESULT_INSUFFICIENT_MEM,
CIRCE_RESULT_INVALID_PACKET, CIRCE_RESULT_INVALID_MAC, CIRCE_RESULT_INTERNAL_ERROR, + CIRCE_RESULT_UNKNOWN_REMOTE, + CIRCE_RESULT_OVER_MTU, + CIRCE_RESULT_HANDSHAKE_PENDING, + CIRCE_RESULT_PEER_TABLE_FULL, + CIRCE_RESULT_RELAY_LOOKUP_FAILED, + CIRCE_RESULT_PACKET_TABLE_FULL, + CIRCE_RESULT_KEX_FAILED, + CIRCE_RESULT_SIGNATURE_FAILED, + CIRCE_RESULT_INVALID_SIGNATURE, + CIRCE_RESULT_INVALID_TIMESTAMP, + CIRCE_RESULT_DUPLICATE_HS_PACKET, + CIRCE_RESULT_HS_OUT_OF_ORDER, }; enum circe_version@@ -36,11 +53,17 @@ CIRCE_DEADLINE_INDEFINITE,
CIRCE_DEADLINE_AS_INDICATED, }; +/// Initialize Circe core's global state +enum circe_result +circe_initialize_core (uint8_t identity[CIRCE_IDENTITY_LEN], + uint8_t mldsa_privkey[MLDSA_SECRETKEYBYTES (44)], + uint64_t now_ms); + /// Process a new incoming raw Circe packet. enum circe_result circe_handle_packet (const struct circe_location remote, - const uint8_t *in, size_t len, - uint64_t now_ms, uint8_t *out, - size_t *out_len, + 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);@@ -58,9 +81,11 @@ /// any other core API function is called to stay up-to-date.
enum circe_deadline circe_next_deadline (uint64_t *ms_timestamp); /// Iterate the Circe core logic. Assumes `now_ms` increases monotonically with -/// time. If `out_len` is nonzero (do not pass in NULL), then `out` contains a -/// new raw Circe packet to be sent out to the location `destination`. -enum circe_result circe_tick (uint64_t now_ms, uint8_t *out, size_t *out_len, - struct circe_location *destination); +/// time. If `new_packet_out` is true (do not pass in NULL for `out`), then +/// `out` contains a new raw Circe packet to be sent out to the location +/// `out.destination`. +enum circe_result circe_tick (uint64_t now_ms, + struct circe_outgoing_packet *out, + bool *new_packet_out); #endif
@@ -1,14 +1,29 @@
#ifndef CIRCE_CORE_PACKET_H #define CIRCE_CORE_PACKET_H +#define CIRCE_MTU 1280 +#define CIRCE_MAX_UDP_PAYLOAD 1472 + +#include "peer_table.h" +#include <stddef.h> +#include <stdint.h> + enum circe_packet_type { + // Some packets need to be splintered because of UDP MTU + CIRCE_PACKET_OPENTUNNEL = 0x0, - CIRCE_PACKET_ACKOPEN, - CIRCE_PACKET_IDENTIFY, + CIRCE_PACKET_ACKOPEN1, + CIRCE_PACKET_ACKOPEN2, + CIRCE_PACKET_ACKOPEN3, + CIRCE_PACKET_IDENTIFY1, + CIRCE_PACKET_IDENTIFY2, CIRCE_PACKET_DATA, - CIRCE_PACKET_COMMAND, - CIRCE_PACKET_ROUTE_CHALLENGE, + CIRCE_PACKET_HEARTBEAT, + CIRCE_PACKET_CLOSETUNNEL1, + CIRCE_PACKET_CLOSETUNNEL2, + CIRCE_PACKET_ROUTE_CHALLENGE1, + CIRCE_PACKET_ROUTE_CHALLENGE2, }; enum circe_packet_field@@ -20,8 +35,17 @@ CIRCE_PACKET_FIELD_ECDH_FRAGMENT,
CIRCE_PACKET_FIELD_MLKEM_PUBKEY, CIRCE_PACKET_FIELD_MLKEM_ENCAPSULATED, CIRCE_PACKET_FIELD_SEQ_NUMBER, - CIRCE_PACKET_FIELD_PAYLOAD, + CIRCE_PACKET_FIELD_DATA_PAYLOAD, + CIRCE_PACKET_FIELD_ID_PAYLOAD1, + CIRCE_PACKET_FIELD_ID_PAYLOAD2, CIRCE_PACKET_FIELD_OVERMAX, +}; + +struct circe_outgoing_packet +{ + struct circe_location destination; + uint8_t buffer[CIRCE_MAX_UDP_PAYLOAD]; + size_t length; }; #endif
@@ -1,6 +1,9 @@
#ifndef CIRCE_CORE_PEER_TABLE_H #define CIRCE_CORE_PEER_TABLE_H +#include "kangarootwelve128.h" +#include "mldsa_native.h" +#include "mlkem_native.h" #include <stdbool.h> #include <stdint.h>@@ -22,6 +25,14 @@
#define CIRCE_PEER_TABLE_LEN 64 #define CIRCE_IDENTITY_LEN 64 // SHA-512 hash of hostname || island name +enum circe_handshake_stage +{ + CIRCE_HANDSHAKE_STAGE_OPEN, + CIRCE_HANDSHAKE_STAGE_ACK, + CIRCE_HANDSHAKE_STAGE_ID, + CIRCE_HANDSHAKE_STAGE_DONE, +}; + struct circe_peer { bool double_lan; // lan1 is not valid if false@@ -45,6 +56,25 @@ uint8_t mldsa_pubkey[1312]; // ML-DSA-44
uint8_t rx_key[32]; uint8_t tx_key[32]; + + uint64_t next_heartbeat_ms; + uint64_t next_kex_ms; + + uint64_t last_seqn_tx; + uint64_t last_seqn_rx; + + enum circe_handshake_stage next_handshake_stage; + uint64_t packets_received_mask; + + // ZERO ALL OF THESE AFTER KEY EXCHANGE IS COMPLETE + uint8_t my_th2[64]; // SHA-512 + 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]; + uint8_t id_payload_halves[2] + [(CIRCE_IDENTITY_LEN + MLDSA_BYTES (44)) / 2 + 16]; + uint8_t id_payload_aads[2][16]; // Yeah that should be enough }; #endif
@@ -7,9 +7,7 @@ #include <stdint.h>
void circe_platform_get_random_bytes (uint8_t *out, uint32_t len); -void circe_platform_store_peer (const struct circe_peer *peer); - -bool circe_platform_recall_peer (uint8_t identity[CIRCE_IDENTITY_LEN], +bool circe_platform_recall_peer (const uint8_t identity[CIRCE_IDENTITY_LEN], struct circe_peer *out); #endif