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.h File Reference

Go to the source code of this file.

Data Structures

struct  crypto_record
 When code needs to work with crypto record, it keeps crypto, key_epoch, last_receive_epoch, previous_crypto here instead of scattering it across globals. Initialization establishes the handles and sizes; teardown must release any allocation, lock, thread, or callback context recorded in those fields. The caller serializes operations and keeps the borrowed crypto context alive. One previous receive epoch survives until each channel crosses its marker. More...

Macros

#define CRYPTO_RECORD_CHANNELS   3
#define CRYPTO_RECORD_CONTROL_CHANNEL   0
#define CRYPTO_RECORD_RELIABLE   0
#define CRYPTO_RECORD_DATAGRAM   1
#define CRYPTO_RECORD_ANY_CHANNEL   UINT_MAX

Typedefs

typedef struct crypto_record crypto_record_t
 When code needs to work with crypto record, it keeps Crypto Module, key_epoch, last_receive_epoch, previous_crypto here instead of scattering it across globals. Initialization establishes the handles and sizes; teardown must release any allocation, lock, thread, or callback context recorded in those fields. The caller serializes operations and keeps the borrowed crypto context alive. One previous receive epoch survives until each channel crosses its marker.
typedef asciichat_error_t(* crypto_record_validate_fn) (void *, unsigned, unsigned, const void *, size_t)

Functions

asciichat_error_t crypto_record_init (crypto_record_t *, crypto_context_t *, bool server)
asciichat_error_t crypto_record_seal_into (crypto_record_t *, unsigned channel, unsigned delivery, const void *, size_t, uint8_t *, size_t, size_t *)
asciichat_error_t crypto_record_seal_into_domain (crypto_record_t *, unsigned channel, unsigned delivery, const void *, size_t, uint8_t *, size_t, size_t *, buffer_domain_t *)
asciichat_error_t crypto_record_open_sequence (crypto_record_t *, unsigned channel, unsigned delivery, const void *, size_t, uint8_t **, size_t *, uint64_t *, crypto_record_validate_fn, void *)
asciichat_error_t crypto_record_prepare_epoch (crypto_record_t *)
asciichat_error_t crypto_record_commit_epoch (crypto_record_t *)
asciichat_error_t crypto_record_retire_epoch_flow (crypto_record_t *, unsigned channel, uint64_t epoch)
void crypto_record_cleanup (crypto_record_t *)

Macro Definition Documentation

◆ CRYPTO_RECORD_ANY_CHANNEL

#define CRYPTO_RECORD_ANY_CHANNEL   UINT_MAX

◆ CRYPTO_RECORD_CHANNELS

◆ CRYPTO_RECORD_CONTROL_CHANNEL

#define CRYPTO_RECORD_CONTROL_CHANNEL   0

Definition at line 8 of file record.h.

Referenced by crypto_record_seal_into_domain().

◆ CRYPTO_RECORD_DATAGRAM

#define CRYPTO_RECORD_DATAGRAM   1

Definition at line 10 of file record.h.

Referenced by crypto_record_open_sequence().

◆ CRYPTO_RECORD_RELIABLE

#define CRYPTO_RECORD_RELIABLE   0

Typedef Documentation

◆ crypto_record_t

When code needs to work with crypto record, it keeps Crypto Module, key_epoch, last_receive_epoch, previous_crypto here instead of scattering it across globals. Initialization establishes the handles and sizes; teardown must release any allocation, lock, thread, or callback context recorded in those fields. The caller serializes operations and keeps the borrowed crypto context alive. One previous receive epoch survives until each channel crosses its marker.

Authenticated records over independent ordered channels and unordered delivery.

◆ crypto_record_validate_fn

typedef asciichat_error_t(* crypto_record_validate_fn) (void *, unsigned, unsigned, const void *, size_t)

Definition at line 30 of file record.h.

Function Documentation

◆ crypto_record_cleanup()

void crypto_record_cleanup ( crypto_record_t * state)

Definition at line 248 of file record.c.

248 {
249 if (state)
250 sodium_memzero(state, sizeof(*state));
251}

Referenced by crypto_session_destroy().

◆ crypto_record_commit_epoch()

asciichat_error_t crypto_record_commit_epoch ( crypto_record_t * state)

Definition at line 211 of file record.c.

211 {
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}
#define SET_ERRNO(code,...)
crypto_result_t crypto_rekey_commit(crypto_context_t *ctx)
Commit to new keys after successful REKEY_COMPLETE.
@ ERROR_INVALID_STATE
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_CRYPTO
Definition error_codes.h:93
#define CRYPTO_RECORD_CHANNELS
Definition record.h:7
Endpoint cryptographic state for handshake, packet protection, authentication, and rekeying.
struct crypto_session * session
Endpoint cryptographic state for handshake, packet protection, authentication, and rekeying.

References ASCIICHAT_OK, CRYPTO_OK, CRYPTO_RECORD_CHANNELS, crypto_rekey_commit(), ERROR_CRYPTO, ERROR_INVALID_STATE, crypto_context_t::session, and SET_ERRNO.

Referenced by crypto_record_open_sequence(), and crypto_rekey_session_receive().

◆ crypto_record_init()

asciichat_error_t crypto_record_init ( crypto_record_t * state,
crypto_context_t * crypto,
bool server )

Definition at line 23 of file record.c.

23 {
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}
struct crypto_record crypto_record_t
When code needs to work with crypto record, it keeps crypto, key_epoch, last_receive_epoch,...
#define CRYPTO_NONCE_SIZE
Nonce size (XSalsa20).
Definition sizes.h:79
#define CRYPTO_MAC_SIZE
MAC size (Poly1305).
Definition sizes.h:85

References ASCIICHAT_OK, CRYPTO_MAC_SIZE, CRYPTO_NONCE_SIZE, crypto_context_t::encrypt_data, ERROR_INVALID_STATE, crypto_context_t::handshake_complete, crypto_context_t::initialized, crypto_context_t::key_exchange_complete, crypto_context_t::mac_size, crypto_context_t::nonce_size, crypto_context_t::rekey_count, crypto_context_t::rekey_in_progress, and SET_ERRNO.

Referenced by crypto_session_lock().

◆ crypto_record_open_sequence()

asciichat_error_t crypto_record_open_sequence ( crypto_record_t * state,
unsigned channel,
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 at line 88 of file record.c.

91 {
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}
void * buffer_alloc(buffer_domain_t *domain, size_t size)
Definition buffer.c:274
#define buffer_release(data)
Definition buffer.h:91
#define NET_TO_HOST_U64(val)
Definition endian.h:228
unsigned long long uint64_t
Definition common.h:63
unsigned char uint8_t
Definition common.h:60
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_NETWORK_PROTOCOL
Definition error_codes.h:76
@ ERROR_MEMORY
Definition error_codes.h:51
@ 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
#define CRYPTO_RECORD_EPOCH_SIZE
Definition record.c:10
#define CRYPTO_RECORD_RELIABLE
Definition record.h:9
#define CRYPTO_RECORD_DATAGRAM
Definition record.h:10
#define CRYPTO_RECORD_ANY_CHANNEL
Definition record.h:11

References ASCIICHAT_OK, buffer_alloc(), buffer_release, crypto_decrypt(), CRYPTO_MAX_PLAINTEXT_SIZE, CRYPTO_OK, CRYPTO_RECORD_ANY_CHANNEL, CRYPTO_RECORD_CHANNELS, crypto_record_commit_epoch(), CRYPTO_RECORD_DATAGRAM, CRYPTO_RECORD_EPOCH_SIZE, CRYPTO_RECORD_PREFIX_SIZE, CRYPTO_RECORD_RELIABLE, ERROR_CRYPTO, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_MEMORY, ERROR_NETWORK_PROTOCOL, crypto_context_t::has_temp_key, NET_TO_HOST_U64, out, crypto_context_t::rekey_in_progress, crypto_context_t::session, SET_ERRNO, and crypto_context_t::shared_key.

Referenced by acip_packet_decrypt_payload().

◆ crypto_record_prepare_epoch()

asciichat_error_t crypto_record_prepare_epoch ( crypto_record_t * state)

Definition at line 203 of file record.c.

203 {
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}

References ASCIICHAT_OK, ERROR_INVALID_STATE, and SET_ERRNO.

Referenced by crypto_rekey_session_receive(), and crypto_rekey_session_tick().

◆ crypto_record_retire_epoch_flow()

asciichat_error_t crypto_record_retire_epoch_flow ( crypto_record_t * state,
unsigned channel,
uint64_t epoch )

Definition at line 236 of file record.c.

236 {
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}

References ASCIICHAT_OK, CRYPTO_RECORD_CHANNELS, ERROR_INVALID_STATE, and SET_ERRNO.

Referenced by crypto_rekey_session_receive().

◆ crypto_record_seal_into()

asciichat_error_t crypto_record_seal_into ( crypto_record_t * state,
unsigned channel,
unsigned delivery,
const void * packet,
size_t length,
uint8_t * wire,
size_t capacity,
size_t * out_length )

Output buffers belong to the buffer library. No packet headers or CRCs are added here.

Definition at line 32 of file record.c.

34 {
35 return crypto_record_seal_into_domain(state, traffic_class, delivery, packet, length, wire, capacity, out_length,
36 NULL);
37}
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

References crypto_record_seal_into_domain().

◆ crypto_record_seal_into_domain()

asciichat_error_t crypto_record_seal_into_domain ( crypto_record_t * state,
unsigned channel,
unsigned delivery,
const void * packet,
size_t length,
uint8_t * wire,
size_t capacity,
size_t * out_length,
buffer_domain_t * requested_domain )

Definition at line 39 of file record.c.

41 {
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}
buffer_domain_t * buffer_control_domain(void)
Definition buffer.c:436
struct buffer_domain buffer_domain_t
Definition buffer.h:24
#define HOST_TO_NET_U64(val)
Definition endian.h:213
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.
@ ERROR_NETWORK_SIZE
Definition error_codes.h:77
#define CRYPTO_RECORD_CONTROL_CHANNEL
Definition record.h:8

References ASCIICHAT_OK, buffer_alloc(), buffer_control_domain(), buffer_release, crypto_encrypt(), CRYPTO_MAX_PLAINTEXT_SIZE, CRYPTO_OK, CRYPTO_RECORD_CONTROL_CHANNEL, CRYPTO_RECORD_EPOCH_SIZE, CRYPTO_RECORD_PREFIX_SIZE, ERROR_CRYPTO, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_MEMORY, ERROR_NETWORK_SIZE, HOST_TO_NET_U64, and SET_ERRNO.

Referenced by acip_packet_encrypt(), and crypto_record_seal_into().