authreadkeys.c revision 1.11 1 1.11 christos /* $NetBSD: authreadkeys.c,v 1.11 2020/05/25 20:47:24 christos Exp $ */
2 1.1 kardel
3 1.1 kardel /*
4 1.1 kardel * authreadkeys.c - routines to support the reading of the key file
5 1.1 kardel */
6 1.1 kardel #include <config.h>
7 1.1 kardel #include <stdio.h>
8 1.1 kardel #include <ctype.h>
9 1.1 kardel
10 1.10 christos //#include "ntpd.h" /* Only for DPRINTF */
11 1.10 christos //#include "ntp_fp.h"
12 1.1 kardel #include "ntp.h"
13 1.1 kardel #include "ntp_syslog.h"
14 1.1 kardel #include "ntp_stdlib.h"
15 1.9 christos #include "ntp_keyacc.h"
16 1.1 kardel
17 1.1 kardel #ifdef OPENSSL
18 1.1 kardel #include "openssl/objects.h"
19 1.3 christos #include "openssl/evp.h"
20 1.3 christos #endif /* OPENSSL */
21 1.1 kardel
22 1.1 kardel /* Forwards */
23 1.1 kardel static char *nexttok (char **);
24 1.1 kardel
25 1.1 kardel /*
26 1.1 kardel * nexttok - basic internal tokenizing routine
27 1.1 kardel */
28 1.1 kardel static char *
29 1.1 kardel nexttok(
30 1.1 kardel char **str
31 1.1 kardel )
32 1.1 kardel {
33 1.1 kardel register char *cp;
34 1.1 kardel char *starttok;
35 1.1 kardel
36 1.1 kardel cp = *str;
37 1.1 kardel
38 1.1 kardel /*
39 1.1 kardel * Space past white space
40 1.1 kardel */
41 1.1 kardel while (*cp == ' ' || *cp == '\t')
42 1.3 christos cp++;
43 1.1 kardel
44 1.1 kardel /*
45 1.1 kardel * Save this and space to end of token
46 1.1 kardel */
47 1.1 kardel starttok = cp;
48 1.1 kardel while (*cp != '\0' && *cp != '\n' && *cp != ' '
49 1.1 kardel && *cp != '\t' && *cp != '#')
50 1.3 christos cp++;
51 1.1 kardel
52 1.1 kardel /*
53 1.1 kardel * If token length is zero return an error, else set end of
54 1.1 kardel * token to zero and return start.
55 1.1 kardel */
56 1.1 kardel if (starttok == cp)
57 1.3 christos return NULL;
58 1.1 kardel
59 1.1 kardel if (*cp == ' ' || *cp == '\t')
60 1.3 christos *cp++ = '\0';
61 1.1 kardel else
62 1.3 christos *cp = '\0';
63 1.1 kardel
64 1.1 kardel *str = cp;
65 1.1 kardel return starttok;
66 1.1 kardel }
67 1.1 kardel
68 1.1 kardel
69 1.7 christos /* TALOS-CAN-0055: possibly DoS attack by setting the key file to the
70 1.7 christos * log file. This is hard to prevent (it would need to check two files
71 1.7 christos * to be the same on the inode level, which will not work so easily with
72 1.7 christos * Windows or VMS) but we can avoid the self-amplification loop: We only
73 1.7 christos * log the first 5 errors, silently ignore the next 10 errors, and give
74 1.7 christos * up when when we have found more than 15 errors.
75 1.7 christos *
76 1.7 christos * This avoids the endless file iteration we will end up with otherwise,
77 1.7 christos * and also avoids overflowing the log file.
78 1.7 christos *
79 1.7 christos * Nevertheless, once this happens, the keys are gone since this would
80 1.7 christos * require a save/swap strategy that is not easy to apply due to the
81 1.7 christos * data on global/static level.
82 1.7 christos */
83 1.7 christos
84 1.8 christos static const u_int nerr_loglimit = 5u;
85 1.8 christos static const u_int nerr_maxlimit = 15;
86 1.7 christos
87 1.8 christos static void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3);
88 1.8 christos
89 1.8 christos typedef struct keydata KeyDataT;
90 1.8 christos struct keydata {
91 1.8 christos KeyDataT *next; /* queue/stack link */
92 1.9 christos KeyAccT *keyacclist; /* key access list */
93 1.8 christos keyid_t keyid; /* stored key ID */
94 1.8 christos u_short keytype; /* stored key type */
95 1.8 christos u_short seclen; /* length of secret */
96 1.8 christos u_char secbuf[1]; /* begin of secret (formal only)*/
97 1.8 christos };
98 1.7 christos
99 1.7 christos static void
100 1.7 christos log_maybe(
101 1.8 christos u_int *pnerr,
102 1.7 christos const char *fmt ,
103 1.7 christos ...)
104 1.7 christos {
105 1.7 christos va_list ap;
106 1.9 christos if ((NULL == pnerr) || (++(*pnerr) <= nerr_loglimit)) {
107 1.7 christos va_start(ap, fmt);
108 1.7 christos mvsyslog(LOG_ERR, fmt, ap);
109 1.7 christos va_end(ap);
110 1.7 christos }
111 1.7 christos }
112 1.7 christos
113 1.9 christos static void
114 1.9 christos free_keydata(
115 1.9 christos KeyDataT *node
116 1.9 christos )
117 1.9 christos {
118 1.9 christos KeyAccT *kap;
119 1.9 christos
120 1.9 christos if (node) {
121 1.9 christos while (node->keyacclist) {
122 1.9 christos kap = node->keyacclist;
123 1.9 christos node->keyacclist = kap->next;
124 1.9 christos free(kap);
125 1.9 christos }
126 1.9 christos
127 1.9 christos /* purge secrets from memory before free()ing it */
128 1.9 christos memset(node, 0, sizeof(*node) + node->seclen);
129 1.9 christos free(node);
130 1.9 christos }
131 1.9 christos }
132 1.9 christos
133 1.1 kardel /*
134 1.1 kardel * authreadkeys - (re)read keys from a file.
135 1.1 kardel */
136 1.1 kardel int
137 1.1 kardel authreadkeys(
138 1.1 kardel const char *file
139 1.1 kardel )
140 1.1 kardel {
141 1.1 kardel FILE *fp;
142 1.1 kardel char *line;
143 1.1 kardel char *token;
144 1.1 kardel keyid_t keyno;
145 1.1 kardel int keytype;
146 1.1 kardel char buf[512]; /* lots of room for line */
147 1.4 christos u_char keystr[32]; /* Bug 2537 */
148 1.2 christos size_t len;
149 1.2 christos size_t j;
150 1.8 christos u_int nerr;
151 1.8 christos KeyDataT *list = NULL;
152 1.8 christos KeyDataT *next = NULL;
153 1.10 christos
154 1.1 kardel /*
155 1.1 kardel * Open file. Complain and return if it can't be opened.
156 1.1 kardel */
157 1.1 kardel fp = fopen(file, "r");
158 1.1 kardel if (fp == NULL) {
159 1.8 christos msyslog(LOG_ERR, "authreadkeys: file '%s': %m",
160 1.1 kardel file);
161 1.8 christos goto onerror;
162 1.1 kardel }
163 1.1 kardel INIT_SSL();
164 1.1 kardel
165 1.1 kardel /*
166 1.8 christos * Now read lines from the file, looking for key entries. Put
167 1.8 christos * the data into temporary store for later propagation to avoid
168 1.8 christos * two-pass processing.
169 1.1 kardel */
170 1.7 christos nerr = 0;
171 1.1 kardel while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
172 1.7 christos if (nerr > nerr_maxlimit)
173 1.7 christos break;
174 1.1 kardel token = nexttok(&line);
175 1.1 kardel if (token == NULL)
176 1.1 kardel continue;
177 1.1 kardel
178 1.1 kardel /*
179 1.1 kardel * First is key number. See if it is okay.
180 1.1 kardel */
181 1.1 kardel keyno = atoi(token);
182 1.9 christos if (keyno < 1) {
183 1.7 christos log_maybe(&nerr,
184 1.7 christos "authreadkeys: cannot change key %s",
185 1.7 christos token);
186 1.1 kardel continue;
187 1.1 kardel }
188 1.1 kardel
189 1.1 kardel if (keyno > NTP_MAXKEY) {
190 1.7 christos log_maybe(&nerr,
191 1.7 christos "authreadkeys: key %s > %d reserved for Autokey",
192 1.7 christos token, NTP_MAXKEY);
193 1.1 kardel continue;
194 1.1 kardel }
195 1.1 kardel
196 1.1 kardel /*
197 1.1 kardel * Next is keytype. See if that is all right.
198 1.1 kardel */
199 1.1 kardel token = nexttok(&line);
200 1.1 kardel if (token == NULL) {
201 1.7 christos log_maybe(&nerr,
202 1.7 christos "authreadkeys: no key type for key %d",
203 1.7 christos keyno);
204 1.1 kardel continue;
205 1.1 kardel }
206 1.9 christos
207 1.9 christos /* We want to silently ignore keys where we do not
208 1.9 christos * support the requested digest type. OTOH, we want to
209 1.9 christos * make sure the file is well-formed. That means we
210 1.9 christos * have to process the line completely and have to
211 1.9 christos * finally throw away the result... This is a bit more
212 1.9 christos * work, but it also results in better error detection.
213 1.9 christos */
214 1.1 kardel #ifdef OPENSSL
215 1.1 kardel /*
216 1.1 kardel * The key type is the NID used by the message digest
217 1.1 kardel * algorithm. There are a number of inconsistencies in
218 1.1 kardel * the OpenSSL database. We attempt to discover them
219 1.1 kardel * here and prevent use of inconsistent data later.
220 1.1 kardel */
221 1.1 kardel keytype = keytype_from_text(token, NULL);
222 1.1 kardel if (keytype == 0) {
223 1.9 christos log_maybe(NULL,
224 1.7 christos "authreadkeys: invalid type for key %d",
225 1.7 christos keyno);
226 1.11 christos # ifdef ENABLE_CMAC
227 1.10 christos } else if (NID_cmac != keytype &&
228 1.10 christos EVP_get_digestbynid(keytype) == NULL) {
229 1.9 christos log_maybe(NULL,
230 1.7 christos "authreadkeys: no algorithm for key %d",
231 1.7 christos keyno);
232 1.9 christos keytype = 0;
233 1.11 christos # endif /* ENABLE_CMAC */
234 1.1 kardel }
235 1.3 christos #else /* !OPENSSL follows */
236 1.1 kardel /*
237 1.1 kardel * The key type is unused, but is required to be 'M' or
238 1.1 kardel * 'm' for compatibility.
239 1.1 kardel */
240 1.1 kardel if (!(*token == 'M' || *token == 'm')) {
241 1.9 christos log_maybe(NULL,
242 1.7 christos "authreadkeys: invalid type for key %d",
243 1.7 christos keyno);
244 1.9 christos keytype = 0;
245 1.9 christos } else {
246 1.9 christos keytype = KEY_TYPE_MD5;
247 1.1 kardel }
248 1.3 christos #endif /* !OPENSSL */
249 1.1 kardel
250 1.1 kardel /*
251 1.1 kardel * Finally, get key and insert it. If it is longer than 20
252 1.1 kardel * characters, it is a binary string encoded in hex;
253 1.1 kardel * otherwise, it is a text string of printable ASCII
254 1.1 kardel * characters.
255 1.1 kardel */
256 1.1 kardel token = nexttok(&line);
257 1.1 kardel if (token == NULL) {
258 1.7 christos log_maybe(&nerr,
259 1.7 christos "authreadkeys: no key for key %d", keyno);
260 1.1 kardel continue;
261 1.1 kardel }
262 1.8 christos next = NULL;
263 1.1 kardel len = strlen(token);
264 1.4 christos if (len <= 20) { /* Bug 2537 */
265 1.8 christos next = emalloc(sizeof(KeyDataT) + len);
266 1.9 christos next->keyacclist = NULL;
267 1.8 christos next->keyid = keyno;
268 1.8 christos next->keytype = keytype;
269 1.8 christos next->seclen = len;
270 1.8 christos memcpy(next->secbuf, token, len);
271 1.1 kardel } else {
272 1.8 christos static const char hex[] = "0123456789abcdef";
273 1.1 kardel u_char temp;
274 1.1 kardel char *ptr;
275 1.2 christos size_t jlim;
276 1.1 kardel
277 1.1 kardel jlim = min(len, 2 * sizeof(keystr));
278 1.1 kardel for (j = 0; j < jlim; j++) {
279 1.2 christos ptr = strchr(hex, tolower((unsigned char)token[j]));
280 1.3 christos if (ptr == NULL)
281 1.3 christos break; /* abort decoding */
282 1.1 kardel temp = (u_char)(ptr - hex);
283 1.1 kardel if (j & 1)
284 1.1 kardel keystr[j / 2] |= temp;
285 1.1 kardel else
286 1.1 kardel keystr[j / 2] = temp << 4;
287 1.1 kardel }
288 1.3 christos if (j < jlim) {
289 1.7 christos log_maybe(&nerr,
290 1.7 christos "authreadkeys: invalid hex digit for key %d",
291 1.7 christos keyno);
292 1.3 christos continue;
293 1.3 christos }
294 1.8 christos len = jlim/2; /* hmmmm.... what about odd length?!? */
295 1.8 christos next = emalloc(sizeof(KeyDataT) + len);
296 1.9 christos next->keyacclist = NULL;
297 1.8 christos next->keyid = keyno;
298 1.8 christos next->keytype = keytype;
299 1.8 christos next->seclen = len;
300 1.8 christos memcpy(next->secbuf, keystr, len);
301 1.1 kardel }
302 1.9 christos
303 1.9 christos token = nexttok(&line);
304 1.9 christos if (token != NULL) { /* A comma-separated IP access list */
305 1.9 christos char *tp = token;
306 1.9 christos
307 1.9 christos while (tp) {
308 1.9 christos char *i;
309 1.10 christos char *snp; /* subnet text pointer */
310 1.10 christos unsigned int snbits;
311 1.9 christos sockaddr_u addr;
312 1.9 christos
313 1.9 christos i = strchr(tp, (int)',');
314 1.10 christos if (i) {
315 1.9 christos *i = '\0';
316 1.10 christos }
317 1.10 christos snp = strchr(tp, (int)'/');
318 1.10 christos if (snp) {
319 1.10 christos char *sp;
320 1.10 christos
321 1.10 christos *snp++ = '\0';
322 1.10 christos snbits = 0;
323 1.10 christos sp = snp;
324 1.10 christos
325 1.10 christos while (*sp != '\0') {
326 1.10 christos if (!isdigit((unsigned char)*sp))
327 1.10 christos break;
328 1.10 christos if (snbits > 1000)
329 1.10 christos break; /* overflow */
330 1.10 christos snbits = 10 * snbits + (*sp++ - '0'); /* ascii dependent */
331 1.10 christos }
332 1.10 christos if (*sp != '\0') {
333 1.10 christos log_maybe(&nerr,
334 1.10 christos "authreadkeys: Invalid character in subnet specification for <%s/%s> in key %d",
335 1.10 christos sp, snp, keyno);
336 1.10 christos goto nextip;
337 1.10 christos }
338 1.10 christos } else {
339 1.10 christos snbits = UINT_MAX;
340 1.10 christos }
341 1.9 christos
342 1.9 christos if (is_ip_address(tp, AF_UNSPEC, &addr)) {
343 1.10 christos /* Make sure that snbits is valid for addr */
344 1.10 christos if ((snbits < UINT_MAX) &&
345 1.10 christos ( (IS_IPV4(&addr) && snbits > 32) ||
346 1.10 christos (IS_IPV6(&addr) && snbits > 128))) {
347 1.10 christos log_maybe(NULL,
348 1.10 christos "authreadkeys: excessive subnet mask <%s/%s> for key %d",
349 1.10 christos tp, snp, keyno);
350 1.10 christos }
351 1.10 christos next->keyacclist = keyacc_new_push(
352 1.10 christos next->keyacclist, &addr, snbits);
353 1.9 christos } else {
354 1.9 christos log_maybe(&nerr,
355 1.9 christos "authreadkeys: invalid IP address <%s> for key %d",
356 1.9 christos tp, keyno);
357 1.9 christos }
358 1.9 christos
359 1.10 christos nextip:
360 1.9 christos if (i) {
361 1.9 christos tp = i + 1;
362 1.9 christos } else {
363 1.9 christos tp = 0;
364 1.9 christos }
365 1.9 christos }
366 1.9 christos }
367 1.9 christos
368 1.9 christos /* check if this has to be weeded out... */
369 1.9 christos if (0 == keytype) {
370 1.9 christos free_keydata(next);
371 1.9 christos next = NULL;
372 1.9 christos continue;
373 1.9 christos }
374 1.9 christos
375 1.8 christos INSIST(NULL != next);
376 1.8 christos next->next = list;
377 1.8 christos list = next;
378 1.1 kardel }
379 1.1 kardel fclose(fp);
380 1.8 christos if (nerr > 0) {
381 1.9 christos const char * why = "";
382 1.9 christos if (nerr > nerr_maxlimit)
383 1.9 christos why = " (emergency break)";
384 1.7 christos msyslog(LOG_ERR,
385 1.9 christos "authreadkeys: rejecting file '%s' after %u error(s)%s",
386 1.9 christos file, nerr, why);
387 1.8 christos goto onerror;
388 1.8 christos }
389 1.8 christos
390 1.8 christos /* first remove old file-based keys */
391 1.8 christos auth_delkeys();
392 1.8 christos /* insert the new key material */
393 1.8 christos while (NULL != (next = list)) {
394 1.8 christos list = next->next;
395 1.8 christos MD5auth_setkey(next->keyid, next->keytype,
396 1.9 christos next->secbuf, next->seclen, next->keyacclist);
397 1.9 christos next->keyacclist = NULL; /* consumed by MD5auth_setkey */
398 1.9 christos free_keydata(next);
399 1.7 christos }
400 1.1 kardel return (1);
401 1.8 christos
402 1.8 christos onerror:
403 1.8 christos /* Mop up temporary storage before bailing out. */
404 1.8 christos while (NULL != (next = list)) {
405 1.8 christos list = next->next;
406 1.9 christos free_keydata(next);
407 1.8 christos }
408 1.8 christos return (0);
409 1.1 kardel }
410