/* * authkeys.c - routines to manage the storage of authentication keys */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include "ntp.h" #include "ntp_fp.h" #include "ntpd.h" #include "ntp_lists.h" #include "ntp_string.h" #include "ntp_malloc.h" #include "ntp_stdlib.h" #include "ntp_keyacc.h" /* * Structure to store keys in in the hash table. */ typedef struct savekey symkey; struct savekey { symkey * hlink; /* next in hash bucket */ DECL_DLIST_LINK(symkey, llink); /* for overall & free lists */ u_char * secret; /* shared secret */ KeyAccT * keyacclist; /* Private key access list */ u_long lifetime; /* remaining lifetime */ keyid_t keyid; /* key identifier */ u_short type; /* OpenSSL digest NID */ size_t secretsize; /* secret octets */ u_short flags; /* KEY_ flags that wave */ }; /* define the payload region of symkey beyond the list pointers */ #define symkey_payload secret #define KEY_TRUSTED 0x001 /* this key is trusted */ #ifdef DEBUG typedef struct symkey_alloc_tag symkey_alloc; struct symkey_alloc_tag { symkey_alloc * link; void * mem; /* enable free() atexit */ }; symkey_alloc * authallocs; #endif /* DEBUG */ static u_short auth_log2(size_t); static void auth_resize_hashtable(void); static void allocsymkey(keyid_t, u_short, u_short, u_long, size_t, u_char *, KeyAccT *); static void freesymkey(symkey *); #ifdef DEBUG static void free_auth_mem(void); #endif symkey key_listhead; /* list of all in-use keys */; /* * The hash table. This is indexed by the low order bits of the * keyid. We make this fairly big for potentially busy servers. */ #define DEF_AUTHHASHSIZE 64 /*#define HASHMASK ((HASHSIZE)-1)*/ #define KEYHASH(keyid) ((keyid) & authhashmask) int authhashdisabled; u_short authhashbuckets = DEF_AUTHHASHSIZE; u_short authhashmask = DEF_AUTHHASHSIZE - 1; symkey **key_hash; u_long authkeynotfound; /* keys not found */ u_long authkeylookups; /* calls to lookup keys */ u_long authnumkeys; /* number of active keys */ u_long authkeyexpired; /* key lifetime expirations */ u_long authkeyuncached; /* cache misses */ u_long authnokey; /* calls to encrypt with no key */ u_long authencryptions; /* calls to encrypt */ u_long authdecryptions; /* calls to decrypt */ /* * Storage for free symkey structures. We malloc() such things but * never free them. */ symkey *authfreekeys; int authnumfreekeys; #define MEMINC 16 /* number of new free ones to get */ /* * The key cache. We cache the last key we looked at here. * Note: this should hold the last *trusted* key. Also the * cache is only loaded when the digest type / MAC algorithm * is valid. */ keyid_t cache_keyid; /* key identifier */ u_char *cache_secret; /* secret */ size_t cache_secretsize; /* secret length */ int cache_type; /* OpenSSL digest NID */ u_short cache_flags; /* flags that wave */ KeyAccT *cache_keyacclist; /* key access list */ /* -------------------------------------------------------------------- * manage key access lists * -------------------------------------------------------------------- */ /* allocate and populate new access node and pushes it on the list. * Returns the new head. */ KeyAccT* keyacc_new_push( KeyAccT * head, const sockaddr_u * addr ) { KeyAccT * node = emalloc(sizeof(KeyAccT)); memcpy(&node->addr, addr, sizeof(sockaddr_u)); node->next = head; return node; } /* ----------------------------------------------------------------- */ /* pop and deallocate the first node of a list of access nodes, if * the list is not empty. Returns the tail of the list. */ KeyAccT* keyacc_pop_free( KeyAccT *head ) { KeyAccT * next = NULL; if (head) { next = head->next; free(head); } return next; } /* ----------------------------------------------------------------- */ /* deallocate the list; returns an empty list. */ KeyAccT* keyacc_all_free( KeyAccT * head ) { while (head) head = keyacc_pop_free(head); return head; } /* ----------------------------------------------------------------- */ /* scan a list to see if it contains a given address. Return the * default result value in case of an empty list. */ int /*BOOL*/ keyacc_contains( const KeyAccT *head, const sockaddr_u *addr, int defv) { if (head) { do { if (SOCK_EQ(&head->addr, addr)) return TRUE; } while (NULL != (head = head->next)); return FALSE; } else { return !!defv; } } /* * init_auth - initialize internal data */ void init_auth(void) { size_t newalloc; /* * Initialize hash table and free list */ newalloc = authhashbuckets * sizeof(key_hash[0]); key_hash = erealloc(key_hash, newalloc); memset(key_hash, '\0', newalloc); INIT_DLIST(key_listhead, llink); #ifdef DEBUG atexit(&free_auth_mem); #endif } /* * free_auth_mem - assist in leak detection by freeing all dynamic * allocations from this module. */ #ifdef DEBUG static void free_auth_mem(void) { symkey * sk; symkey_alloc * alloc; symkey_alloc * next_alloc; while (NULL != (sk = HEAD_DLIST(key_listhead, llink))) { freesymkey(sk); } free(key_hash); key_hash = NULL; cache_keyid = 0; cache_flags = 0; cache_keyacclist = NULL; for (alloc = authallocs; alloc != NULL; alloc = next_alloc) { next_alloc = alloc->link; free(alloc->mem); } authfreekeys = NULL; authnumfreekeys = 0; } #endif /* DEBUG */ /* * auth_moremem - get some more free key structures */ void auth_moremem( int keycount ) { symkey * sk; int i; #ifdef DEBUG void * base; symkey_alloc * allocrec; # define MOREMEM_EXTRA_ALLOC (sizeof(*allocrec)) #else # define MOREMEM_EXTRA_ALLOC (0) #endif i = (keycount > 0) ? keycount : MEMINC; sk = emalloc_zero(i * sizeof(*sk) + MOREMEM_EXTRA_ALLOC); #ifdef DEBUG base = sk; #endif authnumfreekeys += i; for (; i > 0; i--, sk++) { LINK_SLIST(authfreekeys, sk, llink.f); } #ifdef DEBUG allocrec = (void *)sk; allocrec->mem = base; LINK_SLIST(authallocs, allocrec, link); #endif } /* * auth_prealloc_symkeys */ void auth_prealloc_symkeys( int keycount ) { int allocated; int additional; allocated = authnumkeys + authnumfreekeys; additional = keycount - allocated; if (additional > 0) auth_moremem(additional); auth_resize_hashtable(); } static u_short auth_log2(size_t x) { /* ** bithack to calculate floor(log2(x)) ** ** This assumes ** - (sizeof(size_t) is a power of two ** - CHAR_BITS is a power of two ** - returning zero for arguments <= 0 is OK. ** ** Does only shifts, masks and sums in integer arithmetic in ** log2(CHAR_BIT*sizeof(size_t)) steps. (that is, 5/6 steps for ** 32bit/64bit size_t) */ int s; int r = 0; size_t m = ~(size_t)0; for (s = sizeof(size_t) / 2 * CHAR_BIT; s != 0; s >>= 1) { m <<= s; if (x & m) r += s; else x <<= s; } return (u_short)r; } static void authcache_flush_id( keyid_t id ) { if (cache_keyid == id) { cache_keyid = 0; cache_type = 0; cache_flags = 0; cache_secret = NULL; cache_secretsize = 0; cache_keyacclist = NULL; } } /* * auth_resize_hashtable * * Size hash table to average 4 or fewer entries per bucket initially, * within the bounds of at least 4 and no more than 15 bits for the hash * table index. Populate the hash table. */ static void auth_resize_hashtable(void) { u_long totalkeys; u_short hashbits; u_short hash; size_t newalloc; symkey * sk; totalkeys = authnumkeys + authnumfreekeys; hashbits = auth_log2(totalkeys / 4) + 1; hashbits = max(4, hashbits); hashbits = min(15, hashbits); authhashbuckets = 1 << hashbits; authhashmask = authhashbuckets - 1; newalloc = authhashbuckets * sizeof(key_hash[0]); key_hash = erealloc(key_hash, newalloc); memset(key_hash, '\0', newalloc); ITER_DLIST_BEGIN(key_listhead, sk, llink, symkey) hash = KEYHASH(sk->keyid); LINK_SLIST(key_hash[hash], sk, hlink); ITER_DLIST_END() } /* * allocsymkey - common code to allocate and link in symkey * * secret must be allocated with a free-compatible allocator. It is * owned by the referring symkey structure, and will be free()d by * freesymkey(). */ static void allocsymkey( keyid_t id, u_short flags, u_short type, u_long lifetime, size_t secretsize, u_char * secret, KeyAccT * ka ) { symkey * sk; symkey ** bucket; bucket = &key_hash[KEYHASH(id)]; if (authnumfreekeys < 1) auth_moremem(-1); UNLINK_HEAD_SLIST(sk, authfreekeys, llink.f); DEBUG_ENSURE(sk != NULL); sk->keyid = id; sk->flags = flags; sk->type = type; sk->secretsize = secretsize; sk->secret = secret; sk->keyacclist = ka; sk->lifetime = lifetime; LINK_SLIST(*bucket, sk, hlink); LINK_TAIL_DLIST(key_listhead, sk, llink); authnumfreekeys--; authnumkeys++; } /* * freesymkey - common code to remove a symkey and recycle its entry. */ static void freesymkey( symkey * sk ) { symkey ** bucket; symkey * unlinked; if (NULL == sk) return; authcache_flush_id(sk->keyid); keyacc_all_free(sk->keyacclist); bucket = &key_hash[KEYHASH(sk->keyid)]; if (sk->secret != NULL) { memset(sk->secret, '\0', sk->secretsize); free(sk->secret); } UNLINK_SLIST(unlinked, *bucket, sk, hlink, symkey); DEBUG_ENSURE(sk == unlinked); UNLINK_DLIST(sk, llink); memset((char *)sk + offsetof(symkey, symkey_payload), '\0', sizeof(*sk) - offsetof(symkey, symkey_payload)); LINK_SLIST(authfreekeys, sk, llink.f); authnumkeys--; authnumfreekeys++; } /* * auth_findkey - find a key in the hash table */ struct savekey * auth_findkey( keyid_t id ) { symkey * sk; for (sk = key_hash[KEYHASH(id)]; sk != NULL; sk = sk->hlink) if (id == sk->keyid) return sk; return NULL; } /* * auth_havekey - return TRUE if the key id is zero or known. The * key needs not to be trusted. */ int auth_havekey( keyid_t id ) { return (0 == id) || (cache_keyid == id) || (NULL != auth_findkey(id)); } /* * authhavekey - return TRUE and cache the key, if zero or both known * and trusted. */ int authhavekey( keyid_t id ) { symkey * sk; authkeylookups++; if (0 == id || cache_keyid == id) return !!(KEY_TRUSTED & cache_flags); /* * Search the bin for the key. If not found, or found but the key * type is zero, somebody marked it trusted without specifying a * key or key type. In this case consider the key missing. */ authkeyuncached++; sk = auth_findkey(id); if ((sk == NULL) || (sk->type == 0)) { authkeynotfound++; return FALSE; } /* * If the key is not trusted, the key is not considered found. */ if ( ! (KEY_TRUSTED & sk->flags)) { authnokey++; return FALSE; } /* * The key is found and trusted. Initialize the key cache. */ cache_keyid = sk->keyid; cache_type = sk->type; cache_flags = sk->flags; cache_secret = sk->secret; cache_secretsize = sk->secretsize; cache_keyacclist = sk->keyacclist; return TRUE; } /* * authtrust - declare a key to be trusted/untrusted */ void authtrust( keyid_t id, u_long trust ) { symkey * sk; u_long lifetime; /* * Search bin for key; if it does not exist and is untrusted, * forget it. */ sk = auth_findkey(id); if (!trust && sk == NULL) return; /* * There are two conditions remaining. Either it does not * exist and is to be trusted or it does exist and is or is * not to be trusted. */ if (sk != NULL) { /* * Key exists. If it is to be trusted, say so and update * its lifetime. If no longer trusted, return it to the * free list. Flush the cache first to be sure there are * no discrepancies. */ authcache_flush_id(id); if (trust > 0) { sk->flags |= KEY_TRUSTED; if (trust > 1) sk->lifetime = current_time + trust; else sk->lifetime = 0; } else { freesymkey(sk); } return; } /* * keyid is not present, but the is to be trusted. We allocate * a new key, but do not specify a key type or secret. */ if (trust > 1) { lifetime = current_time + trust; } else { lifetime = 0; } allocsymkey(id, KEY_TRUSTED, 0, lifetime, 0, NULL, NULL); } /* * authistrusted - determine whether a key is trusted */ int authistrusted( keyid_t id ) { symkey * sk; if (id == cache_keyid) return !!(KEY_TRUSTED & cache_flags); authkeyuncached++; sk = auth_findkey(id); if (sk == NULL || !(KEY_TRUSTED & sk->flags)) { authkeynotfound++; return FALSE; } return TRUE; } /* * authistrustedip - determine if the IP is OK for the keyid */ int authistrustedip( keyid_t keyno, sockaddr_u * sau ) { symkey * sk; /* That specific key was already used to authenticate the * packet. Therefore, the key *must* exist... There's a chance * that is not trusted, though. */ if (keyno == cache_keyid) { return (KEY_TRUSTED & cache_flags) && keyacc_contains(cache_keyacclist, sau, TRUE); } else { authkeyuncached++; sk = auth_findkey(keyno); INSIST(NULL != sk); return (KEY_TRUSTED & sk->flags) && keyacc_contains(sk->keyacclist, sau, TRUE); } } /* Note: There are two locations below where 'strncpy()' is used. While * this function is a hazard by itself, it's essential that it is used * here. Bug 1243 involved that the secret was filled with NUL bytes * after the first NUL encountered, and 'strlcpy()' simply does NOT have * this behaviour. So disabling the fix and reverting to the buggy * behaviour due to compatibility issues MUST also fill with NUL and * this needs 'strncpy'. Also, the secret is managed as a byte blob of a * given size, and eventually truncating it and replacing the last byte * with a NUL would be a bug. * perlinger@ntp.org 2015-10-10 */ void MD5auth_setkey( keyid_t keyno, int keytype, const u_char *key, size_t secretsize, KeyAccT *ka ) { symkey * sk; u_char * secret; DEBUG_ENSURE(keytype <= USHRT_MAX); DEBUG_ENSURE(secretsize < 4 * 1024); /* * See if we already have the key. If so just stick in the * new value. */ sk = auth_findkey(keyno); if (sk != NULL && keyno == sk->keyid) { /* TALOS-CAN-0054: make sure we have a new buffer! */ if (NULL != sk->secret) { memset(sk->secret, 0, sk->secretsize); free(sk->secret); } sk->secret = emalloc(secretsize + 1); sk->type = (u_short)keytype; sk->secretsize = secretsize; /* make sure access lists don't leak here! */ if (ka != sk->keyacclist) { keyacc_all_free(sk->keyacclist); sk->keyacclist = ka; } #ifndef DISABLE_BUG1243_FIX memcpy(sk->secret, key, secretsize); #else /* >MUST< use 'strncpy()' here! See above! */ strncpy((char *)sk->secret, (const char *)key, secretsize); #endif authcache_flush_id(keyno); return; } /* * Need to allocate new structure. Do it. */ secret = emalloc(secretsize + 1); #ifndef DISABLE_BUG1243_FIX memcpy(secret, key, secretsize); #else /* >MUST< use 'strncpy()' here! See above! */ strncpy((char *)secret, (const char *)key, secretsize); #endif allocsymkey(keyno, 0, (u_short)keytype, 0, secretsize, secret, ka); #ifdef DEBUG if (debug >= 4) { size_t j; printf("auth_setkey: key %d type %d len %d ", (int)keyno, keytype, (int)secretsize); for (j = 0; j < secretsize; j++) { printf("%02x", secret[j]); } printf("\n"); } #endif } /* * auth_delkeys - delete non-autokey untrusted keys, and clear all info * except the trusted bit of non-autokey trusted keys, in * preparation for rereading the keys file. */ void auth_delkeys(void) { symkey * sk; ITER_DLIST_BEGIN(key_listhead, sk, llink, symkey) if (sk->keyid > NTP_MAXKEY) { /* autokey */ continue; } /* * Don't lose info as to which keys are trusted. Make * sure there are no dangling pointers! */ if (KEY_TRUSTED & sk->flags) { if (sk->secret != NULL) { memset(sk->secret, 0, sk->secretsize); free(sk->secret); sk->secret = NULL; /* TALOS-CAN-0054 */ } sk->keyacclist = keyacc_all_free(sk->keyacclist); sk->secretsize = 0; sk->lifetime = 0; } else { freesymkey(sk); } ITER_DLIST_END() } /* * auth_agekeys - delete keys whose lifetimes have expired */ void auth_agekeys(void) { symkey * sk; ITER_DLIST_BEGIN(key_listhead, sk, llink, symkey) if (sk->lifetime > 0 && current_time > sk->lifetime) { freesymkey(sk); authkeyexpired++; } ITER_DLIST_END() DPRINTF(1, ("auth_agekeys: at %lu keys %lu expired %lu\n", current_time, authnumkeys, authkeyexpired)); } /* * authencrypt - generate message authenticator * * Returns length of authenticator field, zero if key not found. */ size_t authencrypt( keyid_t keyno, u_int32 * pkt, size_t length ) { /* * A zero key identifier means the sender has not verified * the last message was correctly authenticated. The MAC * consists of a single word with value zero. */ authencryptions++; pkt[length / 4] = htonl(keyno); if (0 == keyno) { return 4; } if (!authhavekey(keyno)) { return 0; } return MD5authencrypt(cache_type, cache_secret, pkt, length); } /* * authdecrypt - verify message authenticator * * Returns TRUE if authenticator valid, FALSE if invalid or not found. */ int authdecrypt( keyid_t keyno, u_int32 * pkt, size_t length, size_t size ) { /* * A zero key identifier means the sender has not verified * the last message was correctly authenticated. For our * purpose this is an invalid authenticator. */ authdecryptions++; if (0 == keyno || !authhavekey(keyno) || size < 4) { return FALSE; } return MD5authdecrypt(cache_type, cache_secret, pkt, length, size); }