/src/ntpsec/ntpd/nts_extens.c
Line | Count | Source (jump to first uncovered line) |
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 | 168 | bool extens_server_recv(struct ntspacket_t *ntspacket, uint8_t *pkt, int lng) { |
121 | 168 | struct BufCtl_t buf; |
122 | 168 | uint16_t aead; |
123 | 168 | int noncelen, cmaclen; |
124 | 168 | bool sawcookie, sawAEEF; |
125 | 168 | int cookielen; /* cookie and placeholder(s) */ |
126 | | |
127 | 168 | nts_cnt.server_recv_bad++; /* assume bad, undo if OK */ |
128 | | |
129 | 168 | buf.next = pkt+LEN_PKT_NOMAC; |
130 | 168 | buf.left = lng-LEN_PKT_NOMAC; |
131 | | |
132 | 168 | sawcookie = sawAEEF = false; |
133 | 168 | cookielen = 0; |
134 | 168 | ntspacket->uidlen = 0; |
135 | 168 | ntspacket->needed = 0; |
136 | | |
137 | 883 | while (buf.left >= NTS_KE_HDR_LNG) { |
138 | 833 | uint16_t type; |
139 | 833 | bool critical = false; |
140 | 833 | int length, adlength; |
141 | 833 | size_t outlen; |
142 | 833 | uint8_t *nonce, *cmac; |
143 | 833 | bool ok; |
144 | | |
145 | 833 | type = ex_next_record(&buf, &length); /* length excludes header */ |
146 | 833 | if (length&3 || length > buf.left || length < 0) { |
147 | 53 | return false; |
148 | 53 | } |
149 | 780 | if (NTS_CRITICAL & type) { |
150 | 422 | critical = true; |
151 | 422 | type &= ~NTS_CRITICAL; |
152 | 422 | } |
153 | 780 | switch (type) { |
154 | 289 | case Unique_Identifier: |
155 | 289 | if (length > NTS_UID_MAX_LENGTH) { |
156 | 8 | return false; |
157 | 8 | } |
158 | 281 | ntspacket->uidlen = length; |
159 | 281 | next_bytes(&buf, ntspacket->UID, length); |
160 | 281 | break; |
161 | 21 | case NTS_Cookie: |
162 | | /* cookies and placeholders must be the same length |
163 | | * in order to avoid amplification attacks. |
164 | | */ |
165 | 21 | if (sawcookie) { |
166 | 0 | return false; /* second cookie */ |
167 | 0 | } |
168 | 21 | if (0 == cookielen) { |
169 | 2 | cookielen = length; |
170 | 2 | } |
171 | 19 | else if (length != cookielen) { |
172 | 18 | return false; |
173 | 18 | } |
174 | 3 | ok = nts_unpack_cookie(buf.next, length, &aead, ntspacket->c2s, |
175 | 3 | ntspacket->s2c, &ntspacket->keylen); |
176 | 3 | if (!ok) { |
177 | 3 | return false; |
178 | 3 | } |
179 | 0 | buf.next += length; |
180 | 0 | buf.left -= length; |
181 | 0 | sawcookie = true; |
182 | 0 | ntspacket->needed++; |
183 | 0 | ntspacket->aead = aead; |
184 | 0 | break; |
185 | 449 | case NTS_Cookie_Placeholder: |
186 | 449 | if (0 == cookielen) { |
187 | 362 | cookielen = length; |
188 | 362 | } |
189 | 87 | else if (length != cookielen) { |
190 | 15 | return false; |
191 | 15 | } |
192 | 434 | ntspacket->needed++; |
193 | 434 | buf.next += length; |
194 | 434 | buf.left -= length; |
195 | 434 | break; |
196 | 3 | case NTS_AEEF: |
197 | 3 | if (!sawcookie) { |
198 | 3 | return false; /* no cookie yet, no c2s */ |
199 | 3 | } |
200 | 0 | if (length != NTP_EX_HDR_LNG+NONCE_LENGTH+CMAC_LENGTH) { |
201 | 0 | return false; |
202 | 0 | } |
203 | | /* Additional data is up to this exten. */ |
204 | | /* backup over header */ |
205 | 0 | adlength = buf.next-NTP_EX_HDR_LNG-pkt; |
206 | 0 | noncelen = next_uint16(&buf); |
207 | 0 | cmaclen = next_uint16(&buf); |
208 | 0 | if (noncelen & 3) { |
209 | 0 | return false; /* would require padding */ |
210 | 0 | } |
211 | 0 | if (CMAC_LENGTH != cmaclen) { |
212 | 0 | return false; |
213 | 0 | } |
214 | 0 | nonce = buf.next; |
215 | 0 | cmac = nonce+NONCE_LENGTH; |
216 | 0 | outlen = 6; |
217 | 0 | ok = AES_SIV_Decrypt(wire_ctx, |
218 | 0 | NULL, &outlen, |
219 | 0 | ntspacket->c2s, ntspacket->keylen, |
220 | 0 | nonce, noncelen, |
221 | 0 | cmac, CMAC_LENGTH, |
222 | 0 | pkt, adlength); |
223 | 0 | if (!ok) { |
224 | 0 | return false; |
225 | 0 | } |
226 | 0 | if (0 != outlen) { |
227 | 0 | return false; |
228 | 0 | } |
229 | | /* we already used 2 length slots way above*/ |
230 | 0 | length -= (NTP_EX_U16_LNG+NTP_EX_U16_LNG); |
231 | 0 | buf.next += length; |
232 | 0 | buf.left -= length; |
233 | 0 | if (0 != buf.left) { |
234 | 0 | return false; /* Reject extens after AEEF block */ |
235 | 0 | } |
236 | 0 | sawAEEF = true; |
237 | 0 | break; |
238 | 18 | default: |
239 | | /* Non NTS extensions on requests at server. |
240 | | * Call out when we get some that we want. |
241 | | * Until then, it's probably a bug. */ |
242 | 18 | if (critical) { |
243 | 13 | return false; |
244 | 13 | } |
245 | 5 | buf.next += length; |
246 | 5 | buf.left -= length; |
247 | 5 | return false; |
248 | 780 | } |
249 | 780 | } |
250 | | |
251 | 50 | if (!sawAEEF) { |
252 | 50 | return false; |
253 | 50 | } |
254 | 0 | if (buf.left > 0) |
255 | 0 | return false; |
256 | | |
257 | | // printf("ESRx: %d, %d, %d\n", |
258 | | // lng-LEN_PKT_NOMAC, ntspacket->needed, ntspacket->keylen); |
259 | 0 | ntspacket->valid = true; |
260 | 0 | nts_cnt.server_recv_good++; |
261 | 0 | nts_cnt.server_recv_bad--; |
262 | 0 | return true; |
263 | 0 | } |
264 | | |
265 | 0 | int extens_server_send(struct ntspacket_t *ntspacket, struct pkt *xpkt) { |
266 | 0 | struct BufCtl_t buf; |
267 | 0 | int used, adlength; |
268 | 0 | size_t left; |
269 | 0 | uint8_t *nonce, *packet; |
270 | 0 | uint8_t *plaintext, *ciphertext;; |
271 | 0 | uint8_t cookie[NTS_MAX_COOKIELEN]; |
272 | 0 | int cookielen, plainleng, aeadlen; |
273 | 0 | bool ok; |
274 | | |
275 | | /* get first cookie now so we have length */ |
276 | 0 | cookielen = nts_make_cookie(cookie, ntspacket->aead, |
277 | 0 | ntspacket->c2s, ntspacket->s2c, ntspacket->keylen); |
278 | |
|
279 | 0 | packet = (uint8_t*)xpkt; |
280 | 0 | buf.next = xpkt->exten; |
281 | 0 | buf.left = MAX_EXT_LEN; |
282 | | |
283 | | /* UID */ |
284 | 0 | if (0 < ntspacket->uidlen) |
285 | 0 | ex_append_record_bytes(&buf, Unique_Identifier, |
286 | 0 | ntspacket->UID, ntspacket->uidlen); |
287 | |
|
288 | 0 | adlength = buf.next-packet; /* up to here is Additional Data */ |
289 | | |
290 | | /* length of whole AEEF */ |
291 | 0 | plainleng = ntspacket->needed*(NTP_EX_HDR_LNG+cookielen); |
292 | | /* length of whole AEEF header */ |
293 | 0 | aeadlen = NTP_EX_U16_LNG*2+NONCE_LENGTH+CMAC_LENGTH + plainleng; |
294 | 0 | ex_append_header(&buf, NTS_AEEF, aeadlen); |
295 | 0 | append_uint16(&buf, NONCE_LENGTH); |
296 | 0 | append_uint16(&buf, plainleng+CMAC_LENGTH); |
297 | |
|
298 | 0 | nonce = buf.next; |
299 | 0 | ntp_RAND_bytes(nonce, NONCE_LENGTH); |
300 | 0 | buf.next += NONCE_LENGTH; |
301 | 0 | buf.left -= NONCE_LENGTH; |
302 | |
|
303 | 0 | ciphertext = buf.next; /* cipher text starts here */ |
304 | 0 | left = buf.left; |
305 | 0 | buf.next += CMAC_LENGTH; /* skip space for CMAC */ |
306 | 0 | buf.left -= CMAC_LENGTH; |
307 | 0 | plaintext = buf.next; /* encrypt in place */ |
308 | |
|
309 | 0 | ex_append_record_bytes(&buf, NTS_Cookie, |
310 | 0 | cookie, cookielen); |
311 | 0 | for (int i=1; i<ntspacket->needed; i++) { |
312 | | /* WARN: This may get too big for the MTU. See length calculation above. |
313 | | * Responses are the same length as requests to avoid DDoS amplification. |
314 | | * So if it got to us, there is a good chance it will get back. */ |
315 | 0 | nts_make_cookie(cookie, ntspacket->aead, |
316 | 0 | ntspacket->c2s, ntspacket->s2c, ntspacket->keylen); |
317 | 0 | ex_append_record_bytes(&buf, NTS_Cookie, |
318 | 0 | cookie, cookielen); |
319 | 0 | } |
320 | | |
321 | | //printf("ESSa: %d, %d, %d, %d\n", |
322 | | // adlength, plainleng, cookielen, ntspacket->needed); |
323 | |
|
324 | 0 | ok = AES_SIV_Encrypt(wire_ctx, |
325 | 0 | ciphertext, &left, /* left: in: max out length, out: length used */ |
326 | 0 | ntspacket->s2c, ntspacket->keylen, |
327 | 0 | nonce, NONCE_LENGTH, |
328 | 0 | plaintext, plainleng, |
329 | 0 | packet, adlength); |
330 | 0 | if (!ok) { |
331 | 0 | msyslog(LOG_ERR, "NTS: extens_server_send - Error from AES_SIV_Encrypt"); |
332 | 0 | nts_log_ssl_error(); |
333 | | /* I don't think this should happen, |
334 | | * so crash rather than work incorrectly. |
335 | | * Hal, 2019-Feb-17 |
336 | | * Similar code in nts_cookie |
337 | | */ |
338 | 0 | exit(1); |
339 | 0 | } |
340 | | |
341 | 0 | used = buf.next-xpkt->exten; |
342 | | |
343 | | // printf("ESSx: %lu, %d\n", (long unsigned)left, used); |
344 | |
|
345 | 0 | nts_cnt.server_send++; |
346 | 0 | return used; |
347 | 0 | } |
348 | | |
349 | 0 | bool extens_client_recv(struct peer *peer, uint8_t *pkt, int lng) { |
350 | 0 | struct BufCtl_t buf; |
351 | 0 | int idx; |
352 | 0 | bool sawAEEF = false; |
353 | |
|
354 | 0 | nts_cnt.client_recv_bad++; /* assume bad, undo if OK */ |
355 | |
|
356 | 0 | buf.next = pkt+LEN_PKT_NOMAC; |
357 | 0 | buf.left = lng-LEN_PKT_NOMAC; |
358 | |
|
359 | 0 | while (buf.left >= NTS_KE_HDR_LNG) { |
360 | 0 | uint16_t type; |
361 | 0 | bool critical = false; |
362 | 0 | int length, adlength, noncelen; |
363 | 0 | uint8_t *nonce, *ciphertext, *plaintext; |
364 | 0 | size_t outlen; |
365 | 0 | bool ok; |
366 | |
|
367 | 0 | type = ex_next_record(&buf, &length); /* length excludes header */ |
368 | 0 | if (length&3 || length > buf.left || length < 0) |
369 | 0 | return false; |
370 | 0 | if (NTS_CRITICAL & type) { |
371 | 0 | critical = true; |
372 | 0 | type &= ~NTS_CRITICAL; |
373 | 0 | } |
374 | | // printf("ECR: %d, %d, %d\n", type, length, buf.left); |
375 | 0 | switch (type) { |
376 | 0 | case Unique_Identifier: |
377 | 0 | if (NTS_UID_LENGTH != length) |
378 | 0 | return false; |
379 | 0 | if (0 != memcmp(buf.next, peer->nts_state.UID, NTS_UID_LENGTH)) |
380 | 0 | return false; |
381 | 0 | buf.next += length; |
382 | 0 | buf.left -= length; |
383 | 0 | break; |
384 | 0 | case NTS_Cookie: |
385 | 0 | if (!sawAEEF) |
386 | 0 | return false; /* reject unencrypted cookies */ |
387 | 0 | if (NTS_MAX_COOKIES <= peer->nts_state.count) |
388 | 0 | return false; /* reject extra cookies */ |
389 | 0 | if (length != peer->nts_state.cookielen) |
390 | 0 | return false; /* reject length change */ |
391 | 0 | idx = peer->nts_state.writeIdx++; |
392 | 0 | memcpy((uint8_t*)&peer->nts_state.cookies[idx], buf.next, length); |
393 | 0 | peer->nts_state.writeIdx = peer->nts_state.writeIdx % NTS_MAX_COOKIES; |
394 | 0 | peer->nts_state.count++; |
395 | 0 | buf.next += length; |
396 | 0 | buf.left -= length; |
397 | 0 | break; |
398 | 0 | case NTS_AEEF: |
399 | 0 | adlength = buf.next-NTP_EX_HDR_LNG-pkt; /* backup over header */ |
400 | 0 | if (NTP_EX_U16_LNG*2 > length) |
401 | 0 | return false; /* garbage packet */ |
402 | 0 | noncelen = next_uint16(&buf); |
403 | 0 | outlen = next_uint16(&buf); |
404 | 0 | if (noncelen&3 || outlen&3) |
405 | 0 | return false; /* else round up */ |
406 | 0 | nonce = buf.next; |
407 | 0 | ciphertext = nonce+noncelen; |
408 | 0 | plaintext = ciphertext+CMAC_LENGTH; |
409 | 0 | if (noncelen+CMAC_LENGTH > length) |
410 | 0 | return false; /* garbage packet */ |
411 | 0 | outlen = buf.left-noncelen-CMAC_LENGTH; |
412 | | // printf("ECRa: %lu, %d\n", (long unsigned)outlen, noncelen); |
413 | 0 | ok = AES_SIV_Decrypt(wire_ctx, |
414 | 0 | plaintext, &outlen, |
415 | 0 | peer->nts_state.s2c, peer->nts_state.keylen, |
416 | 0 | nonce, noncelen, |
417 | 0 | ciphertext, outlen+CMAC_LENGTH, |
418 | 0 | pkt, adlength); |
419 | | // printf("ECRb: %d, %lu\n", ok, (long unsigned)outlen); |
420 | 0 | if (!ok) |
421 | 0 | return false; |
422 | | /* setup to process encrypted headers */ |
423 | 0 | buf.next += noncelen+CMAC_LENGTH; |
424 | 0 | buf.left -= noncelen+CMAC_LENGTH; |
425 | 0 | sawAEEF = true; |
426 | 0 | break; |
427 | 0 | default: |
428 | | /* Non NTS extensions on reply from server. |
429 | | * Call out when we get some that we want. |
430 | | * For now, it's probably a bug. */ |
431 | 0 | if (critical) |
432 | 0 | return false; |
433 | 0 | buf.next += length; |
434 | 0 | buf.left -= length; |
435 | 0 | return false; |
436 | 0 | } |
437 | 0 | } |
438 | | |
439 | | // printf("ECRx: %d, %d %d, %d\n", sawAEEF, peer->nts_state.count, |
440 | | // peer->nts_state.writeIdx, peer->nts_state.readIdx); |
441 | 0 | if (!sawAEEF) { |
442 | 0 | return false; |
443 | 0 | } |
444 | 0 | if (buf.left > 0) |
445 | 0 | return false; |
446 | 0 | nts_cnt.client_recv_good++; |
447 | 0 | nts_cnt.client_recv_bad--; |
448 | 0 | return true; |
449 | 0 | } |
450 | | /* end */ |