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