ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
record.c
Go to the documentation of this file.
1#include <defer.h>
3
4#include <ascii-chat/buffer.h>
6#include <string.h>
7#include <sodium.h>
8
9#define CRYPTO_RECORD_PREFIX_SIZE 24
10#define CRYPTO_RECORD_EPOCH_SIZE 8
11
12static bool valid_flow(unsigned traffic_class, unsigned delivery) {
13 return (unsigned)traffic_class <= (CRYPTO_RECORD_CHANNELS - 1) && (unsigned)delivery <= CRYPTO_RECORD_DATAGRAM;
14}
15
16static bool ready(const crypto_record_t *state) {
17 return state && state->crypto && state->crypto->initialized && state->crypto->encrypt_data &&
18 state->crypto->key_exchange_complete && state->crypto->handshake_complete &&
19 state->crypto->nonce_size == CRYPTO_NONCE_SIZE && state->crypto->mac_size == CRYPTO_MAC_SIZE &&
20 state->crypto->rekey_count == state->key_epoch;
21}
22
24 if (!state || state->crypto || !crypto || !crypto->initialized || !crypto->encrypt_data ||
25 !crypto->key_exchange_complete || !crypto->handshake_complete || crypto->nonce_size != CRYPTO_NONCE_SIZE ||
26 crypto->mac_size != CRYPTO_MAC_SIZE || crypto->rekey_in_progress)
27 return SET_ERRNO(ERROR_INVALID_STATE, "Session handshake and encryption must be ready");
28 *state = (crypto_record_t){.crypto = crypto, .key_epoch = crypto->rekey_count, .server = server};
29 return ASCIICHAT_OK;
30}
31
32asciichat_error_t crypto_record_seal_into(crypto_record_t *state, unsigned traffic_class, unsigned delivery,
33 const void *packet, size_t length, uint8_t *wire, size_t capacity,
34 size_t *out_length) {
35 return crypto_record_seal_into_domain(state, traffic_class, delivery, packet, length, wire, capacity, out_length,
36 NULL);
37}
38
39asciichat_error_t crypto_record_seal_into_domain(crypto_record_t *state, unsigned traffic_class, unsigned delivery,
40 const void *packet, size_t length, uint8_t *wire, size_t capacity,
41 size_t *out_length, buffer_domain_t *requested_domain) {
42 if (!wire || !out_length)
43 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing Record encrypted packet output");
44 *out_length = 0;
45 if (!ready(state) || !valid_flow(traffic_class, delivery))
46 return SET_ERRNO(ERROR_INVALID_STATE, "Record encryption is not ready for this flow");
47 if (!packet || !length)
48 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing record plaintext");
50 state->send_sequence[traffic_class][delivery] == UINT64_MAX)
51 return SET_ERRNO(ERROR_NETWORK_SIZE, "Record packet or sequence limit exceeded");
52 size_t plain_length = CRYPTO_RECORD_PREFIX_SIZE + length;
53 size_t cipher_capacity = plain_length + state->crypto->nonce_size + state->crypto->mac_size;
54 if (capacity < CRYPTO_RECORD_EPOCH_SIZE + cipher_capacity)
55 return SET_ERRNO(ERROR_NETWORK_SIZE, "Record ciphertext output is too small");
56 buffer_domain_t *record_buffer_domain =
57 requested_domain ? requested_domain
58 : (traffic_class == CRYPTO_RECORD_CONTROL_CHANNEL ? buffer_control_domain() : NULL);
59 uint8_t *plain = buffer_alloc(record_buffer_domain, plain_length);
60 if (!plain) {
61 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate Record packet");
62 }
63 memcpy(plain, "ACR1", 4);
64 plain[4] = state->server ? 1 : 0;
65 plain[5] = (uint8_t)traffic_class;
66 plain[6] = (uint8_t)delivery;
67 plain[7] = 0;
68 uint64_t sequence = state->send_sequence[traffic_class][delivery] + 1;
69 uint64_t network_sequence = HOST_TO_NET_U64(sequence);
70 memcpy(plain + 8, &network_sequence, sizeof(network_sequence));
71 uint64_t network_epoch = HOST_TO_NET_U64(state->key_epoch);
72 memcpy(plain + 16, &network_epoch, sizeof(network_epoch));
73 memcpy(plain + CRYPTO_RECORD_PREFIX_SIZE, packet, length);
74 size_t cipher_length = 0;
75 crypto_result_t crypto_result = crypto_encrypt(state->crypto, plain, plain_length, wire + CRYPTO_RECORD_EPOCH_SIZE,
76 cipher_capacity, &cipher_length);
77 buffer_release(plain);
78 if (crypto_result != CRYPTO_OK) {
79 return SET_ERRNO(ERROR_CRYPTO, "Cannot encrypt Record packet");
80 }
81 memcpy(wire, &network_epoch, sizeof(network_epoch));
82 cipher_length += CRYPTO_RECORD_EPOCH_SIZE;
83 state->send_sequence[traffic_class][delivery] = sequence;
84 *out_length = cipher_length;
85 return ASCIICHAT_OK;
86}
87
88asciichat_error_t crypto_record_open_sequence(crypto_record_t *state, unsigned traffic_class, unsigned delivery,
89 const void *packet, size_t length, uint8_t **out, size_t *out_length,
90 uint64_t *sequence_out, crypto_record_validate_fn validate,
91 void *context) {
92 if (!out || !out_length)
93 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing record decrypted packet output");
94 *out = NULL;
95 *out_length = 0;
96 if (sequence_out)
97 *sequence_out = 0;
98 if (!ready(state) || delivery > CRYPTO_RECORD_DATAGRAM ||
99 (traffic_class != CRYPTO_RECORD_ANY_CHANNEL && !valid_flow(traffic_class, delivery)))
100 return SET_ERRNO(ERROR_INVALID_STATE, "Record decryption is not ready for this flow");
101 if (!packet || length < CRYPTO_RECORD_EPOCH_SIZE + state->crypto->nonce_size + state->crypto->mac_size ||
102 length >
103 CRYPTO_RECORD_EPOCH_SIZE + CRYPTO_MAX_PLAINTEXT_SIZE + state->crypto->nonce_size + state->crypto->mac_size)
104 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid Record encrypted packet size");
105 const uint8_t *cipher = packet;
106 size_t cipher_length = length;
107 uint64_t network_epoch;
108 memcpy(&network_epoch, cipher, sizeof(network_epoch));
109 uint64_t epoch = NET_TO_HOST_U64(network_epoch);
110 crypto_context_t pending_crypto;
111 crypto_context_t *decrypt_crypto = state->crypto;
112 bool pending = false;
113 if (epoch != state->key_epoch) {
114 if (state->previous_valid && epoch == state->previous_epoch) {
115 decrypt_crypto = &state->previous_crypto;
116 } else if (state->key_epoch != UINT64_MAX && epoch == state->key_epoch + 1 && state->pending_prepared &&
117 state->crypto->rekey_in_progress && state->crypto->has_temp_key) {
118 pending_crypto = *state->crypto;
119 pending_crypto.session = NULL;
120 memcpy(pending_crypto.shared_key, state->crypto->temp_shared_key, sizeof(pending_crypto.shared_key));
121 pending_crypto.rekey_in_progress = false;
122 pending_crypto.has_temp_key = false;
123 decrypt_crypto = &pending_crypto;
124 pending = true;
125 } else {
126 return SET_ERRNO(ERROR_CRYPTO, "Unknown or retired record key epoch");
127 }
128 }
129 cipher += CRYPTO_RECORD_EPOCH_SIZE;
130 cipher_length -= CRYPTO_RECORD_EPOCH_SIZE;
131 uint8_t *plain = buffer_alloc(NULL, cipher_length);
132 if (!plain) {
133 if (pending)
134 sodium_memzero(&pending_crypto, sizeof(pending_crypto));
135 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate record decrypted packet");
136 }
137 defer(if (plain) buffer_release(plain));
138 size_t plain_length = 0;
139 crypto_result_t crypto_result =
140 crypto_decrypt(decrypt_crypto, cipher, cipher_length, plain, cipher_length, &plain_length);
141 if (pending)
142 sodium_memzero(&pending_crypto, sizeof(pending_crypto));
143 if (crypto_result != CRYPTO_OK) {
144 return SET_ERRNO(ERROR_CRYPTO, "Cannot authenticate Record packet");
145 }
146 if (plain_length < CRYPTO_RECORD_PREFIX_SIZE || memcmp(plain, "ACR1", 4) || plain[4] != (state->server ? 0 : 1) ||
147 (traffic_class != CRYPTO_RECORD_ANY_CHANNEL && plain[5] != traffic_class) || plain[5] >= CRYPTO_RECORD_CHANNELS ||
148 plain[6] != (uint8_t)delivery || plain[7] || memcmp(plain + 16, &network_epoch, sizeof(network_epoch))) {
149 return SET_ERRNO(ERROR_NETWORK_PROTOCOL, "Invalid authenticated record flow metadata");
150 }
151 traffic_class = plain[5];
152 if (epoch == state->previous_epoch && state->previous_valid && !state->previous_allowed[traffic_class]) {
153 return SET_ERRNO(ERROR_CRYPTO, "Retired record channel epoch");
154 }
155 uint64_t network_sequence;
156 memcpy(&network_sequence, plain + 8, sizeof(network_sequence));
157 uint64_t sequence = NET_TO_HOST_U64(network_sequence);
158 uint64_t high = state->receive_high[traffic_class][delivery];
159 uint64_t seen = state->datagram_seen[traffic_class];
160 if (!sequence || (delivery == CRYPTO_RECORD_RELIABLE && sequence <= high) ||
161 (delivery == CRYPTO_RECORD_DATAGRAM && sequence <= high &&
162 (high - sequence >= 64 || (seen & (UINT64_C(1) << (high - sequence)))))) {
163 return SET_ERRNO(ERROR_CRYPTO, "Replayed or stale Record packet");
164 }
165 size_t inner_length = plain_length - CRYPTO_RECORD_PREFIX_SIZE;
166 asciichat_error_t result =
167 validate ? validate(context, traffic_class, delivery, plain + CRYPTO_RECORD_PREFIX_SIZE, inner_length)
168 : ASCIICHAT_OK;
169 if (result != ASCIICHAT_OK) {
170 return result;
171 }
172 if (pending) {
173 result = crypto_record_commit_epoch(state);
174 if (result != ASCIICHAT_OK) {
175 return result;
176 }
177 }
178 if (delivery == CRYPTO_RECORD_DATAGRAM) {
179 if (sequence > high) {
180 uint64_t advance = sequence - high;
181 seen = advance >= 64 ? UINT64_C(1) : (seen << advance) | UINT64_C(1);
182 high = sequence;
183 } else {
184 seen |= UINT64_C(1) << (high - sequence);
185 }
186 state->datagram_seen[traffic_class] = seen;
187 } else {
188 high = sequence;
189 }
190 state->receive_high[traffic_class][delivery] = high;
191 state->last_receive_epoch = epoch;
192 state->last_receive_channel = traffic_class;
193 state->last_receive_delivery = delivery;
194 memmove(plain, plain + CRYPTO_RECORD_PREFIX_SIZE, inner_length);
195 *out = plain;
196 plain = NULL;
197 *out_length = inner_length;
198 if (sequence_out)
199 *sequence_out = sequence;
200 return ASCIICHAT_OK;
201}
202
204 if (!ready(state) || state->previous_valid || state->pending_prepared || state->key_epoch == UINT64_MAX ||
205 !state->crypto->rekey_in_progress || !state->crypto->has_temp_key)
206 return SET_ERRNO(ERROR_INVALID_STATE, "record key epoch is not ready to prepare");
207 state->pending_prepared = true;
208 return ASCIICHAT_OK;
209}
210
212 if (!ready(state) || !state->pending_prepared || state->previous_valid || !state->crypto->rekey_in_progress ||
213 !state->crypto->has_temp_key)
214 return SET_ERRNO(ERROR_INVALID_STATE, "record key epoch is not ready to commit");
215 crypto_context_t previous = *state->crypto;
216 previous.session = NULL;
217 previous.rekey_in_progress = previous.has_temp_key = false;
218 sodium_memzero(previous.temp_private_key, sizeof(previous.temp_private_key));
219 sodium_memzero(previous.temp_public_key, sizeof(previous.temp_public_key));
220 sodium_memzero(previous.temp_shared_key, sizeof(previous.temp_shared_key));
221 if (crypto_rekey_commit(state->crypto) != CRYPTO_OK) {
222 sodium_memzero(&previous, sizeof(previous));
223 return SET_ERRNO(ERROR_CRYPTO, "Cannot commit record key epoch");
224 }
225 state->previous_crypto = previous;
226 sodium_memzero(&previous, sizeof(previous));
227 state->previous_epoch = state->key_epoch;
228 state->key_epoch = state->crypto->rekey_count;
229 state->previous_valid = true;
230 state->pending_prepared = false;
231 for (unsigned i = 0; i < CRYPTO_RECORD_CHANNELS; ++i)
232 state->previous_allowed[i] = true;
233 return ASCIICHAT_OK;
234}
235
237 if (!ready(state) || (unsigned)traffic_class > (CRYPTO_RECORD_CHANNELS - 1) || epoch != state->key_epoch ||
238 !state->previous_valid)
239 return SET_ERRNO(ERROR_INVALID_STATE, "Invalid record epoch retirement marker");
240 state->previous_allowed[traffic_class] = false;
241 if (!state->previous_allowed[0] && !state->previous_allowed[1] && !state->previous_allowed[2]) {
242 sodium_memzero(&state->previous_crypto, sizeof(state->previous_crypto));
243 state->previous_valid = false;
244 }
245 return ASCIICHAT_OK;
246}
247
249 if (state)
250 sodium_memzero(state, sizeof(*state));
251}
#define SET_ERRNO(code,...)
void * buffer_alloc(buffer_domain_t *domain, size_t size)
Definition buffer.c:274
buffer_domain_t * buffer_control_domain(void)
Definition buffer.c:436
struct buffer_domain buffer_domain_t
Definition buffer.h:24
#define buffer_release(data)
Definition buffer.h:91
🔄 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
crypto_result_t crypto_rekey_commit(crypto_context_t *ctx)
Commit to new keys after successful REKEY_COMPLETE.
crypto_result_t crypto_encrypt(crypto_context_t *ctx, const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext_out, size_t ciphertext_out_size, size_t *ciphertext_len_out)
Encrypt data using XSalsa20-Poly1305.
crypto_result_t
Cryptographic operation result codes.
crypto_result_t crypto_decrypt(crypto_context_t *ctx, const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext_out, size_t plaintext_out_size, size_t *plaintext_len_out)
Decrypt data using XSalsa20-Poly1305.
#define CRYPTO_MAX_PLAINTEXT_SIZE
Maximum plaintext size (1MB).
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
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_NETWORK_SIZE
Definition error_codes.h:77
@ ERROR_CRYPTO
Definition error_codes.h:93
@ ERROR_INVALID_PARAM
static bool size_t gotel_query_t * out
Definition queries.h:8
asciichat_error_t crypto_record_commit_epoch(crypto_record_t *state)
Definition record.c:211
#define CRYPTO_RECORD_PREFIX_SIZE
Definition record.c:9
asciichat_error_t crypto_record_init(crypto_record_t *state, crypto_context_t *crypto, bool server)
Definition record.c:23
asciichat_error_t crypto_record_seal_into_domain(crypto_record_t *state, unsigned traffic_class, unsigned delivery, const void *packet, size_t length, uint8_t *wire, size_t capacity, size_t *out_length, buffer_domain_t *requested_domain)
Definition record.c:39
#define CRYPTO_RECORD_EPOCH_SIZE
Definition record.c:10
void crypto_record_cleanup(crypto_record_t *state)
Definition record.c:248
asciichat_error_t crypto_record_prepare_epoch(crypto_record_t *state)
Definition record.c:203
asciichat_error_t crypto_record_seal_into(crypto_record_t *state, unsigned traffic_class, unsigned delivery, const void *packet, size_t length, uint8_t *wire, size_t capacity, size_t *out_length)
Definition record.c:32
asciichat_error_t crypto_record_open_sequence(crypto_record_t *state, unsigned traffic_class, unsigned delivery, const void *packet, size_t length, uint8_t **out, size_t *out_length, uint64_t *sequence_out, crypto_record_validate_fn validate, void *context)
Definition record.c:88
asciichat_error_t crypto_record_retire_epoch_flow(crypto_record_t *state, unsigned traffic_class, uint64_t epoch)
Definition record.c:236
asciichat_error_t(* crypto_record_validate_fn)(void *, unsigned, unsigned, const void *, size_t)
Definition record.h:30
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_CONTROL_CHANNEL
Definition record.h:8
#define CRYPTO_RECORD_RELIABLE
Definition record.h:9
#define CRYPTO_RECORD_DATAGRAM
Definition record.h:10
#define CRYPTO_RECORD_CHANNELS
Definition record.h:7
#define CRYPTO_RECORD_ANY_CHANNEL
Definition record.h:11
#define CRYPTO_NONCE_SIZE
Nonce size (XSalsa20).
Definition sizes.h:79
#define CRYPTO_MAC_SIZE
MAC size (Poly1305).
Definition sizes.h:85
🔤 String Manipulation and Shell Escaping Utilities
Endpoint cryptographic state for handshake, packet protection, authentication, and rekeying.
struct crypto_session * session
Endpoint cryptographic state for handshake, packet protection, authentication, and rekeying.