Home | History | Annotate | Line # | Download | only in libntp
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