Coverage Report

Created: 2026-03-11 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ntpsec/ntpd/nts_extens.c
Line
Count
Source
1
/*
2
 * ntp_extens.c - Network Time Protocol (NTP) extension processing
3
 * Copyright the NTPsec project contributors
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 *
6
 * NB: This module is working with the wire format packet.
7
 *     It must do byte swapping.
8
 *
9
 * We carefully arrange things so that no padding is necessary.
10
 *
11
 * This is only called by the main ntpd thread, so we don't need
12
 * a lock to protect wire_ctx.
13
 */
14
15
#include "config.h"
16
17
#include <stdbool.h>
18
#include <stdint.h>
19
#include <string.h>
20
21
#include <aes_siv.h>
22
23
#include "ntp_stdlib.h"
24
#include "ntp.h"
25
#include "ntpd.h"
26
#include "nts.h"
27
#include "nts2.h"
28
29
0
#define NONCE_LENGTH 16
30
0
#define CMAC_LENGTH 16
31
32
0
#define NTP_EX_HDR_LNG 4
33
0
#define NTP_EX_U16_LNG 2
34
35
enum NtpExtFieldType {
36
  Unique_Identifier = 0x104,
37
  NTS_Cookie = 0x204,
38
  NTS_Cookie_Placeholder = 0x304,
39
  NTS_AEEF = 0x404 /* Authenticated and Encrypted Extension Fields */
40
};
41
42
/* This is only called by the main thread so we don't need a lock. */
43
AES_SIV_CTX* wire_ctx = NULL;
44
45
46
0
bool extens_init(void) {
47
0
  wire_ctx = AES_SIV_CTX_new();
48
0
  if (NULL == wire_ctx) {
49
0
    msyslog(LOG_ERR, "NTS: Can't init wire_ctx");
50
0
    exit(1);
51
0
  }
52
0
  return true;
53
0
}
54
55
0
int extens_client_send(struct peer *peer, struct pkt *xpkt) {
56
0
  struct BufCtl_t buf;
57
0
  int used, adlength, idx;
58
0
  size_t left;
59
0
  uint8_t *nonce, *packet;
60
0
  bool ok;
61
62
0
  packet = (uint8_t*)xpkt;
63
0
  buf.next = xpkt->exten;
64
0
  buf.left = MAX_EXT_LEN;
65
66
  /* UID */
67
0
  ntp_RAND_bytes(peer->nts_state.UID, NTS_UID_LENGTH);
68
0
  ex_append_record_bytes(&buf, Unique_Identifier,
69
0
             peer->nts_state.UID, NTS_UID_LENGTH);
70
71
  /* cookie */
72
0
  idx = peer->nts_state.readIdx++;
73
0
  ex_append_record_bytes(&buf, NTS_Cookie,
74
0
             peer->nts_state.cookies[idx], peer->nts_state.cookielen);
75
0
  peer->nts_state.readIdx = peer->nts_state.readIdx % NTS_MAX_COOKIES;
76
0
  peer->nts_state.count--;
77
78
  /* Need more cookies? */
79
0
  for (int i=peer->nts_state.count+1; i<NTS_MAX_COOKIES; i++) {
80
    /* WARN: This may get too big for the MTU. */
81
0
    ex_append_header(&buf, NTS_Cookie_Placeholder, peer->nts_state.cookielen);
82
0
    memset(buf.next, 0, peer->nts_state.cookielen);
83
0
    buf.next += peer->nts_state.cookielen;
84
0
    buf.left -= peer->nts_state.cookielen;
85
0
  }
86
87
  /* AEAD */
88
0
  adlength = buf.next-packet;
89
0
  ex_append_header(&buf, NTS_AEEF, NTP_EX_U16_LNG*2+NONCE_LENGTH+CMAC_LENGTH);
90
0
  append_uint16(&buf, NONCE_LENGTH);
91
0
  append_uint16(&buf, CMAC_LENGTH);
92
0
  nonce = buf.next;
93
0
  ntp_RAND_bytes(nonce, NONCE_LENGTH);
94
0
  buf.next += NONCE_LENGTH;
95
0
  buf.left -= NONCE_LENGTH;
96
0
  left = buf.left;
97
0
  ok = AES_SIV_Encrypt(wire_ctx,
98
0
           buf.next, &left,   /* left: in: max out length, out: length used */
99
0
           peer->nts_state.c2s, peer->nts_state.keylen,
100
0
           nonce, NONCE_LENGTH,
101
0
           NULL, 0,           /* no plain/cipher text */
102
0
           packet, adlength);
103
0
  if (!ok) {
104
0
    msyslog(LOG_ERR, "NTS: extens_client_send - Error from AES_SIV_Encrypt");
105
    /* I don't think this should happen,
106
     * so crash rather than work incorrectly.
107
     * Hal, 2019-Feb-17
108
     * Similar code in nts_cookie
109
     */
110
0
    exit(1);
111
0
  }
112
0
  buf.next += left;
113
0
  buf.left -= left;
114
115
0
  used = buf.next-xpkt->exten;
116
0
  nts_cnt.client_send++;
117
0
  return used;
118
0
}
119
120
152
bool extens_server_recv(struct ntspacket_t *ntspacket, uint8_t *pkt, int lng) {
121
152
  struct BufCtl_t buf;
122
152
  uint16_t aead;
123
152
  int noncelen, cmaclen;
124
152
  bool sawcookie, sawAEEF;
125
152
  int cookielen;      /* cookie and placeholder(s) */
126
127
152
  nts_cnt.server_recv_bad++;    /* assume bad, undo if OK */
128
129
152
  buf.next = pkt+LEN_PKT_NOMAC;
130
152
  buf.left = lng-LEN_PKT_NOMAC;
131
132
152
  sawcookie = sawAEEF = false;
133
152
  cookielen = 0;
134
152
  ntspacket->uidlen = 0;
135
152
  ntspacket->needed = 0;
136
137
880
  while (buf.left >= NTS_KE_HDR_LNG) {
138
827
    uint16_t type;
139
827
    int length, adlength;
140
827
    size_t outlen;
141
827
    uint8_t *nonce, *cmac;
142
827
    bool ok;
143
144
827
    type = ex_next_record(&buf, &length); /* length excludes header */
145
827
    if (length&3 || length > buf.left || length < 0) {
146
58
      return false;
147
58
    }
148
769
    switch (type) {
149
208
        case Unique_Identifier:
150
208
      if (length > NTS_UID_MAX_LENGTH) {
151
6
        return false;
152
6
      }
153
202
      ntspacket->uidlen = length;
154
202
      next_bytes(&buf, ntspacket->UID, length);
155
202
      break;
156
19
        case NTS_Cookie:
157
      /* cookies and placeholders must be the same length
158
       * in order to avoid amplification attacks.
159
       */
160
19
      if (sawcookie) {
161
0
        return false; /* second cookie */
162
0
      }
163
19
      if (0 == cookielen) {
164
1
        cookielen = length;
165
1
      }
166
18
      else if (length != cookielen) {
167
17
        return false;
168
17
      }
169
2
      ok = nts_unpack_cookie(buf.next, length, &aead, ntspacket->c2s,
170
2
                 ntspacket->s2c, &ntspacket->keylen);
171
2
      if (!ok) {
172
2
        return false;
173
2
      }
174
0
      buf.next += length;
175
0
      buf.left -= length;
176
0
      sawcookie = true;
177
0
      ntspacket->needed++;
178
0
      ntspacket->aead = aead;
179
0
      break;
180
342
        case NTS_Cookie_Placeholder:
181
342
      if (0 == cookielen) {
182
260
        cookielen = length;
183
260
      }
184
82
      else if (length != cookielen) {
185
15
        return false;
186
15
      }
187
327
      ntspacket->needed++;
188
327
      buf.next += length;
189
327
      buf.left -= length;
190
327
      break;
191
1
        case NTS_AEEF:
192
1
      if (!sawcookie) {
193
1
        return false; /* no cookie yet, no c2s */
194
1
      }
195
0
      if (length != NTP_EX_HDR_LNG+NONCE_LENGTH+CMAC_LENGTH) {
196
0
        return false;
197
0
      }
198
      /* Additional data is up to this exten. */
199
      /* backup over header */
200
0
      adlength = buf.next-NTP_EX_HDR_LNG-pkt;
201
0
      noncelen = next_uint16(&buf);
202
0
      cmaclen = next_uint16(&buf);
203
0
      if (noncelen & 3) {
204
0
        return false; /* would require padding */
205
0
      }
206
0
      if (CMAC_LENGTH != cmaclen) {
207
0
        return false;
208
0
      }
209
0
      nonce = buf.next;
210
0
      cmac = nonce+NONCE_LENGTH;
211
0
      outlen = 6;
212
0
      ok = AES_SIV_Decrypt(wire_ctx,
213
0
               NULL, &outlen,
214
0
               ntspacket->c2s, ntspacket->keylen,
215
0
               nonce, noncelen,
216
0
               cmac, CMAC_LENGTH,
217
0
               pkt, adlength);
218
0
      if (!ok) {
219
0
        return false;
220
0
      }
221
0
      if (0 != outlen) {
222
0
        return false;
223
0
      }
224
      /* we already used 2 length slots way above*/
225
0
      length -= (NTP_EX_U16_LNG+NTP_EX_U16_LNG);
226
0
      buf.next += length;
227
0
      buf.left -= length;
228
0
      if (0 != buf.left) {
229
0
        return false; /* Reject extens after AEEF block */
230
0
      }
231
0
      sawAEEF = true;
232
0
      break;
233
199
        default:
234
      /* Non NTS extensions on requests at server.
235
       * Call out when we get some that we want.
236
       * Until then, just ignore it */
237
199
      buf.next += length;
238
199
      buf.left -= length;
239
199
      break;
240
769
    }
241
769
  }
242
243
53
  if (!sawAEEF) {
244
53
    return false;
245
53
  }
246
0
  if (buf.left > 0)
247
0
    return false;
248
249
  //  printf("ESRx: %d, %d, %d\n",
250
  //      lng-LEN_PKT_NOMAC, ntspacket->needed, ntspacket->keylen);
251
0
  ntspacket->valid = true;
252
0
  nts_cnt.server_recv_good++;
253
0
  nts_cnt.server_recv_bad--;
254
0
  return true;
255
0
}
256
257
0
int extens_server_send(struct ntspacket_t *ntspacket, struct pkt *xpkt) {
258
0
  struct BufCtl_t buf;
259
0
  int used, adlength;
260
0
  size_t left;
261
0
  uint8_t *nonce, *packet;
262
0
  uint8_t *plaintext, *ciphertext;;
263
0
  uint8_t cookie[NTS_MAX_COOKIELEN];
264
0
  int cookielen, plainleng, aeadlen;
265
0
  bool ok;
266
267
  /* get first cookie now so we have length */
268
0
  cookielen = nts_make_cookie(cookie, ntspacket->aead,
269
0
            ntspacket->c2s, ntspacket->s2c, ntspacket->keylen);
270
271
0
  packet = (uint8_t*)xpkt;
272
0
  buf.next = xpkt->exten;
273
0
  buf.left = MAX_EXT_LEN;
274
275
  /* UID */
276
0
  if (0 < ntspacket->uidlen)
277
0
    ex_append_record_bytes(&buf, Unique_Identifier,
278
0
               ntspacket->UID, ntspacket->uidlen);
279
280
0
  adlength = buf.next-packet;   /* up to here is Additional Data */
281
282
  /* length of whole AEEF */
283
0
  plainleng = ntspacket->needed*(NTP_EX_HDR_LNG+cookielen);
284
  /* length of whole AEEF header */
285
0
  aeadlen = NTP_EX_U16_LNG*2+NONCE_LENGTH+CMAC_LENGTH + plainleng;
286
0
  ex_append_header(&buf, NTS_AEEF, aeadlen);
287
0
  append_uint16(&buf, NONCE_LENGTH);
288
0
  append_uint16(&buf, plainleng+CMAC_LENGTH);
289
290
0
  nonce = buf.next;
291
0
  ntp_RAND_bytes(nonce, NONCE_LENGTH);
292
0
  buf.next += NONCE_LENGTH;
293
0
  buf.left -= NONCE_LENGTH;
294
295
0
  ciphertext = buf.next;  /* cipher text starts here */
296
0
  left = buf.left;
297
0
  buf.next += CMAC_LENGTH; /* skip space for CMAC */
298
0
  buf.left -= CMAC_LENGTH;
299
0
  plaintext = buf.next;   /* encrypt in place */
300
301
0
  ex_append_record_bytes(&buf, NTS_Cookie,
302
0
             cookie, cookielen);
303
0
  for (int i=1; i<ntspacket->needed; i++) {
304
    /* WARN: This may get too big for the MTU. See length calculation above.
305
     * Responses are the same length as requests to avoid DDoS amplification.
306
     * So if it got to us, there is a good chance it will get back.  */
307
0
    nts_make_cookie(cookie, ntspacket->aead,
308
0
        ntspacket->c2s, ntspacket->s2c, ntspacket->keylen);
309
0
    ex_append_record_bytes(&buf, NTS_Cookie,
310
0
               cookie, cookielen);
311
0
  }
312
313
  //printf("ESSa: %d, %d, %d, %d\n",
314
  //  adlength, plainleng, cookielen, ntspacket->needed);
315
316
0
  ok = AES_SIV_Encrypt(wire_ctx,
317
0
           ciphertext, &left,   /* left: in: max out length, out: length used */
318
0
           ntspacket->s2c, ntspacket->keylen,
319
0
           nonce, NONCE_LENGTH,
320
0
           plaintext, plainleng,
321
0
           packet, adlength);
322
0
  if (!ok) {
323
0
    msyslog(LOG_ERR, "NTS: extens_server_send - Error from AES_SIV_Encrypt");
324
0
    nts_log_ssl_error();
325
    /* I don't think this should happen,
326
     * so crash rather than work incorrectly.
327
     * Hal, 2019-Feb-17
328
     * Similar code in nts_cookie
329
     */
330
0
    exit(1);
331
0
  }
332
333
0
  used = buf.next-xpkt->exten;
334
335
  // printf("ESSx: %lu, %d\n", (long unsigned)left, used);
336
337
0
  nts_cnt.server_send++;
338
0
  return used;
339
0
}
340
341
0
bool extens_client_recv(struct peer *peer, uint8_t *pkt, int lng) {
342
0
  struct BufCtl_t buf;
343
0
  int idx;
344
0
  bool sawAEEF = false;
345
346
0
  nts_cnt.client_recv_bad++;  /* assume bad, undo if OK */
347
348
0
  buf.next = pkt+LEN_PKT_NOMAC;
349
0
  buf.left = lng-LEN_PKT_NOMAC;
350
351
0
  while (buf.left >= NTS_KE_HDR_LNG) {
352
0
    uint16_t type;
353
0
    int length, adlength, noncelen;
354
0
    uint8_t *nonce, *ciphertext, *plaintext;
355
0
    size_t outlen;
356
0
    bool ok;
357
358
0
    type = ex_next_record(&buf, &length); /* length excludes header */
359
0
    if (length&3 || length > buf.left || length < 0)
360
0
      return false;
361
    //     printf("ECR: %d, %d, %d\n", type, length, buf.left);
362
0
    switch (type) {
363
0
        case Unique_Identifier:
364
0
      if (NTS_UID_LENGTH != length)
365
0
        return false;
366
0
      if (0 != memcmp(buf.next, peer->nts_state.UID, NTS_UID_LENGTH))
367
0
        return false;
368
0
      buf.next += length;
369
0
      buf.left -= length;
370
0
      break;
371
0
        case NTS_Cookie:
372
0
      if (!sawAEEF)
373
0
        return false;     /* reject unencrypted cookies */
374
0
      if (NTS_MAX_COOKIES <= peer->nts_state.count)
375
0
        return false;     /* reject extra cookies */
376
0
      if (length != peer->nts_state.cookielen)
377
0
        return false;     /* reject length change */
378
0
      idx = peer->nts_state.writeIdx++;
379
0
      memcpy((uint8_t*)&peer->nts_state.cookies[idx], buf.next, length);
380
0
      peer->nts_state.writeIdx = peer->nts_state.writeIdx % NTS_MAX_COOKIES;
381
0
      peer->nts_state.count++;
382
0
      buf.next += length;
383
0
      buf.left -= length;
384
0
      break;
385
0
        case NTS_AEEF:
386
0
      adlength = buf.next-NTP_EX_HDR_LNG-pkt;  /* backup over header */
387
0
      if (NTP_EX_U16_LNG*2 > length)
388
0
        return false;        /* garbage packet */
389
0
      noncelen = next_uint16(&buf);
390
0
      outlen = next_uint16(&buf);
391
0
      if (noncelen&3 || outlen&3)
392
0
        return false;        /* else round up */
393
0
      nonce = buf.next;
394
0
      ciphertext = nonce+noncelen;
395
0
      plaintext = ciphertext+CMAC_LENGTH;
396
0
      if (noncelen+CMAC_LENGTH > length)
397
0
        return false;        /* garbage packet */
398
0
      outlen = buf.left-noncelen-CMAC_LENGTH;
399
      //      printf("ECRa: %lu, %d\n", (long unsigned)outlen, noncelen);
400
0
      ok = AES_SIV_Decrypt(wire_ctx,
401
0
               plaintext, &outlen,
402
0
               peer->nts_state.s2c, peer->nts_state.keylen,
403
0
               nonce, noncelen,
404
0
               ciphertext, outlen+CMAC_LENGTH,
405
0
               pkt, adlength);
406
      //      printf("ECRb: %d, %lu\n", ok, (long unsigned)outlen);
407
0
      if (!ok)
408
0
        return false;
409
      /* setup to process encrypted headers */
410
0
      buf.next += noncelen+CMAC_LENGTH;
411
0
      buf.left -= noncelen+CMAC_LENGTH;
412
0
      sawAEEF = true;
413
0
      break;
414
0
        default:
415
      /* Non NTS extensions on reply from server.
416
       * Call out when we get some that we want.
417
       * For now, just ignore it */
418
0
      buf.next += length;
419
0
      buf.left -= length;
420
0
      break;
421
0
    }
422
0
  }
423
424
  //  printf("ECRx: %d, %d  %d, %d\n", sawAEEF, peer->nts_state.count,
425
  //      peer->nts_state.writeIdx, peer->nts_state.readIdx);
426
0
  if (!sawAEEF) {
427
0
    return false;
428
0
  }
429
0
  if (buf.left > 0)
430
0
    return false;
431
0
  nts_cnt.client_recv_good++;
432
0
  nts_cnt.client_recv_bad--;
433
  return true;
434
0
}
435
/* end */