1 /*        $NetBSD: authreadkeys.c,v 1.12 2024/08/18 20:47:13 christos Exp $     */
2 
3 /*
4  * authreadkeys.c - routines to support the reading of the key file
5  */
6 #include <config.h>
7 #include <stdio.h>
8 #include <ctype.h>
9 
10 //#include "ntpd.h" /* Only for DPRINTF */
11 //#include "ntp_fp.h"
12 #include "ntp.h"
13 #include "ntp_syslog.h"
14 #include "ntp_stdlib.h"
15 #include "ntp_keyacc.h"
16 
17 #ifdef OPENSSL
18 #include "openssl/objects.h"
19 #include "openssl/evp.h"
20 #endif    /* OPENSSL */
21 
22 /* Forwards */
23 static char *nexttok (char **);
24 
25 /*
26  * nexttok - basic internal tokenizing routine
27  */
28 static char *
nexttok(char ** str)29 nexttok(
30           char      **str
31           )
32 {
33           register char *cp;
34           char *starttok;
35 
36           cp = *str;
37 
38           /*
39            * Space past white space
40            */
41           while (*cp == ' ' || *cp == '\t')
42                     cp++;
43 
44           /*
45            * Save this and space to end of token
46            */
47           starttok = cp;
48           while (*cp != '\0' && *cp != '\n' && *cp != ' '
49                  && *cp != '\t' && *cp != '#')
50                     cp++;
51 
52           /*
53            * If token length is zero return an error, else set end of
54            * token to zero and return start.
55            */
56           if (starttok == cp)
57                     return NULL;
58 
59           if (*cp == ' ' || *cp == '\t')
60                     *cp++ = '\0';
61           else
62                     *cp = '\0';
63 
64           *str = cp;
65           return starttok;
66 }
67 
68 
69 /* TALOS-CAN-0055: possibly DoS attack by setting the key file to the
70  * log file. This is hard to prevent (it would need to check two files
71  * to be the same on the inode level, which will not work so easily with
72  * Windows or VMS) but we can avoid the self-amplification loop: We only
73  * log the first 5 errors, silently ignore the next 10 errors, and give
74  * up when when we have found more than 15 errors.
75  *
76  * This avoids the endless file iteration we will end up with otherwise,
77  * and also avoids overflowing the log file.
78  *
79  * Nevertheless, once this happens, the keys are gone since this would
80  * require a save/swap strategy that is not easy to apply due to the
81  * data on global/static level.
82  */
83 
84 static const u_int nerr_loglimit = 5u;
85 static const u_int nerr_maxlimit = 15;
86 
87 static void log_maybe(u_int*, const char*, ...) NTP_PRINTF(2, 3);
88 
89 typedef struct keydata KeyDataT;
90 struct keydata {
91           KeyDataT *next;               /* queue/stack link           */
92           KeyAccT  *keyacclist;         /* key access list            */
93           keyid_t   keyid;    /* stored key ID              */
94           u_short   keytype;  /* stored key type            */
95           u_short   seclen;   /* length of secret           */
96           u_char    secbuf[1];          /* begin of secret (formal only)*/
97 };
98 
99 static void
log_maybe(u_int * pnerr,const char * fmt,...)100 log_maybe(
101           u_int      *pnerr,
102           const char *fmt  ,
103           ...)
104 {
105           va_list ap;
106           if ((NULL == pnerr) || (++(*pnerr) <= nerr_loglimit)) {
107                     va_start(ap, fmt);
108                     mvsyslog(LOG_ERR, fmt, ap);
109                     va_end(ap);
110           }
111 }
112 
113 static void
free_keydata(KeyDataT * node)114 free_keydata(
115           KeyDataT *node
116           )
117 {
118           KeyAccT *kap;
119 
120           if (node) {
121                     while (node->keyacclist) {
122                               kap = node->keyacclist;
123                               node->keyacclist = kap->next;
124                               free(kap);
125                     }
126 
127                     /* purge secrets from memory before free()ing it */
128                     memset(node, 0, sizeof(*node) + node->seclen);
129                     free(node);
130           }
131 }
132 
133 /*
134  * authreadkeys - (re)read keys from a file.
135  */
136 int
authreadkeys(const char * file)137 authreadkeys(
138           const char *file
139           )
140 {
141           FILE      *fp;
142           char      *line;
143           char      *token;
144           keyid_t   keyno;
145           int       keytype;
146           char      buf[512];           /* lots of room for line */
147           u_char    keystr[AUTHPWD_MAXSECLEN];
148           size_t    len;
149           u_int   nerr;
150           KeyDataT *list = NULL;
151           KeyDataT *next = NULL;
152 
153           /*
154            * Open file.  Complain and return if it can't be opened.
155            */
156           fp = fopen(file, "r");
157           if (fp == NULL) {
158                     msyslog(LOG_ERR, "authreadkeys: file '%s': %m",
159                         file);
160                     goto onerror;
161           }
162           INIT_SSL();
163 
164           /*
165            * Now read lines from the file, looking for key entries. Put
166            * the data into temporary store for later propagation to avoid
167            * two-pass processing.
168            */
169           nerr = 0;
170           while ((line = fgets(buf, sizeof buf, fp)) != NULL) {
171                     if (nerr > nerr_maxlimit)
172                               break;
173                     token = nexttok(&line);
174                     if (token == NULL)
175                               continue;
176 
177                     /*
178                      * First is key number.  See if it is okay.
179                      */
180                     keyno = atoi(token);
181                     if (keyno < 1) {
182                               log_maybe(&nerr,
183                                           "authreadkeys: cannot change key %s",
184                                           token);
185                               continue;
186                     }
187 
188                     if (keyno > NTP_MAXKEY) {
189                               log_maybe(&nerr,
190                                           "authreadkeys: key %s > %d reserved for Autokey",
191                                           token, NTP_MAXKEY);
192                               continue;
193                     }
194 
195                     /*
196                      * Next is keytype. See if that is all right.
197                      */
198                     token = nexttok(&line);
199                     if (token == NULL) {
200                               log_maybe(&nerr,
201                                           "authreadkeys: no key type for key %d",
202                                           keyno);
203                               continue;
204                     }
205 
206                     /* We want to silently ignore keys where we do not
207                      * support the requested digest type. OTOH, we want to
208                      * make sure the file is well-formed.  That means we
209                      * have to process the line completely and have to
210                      * finally throw away the result... This is a bit more
211                      * work, but it also results in better error detection.
212                      */
213 #ifdef OPENSSL
214                     /*
215                      * The key type is the NID used by the message digest
216                      * algorithm. There are a number of inconsistencies in
217                      * the OpenSSL database. We attempt to discover them
218                      * here and prevent use of inconsistent data later.
219                      */
220                     keytype = keytype_from_text(token, NULL);
221                     if (keytype == 0) {
222                               log_maybe(NULL,
223                                           "authreadkeys: unsupported type %s for key %d",
224                                           token, keyno);
225 #  ifdef ENABLE_CMAC
226                     } else if (NID_cmac != keytype &&
227                                         EVP_get_digestbynid(keytype) == NULL) {
228                               log_maybe(NULL,
229                                           "authreadkeys: no algorithm for %s key %d",
230                                           token, keyno);
231                               keytype = 0;
232 #  endif /* ENABLE_CMAC */
233                     }
234 #else     /* !OPENSSL follows */
235                     /*
236                      * The key type is unused, but is required to be 'M' or
237                      * 'm' for compatibility.
238                      */
239                     if (! (toupper(*token) == 'M')) {
240                               log_maybe(NULL,
241                                           "authreadkeys: invalid type for key %d",
242                                           keyno);
243                               keytype = 0;
244                     } else {
245                               keytype = KEY_TYPE_MD5;
246                     }
247 #endif    /* !OPENSSL */
248 
249                     /*
250                      * Finally, get key and insert it. If it is longer than 20
251                      * characters, it is a binary string encoded in hex;
252                      * otherwise, it is a text string of printable ASCII
253                      * characters.
254                      */
255                     token = nexttok(&line);
256                     if (token == NULL) {
257                               log_maybe(&nerr,
258                                           "authreadkeys: no key for key %d", keyno);
259                               continue;
260                     }
261                     next = NULL;
262                     len = authdecodepw(keystr, sizeof(keystr), token, AUTHPWD_UNSPEC);
263                     if (len > sizeof(keystr)) {
264                               switch (errno) {
265                               case ENOMEM:
266                                         log_maybe(&nerr,
267                                                     "authreadkeys: passwd too long for key %d",
268                                                     keyno);
269                                         break;
270                               case EINVAL:
271                                         log_maybe(&nerr,
272                                                     "authreadkeys: passwd has bad char for key %d",
273                                                     keyno);
274                                         break;
275 #ifdef DEBUG
276                               default:
277                                         log_maybe(&nerr,
278                                                     "authreadkeys: unexpected errno %d for key %d",
279                                                     errno, keyno);
280                                         break;
281 #endif
282                               }
283                               continue;
284                     }
285                     next = emalloc(sizeof(KeyDataT) + len);
286                     next->keyacclist = NULL;
287                     next->keyid   = keyno;
288                     next->keytype = keytype;
289                     next->seclen  = len;
290                     memcpy(next->secbuf, keystr, len);
291 
292                     token = nexttok(&line);
293                     if (token != NULL) {          /* A comma-separated IP access list */
294                               char *tp = token;
295 
296                               while (tp) {
297                                         char *i;
298                                         char *snp;          /* subnet text pointer */
299                                         unsigned int snbits;
300                                         sockaddr_u addr;
301 
302                                         i = strchr(tp, (int)',');
303                                         if (i) {
304                                                   *i = '\0';
305                                         }
306                                         snp = strchr(tp, (int)'/');
307                                         if (snp) {
308                                                   char *sp;
309 
310                                                   *snp++ = '\0';
311                                                   snbits = 0;
312                                                   sp = snp;
313 
314                                                   while (*sp != '\0') {
315                                                             if (!isdigit((unsigned char)*sp))
316                                                                 break;
317                                                             if (snbits > 1000)
318                                                                 break;          /* overflow */
319                                                             snbits = 10 * snbits + (*sp++ - '0');       /* ascii dependent */
320                                                   }
321                                                   if (*sp != '\0') {
322                                                             log_maybe(&nerr,
323                                                                         "authreadkeys: Invalid character in subnet specification for <%s/%s> in key %d",
324                                                                         sp, snp, keyno);
325                                                             goto nextip;
326                                                   }
327                                         } else {
328                                                   snbits = UINT_MAX;
329                                         }
330 
331                                         if (is_ip_address(tp, AF_UNSPEC, &addr)) {
332                                                   /* Make sure that snbits is valid for addr */
333                                             if ((snbits < UINT_MAX) &&
334                                                   ( (IS_IPV4(&addr) && snbits > 32) ||
335                                                     (IS_IPV6(&addr) && snbits > 128))) {
336                                                             log_maybe(NULL,
337                                                                         "authreadkeys: excessive subnet mask <%s/%s> for key %d",
338                                                                         tp, snp, keyno);
339                                             }
340                                             next->keyacclist = keyacc_new_push(
341                                                   next->keyacclist, &addr, snbits);
342                                         } else {
343                                                   log_maybe(&nerr,
344                                                               "authreadkeys: invalid IP address <%s> for key %d",
345                                                               tp, keyno);
346                                         }
347 
348                               nextip:
349                                         if (i) {
350                                                   tp = i + 1;
351                                         } else {
352                                                   tp = 0;
353                                         }
354                               }
355                     }
356 
357                     /* check if this has to be weeded out... */
358                     if (0 == keytype) {
359                               free_keydata(next);
360                               next = NULL;
361                               continue;
362                     }
363 
364                     DEBUG_INSIST(NULL != next);
365 #if defined(OPENSSL) && defined(ENABLE_CMAC)
366                     if (NID_cmac == keytype && len < 16) {
367                               msyslog(LOG_WARNING, CMAC " keys are 128 bits, "
368                                         "zero-extending key %u by %u bits",
369                                         (u_int)keyno, 8 * (16 - (u_int)len));
370                     }
371 #endif    /* OPENSSL && ENABLE_CMAC */
372                     next->next = list;
373                     list = next;
374           }
375           fclose(fp);
376           if (nerr > 0) {
377                     const char * why = "";
378 
379                     if (nerr > nerr_maxlimit)
380                               why = " (emergency break)";
381                     msyslog(LOG_ERR,
382                               "authreadkeys: rejecting file '%s' after %u error(s)%s",
383                               file, nerr, why);
384                     goto onerror;
385           }
386 
387           /* first remove old file-based keys */
388           auth_delkeys();
389           /* insert the new key material */
390           while (NULL != (next = list)) {
391                     list = next->next;
392                     MD5auth_setkey(next->keyid, next->keytype,
393                                      next->secbuf, next->seclen, next->keyacclist);
394                     next->keyacclist = NULL; /* consumed by MD5auth_setkey */
395                     free_keydata(next);
396           }
397           return (1);
398 
399   onerror:
400           /* Mop up temporary storage before bailing out. */
401           while (NULL != (next = list)) {
402                     list = next->next;
403                     free_keydata(next);
404           }
405           return (0);
406 }
407