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