xref: /netbsd-src/external/bsd/ntp/dist/libntp/authreadkeys.c (revision eabc0478de71e4e011a5b4e0392741e01d491794)
1*eabc0478Schristos /*	$NetBSD: authreadkeys.c,v 1.12 2024/08/18 20:47:13 christos Exp $	*/
2abb0f93cSkardel 
3abb0f93cSkardel /*
4abb0f93cSkardel  * authreadkeys.c - routines to support the reading of the key file
5abb0f93cSkardel  */
6abb0f93cSkardel #include <config.h>
7abb0f93cSkardel #include <stdio.h>
8abb0f93cSkardel #include <ctype.h>
9abb0f93cSkardel 
104eea345dSchristos //#include "ntpd.h"	/* Only for DPRINTF */
114eea345dSchristos //#include "ntp_fp.h"
12abb0f93cSkardel #include "ntp.h"
13abb0f93cSkardel #include "ntp_syslog.h"
14abb0f93cSkardel #include "ntp_stdlib.h"
1568dbbb44Schristos #include "ntp_keyacc.h"
16abb0f93cSkardel 
17abb0f93cSkardel #ifdef OPENSSL
18abb0f93cSkardel #include "openssl/objects.h"
192950cc38Schristos #include "openssl/evp.h"
20abb0f93cSkardel #endif	/* OPENSSL */
21abb0f93cSkardel 
22abb0f93cSkardel /* Forwards */
23abb0f93cSkardel static char *nexttok (char **);
24abb0f93cSkardel 
25abb0f93cSkardel /*
26abb0f93cSkardel  * nexttok - basic internal tokenizing routine
27abb0f93cSkardel  */
28abb0f93cSkardel static char *
29abb0f93cSkardel nexttok(
30abb0f93cSkardel 	char	**str
31abb0f93cSkardel 	)
32abb0f93cSkardel {
33abb0f93cSkardel 	register char *cp;
34abb0f93cSkardel 	char *starttok;
35abb0f93cSkardel 
36abb0f93cSkardel 	cp = *str;
37abb0f93cSkardel 
38abb0f93cSkardel 	/*
39abb0f93cSkardel 	 * Space past white space
40abb0f93cSkardel 	 */
41abb0f93cSkardel 	while (*cp == ' ' || *cp == '\t')
42abb0f93cSkardel 		cp++;
43abb0f93cSkardel 
44abb0f93cSkardel 	/*
45abb0f93cSkardel 	 * Save this and space to end of token
46abb0f93cSkardel 	 */
47abb0f93cSkardel 	starttok = cp;
48abb0f93cSkardel 	while (*cp != '\0' && *cp != '\n' && *cp != ' '
49abb0f93cSkardel 	       && *cp != '\t' && *cp != '#')
50abb0f93cSkardel 		cp++;
51abb0f93cSkardel 
52abb0f93cSkardel 	/*
53abb0f93cSkardel 	 * If token length is zero return an error, else set end of
54abb0f93cSkardel 	 * token to zero and return start.
55abb0f93cSkardel 	 */
56abb0f93cSkardel 	if (starttok == cp)
572950cc38Schristos 		return NULL;
58abb0f93cSkardel 
59abb0f93cSkardel 	if (*cp == ' ' || *cp == '\t')
60abb0f93cSkardel 		*cp++ = '\0';
61abb0f93cSkardel 	else
62abb0f93cSkardel 		*cp = '\0';
63abb0f93cSkardel 
64abb0f93cSkardel 	*str = cp;
65abb0f93cSkardel 	return starttok;
66abb0f93cSkardel }
67abb0f93cSkardel 
68abb0f93cSkardel 
69af12ab5eSchristos /* TALOS-CAN-0055: possibly DoS attack by setting the key file to the
70af12ab5eSchristos  * log file. This is hard to prevent (it would need to check two files
71af12ab5eSchristos  * to be the same on the inode level, which will not work so easily with
72af12ab5eSchristos  * Windows or VMS) but we can avoid the self-amplification loop: We only
73af12ab5eSchristos  * log the first 5 errors, silently ignore the next 10 errors, and give
74af12ab5eSchristos  * up when when we have found more than 15 errors.
75af12ab5eSchristos  *
76af12ab5eSchristos  * This avoids the endless file iteration we will end up with otherwise,
77af12ab5eSchristos  * and also avoids overflowing the log file.
78af12ab5eSchristos  *
79af12ab5eSchristos  * Nevertheless, once this happens, the keys are gone since this would
80af12ab5eSchristos  * require a save/swap strategy that is not easy to apply due to the
81af12ab5eSchristos  * data on global/static level.
82af12ab5eSchristos  */
83af12ab5eSchristos 
848b8da087Schristos static const u_int nerr_loglimit = 5u;
858b8da087Schristos static const u_int nerr_maxlimit = 15;
86af12ab5eSchristos 
878b8da087Schristos static void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3);
888b8da087Schristos 
898b8da087Schristos typedef struct keydata KeyDataT;
908b8da087Schristos struct keydata {
918b8da087Schristos 	KeyDataT *next;		/* queue/stack link		*/
9268dbbb44Schristos 	KeyAccT  *keyacclist;	/* key access list		*/
938b8da087Schristos 	keyid_t   keyid;	/* stored key ID		*/
948b8da087Schristos 	u_short   keytype;	/* stored key type		*/
958b8da087Schristos 	u_short   seclen;	/* length of secret		*/
968b8da087Schristos 	u_char    secbuf[1];	/* begin of secret (formal only)*/
978b8da087Schristos };
98af12ab5eSchristos 
99af12ab5eSchristos static void
100af12ab5eSchristos log_maybe(
1018b8da087Schristos 	u_int      *pnerr,
102af12ab5eSchristos 	const char *fmt  ,
103af12ab5eSchristos 	...)
104af12ab5eSchristos {
105af12ab5eSchristos 	va_list ap;
10668dbbb44Schristos 	if ((NULL == pnerr) || (++(*pnerr) <= nerr_loglimit)) {
107af12ab5eSchristos 		va_start(ap, fmt);
108af12ab5eSchristos 		mvsyslog(LOG_ERR, fmt, ap);
109af12ab5eSchristos 		va_end(ap);
110af12ab5eSchristos 	}
111af12ab5eSchristos }
112af12ab5eSchristos 
11368dbbb44Schristos static void
11468dbbb44Schristos free_keydata(
11568dbbb44Schristos 	KeyDataT *node
11668dbbb44Schristos 	)
11768dbbb44Schristos {
11868dbbb44Schristos 	KeyAccT *kap;
11968dbbb44Schristos 
12068dbbb44Schristos 	if (node) {
12168dbbb44Schristos 		while (node->keyacclist) {
12268dbbb44Schristos 			kap = node->keyacclist;
12368dbbb44Schristos 			node->keyacclist = kap->next;
12468dbbb44Schristos 			free(kap);
12568dbbb44Schristos 		}
12668dbbb44Schristos 
12768dbbb44Schristos 		/* purge secrets from memory before free()ing it */
12868dbbb44Schristos 		memset(node, 0, sizeof(*node) + node->seclen);
12968dbbb44Schristos 		free(node);
13068dbbb44Schristos 	}
13168dbbb44Schristos }
13268dbbb44Schristos 
133abb0f93cSkardel /*
134abb0f93cSkardel  * authreadkeys - (re)read keys from a file.
135abb0f93cSkardel  */
136abb0f93cSkardel int
137abb0f93cSkardel authreadkeys(
138abb0f93cSkardel 	const char *file
139abb0f93cSkardel 	)
140abb0f93cSkardel {
141abb0f93cSkardel 	FILE	*fp;
142abb0f93cSkardel 	char	*line;
143abb0f93cSkardel 	char	*token;
144abb0f93cSkardel 	keyid_t	keyno;
145abb0f93cSkardel 	int	keytype;
146abb0f93cSkardel 	char	buf[512];		/* lots of room for line */
147*eabc0478Schristos 	u_char	keystr[AUTHPWD_MAXSECLEN];
148e19314b7Schristos 	size_t	len;
1498b8da087Schristos 	u_int   nerr;
1508b8da087Schristos 	KeyDataT *list = NULL;
1518b8da087Schristos 	KeyDataT *next = NULL;
1524eea345dSchristos 
153abb0f93cSkardel 	/*
154abb0f93cSkardel 	 * Open file.  Complain and return if it can't be opened.
155abb0f93cSkardel 	 */
156abb0f93cSkardel 	fp = fopen(file, "r");
157abb0f93cSkardel 	if (fp == NULL) {
1588b8da087Schristos 		msyslog(LOG_ERR, "authreadkeys: file '%s': %m",
159abb0f93cSkardel 		    file);
1608b8da087Schristos 		goto onerror;
161abb0f93cSkardel 	}
162abb0f93cSkardel 	INIT_SSL();
163abb0f93cSkardel 
164abb0f93cSkardel 	/*
1658b8da087Schristos 	 * Now read lines from the file, looking for key entries. Put
1668b8da087Schristos 	 * the data into temporary store for later propagation to avoid
1678b8da087Schristos 	 * two-pass processing.
168abb0f93cSkardel 	 */
169af12ab5eSchristos 	nerr = 0;
170abb0f93cSkardel 	while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
171af12ab5eSchristos 		if (nerr > nerr_maxlimit)
172af12ab5eSchristos 			break;
173abb0f93cSkardel 		token = nexttok(&line);
174abb0f93cSkardel 		if (token == NULL)
175abb0f93cSkardel 			continue;
176abb0f93cSkardel 
177abb0f93cSkardel 		/*
178abb0f93cSkardel 		 * First is key number.  See if it is okay.
179abb0f93cSkardel 		 */
180abb0f93cSkardel 		keyno = atoi(token);
18168dbbb44Schristos 		if (keyno < 1) {
182af12ab5eSchristos 			log_maybe(&nerr,
183af12ab5eSchristos 				  "authreadkeys: cannot change key %s",
184af12ab5eSchristos 				  token);
185abb0f93cSkardel 			continue;
186abb0f93cSkardel 		}
187abb0f93cSkardel 
188abb0f93cSkardel 		if (keyno > NTP_MAXKEY) {
189af12ab5eSchristos 			log_maybe(&nerr,
190abb0f93cSkardel 				  "authreadkeys: key %s > %d reserved for Autokey",
191abb0f93cSkardel 				  token, NTP_MAXKEY);
192abb0f93cSkardel 			continue;
193abb0f93cSkardel 		}
194abb0f93cSkardel 
195abb0f93cSkardel 		/*
196abb0f93cSkardel 		 * Next is keytype. See if that is all right.
197abb0f93cSkardel 		 */
198abb0f93cSkardel 		token = nexttok(&line);
199abb0f93cSkardel 		if (token == NULL) {
200af12ab5eSchristos 			log_maybe(&nerr,
201af12ab5eSchristos 				  "authreadkeys: no key type for key %d",
202af12ab5eSchristos 				  keyno);
203abb0f93cSkardel 			continue;
204abb0f93cSkardel 		}
20568dbbb44Schristos 
20668dbbb44Schristos 		/* We want to silently ignore keys where we do not
20768dbbb44Schristos 		 * support the requested digest type. OTOH, we want to
20868dbbb44Schristos 		 * make sure the file is well-formed.  That means we
20968dbbb44Schristos 		 * have to process the line completely and have to
21068dbbb44Schristos 		 * finally throw away the result... This is a bit more
21168dbbb44Schristos 		 * work, but it also results in better error detection.
21268dbbb44Schristos 		 */
213abb0f93cSkardel #ifdef OPENSSL
214abb0f93cSkardel 		/*
215abb0f93cSkardel 		 * The key type is the NID used by the message digest
216abb0f93cSkardel 		 * algorithm. There are a number of inconsistencies in
217abb0f93cSkardel 		 * the OpenSSL database. We attempt to discover them
218abb0f93cSkardel 		 * here and prevent use of inconsistent data later.
219abb0f93cSkardel 		 */
220abb0f93cSkardel 		keytype = keytype_from_text(token, NULL);
221abb0f93cSkardel 		if (keytype == 0) {
22268dbbb44Schristos 			log_maybe(NULL,
223*eabc0478Schristos 				  "authreadkeys: unsupported type %s for key %d",
224*eabc0478Schristos 				  token, keyno);
225cdfa2a7eSchristos #  ifdef ENABLE_CMAC
2264eea345dSchristos 		} else if (NID_cmac != keytype &&
2274eea345dSchristos 				EVP_get_digestbynid(keytype) == NULL) {
22868dbbb44Schristos 			log_maybe(NULL,
229*eabc0478Schristos 				  "authreadkeys: no algorithm for %s key %d",
230*eabc0478Schristos 				  token, keyno);
23168dbbb44Schristos 			keytype = 0;
232cdfa2a7eSchristos #  endif /* ENABLE_CMAC */
233abb0f93cSkardel 		}
2342950cc38Schristos #else	/* !OPENSSL follows */
235abb0f93cSkardel 		/*
236abb0f93cSkardel 		 * The key type is unused, but is required to be 'M' or
237abb0f93cSkardel 		 * 'm' for compatibility.
238abb0f93cSkardel 		 */
239*eabc0478Schristos 		if (! (toupper(*token) == 'M')) {
24068dbbb44Schristos 			log_maybe(NULL,
241af12ab5eSchristos 				  "authreadkeys: invalid type for key %d",
242af12ab5eSchristos 				  keyno);
24368dbbb44Schristos 			keytype = 0;
24468dbbb44Schristos 		} else {
245abb0f93cSkardel 			keytype = KEY_TYPE_MD5;
24668dbbb44Schristos 		}
2472950cc38Schristos #endif	/* !OPENSSL */
248abb0f93cSkardel 
249abb0f93cSkardel 		/*
250abb0f93cSkardel 		 * Finally, get key and insert it. If it is longer than 20
251abb0f93cSkardel 		 * characters, it is a binary string encoded in hex;
252abb0f93cSkardel 		 * otherwise, it is a text string of printable ASCII
253abb0f93cSkardel 		 * characters.
254abb0f93cSkardel 		 */
255abb0f93cSkardel 		token = nexttok(&line);
256abb0f93cSkardel 		if (token == NULL) {
257af12ab5eSchristos 			log_maybe(&nerr,
258abb0f93cSkardel 				  "authreadkeys: no key for key %d", keyno);
259abb0f93cSkardel 			continue;
260abb0f93cSkardel 		}
2618b8da087Schristos 		next = NULL;
262*eabc0478Schristos 		len = authdecodepw(keystr, sizeof(keystr), token, AUTHPWD_UNSPEC);
263*eabc0478Schristos 		if (len > sizeof(keystr)) {
264*eabc0478Schristos 			switch (errno) {
265*eabc0478Schristos 			case ENOMEM:
266af12ab5eSchristos 				log_maybe(&nerr,
267*eabc0478Schristos 					  "authreadkeys: passwd too long for key %d",
268af12ab5eSchristos 					  keyno);
269*eabc0478Schristos 				break;
270*eabc0478Schristos 			case EINVAL:
271*eabc0478Schristos 				log_maybe(&nerr,
272*eabc0478Schristos 					  "authreadkeys: passwd has bad char for key %d",
273*eabc0478Schristos 					  keyno);
274*eabc0478Schristos 				break;
275*eabc0478Schristos #ifdef DEBUG
276*eabc0478Schristos 			default:
277*eabc0478Schristos 				log_maybe(&nerr,
278*eabc0478Schristos 					  "authreadkeys: unexpected errno %d for key %d",
279*eabc0478Schristos 					  errno, keyno);
280*eabc0478Schristos 				break;
281*eabc0478Schristos #endif
282*eabc0478Schristos 			}
2832950cc38Schristos 			continue;
2842950cc38Schristos 		}
2858b8da087Schristos 		next = emalloc(sizeof(KeyDataT) + len);
28668dbbb44Schristos 		next->keyacclist = NULL;
2878b8da087Schristos 		next->keyid   = keyno;
2888b8da087Schristos 		next->keytype = keytype;
2898b8da087Schristos 		next->seclen  = len;
2908b8da087Schristos 		memcpy(next->secbuf, keystr, len);
29168dbbb44Schristos 
29268dbbb44Schristos 		token = nexttok(&line);
29368dbbb44Schristos 		if (token != NULL) {	/* A comma-separated IP access list */
29468dbbb44Schristos 			char *tp = token;
29568dbbb44Schristos 
29668dbbb44Schristos 			while (tp) {
29768dbbb44Schristos 				char *i;
2984eea345dSchristos 				char *snp;	/* subnet text pointer */
2994eea345dSchristos 				unsigned int snbits;
30068dbbb44Schristos 				sockaddr_u addr;
30168dbbb44Schristos 
30268dbbb44Schristos 				i = strchr(tp, (int)',');
3034eea345dSchristos 				if (i) {
30468dbbb44Schristos 					*i = '\0';
3054eea345dSchristos 				}
3064eea345dSchristos 				snp = strchr(tp, (int)'/');
3074eea345dSchristos 				if (snp) {
3084eea345dSchristos 					char *sp;
3094eea345dSchristos 
3104eea345dSchristos 					*snp++ = '\0';
3114eea345dSchristos 					snbits = 0;
3124eea345dSchristos 					sp = snp;
3134eea345dSchristos 
3144eea345dSchristos 					while (*sp != '\0') {
3154eea345dSchristos 						if (!isdigit((unsigned char)*sp))
3164eea345dSchristos 						    break;
3174eea345dSchristos 						if (snbits > 1000)
3184eea345dSchristos 						    break;	/* overflow */
3194eea345dSchristos 						snbits = 10 * snbits + (*sp++ - '0');       /* ascii dependent */
3204eea345dSchristos 					}
3214eea345dSchristos 					if (*sp != '\0') {
3224eea345dSchristos 						log_maybe(&nerr,
3234eea345dSchristos 							  "authreadkeys: Invalid character in subnet specification for <%s/%s> in key %d",
3244eea345dSchristos 							  sp, snp, keyno);
3254eea345dSchristos 						goto nextip;
3264eea345dSchristos 					}
3274eea345dSchristos 				} else {
3284eea345dSchristos 					snbits = UINT_MAX;
3294eea345dSchristos 				}
33068dbbb44Schristos 
33168dbbb44Schristos 				if (is_ip_address(tp, AF_UNSPEC, &addr)) {
3324eea345dSchristos 					/* Make sure that snbits is valid for addr */
3334eea345dSchristos 				    if ((snbits < UINT_MAX) &&
3344eea345dSchristos 					( (IS_IPV4(&addr) && snbits > 32) ||
3354eea345dSchristos 					  (IS_IPV6(&addr) && snbits > 128))) {
3364eea345dSchristos 						log_maybe(NULL,
3374eea345dSchristos 							  "authreadkeys: excessive subnet mask <%s/%s> for key %d",
3384eea345dSchristos 							  tp, snp, keyno);
3394eea345dSchristos 				    }
34068dbbb44Schristos 				    next->keyacclist = keyacc_new_push(
3414eea345dSchristos 					next->keyacclist, &addr, snbits);
34268dbbb44Schristos 				} else {
34368dbbb44Schristos 					log_maybe(&nerr,
34468dbbb44Schristos 						  "authreadkeys: invalid IP address <%s> for key %d",
34568dbbb44Schristos 						  tp, keyno);
34668dbbb44Schristos 				}
34768dbbb44Schristos 
3484eea345dSchristos 			nextip:
34968dbbb44Schristos 				if (i) {
35068dbbb44Schristos 					tp = i + 1;
35168dbbb44Schristos 				} else {
35268dbbb44Schristos 					tp = 0;
35368dbbb44Schristos 				}
35468dbbb44Schristos 			}
35568dbbb44Schristos 		}
35668dbbb44Schristos 
35768dbbb44Schristos 		/* check if this has to be weeded out... */
35868dbbb44Schristos 		if (0 == keytype) {
35968dbbb44Schristos 			free_keydata(next);
36068dbbb44Schristos 			next = NULL;
36168dbbb44Schristos 			continue;
36268dbbb44Schristos 		}
36368dbbb44Schristos 
364*eabc0478Schristos 		DEBUG_INSIST(NULL != next);
365*eabc0478Schristos #if defined(OPENSSL) && defined(ENABLE_CMAC)
366*eabc0478Schristos 		if (NID_cmac == keytype && len < 16) {
367*eabc0478Schristos 			msyslog(LOG_WARNING, CMAC " keys are 128 bits, "
368*eabc0478Schristos 				"zero-extending key %u by %u bits",
369*eabc0478Schristos 				(u_int)keyno, 8 * (16 - (u_int)len));
370*eabc0478Schristos 		}
371*eabc0478Schristos #endif	/* OPENSSL && ENABLE_CMAC */
3728b8da087Schristos 		next->next = list;
3738b8da087Schristos 		list = next;
374abb0f93cSkardel 	}
375abb0f93cSkardel 	fclose(fp);
3768b8da087Schristos 	if (nerr > 0) {
37768dbbb44Schristos 		const char * why = "";
378*eabc0478Schristos 
37968dbbb44Schristos 		if (nerr > nerr_maxlimit)
38068dbbb44Schristos 			why = " (emergency break)";
381af12ab5eSchristos 		msyslog(LOG_ERR,
38268dbbb44Schristos 			"authreadkeys: rejecting file '%s' after %u error(s)%s",
38368dbbb44Schristos 			file, nerr, why);
3848b8da087Schristos 		goto onerror;
3858b8da087Schristos 	}
3868b8da087Schristos 
3878b8da087Schristos 	/* first remove old file-based keys */
3888b8da087Schristos 	auth_delkeys();
3898b8da087Schristos 	/* insert the new key material */
3908b8da087Schristos 	while (NULL != (next = list)) {
3918b8da087Schristos 		list = next->next;
3928b8da087Schristos 		MD5auth_setkey(next->keyid, next->keytype,
39368dbbb44Schristos 			       next->secbuf, next->seclen, next->keyacclist);
39468dbbb44Schristos 		next->keyacclist = NULL; /* consumed by MD5auth_setkey */
39568dbbb44Schristos 		free_keydata(next);
396af12ab5eSchristos 	}
397abb0f93cSkardel 	return (1);
3988b8da087Schristos 
3998b8da087Schristos   onerror:
4008b8da087Schristos 	/* Mop up temporary storage before bailing out. */
4018b8da087Schristos 	while (NULL != (next = list)) {
4028b8da087Schristos 		list = next->next;
40368dbbb44Schristos 		free_keydata(next);
4048b8da087Schristos 	}
4058b8da087Schristos 	return (0);
406abb0f93cSkardel }
407