/src/ntpsec/ntpd/nts_cookie.c
Line | Count | Source |
1 | | /* |
2 | | * nts_cookie.c - Network Time Security (NTS) cookie processing |
3 | | * Copyright the NTPsec project contributors |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | * |
6 | | * Section references are to |
7 | | * https://tools.ietf.org/html/rfc8915 |
8 | | * |
9 | | * This follows section 6, Suggested Format for NTS Cookies |
10 | | * It uses AEAD_AES_SIV_CMAC_256/384/512 from RFC 5297 |
11 | | * The selection is done by the key length. |
12 | | * |
13 | | * We use the implementation in libaes_siv by Daniel Franke (Akamai) |
14 | | * There is a similar implementation in OpenSSL (or soon will be) |
15 | | * It has a slightly different API. See libaes_siv/README.md |
16 | | * |
17 | | */ |
18 | | |
19 | | #include "config.h" |
20 | | |
21 | | #include <sys/types.h> |
22 | | #include <sys/stat.h> |
23 | | #include <fcntl.h> |
24 | | #include <stdbool.h> |
25 | | #include <stdint.h> |
26 | | #include <stdio.h> |
27 | | #include <string.h> |
28 | | #include <pthread.h> |
29 | | #include <unistd.h> |
30 | | |
31 | | #include <aes_siv.h> |
32 | | |
33 | | #include "ntpd.h" |
34 | | #include "ntp_stdlib.h" |
35 | | #include "nts.h" |
36 | | #include "nts2.h" |
37 | | |
38 | | /* Cookie format: |
39 | | * cookie is I,N,CMAC,C |
40 | | * I Key index, see below |
41 | | * N nonce |
42 | | * C is encrypt(K, N, P) |
43 | | * P is AEAD, C2S, S2C |
44 | | * length of C2S and S2C depends upon AEAD |
45 | | * CMAC is 16 bytes |
46 | | */ |
47 | | |
48 | | /* K and I should be preserved across boots, and rotated every day or so. |
49 | | * We need to support the old K/I for another day. |
50 | | * |
51 | | * If the file gets corrupted, blow it away and reboot. It will get |
52 | | * recreated, we will start using new cookies, packets from clients |
53 | | * with old cookies will get dropped, and eventually clients will |
54 | | * run out of cookies and use NTS-KE to get new ones. |
55 | | * |
56 | | * It would be possible to run without a cookie file. Nobody would |
57 | | * notice until the server was restarted. Then there would be a flurry |
58 | | * of NTS-KE requests until all clients obtained new/working cookies. |
59 | | */ |
60 | | |
61 | | /* Encryption within cookies uses AEAD_AES_SIV_CMAC_nnn. That's the |
62 | | * same family of algorithms as NTS uses on the wire. |
63 | | * The nnn is selected by the key length. |
64 | | * 32 => 256 |
65 | | * 48 => 384 |
66 | | * 64 => 512 |
67 | | */ |
68 | | |
69 | | /* NTS_MAX_COOKIELEN: |
70 | | * 4 I |
71 | | * 16 N |
72 | | * 16 CMAC |
73 | | * 4 AEAD |
74 | | * 64 C2S NTS_MAX_KEYLEN |
75 | | * 64 S2C NTS_MAX_KEYLEN |
76 | | * ------ |
77 | | * 168 |
78 | | * |
79 | | * That's the max length for our cookies. |
80 | | * Round up a bit in case another implementation uses more. |
81 | | * #define is in include/nts.h |
82 | | */ |
83 | | |
84 | | /* cookies use same AEAD algorithms as wire */ |
85 | | /* This determines which algorithm we use. |
86 | | * Valid choices are 32, 48, and 64 |
87 | | * making this a variable rather than #define |
88 | | * opens up the opportunity to pick one at run time. |
89 | | * The default (below) is 32/AEAD_AES_SIV_CMAC_256 |
90 | | * You can change that by editing the keys file. |
91 | | */ |
92 | | int K_length = AEAD_AES_SIV_CMAC_256_KEYLEN; |
93 | | time_t K_time = 0; /* time K was created, 0 for none */ |
94 | | struct NTS_Key nts_keys[NTS_nKEYS]; |
95 | | int nts_nKeys = 0; |
96 | | |
97 | | /* The mutex protects cookie_ctx |
98 | | * The NTS-KE servers can make cookies |
99 | | * while the main NTP server thread is unpacking and making cookies. |
100 | | * If this becomes a bottleneck, we could use a cookie_ctx per thread. */ |
101 | | pthread_mutex_t cookie_lock = PTHREAD_MUTEX_INITIALIZER; |
102 | | AES_SIV_CTX* cookie_ctx; |
103 | | |
104 | | void nts_lock_cookielock(void); |
105 | | void nts_unlock_cookielock(void); |
106 | | |
107 | | // FIXME AEAD_LENGTH |
108 | | /* Associated data: aead (rounded up to 4) plus NONCE */ |
109 | 0 | #define AD_LENGTH 20 |
110 | 0 | #define AEAD_LENGTH 4 |
111 | | |
112 | | /* cookie_ctx needed for client side */ |
113 | 0 | bool nts_cookie_init(void) { |
114 | 0 | cookie_ctx = AES_SIV_CTX_new(); |
115 | 0 | if (NULL == cookie_ctx) { |
116 | 0 | msyslog(LOG_ERR, "NTS: Can't init cookie_ctx"); |
117 | 0 | exit(1); |
118 | 0 | } |
119 | 0 | return true; |
120 | 0 | } |
121 | | |
122 | | /* cookie key needed for server side */ |
123 | 0 | bool nts_cookie_init2(void) { |
124 | 0 | bool OK = true; |
125 | 0 | if (!nts_read_cookie_keys()) { |
126 | | /* Can't read cookie file. Make one */ |
127 | 0 | nts_make_cookie_key(); |
128 | 0 | K_time = time(NULL); |
129 | 0 | nts_write_cookie_keys(); |
130 | 0 | } |
131 | 0 | return OK; |
132 | 0 | } |
133 | | |
134 | | /* Rotate key -- 24 hours after last rotation |
135 | | * That allows a cluster NTS-KE server to keep in sync |
136 | | * if we use ratchet rather than random. |
137 | | */ |
138 | 0 | #define SecondsPerDay (24*60*60) |
139 | | // Set this shorter for debugging |
140 | | // keys will timeout, packets will get dropped |
141 | | // after 8 lost packets, it should go through the NTS-KE dance again |
142 | | // Just uncommenting the next line will generate a warning reminder. |
143 | | // #define SecondsPerDay 3600 |
144 | 0 | void nts_cookie_timer(void) { |
145 | 0 | time_t now; |
146 | 0 | if (0 == K_time) { |
147 | 0 | return; |
148 | 0 | } |
149 | 0 | now = time(NULL); |
150 | 0 | if (SecondsPerDay > (now-K_time)) { |
151 | 0 | return; |
152 | 0 | } |
153 | 0 | nts_make_cookie_key(); |
154 | | /* In case we were off for many days. */ |
155 | 0 | while (SecondsPerDay < (now-K_time)) { |
156 | 0 | K_time += SecondsPerDay; |
157 | 0 | } |
158 | 0 | if (nts_write_cookie_keys() ) |
159 | 0 | msyslog(LOG_INFO, "NTS: Wrote new cookie file, %d keys.", nts_nKeys); |
160 | 0 | else |
161 | 0 | msyslog(LOG_INFO, "NTS: Trouble writing new cookie file."); |
162 | 0 | return; |
163 | 0 | } |
164 | | |
165 | | |
166 | 0 | bool nts_read_cookie_keys(void) { |
167 | 0 | const char *cookie_filename = NTS_COOKIE_KEY_FILE; |
168 | 0 | FILE *in; |
169 | 0 | unsigned long templ; |
170 | 0 | if (NULL != ntsconfig.KI) |
171 | 0 | cookie_filename = ntsconfig.KI; |
172 | 0 | in = fopen(cookie_filename, "r"); |
173 | 0 | if (NULL == in) { |
174 | 0 | char errbuf[100]; |
175 | 0 | if (ENOENT == errno) |
176 | 0 | return false; /* File doesn't exist */ |
177 | 0 | ntp_strerror_r(errno, errbuf, sizeof(errbuf)); |
178 | 0 | msyslog(LOG_ERR, "NTSs: can't read old cookie file: %s=>%s", |
179 | 0 | cookie_filename, errbuf); |
180 | 0 | exit(1); |
181 | 0 | } |
182 | 0 | if (1 != fscanf(in, "T: %lu\n", &templ)) { |
183 | 0 | goto bail; |
184 | 0 | } |
185 | 0 | K_time = templ; |
186 | 0 | if (1 != fscanf(in, "L: %d\n", &K_length)) { |
187 | 0 | goto bail; |
188 | 0 | } |
189 | 0 | if ( !((32 == K_length) || (48 == K_length) || (64 == K_length))) { |
190 | 0 | goto bail; |
191 | 0 | } |
192 | 0 | nts_nKeys = 0; |
193 | 0 | for (int i=0; i<NTS_nKEYS; i++) { |
194 | 0 | struct NTS_Key *key = &nts_keys[i]; |
195 | 0 | if (1 != fscanf(in, "I: %u\n", &key->I)) { |
196 | 0 | if (0 < nts_nKeys) break; |
197 | 0 | goto bail; |
198 | 0 | } |
199 | 0 | if (0 != fscanf(in, "K: ")) { |
200 | 0 | goto bail; |
201 | 0 | } |
202 | 0 | for (int j=0; j< K_length; j++) { |
203 | 0 | unsigned int temp; |
204 | 0 | if (1 != fscanf(in, "%02x", &temp)) { |
205 | 0 | goto bail; |
206 | 0 | } |
207 | 0 | key->K[j] = temp; |
208 | 0 | } |
209 | 0 | if (0 != fscanf(in, "\n")) { |
210 | 0 | goto bail; |
211 | 0 | } |
212 | 0 | nts_nKeys = i+1; |
213 | 0 | } |
214 | 0 | fclose(in); |
215 | 0 | msyslog(LOG_INFO, "NTS: Read cookie file, %d keys.", nts_nKeys); |
216 | 0 | return true; |
217 | | |
218 | 0 | bail: |
219 | 0 | msyslog(LOG_ERR, "ERR: Error parsing cookie keys file"); |
220 | 0 | fclose(in); |
221 | 0 | return false; |
222 | 0 | } |
223 | | |
224 | | /* RFC 8915 describes a ratchet mode to make new keys |
225 | | * That's one way to implement a KE server for a cluster of NTP servers. |
226 | | * The KE server and the NTP servers stay in sync without communication |
227 | | * after a one-time copy of the cookie file from NTP server to KE server. |
228 | | * An alternative is to elect one system to generate new keys and |
229 | | * they copy the key file to other systems and have them load it. |
230 | | */ |
231 | 0 | void nts_make_cookie_key(void) { |
232 | 0 | if (nts_nKeys < NTS_nKEYS) nts_nKeys++; |
233 | 0 | for (int i=nts_nKeys-1; i>0; i--) { |
234 | 0 | nts_keys[i] = nts_keys[i-1]; |
235 | 0 | } |
236 | 0 | ntp_RAND_priv_bytes(nts_keys[0].K, K_length); |
237 | 0 | ntp_RAND_bytes((uint8_t *)&nts_keys[0].I, sizeof(nts_keys[0].I)); |
238 | 0 | return; |
239 | 0 | } |
240 | | |
241 | 0 | bool nts_write_cookie_keys(void) { |
242 | 0 | const char *cookiefile = NTS_COOKIE_KEY_FILE; |
243 | 0 | char tempfile[PATH_MAX]; |
244 | 0 | int fd; |
245 | 0 | FILE *out; |
246 | 0 | char errbuf[100]; |
247 | 0 | if (NULL != ntsconfig.KI) |
248 | 0 | cookiefile = ntsconfig.KI; |
249 | 0 | strlcpy(tempfile, cookiefile, sizeof(tempfile)); |
250 | 0 | strlcat(tempfile, "-tmp", sizeof(tempfile)); |
251 | 0 | fd = open(tempfile, O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR); |
252 | 0 | if (-1 == fd) { |
253 | 0 | ntp_strerror_r(errno, errbuf, sizeof(errbuf)); |
254 | 0 | msyslog(LOG_ERR, "ERR: can't open %s: %s", tempfile, errbuf); |
255 | 0 | return false; |
256 | 0 | } |
257 | 0 | out = fdopen(fd, "w"); |
258 | 0 | if (NULL == out) { |
259 | 0 | ntp_strerror_r(errno, errbuf, sizeof(errbuf)); |
260 | 0 | msyslog(LOG_ERR, "ERR: can't fdopen %s: %s", tempfile, errbuf); |
261 | 0 | close(fd); |
262 | 0 | return false; |
263 | 0 | } |
264 | | |
265 | 0 | fprintf(out, "T: %lu\n", (unsigned long)K_time); |
266 | 0 | fprintf(out, "L: %d\n", K_length); |
267 | 0 | for (int i=0; i<nts_nKeys; i++) { |
268 | 0 | struct NTS_Key *key = &nts_keys[i]; |
269 | 0 | fprintf(out, "I: %u\n", key->I); |
270 | 0 | fprintf(out, "K: "); |
271 | 0 | for (int j=0; j< K_length; j++) fprintf(out, "%02x", key->K[j]); |
272 | 0 | fprintf(out, "\n"); |
273 | 0 | key++; |
274 | 0 | } |
275 | 0 | fclose(out); |
276 | 0 | if (rename(tempfile, cookiefile)) { |
277 | 0 | ntp_strerror_r(errno, errbuf, sizeof(errbuf)); |
278 | 0 | msyslog(LOG_WARNING, |
279 | 0 | "LOG: Unable to rename temp cookie file %s to %s, %s", |
280 | 0 | tempfile, cookiefile, errbuf); |
281 | 0 | return false; |
282 | 0 | } |
283 | 0 | return true; |
284 | 0 | } |
285 | | |
286 | | /* returns actual length */ |
287 | | int nts_make_cookie(uint8_t *cookie, |
288 | | uint16_t aead, |
289 | 0 | uint8_t *c2s, uint8_t *s2c, int keylen) { |
290 | 0 | uint8_t plaintext[NTS_MAX_COOKIELEN]; |
291 | 0 | uint8_t *nonce; |
292 | 0 | int used, plainlength; |
293 | 0 | bool ok; |
294 | 0 | uint8_t * finger; |
295 | 0 | uint32_t temp; /* keep 4 byte alignment */ |
296 | 0 | size_t left; |
297 | |
|
298 | 0 | if (NULL == cookie_ctx) |
299 | 0 | return 0; /* We aren't initialized yet. */ |
300 | | |
301 | 0 | nts_cnt.cookie_make++; |
302 | |
|
303 | 0 | INSIST(keylen <= NTS_MAX_KEYLEN); |
304 | | |
305 | | /* collect plaintext |
306 | | * separate buffer avoids encrypt in place |
307 | | * but costs cache space |
308 | | */ |
309 | 0 | finger = plaintext; |
310 | 0 | temp = aead; |
311 | 0 | memcpy(finger, &temp, AEAD_LENGTH); |
312 | 0 | finger += AEAD_LENGTH; |
313 | 0 | memcpy(finger, c2s, keylen); |
314 | 0 | finger += keylen; |
315 | 0 | memcpy(finger, s2c, keylen); |
316 | 0 | finger += keylen; |
317 | 0 | plainlength = finger-plaintext; |
318 | | |
319 | | /* collect associated data */ |
320 | 0 | finger = cookie; |
321 | |
|
322 | 0 | memcpy(finger, &nts_keys[0].I, sizeof(nts_keys[0].I)); |
323 | 0 | finger += sizeof(nts_keys[0].I); |
324 | |
|
325 | 0 | nonce = finger; |
326 | 0 | ntp_RAND_bytes(finger, NONCE_LENGTH); |
327 | 0 | finger += NONCE_LENGTH; |
328 | |
|
329 | 0 | used = finger-cookie; |
330 | 0 | left = NTS_MAX_COOKIELEN-used; |
331 | |
|
332 | 0 | nts_lock_cookielock(); |
333 | |
|
334 | 0 | ok = AES_SIV_Encrypt(cookie_ctx, |
335 | 0 | finger, &left, /* left: in: max out length, out: length used */ |
336 | 0 | nts_keys[0].K, K_length, |
337 | 0 | nonce, NONCE_LENGTH, |
338 | 0 | plaintext, plainlength, |
339 | 0 | cookie, AD_LENGTH); |
340 | |
|
341 | 0 | nts_unlock_cookielock(); |
342 | |
|
343 | 0 | if (!ok) { |
344 | 0 | msyslog(LOG_ERR, "NTS: nts_make_cookie - Error from AES_SIV_Encrypt"); |
345 | | /* I don't think this should happen, |
346 | | * so crash rather than work incorrectly. |
347 | | * Hal, 2019-Feb-17 |
348 | | * Similar code in ntp_extens |
349 | | */ |
350 | 0 | exit(1); |
351 | 0 | } |
352 | | |
353 | 0 | used += left; |
354 | 0 | INSIST(used <= NTS_MAX_COOKIELEN); |
355 | | |
356 | 0 | return used; |
357 | 0 | } |
358 | | |
359 | | /* can't decrypt in place - that would trash the unauthenticated packet */ |
360 | | bool nts_unpack_cookie(uint8_t *cookie, int cookielen, |
361 | | uint16_t *aead, |
362 | 2 | uint8_t *c2s, uint8_t *s2c, int *keylen) { |
363 | 2 | uint8_t *finger; |
364 | 2 | uint8_t plaintext[NTS_MAX_COOKIELEN]; |
365 | 2 | uint8_t *nonce; |
366 | 2 | uint32_t temp; |
367 | 2 | size_t plainlength; |
368 | 2 | int cipherlength; |
369 | 2 | bool ok; |
370 | 2 | struct NTS_Key *key; |
371 | 2 | int i; |
372 | | |
373 | 2 | if (NULL == cookie_ctx) |
374 | 2 | return false; /* We aren't initialized yet. */ |
375 | | |
376 | 0 | if (0 == nts_nKeys) { |
377 | 0 | nts_cnt.cookie_not_server++; |
378 | 0 | return false; /* We are not a NTS enabled server. */ |
379 | 0 | } |
380 | | |
381 | | /* We may get garbage from the net */ |
382 | 0 | if (cookielen > NTS_MAX_COOKIELEN) |
383 | 0 | return false; |
384 | | |
385 | 0 | finger = cookie; |
386 | 0 | key = NULL; /* squash uninitialized warning */ |
387 | 0 | for (i=0; i<nts_nKeys; i++) { |
388 | 0 | key = &nts_keys[i]; |
389 | 0 | if (0 == memcmp(finger, &key->I, sizeof(key->I))) { |
390 | 0 | break; |
391 | 0 | } |
392 | 0 | } |
393 | 0 | nts_cnt.cookie_decode_total++; /* total attempts, includes too old */ |
394 | 0 | if (nts_nKeys == i) { |
395 | 0 | nts_cnt.cookie_decode_too_old++; |
396 | 0 | return false; |
397 | 0 | } |
398 | 0 | if (0 == i) { |
399 | 0 | nts_cnt.cookie_decode_current++; |
400 | 0 | } else if (1 == i) { |
401 | 0 | nts_cnt.cookie_decode_old++; |
402 | 0 | } else if (2 == i) { |
403 | 0 | nts_cnt.cookie_decode_old2++; |
404 | 0 | } else { |
405 | 0 | nts_cnt.cookie_decode_older++; |
406 | 0 | } |
407 | | #if 0 |
408 | | if (1<i) { |
409 | | /* Hack for debugging */ |
410 | | /* Beware: DoS possibility on a public server */ |
411 | | msyslog(LOG_INFO, "NTS: Old cookie: %d days.", i); |
412 | | } |
413 | | #endif |
414 | |
|
415 | 0 | finger += sizeof(key->I); |
416 | 0 | nonce = finger; |
417 | 0 | finger += NONCE_LENGTH; |
418 | | |
419 | | // require(AD_LENGTH==finger-cookie); |
420 | |
|
421 | 0 | cipherlength = cookielen - AD_LENGTH; |
422 | 0 | plainlength = NTS_MAX_COOKIELEN; |
423 | |
|
424 | 0 | nts_lock_cookielock(); |
425 | |
|
426 | 0 | ok = AES_SIV_Decrypt(cookie_ctx, |
427 | 0 | plaintext, &plainlength, |
428 | 0 | key->K, K_length, |
429 | 0 | nonce, NONCE_LENGTH, |
430 | 0 | finger, cipherlength, |
431 | 0 | cookie, AD_LENGTH); |
432 | |
|
433 | 0 | nts_unlock_cookielock(); |
434 | |
|
435 | 0 | if (!ok) { |
436 | 0 | nts_cnt.cookie_decode_error++; |
437 | 0 | return false; |
438 | 0 | } |
439 | | |
440 | 0 | *keylen = (plainlength-AEAD_LENGTH)/2; |
441 | 0 | finger = plaintext; |
442 | 0 | memcpy(&temp, finger, AEAD_LENGTH); |
443 | 0 | *aead = temp; |
444 | 0 | finger += AEAD_LENGTH; |
445 | 0 | memcpy(c2s, finger, *keylen); |
446 | 0 | finger += *keylen; |
447 | 0 | memcpy(s2c, finger, *keylen); |
448 | 0 | finger += *keylen; |
449 | |
|
450 | 0 | return true; |
451 | 0 | } |
452 | | |
453 | 0 | void nts_lock_cookielock(void) { |
454 | 0 | int err = pthread_mutex_lock(&cookie_lock); |
455 | 0 | if (0 != err) { |
456 | 0 | msyslog(LOG_ERR, "ERR: Can't lock cookie_lock: %d", err); |
457 | 0 | exit(2); |
458 | 0 | } |
459 | 0 | } |
460 | | |
461 | 0 | void nts_unlock_cookielock(void) { |
462 | 0 | int err = pthread_mutex_unlock(&cookie_lock); |
463 | 0 | if (0 != err) { |
464 | | msyslog(LOG_ERR, "ERR: Can't unlock cookie_lock: %d", err); |
465 | 0 | exit(2); |
466 | 0 | } |
467 | 0 | } |
468 | | |
469 | | /* end */ |