Skip to content

Commit 4d37298

Browse files
committed
Save memory and tighten up CBC processing
This commit makes two changes: * Convert our CBC padding checking to using a bit-fiddling generated mask, instead of pre-generated masks. Previously we generated 256 masks of 255 bytes each at init time and used those masks to validate CBC padding. This works, but ends up costing 32k of memory. In this commit we convert to using a bit-fiddling mask similar to how do we constant time copies. * Add and use s2n_hmac_digest_two_compression_rounds() . Since we launched, several bug reporters (including Martin R. Albrecht and Kenny Paterson from Royal Holloway, University of London) got in touch to point out that s2n_hmac_digest() does not run in constant time and varies depending in the length of the padding. If the length of the data section covered by the mac leaves fewer than 8 bytes spare in the hash block used by HMAC, then the underlying hash function will add and compress an additional hash block when _digest() is called. This doesn't result in leaking a measureable timing side-channel because of the additional timing blinding in s2n_recv.c (s2n adds between 1ms and 10 seconds of delay in the event of an error, which raises the number of trials required to measure any signal by at least a factor of 83 trillion, and more likely renders it completely unmeasureable). But it's still worth tightening up here. Previously we'd been thinking that doing anything here would involve "opening up" the hash function in ways that prevent us from using hardware hash acceleration, but Martin R. Albrecht and Kenny Paterson had a good idea: count and use compression rounds explicitly, which is what this change goes with.
1 parent 621eec8 commit 4d37298

6 files changed

Lines changed: 36 additions & 27 deletions

File tree

crypto/s2n_hmac.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,10 @@ static int s2n_sslv3_mac_digest(struct s2n_hmac_state *state, void *out, uint32_
8484
int s2n_hmac_init(struct s2n_hmac_state *state, s2n_hmac_algorithm alg, const void *key, uint32_t klen)
8585
{
8686
s2n_hash_algorithm hash_alg = S2N_HASH_NONE;
87+
state->currently_in_hash_block = 0;
8788
state->digest_size = 0;
8889
state->block_size = 64;
90+
state->hash_block_size = 64;
8991

9092
switch (alg) {
9193
case S2N_HMAC_NONE:
@@ -116,11 +118,13 @@ int s2n_hmac_init(struct s2n_hmac_state *state, s2n_hmac_algorithm alg, const vo
116118
hash_alg = S2N_HASH_SHA384;
117119
state->digest_size = SHA384_DIGEST_LENGTH;
118120
state->block_size = 128;
121+
state->hash_block_size = 128;
119122
break;
120123
case S2N_HMAC_SHA512:
121124
hash_alg = S2N_HASH_SHA512;
122125
state->digest_size = SHA512_DIGEST_LENGTH;
123126
state->block_size = 128;
127+
state->hash_block_size = 128;
124128
break;
125129
default:
126130
S2N_ERROR(S2N_ERR_HMAC_INVALID_ALGORITHM);
@@ -168,6 +172,10 @@ int s2n_hmac_init(struct s2n_hmac_state *state, s2n_hmac_algorithm alg, const vo
168172

169173
int s2n_hmac_update(struct s2n_hmac_state *state, const void *in, uint32_t size)
170174
{
175+
/* Keep track of how much of the current hash block is full */
176+
state->currently_in_hash_block += (128000 + size) % state->hash_block_size;
177+
state->currently_in_hash_block %= state->block_size;
178+
171179
return s2n_hash_update(&state->inner, in, size);
172180
}
173181

@@ -185,6 +193,24 @@ int s2n_hmac_digest(struct s2n_hmac_state *state, void *out, uint32_t size)
185193
return s2n_hash_digest(&state->outer, out, size);
186194
}
187195

196+
int s2n_hmac_digest_two_compression_rounds(struct s2n_hmac_state *state, void *out, uint32_t size)
197+
{
198+
GUARD(s2n_hmac_digest(state, out, size));
199+
200+
/* If there were 8 or more bytes of space left in the current hash block
201+
* then the serialized length will have fit in that block. If there were
202+
* fewer than 8 then adding the length will have caused an extra compression
203+
* block round. This digest function always does two compression rounds,
204+
* even if there is no need for the second.
205+
*/
206+
if (state->currently_in_hash_block > (state->hash_block_size - 8))
207+
{
208+
return 0;
209+
}
210+
211+
return s2n_hash_update(&state->inner, state->xor_pad, state->hash_block_size);
212+
}
213+
188214
int s2n_hmac_reset(struct s2n_hmac_state *state)
189215
{
190216
memcpy_check(&state->inner, &state->inner_just_key, sizeof(state->inner));

crypto/s2n_hmac.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ typedef enum { S2N_HMAC_NONE, S2N_HMAC_MD5, S2N_HMAC_SHA1, S2N_HMAC_SHA224, S2N_
2626
struct s2n_hmac_state {
2727
s2n_hmac_algorithm alg;
2828

29+
uint16_t hash_block_size;
30+
uint32_t currently_in_hash_block;
2931
uint16_t block_size;
3032
uint8_t digest_size;
3133

@@ -45,6 +47,7 @@ extern int s2n_hmac_digest_size(s2n_hmac_algorithm alg);
4547
extern int s2n_hmac_init(struct s2n_hmac_state *state, s2n_hmac_algorithm alg, const void *key, uint32_t klen);
4648
extern int s2n_hmac_update(struct s2n_hmac_state *state, const void *in, uint32_t size);
4749
extern int s2n_hmac_digest(struct s2n_hmac_state *state, void *out, uint32_t size);
50+
extern int s2n_hmac_digest_two_compression_rounds(struct s2n_hmac_state *state, void *out, uint32_t size);
4851
extern int s2n_hmac_digest_verify(const void *a, uint32_t alen, const void *b, uint32_t blen);
4952
extern int s2n_hmac_reset(struct s2n_hmac_state *state);
5053
extern int s2n_hmac_copy(struct s2n_hmac_state *to, struct s2n_hmac_state *from);

tls/s2n_cbc.c

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,6 @@
2525
#include "tls/s2n_record.h"
2626
#include "tls/s2n_prf.h"
2727

28-
static uint8_t masks[256][255];
29-
30-
int s2n_cbc_masks_init()
31-
{
32-
/* We have 256 different 255-byte sized masks for checking padding. 0's indicate where we would expect
33-
* payload or MAC data to be. 0xff's indicate where we expected padding bytes, or the padding length
34-
* byte to be.
35-
*/
36-
for (int i = 0; i < 256; i++) {
37-
memset_check(&masks[i][0], 0, 255 - i);
38-
memset_check(&masks[i][255 - i], 0xFF, i);
39-
}
40-
41-
return 0;
42-
}
43-
4428
/* A TLS CBC record looks like ..
4529
*
4630
* [ Payload data ] [ HMAC ] [ Padding ] [ Padding length byte ]
@@ -81,7 +65,7 @@ int s2n_verify_cbc(struct s2n_connection *conn, struct s2n_hmac_state *hmac, str
8165
/* Check the MAC */
8266
uint8_t check_digest[S2N_MAX_DIGEST_LEN];
8367
lte_check(mac_digest_size, sizeof(check_digest));
84-
GUARD(s2n_hmac_digest(hmac, check_digest, mac_digest_size));
68+
GUARD(s2n_hmac_digest_two_compression_rounds(hmac, check_digest, mac_digest_size));
8569

8670
int mismatches = s2n_constant_time_equals(decrypted->data + payload_length, check_digest, mac_digest_size) ^ 1;
8771

@@ -94,15 +78,15 @@ int s2n_verify_cbc(struct s2n_connection *conn, struct s2n_hmac_state *hmac, str
9478
}
9579

9680
/* Check the padding */
97-
uint8_t *mask = masks[ padding_length ];
98-
9981
int check = 255;
10082
if (check > payload_and_padding_size) {
10183
check = payload_and_padding_size;
10284
}
10385

104-
for (int i = 255 - check, j = decrypted->size - check; i < 255 && j < decrypted->size; i++, j++) {
105-
mismatches |= (decrypted->data[j] ^ padding_length) & mask[i];
86+
int cutoff = check - padding_length;
87+
for (int i = 0, j = decrypted->size - check; i < check && j < decrypted->size; i++, j++) {
88+
uint8_t mask = ~(0xff << ((i >= cutoff) * 8));
89+
mismatches |= (decrypted->data[j] ^ padding_length) & mask;
10690
}
10791

10892
if (mismatches) {

tls/s2n_connection.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ int s2n_connection_wipe(struct s2n_connection *conn)
220220
GUARD(s2n_hash_init(&conn->handshake.server_md5, S2N_HASH_MD5));
221221
GUARD(s2n_hash_init(&conn->handshake.server_sha1, S2N_HASH_SHA1));
222222
GUARD(s2n_hash_init(&conn->handshake.server_sha256, S2N_HASH_SHA256));
223+
GUARD(s2n_hmac_init(&conn->client->client_record_mac, S2N_HMAC_NONE, NULL, 0));
224+
GUARD(s2n_hmac_init(&conn->server->server_record_mac, S2N_HMAC_NONE, NULL, 0));
223225

224226
memcpy_check(&conn->alert_in, &alert_in, sizeof(struct s2n_stuffer));
225227
memcpy_check(&conn->reader_alert_out, &reader_alert_out, sizeof(struct s2n_stuffer));

tls/s2n_record.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,5 @@ extern int s2n_record_write(struct s2n_connection *conn, uint8_t content_type, s
2424
extern int s2n_record_parse(struct s2n_connection *conn);
2525
extern int s2n_record_header_parse(struct s2n_connection *conn, uint8_t *content_type, uint16_t *fragment_length);
2626
extern int s2n_sslv2_record_header_parse(struct s2n_connection *conn, uint8_t *record_type, uint8_t *client_protocol_version, uint16_t *fragment_length);
27-
extern int s2n_cbc_masks_init();
2827
extern int s2n_verify_cbc(struct s2n_connection *conn, struct s2n_hmac_state *hmac, struct s2n_blob *decrypted);
2928
extern int s2n_aead_aad_init(const struct s2n_connection *conn, uint8_t *sequence_number, uint8_t content_type, uint16_t record_length, struct s2n_stuffer *ad);

utils/s2n_random.c

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@
3131

3232
#include "error/s2n_errno.h"
3333

34-
#include "tls/s2n_record.h"
35-
3634
#include "utils/s2n_safety.h"
3735
#include "utils/s2n_random.h"
3836

@@ -197,9 +195,6 @@ int s2n_init(void)
197195
S2N_ERROR(S2N_ERR_OPEN_RANDOM);
198196
}
199197

200-
/* Create the CBC masks */
201-
GUARD(s2n_cbc_masks_init());
202-
203198
#if defined(MAP_INHERIT_ZERO)
204199
if ((zero_if_forked_ptr = mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE,
205200
MAP_ANON|MAP_PRIVATE, -1, 0)) == MAP_FAILED) {

0 commit comments

Comments
 (0)