ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
rekey.c
Go to the documentation of this file.
3#include <sodium.h>
4#include <string.h>
5
6#define CRYPTO_REKEY_TIMEOUT_NS (30ULL * NS_PER_SEC_INT)
7
8static asciichat_error_t clock_step(crypto_rekey_session_t *s, uint64_t now) {
9 if (!s || !s->security || !s->security->crypto || !s->send || now < s->last_ns || now == UINT64_MAX)
10 return SET_ERRNO(ERROR_INVALID_STATE, "Invalid session rekey state or monotonic clock");
11 s->last_ns = now;
12 return ASCIICHAT_OK;
13}
14
15static asciichat_error_t send_rekey_packet(crypto_rekey_session_t *s, unsigned flow, crypto_rekey_message_t type,
16 const void *payload, size_t length) {
17 if (length > CRYPTO_PUBLIC_KEY_SIZE)
18 return SET_ERRNO(ERROR_INVALID_PARAM, "Rekey message exceeds its bound");
19 return s->send(s->context, flow, type, payload, length);
20}
21
22static bool settled(crypto_rekey_session_t *s) {
25}
26
27static asciichat_error_t observe_epoch(crypto_rekey_session_t *s) {
28 if (s->seen_epoch != s->security->key_epoch) {
29 if (s->seen_epoch == UINT64_MAX || s->security->key_epoch != s->seen_epoch + 1 || !s->exchange_ns)
30 return SET_ERRNO(ERROR_CRYPTO, "Unexpected session key epoch transition");
32 s->markers_sent = true;
33 s->ack_sent = false;
35 for (unsigned flow = 0; flow < CRYPTO_RECORD_CHANNELS; ++flow) {
36 asciichat_error_t result = send_rekey_packet(s, (unsigned)flow, CRYPTO_REKEY_MARKER, &epoch, sizeof(epoch));
37 if (result != ASCIICHAT_OK)
38 return result;
39 }
40 }
41 if (s->markers_sent && !s->ack_sent && !s->security->previous_valid && !s->expecting_complete) {
43 asciichat_error_t result = send_rekey_packet(s, 0, CRYPTO_REKEY_ACK, &epoch, sizeof(epoch));
44 if (result != ASCIICHAT_OK)
45 return result;
46 s->ack_sent = true;
47 }
48 if (settled(s))
49 s->exchange_ns = 0;
50 return ASCIICHAT_OK;
51}
52
54 crypto_rekey_emit_fn send, void *context, uint64_t now) {
55 if (!s || s->security || !security || !security->crypto || !send || now == UINT64_MAX)
56 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid session rekey initialization");
57 *s = (crypto_rekey_session_t){.security = security,
58 .send = send,
59 .context = context,
60 .activated_ns = now,
61 .last_ns = now,
62 .seen_epoch = security->key_epoch,
63 .peer_ack = security->key_epoch};
64 return ASCIICHAT_OK;
65}
66
67static bool request_allowed(crypto_rekey_session_t *s, uint64_t now) {
68 return now - s->activated_ns >= (uint64_t)REKEY_MIN_INTERVAL &&
70}
71
73 asciichat_error_t result = clock_step(s, now);
74 if (result != ASCIICHAT_OK)
75 return result;
76 result = observe_epoch(s);
77 if (result != ASCIICHAT_OK)
78 return result;
80 return SET_ERRNO(ERROR_NETWORK_TIMEOUT, "session key exchange or flow retirement timed out");
81 // A peer measures its interval at send time. Keep one authenticated request
82 // until our receive-side interval elapses; network jitter must not turn a
83 // valid rotation into a protocol violation or bypass the rate limit.
84 if (s->deferred_request && request_allowed(s, now)) {
85 crypto_context_t *crypto = s->security->crypto;
86 s->deferred_request = false;
87 s->expecting_complete = true;
88 s->last_request_ns = now;
90 return SET_ERRNO(ERROR_CRYPTO, "Cannot compute session rekey response");
91 sodium_memzero(s->deferred_key, sizeof(s->deferred_key));
93 if (result == ASCIICHAT_OK)
94 result = send_rekey_packet(s, 0, CRYPTO_REKEY_RESPONSE, crypto->temp_public_key, CRYPTO_PUBLIC_KEY_SIZE);
95 return result;
96 }
97 if (!settled(s) || !request_allowed(s, now))
98 return ASCIICHAT_OK;
99 crypto_context_t *crypto = s->security->crypto;
100 bool threshold =
101 crypto->rekey_packet_count >= crypto->rekey_packet_threshold ||
102 (crypto->rekey_time_threshold > 0 && now - s->activated_ns >= (uint64_t)crypto->rekey_time_threshold);
103 if (!s->requested && !threshold)
104 return ASCIICHAT_OK;
105 s->requested = false;
106 s->exchange_ns = now;
107 if (!s->security->server) {
108 s->remote_requested = true;
109 return send_rekey_packet(s, 0, CRYPTO_REKEY_NEEDED, NULL, 0);
110 }
111 if (crypto_rekey_init(crypto) != CRYPTO_OK)
112 return SET_ERRNO(ERROR_CRYPTO, "Cannot initiate session ephemeral rekey");
113 s->waiting_response = true;
114 s->last_request_ns = now;
115 return send_rekey_packet(s, 0, CRYPTO_REKEY_REQUEST, crypto->temp_public_key, CRYPTO_PUBLIC_KEY_SIZE);
116}
117
119 asciichat_error_t result = clock_step(s, now);
120 if (result != ASCIICHAT_OK)
121 return result;
122 if (!s->exchange_ns)
123 s->requested = true;
124 return crypto_rekey_session_tick(s, now);
125}
126
128 crypto_rekey_message_t type, const void *payload, size_t bytes,
129 uint64_t now, bool *consumed) {
130 if (!consumed || (!payload && bytes) || flow >= CRYPTO_RECORD_CHANNELS || type > CRYPTO_REKEY_NEEDED)
131 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid rekey input");
132 *consumed = false;
133 asciichat_error_t result = clock_step(s, now);
134 if (result != ASCIICHAT_OK)
135 return result;
136 result = observe_epoch(s);
137 if (result != ASCIICHAT_OK)
138 return result;
139 if (type == CRYPTO_REKEY_NONE)
140 return crypto_rekey_session_tick(s, now);
141 *consumed = true;
142 uint64_t epoch = s->security->key_epoch;
143 if (delivery != CRYPTO_RECORD_RELIABLE || s->security->last_receive_epoch != epoch ||
144 (type != CRYPTO_REKEY_MARKER && flow != 0))
145 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "session rekey message has invalid flow, delivery, or epoch");
146 crypto_context_t *crypto = s->security->crypto;
147 if (type == CRYPTO_REKEY_MARKER || type == CRYPTO_REKEY_ACK) {
148 uint64_t value;
149 if (bytes != sizeof(value))
150 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid session epoch marker length");
151 memcpy(&value, payload, sizeof(value));
152 if (!epoch || NET_TO_HOST_U64(value) != epoch || !s->markers_sent)
153 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Unexpected session epoch marker");
154 if (type == CRYPTO_REKEY_MARKER) {
155 if (!s->security->previous_valid || !s->security->previous_allowed[flow])
156 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Duplicate session flow retirement marker");
157 result = crypto_record_retire_epoch_flow(s->security, flow, epoch);
158 } else {
159 s->peer_ack = epoch;
160 }
161 } else if (type == CRYPTO_REKEY_NEEDED) {
162 if (!s->security->server || bytes)
163 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid session rekey request signal");
164 if (!s->exchange_ns)
165 s->requested = true;
166 } else if (type == CRYPTO_REKEY_REQUEST) {
169 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Unexpected session ephemeral rekey request");
170 s->exchange_ns = now;
171 s->remote_requested = false;
172 s->deferred_request = true;
173 memcpy(s->deferred_key, payload, sizeof(s->deferred_key));
174 } else if (type == CRYPTO_REKEY_RESPONSE) {
175 if (!s->security->server || !s->waiting_response || bytes != CRYPTO_PUBLIC_KEY_SIZE)
176 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Unexpected session ephemeral rekey response");
177 if (crypto_rekey_process_response(crypto, payload) != CRYPTO_OK)
178 return SET_ERRNO(ERROR_CRYPTO, "Cannot compute session next epoch");
180 if (result == ASCIICHAT_OK)
182 if (result == ASCIICHAT_OK) {
183 s->waiting_response = false;
184 s->activated_ns = now;
185 uint8_t proof = 0;
186 result = send_rekey_packet(s, 0, CRYPTO_REKEY_COMPLETE, &proof, 1);
187 }
188 } else {
189 if (s->security->server || !s->expecting_complete || bytes != 1 || ((const uint8_t *)payload)[0] || !epoch ||
191 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Unexpected session rekey confirmation");
192 s->expecting_complete = false;
193 s->activated_ns = now;
194 }
195 if (result != ASCIICHAT_OK)
196 return result;
197 result = observe_epoch(s);
198 if (result != ASCIICHAT_OK)
199 return result;
200 return crypto_rekey_session_tick(s, now);
201}
202
204 if (!s)
205 return;
208 sodium_memzero(s, sizeof(*s));
209}
#define SET_ERRNO(code,...)
🔄 Network byte order conversion helpers
#define NET_TO_HOST_U64(val)
Definition endian.h:228
#define HOST_TO_NET_U64(val)
Definition endian.h:213
unsigned long long uint64_t
Definition common.h:63
unsigned char uint8_t
Definition common.h:60
#define REKEY_MIN_INTERVAL
Minimum time interval between rekey requests (3 seconds in nanoseconds for testing,...
void crypto_rekey_abort(crypto_context_t *ctx)
Abort rekeying and fallback to old keys.
crypto_result_t crypto_rekey_process_request(crypto_context_t *ctx, const uint8_t *peer_new_public_key)
Process REKEY_REQUEST from peer (responder side).
#define REKEY_MIN_REQUEST_INTERVAL
Minimum interval between rekey requests (60 seconds in nanoseconds, DDoS protection).
crypto_result_t crypto_rekey_init(crypto_context_t *ctx)
Initiate rekeying by generating new ephemeral keys.
crypto_result_t crypto_rekey_process_response(crypto_context_t *ctx, const uint8_t *peer_new_public_key)
Process REKEY_RESPONSE from peer (initiator side).
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_INVALID_STATE
@ ERROR_NETWORK_PROTOCOL
Definition error_codes.h:76
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_CRYPTO
Definition error_codes.h:93
@ ERROR_INVALID_PARAM
@ ERROR_NETWORK_TIMEOUT
Definition error_codes.h:75
asciichat_error_t crypto_record_commit_epoch(crypto_record_t *state)
Definition record.c:211
asciichat_error_t crypto_record_prepare_epoch(crypto_record_t *state)
Definition record.c:203
asciichat_error_t crypto_record_retire_epoch_flow(crypto_record_t *state, unsigned traffic_class, uint64_t epoch)
Definition record.c:236
struct crypto_record crypto_record_t
When code needs to work with crypto record, it keeps crypto, key_epoch, last_receive_epoch,...
#define CRYPTO_RECORD_RELIABLE
Definition record.h:9
#define CRYPTO_RECORD_CHANNELS
Definition record.h:7
asciichat_error_t crypto_rekey_session_receive(crypto_rekey_session_t *s, unsigned flow, unsigned delivery, crypto_rekey_message_t type, const void *payload, size_t bytes, uint64_t now, bool *consumed)
Definition rekey.c:127
#define CRYPTO_REKEY_TIMEOUT_NS
Definition rekey.c:6
asciichat_error_t crypto_rekey_session_tick(crypto_rekey_session_t *s, uint64_t now)
Definition rekey.c:72
asciichat_error_t crypto_rekey_session_init(crypto_rekey_session_t *s, crypto_record_t *security, crypto_rekey_emit_fn send, void *context, uint64_t now)
Definition rekey.c:53
void crypto_rekey_session_cleanup(crypto_rekey_session_t *s)
Definition rekey.c:203
asciichat_error_t crypto_rekey_session_request(crypto_rekey_session_t *s, uint64_t now)
Definition rekey.c:118
asciichat_error_t(* crypto_rekey_emit_fn)(void *, unsigned, crypto_rekey_message_t, const void *, size_t)
Definition rekey.h:15
crypto_rekey_message_t
Definition rekey.h:6
@ CRYPTO_REKEY_REQUEST
Definition rekey.h:8
@ CRYPTO_REKEY_NONE
Definition rekey.h:7
@ CRYPTO_REKEY_RESPONSE
Definition rekey.h:9
@ CRYPTO_REKEY_MARKER
Definition rekey.h:11
@ CRYPTO_REKEY_NEEDED
Definition rekey.h:13
@ CRYPTO_REKEY_COMPLETE
Definition rekey.h:10
@ CRYPTO_REKEY_ACK
Definition rekey.h:12
#define CRYPTO_PUBLIC_KEY_SIZE
Public key size (X25519).
Definition sizes.h:67
🔤 String Manipulation and Shell Escaping Utilities
Endpoint cryptographic state for handshake, packet protection, authentication, and rekeying.
uint64_t last_receive_epoch
Definition record.h:19
bool previous_valid
Definition record.h:22
bool pending_prepared
Definition record.h:22
crypto_context_t * crypto
Definition record.h:18
uint64_t key_epoch
Definition record.h:19
bool server
Definition record.h:27
bool previous_allowed[3]
Definition record.h:22
crypto rekey session is intentionally a small, concrete record. Fields such as security,...
Definition rekey.h:20
uint8_t deferred_key[CRYPTO_PUBLIC_KEY_SIZE]
Definition rekey.h:25
uint64_t activated_ns
Definition rekey.h:24
crypto_record_t * security
Definition rekey.h:21
crypto_rekey_emit_fn send
Definition rekey.h:22
uint64_t seen_epoch
Definition rekey.h:24
uint64_t last_request_ns
Definition rekey.h:24
uint64_t peer_ack
Definition rekey.h:24
uint64_t exchange_ns
Definition rekey.h:24