xref: /freebsd/contrib/ntp/libntp/authreadkeys.c (revision fafb1ee7bdc5d8a7d07cd03b2fb0bbb76f7a9d7c)
1 /*
2  * authreadkeys.c - routines to support the reading of the key file
3  */
4 #include <config.h>
5 #include <stdio.h>
6 #include <ctype.h>
7 
8 #include "ntp_fp.h"
9 #include "ntp.h"
10 #include "ntp_syslog.h"
11 #include "ntp_stdlib.h"
12 
13 #ifdef OPENSSL
14 #include "openssl/objects.h"
15 #include "openssl/evp.h"
16 #endif	/* OPENSSL */
17 
18 /* Forwards */
19 static char *nexttok (char **);
20 
21 /*
22  * nexttok - basic internal tokenizing routine
23  */
24 static char *
25 nexttok(
26 	char	**str
27 	)
28 {
29 	register char *cp;
30 	char *starttok;
31 
32 	cp = *str;
33 
34 	/*
35 	 * Space past white space
36 	 */
37 	while (*cp == ' ' || *cp == '\t')
38 		cp++;
39 
40 	/*
41 	 * Save this and space to end of token
42 	 */
43 	starttok = cp;
44 	while (*cp != '\0' && *cp != '\n' && *cp != ' '
45 	       && *cp != '\t' && *cp != '#')
46 		cp++;
47 
48 	/*
49 	 * If token length is zero return an error, else set end of
50 	 * token to zero and return start.
51 	 */
52 	if (starttok == cp)
53 		return NULL;
54 
55 	if (*cp == ' ' || *cp == '\t')
56 		*cp++ = '\0';
57 	else
58 		*cp = '\0';
59 
60 	*str = cp;
61 	return starttok;
62 }
63 
64 
65 /* TALOS-CAN-0055: possibly DoS attack by setting the key file to the
66  * log file. This is hard to prevent (it would need to check two files
67  * to be the same on the inode level, which will not work so easily with
68  * Windows or VMS) but we can avoid the self-amplification loop: We only
69  * log the first 5 errors, silently ignore the next 10 errors, and give
70  * up when when we have found more than 15 errors.
71  *
72  * This avoids the endless file iteration we will end up with otherwise,
73  * and also avoids overflowing the log file.
74  *
75  * Nevertheless, once this happens, the keys are gone since this would
76  * require a save/swap strategy that is not easy to apply due to the
77  * data on global/static level.
78  */
79 
80 static const u_int nerr_loglimit = 5u;
81 static const u_int nerr_maxlimit = 15;
82 
83 static void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3);
84 
85 typedef struct keydata KeyDataT;
86 struct keydata {
87 	KeyDataT *next;		/* queue/stack link		*/
88 	keyid_t   keyid;	/* stored key ID		*/
89 	u_short   keytype;	/* stored key type		*/
90 	u_short   seclen;	/* length of secret		*/
91 	u_char    secbuf[1];	/* begin of secret (formal only)*/
92 };
93 
94 static void
95 log_maybe(
96 	u_int      *pnerr,
97 	const char *fmt  ,
98 	...)
99 {
100 	va_list ap;
101 	if (++(*pnerr) <= nerr_loglimit) {
102 		va_start(ap, fmt);
103 		mvsyslog(LOG_ERR, fmt, ap);
104 		va_end(ap);
105 	}
106 }
107 
108 /*
109  * authreadkeys - (re)read keys from a file.
110  */
111 int
112 authreadkeys(
113 	const char *file
114 	)
115 {
116 	FILE	*fp;
117 	char	*line;
118 	char	*token;
119 	keyid_t	keyno;
120 	int	keytype;
121 	char	buf[512];		/* lots of room for line */
122 	u_char	keystr[32];		/* Bug 2537 */
123 	size_t	len;
124 	size_t	j;
125 	u_int   nerr;
126 	KeyDataT *list = NULL;
127 	KeyDataT *next = NULL;
128 	/*
129 	 * Open file.  Complain and return if it can't be opened.
130 	 */
131 	fp = fopen(file, "r");
132 	if (fp == NULL) {
133 		msyslog(LOG_ERR, "authreadkeys: file '%s': %m",
134 		    file);
135 		goto onerror;
136 	}
137 	INIT_SSL();
138 
139 	/*
140 	 * Now read lines from the file, looking for key entries. Put
141 	 * the data into temporary store for later propagation to avoid
142 	 * two-pass processing.
143 	 */
144 	nerr = 0;
145 	while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
146 		if (nerr > nerr_maxlimit)
147 			break;
148 		token = nexttok(&line);
149 		if (token == NULL)
150 			continue;
151 
152 		/*
153 		 * First is key number.  See if it is okay.
154 		 */
155 		keyno = atoi(token);
156 		if (keyno == 0) {
157 			log_maybe(&nerr,
158 				  "authreadkeys: cannot change key %s",
159 				  token);
160 			continue;
161 		}
162 
163 		if (keyno > NTP_MAXKEY) {
164 			log_maybe(&nerr,
165 				  "authreadkeys: key %s > %d reserved for Autokey",
166 				  token, NTP_MAXKEY);
167 			continue;
168 		}
169 
170 		/*
171 		 * Next is keytype. See if that is all right.
172 		 */
173 		token = nexttok(&line);
174 		if (token == NULL) {
175 			log_maybe(&nerr,
176 				  "authreadkeys: no key type for key %d",
177 				  keyno);
178 			continue;
179 		}
180 #ifdef OPENSSL
181 		/*
182 		 * The key type is the NID used by the message digest
183 		 * algorithm. There are a number of inconsistencies in
184 		 * the OpenSSL database. We attempt to discover them
185 		 * here and prevent use of inconsistent data later.
186 		 */
187 		keytype = keytype_from_text(token, NULL);
188 		if (keytype == 0) {
189 			log_maybe(&nerr,
190 				  "authreadkeys: invalid type for key %d",
191 				  keyno);
192 			continue;
193 		}
194 		if (EVP_get_digestbynid(keytype) == NULL) {
195 			log_maybe(&nerr,
196 				  "authreadkeys: no algorithm for key %d",
197 				  keyno);
198 			continue;
199 		}
200 #else	/* !OPENSSL follows */
201 
202 		/*
203 		 * The key type is unused, but is required to be 'M' or
204 		 * 'm' for compatibility.
205 		 */
206 		if (!(*token == 'M' || *token == 'm')) {
207 			log_maybe(&nerr,
208 				  "authreadkeys: invalid type for key %d",
209 				  keyno);
210 			continue;
211 		}
212 		keytype = KEY_TYPE_MD5;
213 #endif	/* !OPENSSL */
214 
215 		/*
216 		 * Finally, get key and insert it. If it is longer than 20
217 		 * characters, it is a binary string encoded in hex;
218 		 * otherwise, it is a text string of printable ASCII
219 		 * characters.
220 		 */
221 		token = nexttok(&line);
222 		if (token == NULL) {
223 			log_maybe(&nerr,
224 				  "authreadkeys: no key for key %d", keyno);
225 			continue;
226 		}
227 		next = NULL;
228 		len = strlen(token);
229 		if (len <= 20) {	/* Bug 2537 */
230 			next = emalloc(sizeof(KeyDataT) + len);
231 			next->keyid   = keyno;
232 			next->keytype = keytype;
233 			next->seclen  = len;
234 			memcpy(next->secbuf, token, len);
235 		} else {
236 			static const char hex[] = "0123456789abcdef";
237 			u_char	temp;
238 			char	*ptr;
239 			size_t	jlim;
240 
241 			jlim = min(len, 2 * sizeof(keystr));
242 			for (j = 0; j < jlim; j++) {
243 				ptr = strchr(hex, tolower((unsigned char)token[j]));
244 				if (ptr == NULL)
245 					break;	/* abort decoding */
246 				temp = (u_char)(ptr - hex);
247 				if (j & 1)
248 					keystr[j / 2] |= temp;
249 				else
250 					keystr[j / 2] = temp << 4;
251 			}
252 			if (j < jlim) {
253 				log_maybe(&nerr,
254 					  "authreadkeys: invalid hex digit for key %d",
255 					  keyno);
256 				continue;
257 			}
258 			len = jlim/2; /* hmmmm.... what about odd length?!? */
259 			next = emalloc(sizeof(KeyDataT) + len);
260 			next->keyid   = keyno;
261 			next->keytype = keytype;
262 			next->seclen  = len;
263 			memcpy(next->secbuf, keystr, len);
264 		}
265 		INSIST(NULL != next);
266 		next->next = list;
267 		list = next;
268 	}
269 	fclose(fp);
270 	if (nerr > nerr_maxlimit) {
271 		msyslog(LOG_ERR,
272 			"authreadkeys: rejecting file '%s' after %u errors (emergency break)",
273 			file, nerr);
274 		goto onerror;
275 	}
276 	if (nerr > 0) {
277 		msyslog(LOG_ERR,
278 			"authreadkeys: rejecting file '%s' after %u error(s)",
279 			file, nerr);
280 		goto onerror;
281 	}
282 
283 	/* first remove old file-based keys */
284 	auth_delkeys();
285 	/* insert the new key material */
286 	while (NULL != (next = list)) {
287 		list = next->next;
288 		MD5auth_setkey(next->keyid, next->keytype,
289 			       next->secbuf, next->seclen);
290 		/* purge secrets from memory before free()ing it */
291 		memset(next, 0, sizeof(*next) + next->seclen);
292 		free(next);
293 	}
294 	return (1);
295 
296   onerror:
297 	/* Mop up temporary storage before bailing out. */
298 	while (NULL != (next = list)) {
299 		list = next->next;
300 		/* purge secrets from memory before free()ing it */
301 		memset(next, 0, sizeof(*next) + next->seclen);
302 		free(next);
303 	}
304 	return (0);
305 }
306