Home | History | Annotate | Line # | Download | only in libntp
      1  1.11  christos /*	$NetBSD: authreadkeys.c,v 1.12 2024/08/18 20:47:13 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.12  christos 
     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.12  christos 
     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.12  christos 
     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.12  christos 
     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.12  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.12  christos 	u_char	keystr[AUTHPWD_MAXSECLEN];
    148   1.2  christos 	size_t	len;
    149   1.8  christos 	u_int   nerr;
    150   1.8  christos 	KeyDataT *list = NULL;
    151   1.8  christos 	KeyDataT *next = NULL;
    152  1.10  christos 
    153   1.1    kardel 	/*
    154   1.1    kardel 	 * Open file.  Complain and return if it can't be opened.
    155   1.1    kardel 	 */
    156   1.1    kardel 	fp = fopen(file, "r");
    157   1.1    kardel 	if (fp == NULL) {
    158   1.8  christos 		msyslog(LOG_ERR, "authreadkeys: file '%s': %m",
    159   1.1    kardel 		    file);
    160   1.8  christos 		goto onerror;
    161   1.1    kardel 	}
    162   1.1    kardel 	INIT_SSL();
    163   1.1    kardel 
    164   1.1    kardel 	/*
    165   1.8  christos 	 * Now read lines from the file, looking for key entries. Put
    166   1.8  christos 	 * the data into temporary store for later propagation to avoid
    167   1.8  christos 	 * two-pass processing.
    168   1.1    kardel 	 */
    169   1.7  christos 	nerr = 0;
    170   1.1    kardel 	while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
    171   1.7  christos 		if (nerr > nerr_maxlimit)
    172   1.7  christos 			break;
    173   1.1    kardel 		token = nexttok(&line);
    174   1.1    kardel 		if (token == NULL)
    175   1.1    kardel 			continue;
    176  1.12  christos 
    177   1.1    kardel 		/*
    178   1.1    kardel 		 * First is key number.  See if it is okay.
    179   1.1    kardel 		 */
    180   1.1    kardel 		keyno = atoi(token);
    181   1.9  christos 		if (keyno < 1) {
    182   1.7  christos 			log_maybe(&nerr,
    183   1.7  christos 				  "authreadkeys: cannot change key %s",
    184   1.7  christos 				  token);
    185   1.1    kardel 			continue;
    186   1.1    kardel 		}
    187   1.1    kardel 
    188   1.1    kardel 		if (keyno > NTP_MAXKEY) {
    189   1.7  christos 			log_maybe(&nerr,
    190   1.7  christos 				  "authreadkeys: key %s > %d reserved for Autokey",
    191   1.7  christos 				  token, NTP_MAXKEY);
    192   1.1    kardel 			continue;
    193   1.1    kardel 		}
    194   1.1    kardel 
    195   1.1    kardel 		/*
    196   1.1    kardel 		 * Next is keytype. See if that is all right.
    197   1.1    kardel 		 */
    198   1.1    kardel 		token = nexttok(&line);
    199   1.1    kardel 		if (token == NULL) {
    200   1.7  christos 			log_maybe(&nerr,
    201   1.7  christos 				  "authreadkeys: no key type for key %d",
    202   1.7  christos 				  keyno);
    203   1.1    kardel 			continue;
    204   1.1    kardel 		}
    205   1.9  christos 
    206   1.9  christos 		/* We want to silently ignore keys where we do not
    207   1.9  christos 		 * support the requested digest type. OTOH, we want to
    208   1.9  christos 		 * make sure the file is well-formed.  That means we
    209   1.9  christos 		 * have to process the line completely and have to
    210   1.9  christos 		 * finally throw away the result... This is a bit more
    211   1.9  christos 		 * work, but it also results in better error detection.
    212  1.12  christos 		 */
    213   1.1    kardel #ifdef OPENSSL
    214   1.1    kardel 		/*
    215  1.12  christos 		 * The key type is the NID used by the message digest
    216   1.1    kardel 		 * algorithm. There are a number of inconsistencies in
    217   1.1    kardel 		 * the OpenSSL database. We attempt to discover them
    218   1.1    kardel 		 * here and prevent use of inconsistent data later.
    219   1.1    kardel 		 */
    220   1.1    kardel 		keytype = keytype_from_text(token, NULL);
    221   1.1    kardel 		if (keytype == 0) {
    222   1.9  christos 			log_maybe(NULL,
    223  1.12  christos 				  "authreadkeys: unsupported type %s for key %d",
    224  1.12  christos 				  token, keyno);
    225  1.11  christos #  ifdef ENABLE_CMAC
    226  1.10  christos 		} else if (NID_cmac != keytype &&
    227  1.10  christos 				EVP_get_digestbynid(keytype) == NULL) {
    228   1.9  christos 			log_maybe(NULL,
    229  1.12  christos 				  "authreadkeys: no algorithm for %s key %d",
    230  1.12  christos 				  token, keyno);
    231   1.9  christos 			keytype = 0;
    232  1.11  christos #  endif /* ENABLE_CMAC */
    233   1.1    kardel 		}
    234   1.3  christos #else	/* !OPENSSL follows */
    235   1.1    kardel 		/*
    236   1.1    kardel 		 * The key type is unused, but is required to be 'M' or
    237   1.1    kardel 		 * 'm' for compatibility.
    238   1.1    kardel 		 */
    239  1.12  christos 		if (! (toupper(*token) == 'M')) {
    240   1.9  christos 			log_maybe(NULL,
    241   1.7  christos 				  "authreadkeys: invalid type for key %d",
    242   1.7  christos 				  keyno);
    243   1.9  christos 			keytype = 0;
    244   1.9  christos 		} else {
    245   1.9  christos 			keytype = KEY_TYPE_MD5;
    246   1.1    kardel 		}
    247   1.3  christos #endif	/* !OPENSSL */
    248   1.1    kardel 
    249   1.1    kardel 		/*
    250   1.1    kardel 		 * Finally, get key and insert it. If it is longer than 20
    251   1.1    kardel 		 * characters, it is a binary string encoded in hex;
    252   1.1    kardel 		 * otherwise, it is a text string of printable ASCII
    253   1.1    kardel 		 * characters.
    254   1.1    kardel 		 */
    255   1.1    kardel 		token = nexttok(&line);
    256   1.1    kardel 		if (token == NULL) {
    257   1.7  christos 			log_maybe(&nerr,
    258   1.7  christos 				  "authreadkeys: no key for key %d", keyno);
    259   1.1    kardel 			continue;
    260   1.1    kardel 		}
    261   1.8  christos 		next = NULL;
    262  1.12  christos 		len = authdecodepw(keystr, sizeof(keystr), token, AUTHPWD_UNSPEC);
    263  1.12  christos 		if (len > sizeof(keystr)) {
    264  1.12  christos 			switch (errno) {
    265  1.12  christos 			case ENOMEM:
    266  1.12  christos 				log_maybe(&nerr,
    267  1.12  christos 					  "authreadkeys: passwd too long for key %d",
    268  1.12  christos 					  keyno);
    269  1.12  christos 				break;
    270  1.12  christos 			case EINVAL:
    271   1.7  christos 				log_maybe(&nerr,
    272  1.12  christos 					  "authreadkeys: passwd has bad char for key %d",
    273   1.7  christos 					  keyno);
    274  1.12  christos 				break;
    275  1.12  christos #ifdef DEBUG
    276  1.12  christos 			default:
    277  1.12  christos 				log_maybe(&nerr,
    278  1.12  christos 					  "authreadkeys: unexpected errno %d for key %d",
    279  1.12  christos 					  errno, keyno);
    280  1.12  christos 				break;
    281  1.12  christos #endif
    282   1.3  christos 			}
    283  1.12  christos 			continue;
    284   1.1    kardel 		}
    285  1.12  christos 		next = emalloc(sizeof(KeyDataT) + len);
    286  1.12  christos 		next->keyacclist = NULL;
    287  1.12  christos 		next->keyid   = keyno;
    288  1.12  christos 		next->keytype = keytype;
    289  1.12  christos 		next->seclen  = len;
    290  1.12  christos 		memcpy(next->secbuf, keystr, len);
    291   1.9  christos 
    292   1.9  christos 		token = nexttok(&line);
    293   1.9  christos 		if (token != NULL) {	/* A comma-separated IP access list */
    294   1.9  christos 			char *tp = token;
    295   1.9  christos 
    296   1.9  christos 			while (tp) {
    297   1.9  christos 				char *i;
    298  1.10  christos 				char *snp;	/* subnet text pointer */
    299  1.10  christos 				unsigned int snbits;
    300   1.9  christos 				sockaddr_u addr;
    301   1.9  christos 
    302   1.9  christos 				i = strchr(tp, (int)',');
    303  1.10  christos 				if (i) {
    304   1.9  christos 					*i = '\0';
    305  1.10  christos 				}
    306  1.10  christos 				snp = strchr(tp, (int)'/');
    307  1.10  christos 				if (snp) {
    308  1.10  christos 					char *sp;
    309  1.10  christos 
    310  1.10  christos 					*snp++ = '\0';
    311  1.10  christos 					snbits = 0;
    312  1.10  christos 					sp = snp;
    313  1.10  christos 
    314  1.10  christos 					while (*sp != '\0') {
    315  1.10  christos 						if (!isdigit((unsigned char)*sp))
    316  1.10  christos 						    break;
    317  1.10  christos 						if (snbits > 1000)
    318  1.10  christos 						    break;	/* overflow */
    319  1.10  christos 						snbits = 10 * snbits + (*sp++ - '0');       /* ascii dependent */
    320  1.10  christos 					}
    321  1.10  christos 					if (*sp != '\0') {
    322  1.10  christos 						log_maybe(&nerr,
    323  1.10  christos 							  "authreadkeys: Invalid character in subnet specification for <%s/%s> in key %d",
    324  1.10  christos 							  sp, snp, keyno);
    325  1.10  christos 						goto nextip;
    326  1.10  christos 					}
    327  1.10  christos 				} else {
    328  1.10  christos 					snbits = UINT_MAX;
    329  1.10  christos 				}
    330   1.9  christos 
    331   1.9  christos 				if (is_ip_address(tp, AF_UNSPEC, &addr)) {
    332  1.10  christos 					/* Make sure that snbits is valid for addr */
    333  1.10  christos 				    if ((snbits < UINT_MAX) &&
    334  1.10  christos 					( (IS_IPV4(&addr) && snbits > 32) ||
    335  1.10  christos 					  (IS_IPV6(&addr) && snbits > 128))) {
    336  1.10  christos 						log_maybe(NULL,
    337  1.10  christos 							  "authreadkeys: excessive subnet mask <%s/%s> for key %d",
    338  1.10  christos 							  tp, snp, keyno);
    339  1.10  christos 				    }
    340  1.10  christos 				    next->keyacclist = keyacc_new_push(
    341  1.10  christos 					next->keyacclist, &addr, snbits);
    342   1.9  christos 				} else {
    343   1.9  christos 					log_maybe(&nerr,
    344   1.9  christos 						  "authreadkeys: invalid IP address <%s> for key %d",
    345   1.9  christos 						  tp, keyno);
    346   1.9  christos 				}
    347   1.9  christos 
    348  1.10  christos 			nextip:
    349   1.9  christos 				if (i) {
    350   1.9  christos 					tp = i + 1;
    351   1.9  christos 				} else {
    352   1.9  christos 					tp = 0;
    353   1.9  christos 				}
    354   1.9  christos 			}
    355   1.9  christos 		}
    356   1.9  christos 
    357   1.9  christos 		/* check if this has to be weeded out... */
    358   1.9  christos 		if (0 == keytype) {
    359   1.9  christos 			free_keydata(next);
    360   1.9  christos 			next = NULL;
    361   1.9  christos 			continue;
    362   1.9  christos 		}
    363  1.12  christos 
    364  1.12  christos 		DEBUG_INSIST(NULL != next);
    365  1.12  christos #if defined(OPENSSL) && defined(ENABLE_CMAC)
    366  1.12  christos 		if (NID_cmac == keytype && len < 16) {
    367  1.12  christos 			msyslog(LOG_WARNING, CMAC " keys are 128 bits, "
    368  1.12  christos 				"zero-extending key %u by %u bits",
    369  1.12  christos 				(u_int)keyno, 8 * (16 - (u_int)len));
    370  1.12  christos 		}
    371  1.12  christos #endif	/* OPENSSL && ENABLE_CMAC */
    372   1.8  christos 		next->next = list;
    373   1.8  christos 		list = next;
    374   1.1    kardel 	}
    375   1.1    kardel 	fclose(fp);
    376   1.8  christos 	if (nerr > 0) {
    377   1.9  christos 		const char * why = "";
    378  1.12  christos 
    379   1.9  christos 		if (nerr > nerr_maxlimit)
    380   1.9  christos 			why = " (emergency break)";
    381   1.7  christos 		msyslog(LOG_ERR,
    382   1.9  christos 			"authreadkeys: rejecting file '%s' after %u error(s)%s",
    383   1.9  christos 			file, nerr, why);
    384   1.8  christos 		goto onerror;
    385   1.8  christos 	}
    386   1.8  christos 
    387   1.8  christos 	/* first remove old file-based keys */
    388   1.8  christos 	auth_delkeys();
    389   1.8  christos 	/* insert the new key material */
    390   1.8  christos 	while (NULL != (next = list)) {
    391   1.8  christos 		list = next->next;
    392   1.8  christos 		MD5auth_setkey(next->keyid, next->keytype,
    393   1.9  christos 			       next->secbuf, next->seclen, next->keyacclist);
    394   1.9  christos 		next->keyacclist = NULL; /* consumed by MD5auth_setkey */
    395   1.9  christos 		free_keydata(next);
    396   1.7  christos 	}
    397   1.1    kardel 	return (1);
    398   1.8  christos 
    399   1.8  christos   onerror:
    400   1.8  christos 	/* Mop up temporary storage before bailing out. */
    401   1.8  christos 	while (NULL != (next = list)) {
    402   1.8  christos 		list = next->next;
    403   1.9  christos 		free_keydata(next);
    404   1.8  christos 	}
    405   1.8  christos 	return (0);
    406   1.1    kardel }
    407