Coverage Report

Created: 2023-03-26 07:33

/src/gnutls/lib/stek.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (C) 2018 Free Software Foundation, Inc.
3
 *
4
 * Author: Ander Juaristi
5
 *
6
 * This file is part of GnuTLS.
7
 *
8
 * The GnuTLS is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public License
10
 * as published by the Free Software Foundation; either version 2.1 of
11
 * the License, or (at your option) any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful, but
14
 * WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public License
19
 * along with this program.  If not, see <https://www.gnu.org/licenses/>
20
 *
21
 */
22
#include "gnutls_int.h"
23
#include "stek.h"
24
25
0
#define NAME_POS (0)
26
0
#define KEY_POS (TICKET_KEY_NAME_SIZE)
27
0
#define MAC_SECRET_POS (TICKET_KEY_NAME_SIZE+TICKET_CIPHER_KEY_SIZE)
28
29
static int totp_sha3(gnutls_session_t session,
30
         uint64_t t,
31
         const gnutls_datum_t * secret,
32
         uint8_t out[TICKET_MASTER_KEY_SIZE])
33
0
{
34
0
  int retval;
35
0
  uint8_t t_be[8];
36
0
  digest_hd_st hd;
37
  /*
38
   * We choose SHA3-512 because it outputs 64 bytes,
39
   * just the same length as the ticket key.
40
   */
41
0
  const gnutls_digest_algorithm_t algo = GNUTLS_DIG_SHA3_512;
42
#if TICKET_MASTER_KEY_SIZE != 64
43
# error "TICKET_MASTER_KEY_SIZE must be 64 bytes"
44
#endif
45
46
0
  if (unlikely(secret == NULL))
47
0
    return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
48
49
0
  if ((retval = _gnutls_hash_init(&hd, hash_to_entry(algo))) < 0)
50
0
    return gnutls_assert_val(retval);
51
52
0
  _gnutls_write_uint64(t, t_be);
53
54
0
  if ((retval = _gnutls_hash(&hd, t_be, sizeof(t_be))) < 0)
55
0
    return gnutls_assert_val(retval);
56
0
  if ((retval = _gnutls_hash(&hd, secret->data, secret->size)) < 0)
57
0
    return gnutls_assert_val(retval);
58
59
0
  _gnutls_hash_deinit(&hd, out);
60
0
  return GNUTLS_E_SUCCESS;
61
0
}
62
63
static uint64_t T(gnutls_session_t session, time_t t)
64
0
{
65
0
  uint64_t numeral = t;
66
0
  unsigned int x =
67
0
      session->internals.expire_time * STEK_ROTATION_PERIOD_PRODUCT;
68
69
0
  if (numeral <= 0)
70
0
    return 0;
71
72
0
  return (numeral / x);
73
0
}
74
75
static int64_t totp_next(gnutls_session_t session)
76
0
{
77
0
  time_t t;
78
0
  uint64_t result;
79
80
0
  t = gnutls_time(NULL);
81
0
  if (unlikely(t == (time_t) - 1))
82
0
    return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
83
84
0
  result = T(session, t);
85
0
  if (result == 0)
86
0
    return 0;
87
88
0
  if (result == session->key.totp.last_result)
89
0
    return 0;
90
91
0
  return result;
92
0
}
93
94
static int64_t totp_previous(gnutls_session_t session)
95
0
{
96
0
  uint64_t result;
97
98
0
  if (session->key.totp.last_result == 0)
99
0
    return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
100
0
  if (!session->key.totp.was_rotated)
101
0
    return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
102
103
0
  result = session->key.totp.last_result - 1;
104
0
  if (result == 0)
105
0
    return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
106
107
0
  return result;
108
0
}
109
110
static void call_rotation_callback(gnutls_session_t session,
111
           uint8_t key[TICKET_MASTER_KEY_SIZE],
112
           uint64_t t)
113
0
{
114
0
  gnutls_datum_t prev_key, new_key;
115
116
0
  if (session->key.totp.cb) {
117
0
    new_key.data = key;
118
0
    new_key.size = TICKET_MASTER_KEY_SIZE;
119
0
    prev_key.data = session->key.session_ticket_key;
120
0
    prev_key.size = TICKET_MASTER_KEY_SIZE;
121
122
0
    session->key.totp.cb(&prev_key, &new_key, t);
123
0
  }
124
0
}
125
126
static int rotate(gnutls_session_t session)
127
0
{
128
0
  int64_t t;
129
0
  gnutls_datum_t secret;
130
0
  uint8_t key[TICKET_MASTER_KEY_SIZE];
131
132
  /* Do we need to calculate new totp? */
133
0
  t = totp_next(session);
134
0
  if (t > 0) {
135
0
    secret.data = session->key.initial_stek;
136
0
    secret.size = TICKET_MASTER_KEY_SIZE;
137
138
    /* Generate next key */
139
0
    if (totp_sha3(session, t, &secret, key) < 0) {
140
0
      gnutls_assert();
141
0
      return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
142
0
    }
143
144
    /* Replace old key with new one, and call callback if provided */
145
0
    call_rotation_callback(session, key, t);
146
0
    session->key.totp.last_result = t;
147
0
    _gnutls_memory_mark_defined(session->key.session_ticket_key,
148
0
              sizeof(key));
149
0
    memcpy(session->key.session_ticket_key, key, sizeof(key));
150
151
0
    session->key.totp.was_rotated = 1;
152
0
  } else if (t < 0) {
153
0
    return gnutls_assert_val(t);
154
0
  }
155
156
0
  return GNUTLS_E_SUCCESS;
157
0
}
158
159
static int rotate_back_and_peek(gnutls_session_t session,
160
        uint8_t key[TICKET_MASTER_KEY_SIZE])
161
0
{
162
0
  int64_t t;
163
0
  gnutls_datum_t secret;
164
165
  /* Get the previous TOTP */
166
0
  t = totp_previous(session);
167
0
  if (t < 0)
168
0
    return gnutls_assert_val(t);
169
170
0
  secret.data = session->key.initial_stek;
171
0
  secret.size = TICKET_MASTER_KEY_SIZE;
172
173
0
  if (totp_sha3(session, t, &secret, key) < 0)
174
0
    return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
175
176
0
  return 0;
177
0
}
178
179
/*
180
 * _gnutls_get_session_ticket_encryption_key:
181
 * @key_name: an empty datum that will receive the key name part of the STEK
182
 * @mac_key: an empty datum that will receive the MAC key part of the STEK
183
 * @enc_key: an empty datum that will receive the encryption key part of the STEK
184
 *
185
 * Get the currently active session ticket encryption key (STEK).
186
 *
187
 * The STEK is a 64-byte blob which is further divided in three parts,
188
 * and this function requires the caller to supply three separate datums for each one.
189
 * Though the caller might omit one or more of those if not interested in that part of the STEK.
190
 *
191
 * These are the three parts the STEK is divided in:
192
 *
193
 *  - Key name: 16 bytes
194
 *  - Encryption key: 32 bytes
195
 *  - MAC key: 16 bytes
196
 *
197
 * This function will transparently rotate the key, if the time has come for that,
198
 * before returning it to the caller.
199
 */
200
int _gnutls_get_session_ticket_encryption_key(gnutls_session_t session,
201
                gnutls_datum_t * key_name,
202
                gnutls_datum_t * mac_key,
203
                gnutls_datum_t * enc_key)
204
0
{
205
0
  int retval;
206
207
0
  if (unlikely(session == NULL)) {
208
0
    gnutls_assert();
209
0
    return GNUTLS_E_INTERNAL_ERROR;
210
0
  }
211
212
0
  if (!session->key.stek_initialized) {
213
0
    return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
214
0
  }
215
216
0
  if ((retval = rotate(session)) < 0)
217
0
    return gnutls_assert_val(retval);
218
219
  /* Copy key parts to user-supplied datums (if provided) */
220
0
  if (key_name) {
221
0
    key_name->data = &session->key.session_ticket_key[NAME_POS];
222
0
    key_name->size = TICKET_KEY_NAME_SIZE;
223
0
  }
224
0
  if (mac_key) {
225
0
    mac_key->data =
226
0
        &session->key.session_ticket_key[MAC_SECRET_POS];
227
0
    mac_key->size = TICKET_MAC_SECRET_SIZE;
228
0
  }
229
0
  if (enc_key) {
230
0
    enc_key->data = &session->key.session_ticket_key[KEY_POS];
231
0
    enc_key->size = TICKET_CIPHER_KEY_SIZE;
232
0
  }
233
234
0
  return retval;
235
0
}
236
237
/*
238
 * _gnutls_get_session_ticket_decryption_key:
239
 * @ticket_data: the bytes of a session ticket that must be decrypted
240
 * @key_name: an empty datum that will receive the key name part of the STEK
241
 * @mac_key: an empty datum that will receive the MAC key part of the STEK
242
 * @enc_key: an empty datum that will receive the encryption key part of the STEK
243
 *
244
 * Get the key (STEK) the given session ticket was encrypted with.
245
 *
246
 * As with its encryption counterpart (%_gnutls_get_session_ticket_encryption_key),
247
 * this function will also transparently rotate
248
 * the currently active STEK if time has come for that, and it also requires the different
249
 * parts of the STEK to be obtained in different datums.
250
 *
251
 * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, or a negative error code, such as
252
 * %GNUTLS_E_REQUSTED_DATA_NOT_AVAILABLE if no key could be found for the supplied ticket.
253
 */
254
int _gnutls_get_session_ticket_decryption_key(gnutls_session_t session,
255
                const gnutls_datum_t *
256
                ticket_data,
257
                gnutls_datum_t * key_name,
258
                gnutls_datum_t * mac_key,
259
                gnutls_datum_t * enc_key)
260
0
{
261
0
  int retval;
262
0
  uint8_t *key_data;
263
264
0
  if (unlikely
265
0
      (session == NULL || ticket_data == NULL
266
0
       || ticket_data->data == NULL))
267
0
    return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
268
269
0
  if (ticket_data->size < TICKET_KEY_NAME_SIZE)
270
0
    return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
271
272
0
  if (!session->key.stek_initialized) {
273
0
    return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
274
0
  }
275
276
0
  if ((retval = rotate(session)) < 0)
277
0
    return gnutls_assert_val(retval);
278
279
  /*
280
   * Is current key valid?
281
   * We compare the first 16 bytes --> The key_name field.
282
   */
283
0
  if (memcmp(ticket_data->data,
284
0
       &session->key.session_ticket_key[NAME_POS],
285
0
       TICKET_KEY_NAME_SIZE) == 0) {
286
0
    key_data = session->key.session_ticket_key;
287
0
    goto key_found;
288
0
  }
289
290
  /*
291
   * Current key is not valid.
292
   * Compute previous key and see if that matches.
293
   */
294
0
  _gnutls_memory_mark_defined(session->key.previous_ticket_key,
295
0
            TICKET_MASTER_KEY_SIZE);
296
0
  if ((retval =
297
0
       rotate_back_and_peek(session,
298
0
          session->key.previous_ticket_key)) < 0) {
299
0
    _gnutls_memory_mark_undefined(session->key.previous_ticket_key,
300
0
                TICKET_MASTER_KEY_SIZE);
301
0
    return gnutls_assert_val(retval);
302
0
  }
303
304
0
  if (memcmp(ticket_data->data,
305
0
       &session->key.previous_ticket_key[NAME_POS],
306
0
       TICKET_KEY_NAME_SIZE) == 0) {
307
0
    key_data = session->key.previous_ticket_key;
308
0
    goto key_found;
309
0
  }
310
311
0
  return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
312
313
0
 key_found:
314
0
  if (key_name) {
315
0
    key_name->data = &key_data[NAME_POS];
316
0
    key_name->size = TICKET_KEY_NAME_SIZE;
317
0
  }
318
0
  if (mac_key) {
319
0
    mac_key->data = &key_data[MAC_SECRET_POS];
320
0
    mac_key->size = TICKET_MAC_SECRET_SIZE;
321
0
  }
322
0
  if (enc_key) {
323
0
    enc_key->data = &key_data[KEY_POS];
324
0
    enc_key->size = TICKET_CIPHER_KEY_SIZE;
325
0
  }
326
327
0
  return GNUTLS_E_SUCCESS;
328
0
}
329
330
/*
331
 * _gnutls_initialize_session_ticket_key_rotation:
332
 * @key: Initial session ticket key
333
 *
334
 * Initialize the session ticket key rotation.
335
 *
336
 * This function will not enable session ticket keys on the server side. That is done
337
 * with the gnutls_session_ticket_enable_server() function. This function just initializes
338
 * the internal state to support periodical rotation of the session ticket encryption key.
339
 *
340
 * Returns: %GNUTLS_E_SUCCESS (0) on success, or %GNUTLS_E_INVALID_REQUEST on error.
341
 */
342
int _gnutls_initialize_session_ticket_key_rotation(gnutls_session_t session,
343
               const gnutls_datum_t * key)
344
0
{
345
0
  if (unlikely(session == NULL || key == NULL))
346
0
    return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
347
348
0
  if (unlikely(session->key.totp.last_result != 0))
349
0
    return GNUTLS_E_INVALID_REQUEST;
350
351
0
  _gnutls_memory_mark_defined(session->key.initial_stek,
352
0
            TICKET_MASTER_KEY_SIZE);
353
0
  memcpy(session->key.initial_stek, key->data, key->size);
354
355
0
  session->key.stek_initialized = true;
356
0
  session->key.totp.was_rotated = 0;
357
0
  return 0;
358
0
}
359
360
/*
361
 * _gnutls_set_session_ticket_key_rotation_callback:
362
 * @cb: the callback function
363
 *
364
 * Set a callback function that will be invoked every time the session ticket key
365
 * is rotated.
366
 *
367
 * The function will take as arguments the previous key, the new key and the time
368
 * step value that caused the key to rotate.
369
 *
370
 */
371
void _gnutls_set_session_ticket_key_rotation_callback(gnutls_session_t session,
372
                  gnutls_stek_rotation_callback_t
373
                  cb)
374
0
{
375
0
  if (session)
376
0
    session->key.totp.cb = cb;
377
0
}