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