#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 "sha2.h" #include "x25519.h" #include #include #include #define CIRCE_P1305_MAC_LEN 16 #define CIRCE_CORE_ASSERT(x, err) \ do \ { \ if (!(x)) \ { \ return err; \ } \ } \ while (false) #define CIRCE_CORE_ASSERT_WITH_CLEANUP(x, err, errvar, label) \ do \ { \ if (!(x)) \ { \ (errvar) = (err); \ goto label; \ } \ } \ while (false) /* GENERAL HELPER FUNCTIONS */ #if defined(__GNUC__) || defined(__clang__) #define CIRCE_FIRST_FREE_OUTGOING_PACKET_ENTRY(ctx) \ __builtin_ffsll ((ctx)->outgoing_packet_table_free_mask) #define CIRCE_FIRST_TAKEN_OUTGOING_PACKET_ENTRY(ctx) \ __builtin_ffsll (~((ctx)->outgoing_packet_table_free_mask)) #define CIRCE_FIRST_FREE_PEER_TABLE_ENTRY(ctx) \ __builtin_ffsll ((ctx)->peer_table_free_mask) #else #error Circe can currently only be built with GCC or Clang. #endif static bool 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 (struct circe_context *ctx, const uint8_t identity[CIRCE_IDENTITY_LEN], struct circe_peer **const out) { 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 ( identity, ctx->peer_table[(i + start_idx) % CIRCE_PEER_TABLE_LEN].identity, CIRCE_IDENTITY_LEN)) { *out = &(ctx->peer_table[(i + start_idx) % CIRCE_PEER_TABLE_LEN]); return CIRCE_RESULT_SUCCESS; } } size_t out_idx = CIRCE_FIRST_FREE_PEER_TABLE_ENTRY (ctx); if (out_idx == 0) { return CIRCE_RESULT_PEER_TABLE_FULL; } if (!ctx->recall_peer_callback (ctx->ctx_handle, identity, &ctx->peer_table[out_idx - 1])) { return CIRCE_RESULT_UNKNOWN_REMOTE; } ctx->peer_table_free_mask &= ~(1ull << (out_idx - 1)); ctx->peer_handshake_pending_mask |= (1ull << (out_idx - 1)); *out = &(ctx->peer_table[out_idx - 1]); (*out)->next_handshake_stage = CIRCE_HANDSHAKE_STAGE_OPEN; return CIRCE_RESULT_HANDSHAKE_PENDING; } static enum circe_result unseal_payload (struct circe_peer *peer, struct circe_cbor_decoder *cd, uint8_t *out, size_t *out_len) { enum circe_cbor_major_type major_type; uint64_t field; uint64_t sequence_number; 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_SEQ_NUMBER, 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_UNSIGNED, 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_DATA_PAYLOAD, 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 sealed; CIRCE_CORE_ASSERT (circe_cbor_extract_bytestr (cd, &sealed), CIRCE_RESULT_INVALID_PACKET); // Make sure we got the full payload CIRCE_CORE_ASSERT (sealed.buf_len <= cd->buf_len - (cd->cursor - cd->buf_start), CIRCE_RESULT_INVALID_PACKET); uint8_t nonce[sizeof (uint64_t)]; for (unsigned int i = 0; i < sizeof (uint64_t); ++i) { nonce[i] = (sequence_number >> (i * 8)) & 0xff; } if (!tct_aead_chacha20_poly1305_decrypt_and_verify ( 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 - CIRCE_P1305_MAC_LEN; // Don't include the MAC's length return CIRCE_RESULT_SUCCESS; } static void close_without_saying_goodbye (struct circe_context *ctx, struct circe_peer *remote_pt_entry) { 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) { if (circe_memory_equal ( remote_pt_entry->identity, ctx->peer_table[(i + start_idx) % CIRCE_PEER_TABLE_LEN].identity, CIRCE_IDENTITY_LEN)) { peer_idx = start_idx; break; } } 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)); } /* END GENERAL HELPER FUNCTIONS */ /* HANDSHAKE PROCESSING FUNCTIONS */ static enum circe_result process_opentunnel (struct circe_context *ctx, 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 (ctx); ctx->outgoing_packet_table_free_mask &= ~(1 << (ack1_idx - 1)); if (ack1_idx == 0) { circe_memory_set (mlkem_pubkey.buf_start, 0x0, MLKEM_PUBLICKEYBYTES (768)); circe_memory_set (remote_ecdh_frag, 0x0, 32); ctx->outgoing_packet_table_free_mask |= (1 << (ack1_idx - 1)); return CIRCE_RESULT_PACKET_TABLE_FULL; } size_t ack2_idx = CIRCE_FIRST_FREE_OUTGOING_PACKET_ENTRY (ctx); ctx->outgoing_packet_table_free_mask &= ~(1 << (ack2_idx - 1)); if (ack2_idx == 0) { circe_memory_set (mlkem_pubkey.buf_start, 0x0, MLKEM_PUBLICKEYBYTES (768)); circe_memory_set (remote_ecdh_frag, 0x0, 32); ctx->outgoing_packet_table_free_mask |= (1 << (ack1_idx - 1)); ctx->outgoing_packet_table_free_mask |= (1 << (ack2_idx - 1)); return CIRCE_RESULT_PACKET_TABLE_FULL; } size_t ack3_idx = CIRCE_FIRST_FREE_OUTGOING_PACKET_ENTRY (ctx); ctx->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); ctx->outgoing_packet_table_free_mask |= (1 << (ack1_idx - 1)); ctx->outgoing_packet_table_free_mask |= (1 << (ack2_idx - 1)); ctx->outgoing_packet_table_free_mask |= (1 << (ack3_idx - 1)); return CIRCE_RESULT_PACKET_TABLE_FULL; } uint8_t ecdh_privkey[32]; ctx->random_bytes_callback (ctx->ctx_handle, 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); ctx->outgoing_packets[ack1_idx - 1].destination = remote; ctx->outgoing_packets[ack2_idx - 1].destination = remote; ctx->outgoing_packets[ack3_idx - 1].destination = remote; struct circe_cbor_encoder ec = { .buf_start = ctx->outgoing_packets[ack1_idx - 1].buffer, .cursor = ctx->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 = ctx->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); ctx->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 (ctx->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, ctx->my_mldsa_privkey) == 0, CIRCE_RESULT_SIGNATURE_FAILED, err, process_opentunnel_cleanup); uint8_t encrypted_buf[sizeof (payload_clear) + CIRCE_P1305_MAC_LEN]; uint8_t nonce[sizeof (uint64_t)] = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; ec.buf_start = ctx->outgoing_packets[ack2_idx - 1].buffer; ec.cursor = ctx->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 = ctx->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); ctx->outgoing_packets[ack2_idx - 1].length = ec.cursor - ec.buf_start; ec.buf_start = ctx->outgoing_packets[ack3_idx - 1].buffer; ec.cursor = ctx->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 = ctx->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); ctx->outgoing_packets[ack3_idx - 1].length = ec.cursor - ec.buf_start; process_opentunnel_cleanup: if (err != CIRCE_RESULT_SUCCESS) { ctx->outgoing_packet_table_free_mask |= (1 << (ack1_idx - 1)); ctx->outgoing_packet_table_free_mask |= (1 << (ack2_idx - 1)); ctx->outgoing_packet_table_free_mask |= (1 << (ack3_idx - 1)); circe_memory_set ((uint8_t *)&ctx->outgoing_packets[ack1_idx - 1], 0x0, sizeof (struct circe_outgoing_packet)); circe_memory_set ((uint8_t *)&ctx->outgoing_packets[ack2_idx - 1], 0x0, sizeof (struct circe_outgoing_packet)); circe_memory_set ((uint8_t *)&ctx->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_context *ctx, 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 (ctx); ctx->outgoing_packet_table_free_mask &= ~(1 << (id1_idx - 1)); if (id1_idx == 0) { ctx->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 (ctx); ctx->outgoing_packet_table_free_mask &= ~(1 << (id2_idx - 1)); if (id2_idx == 0) { ctx->outgoing_packet_table_free_mask |= (1 << (id1_idx - 1)); ctx->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 (ctx->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, ctx->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) ? ctx->outgoing_packets[id1_idx - 1].buffer : ctx->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 = ctx->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 + CIRCE_P1305_MAC_LEN, .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); ctx->outgoing_packets[(i == 0) ? (id1_idx - 1) : (id2_idx - 1)] .destination = remote; ctx->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) { ctx->outgoing_packet_table_free_mask |= (1 << (id1_idx - 1)); ctx->outgoing_packet_table_free_mask |= (1 << (id2_idx - 1)); circe_memory_set ((uint8_t *)&ctx->outgoing_packets[id1_idx - 1], 0x0, sizeof (struct circe_outgoing_packet)); circe_memory_set ((uint8_t *)&ctx->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_context *ctx, 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 + CIRCE_P1305_MAC_LEN, 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 (ctx, 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_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 *)) { ctx->last_tick_ms = now_ms; circe_memory_copy (identity, ctx->my_identity, CIRCE_IDENTITY_LEN); circe_memory_copy (mldsa_privkey, ctx->my_mldsa_privkey, MLDSA_SECRETKEYBYTES (44)); ctx->peer_handshake_pending_mask = 0x0ull; ctx->peer_table_free_mask = ~0x0ull; ctx->outgoing_packet_table_free_mask = ~0x0ull; 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; } enum circe_result circe_handle_packet (struct circe_context *ctx, 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; struct circe_cbor_decoder cd = { .buf_start = in, .buf_len = len, .cursor = in, }; enum circe_cbor_major_type major_type; CIRCE_CORE_ASSERT (circe_cbor_get_major_type (&cd, &major_type), CIRCE_RESULT_INVALID_PACKET); CIRCE_CORE_ASSERT (major_type == CIRCE_CBOR_MAJOR_MAPPING, CIRCE_RESULT_INVALID_PACKET); uint64_t num_fields; CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (&cd, &num_fields), CIRCE_RESULT_INVALID_PACKET); CIRCE_CORE_ASSERT (num_fields >= 2, CIRCE_RESULT_INVALID_PACKET); // Version 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); uint64_t field; CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (&cd, &field), CIRCE_RESULT_INVALID_PACKET); CIRCE_CORE_ASSERT (field == CIRCE_PACKET_FIELD_VERSION, CIRCE_RESULT_INVALID_PACKET); uint64_t version; 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, &version), CIRCE_RESULT_INVALID_PACKET); // If it's anything but v1 we can't handle it (there is no v2) CIRCE_CORE_ASSERT (field == CIRCE_VERSION_V1, CIRCE_RESULT_INVALID_PACKET); // Remote's identity 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_IDENTITY, CIRCE_RESULT_INVALID_PACKET); struct circe_cbor_buffer remote_id_buf; 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, &remote_id_buf), 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; enum circe_result peer_in_ram = peer_table_lookup (ctx, 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), 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_TYPE, 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_UNSIGNED, CIRCE_RESULT_INVALID_PACKET); uint64_t packet_type; CIRCE_CORE_ASSERT (circe_cbor_extract_unsigned (&cd, &packet_type), CIRCE_RESULT_INVALID_PACKET); 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; switch (remote_pt_entry->current_route) { case CIRCE_ROUTE_LAN0: good_location = locations_equal (remote_pt_entry->lan0_loc, remote); break; case CIRCE_ROUTE_LAN1: good_location = locations_equal (remote_pt_entry->lan1_loc, remote); break; case CIRCE_ROUTE_WAN: good_location = locations_equal (remote_pt_entry->wan_loc, remote); break; case CIRCE_ROUTE_RELAY: // What? good_location = false; break; default: return CIRCE_RESULT_INTERNAL_ERROR; } if (!good_location) { (*n_events)++; events[0] = (struct circe_event){ .event_type = CIRCE_EVENT_REJECTED_REMOTE, }; return CIRCE_RESULT_SUCCESS; } } enum circe_result err; switch (packet_type) { case CIRCE_PACKET_OPENTUNNEL: 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 (ctx, &cd, remote, remote_pt_entry); if (err != CIRCE_RESULT_SUCCESS) { close_without_saying_goodbye (ctx, 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 (ctx, &cd, remote, remote_pt_entry, (packet_type - CIRCE_PACKET_ACKOPEN1) + 1); if (err != CIRCE_RESULT_SUCCESS) { close_without_saying_goodbye (ctx, 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: 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_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; } } enum circe_result circe_send_outgoing_packet (struct circe_context *ctx, 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 (ctx, 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 (ctx); if (next_entry_idx == 0) { // No free entries return CIRCE_RESULT_PACKET_TABLE_FULL; } ctx->outgoing_packet_table_free_mask &= ~(1ull << (next_entry_idx - 1)); struct circe_cbor_encoder ec = { .buf_start = ctx->outgoing_packets[next_entry_idx].buffer, .buf_len = sizeof (ctx->outgoing_packets[next_entry_idx].buffer), .cursor = ctx->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 = ctx->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: ctx->outgoing_packets[next_entry_idx - 1].destination = remote_pt_entry->lan0_loc; break; case CIRCE_ROUTE_LAN1: ctx->outgoing_packets[next_entry_idx - 1].destination = remote_pt_entry->lan1_loc; break; case CIRCE_ROUTE_WAN: ctx->outgoing_packets[next_entry_idx - 1].destination = remote_pt_entry->wan_loc; break; case CIRCE_ROUTE_RELAY: relay_server_in_ram = peer_table_lookup ( ctx, 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); ctx->outgoing_packets[next_entry_idx - 1].destination = relay_server_pt_entry->wan_loc; break; } ctx->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--; ctx->outgoing_packet_table_free_mask |= (1ull << (next_entry_idx - 1)); } return err; } enum circe_result circe_tick (struct circe_context *ctx, uint64_t now_ms, struct circe_outgoing_packet *out, bool *new_packet_out) { if (now_ms < ctx->last_tick_ms) { return CIRCE_RESULT_INVALID_TIMESTAMP; } ctx->last_tick_ms = now_ms; size_t packet_idx = CIRCE_FIRST_TAKEN_OUTGOING_PACKET_ENTRY (ctx); if (packet_idx == 0) { *new_packet_out = false; return CIRCE_RESULT_SUCCESS; } *new_packet_out = true; *out = ctx->outgoing_packets[packet_idx - 1]; circe_memory_set ((uint8_t *)(&ctx->outgoing_packets[packet_idx - 1]), 0x0, CIRCE_MAX_UDP_PAYLOAD); ctx->outgoing_packet_table_free_mask |= (1ull << (packet_idx - 1)); return CIRCE_RESULT_SUCCESS; } /* END PUBLIC API */