mirror of
https://gitlab.com/chrony/chrony.git
synced 2025-12-03 18:25:07 -05:00
Switch from memcmp() to the new constant-time function to compare the received and expected authentication data generated with a symmetric key (NTP MAC or AES CMAC). While this doesn't seem to be strictly necessary with the current code, it is a recommended practice to prevent timing attacks. If memcmp() compared the MACs one byte at a time (a typical memcmp() implementation works with wider integers for better performance) and chronyd as an NTP client/server/peer was leaking the timing of the comparison (e.g. in the monitoring protocol), an attacker might be able for a given NTP request or response find in a sequence the individual bytes of the MAC by observing differences in the timing over a large number of attempts. However, this process would likely be so slow the authenticated request or response would not be useful in a MITM attack as the expected origin timestamp is changing with each poll. Extend the keys unit test to compare the time the function takes to compare two identical MACs and MACs differing in the first byte (maximizing the timing difference). It should fail if the compiler's optimizations figure out the function can return early. The test is not included in the util unit test to avoid compile-time optimizations with the function and its caller together. The test can be disabled by setting NO_TIMING_TESTS environment variable if it turns out to be unreliable.
442 lines
9.8 KiB
C
442 lines
9.8 KiB
C
/*
|
|
chronyd/chronyc - Programs for keeping computer clocks accurate.
|
|
|
|
**********************************************************************
|
|
* Copyright (C) Richard P. Curnow 1997-2003
|
|
* Copyright (C) Miroslav Lichvar 2012-2016, 2019-2020
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of version 2 of the GNU General Public License as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
**********************************************************************
|
|
|
|
=======================================================================
|
|
|
|
Module for managing keys used for authenticating NTP packets and commands
|
|
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "sysincl.h"
|
|
|
|
#include "array.h"
|
|
#include "keys.h"
|
|
#include "cmac.h"
|
|
#include "cmdparse.h"
|
|
#include "conf.h"
|
|
#include "memory.h"
|
|
#include "util.h"
|
|
#include "local.h"
|
|
#include "logging.h"
|
|
|
|
/* Consider 80 bits as the absolute minimum for a secure key */
|
|
#define MIN_SECURE_KEY_LENGTH 10
|
|
|
|
typedef enum {
|
|
NTP_MAC,
|
|
CMAC,
|
|
} KeyClass;
|
|
|
|
typedef struct {
|
|
uint32_t id;
|
|
int type;
|
|
int length;
|
|
KeyClass class;
|
|
union {
|
|
struct {
|
|
unsigned char *value;
|
|
int hash_id;
|
|
} ntp_mac;
|
|
CMC_Instance cmac;
|
|
} data;
|
|
} Key;
|
|
|
|
static ARR_Instance keys;
|
|
|
|
static int cache_valid;
|
|
static uint32_t cache_key_id;
|
|
static int cache_key_pos;
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
free_keys(void)
|
|
{
|
|
unsigned int i;
|
|
Key *key;
|
|
|
|
for (i = 0; i < ARR_GetSize(keys); i++) {
|
|
key = ARR_GetElement(keys, i);
|
|
switch (key->class) {
|
|
case NTP_MAC:
|
|
Free(key->data.ntp_mac.value);
|
|
break;
|
|
case CMAC:
|
|
CMC_DestroyInstance(key->data.cmac);
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
ARR_SetSize(keys, 0);
|
|
cache_valid = 0;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
KEY_Initialise(void)
|
|
{
|
|
keys = ARR_CreateInstance(sizeof (Key));
|
|
cache_valid = 0;
|
|
KEY_Reload();
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
KEY_Finalise(void)
|
|
{
|
|
free_keys();
|
|
ARR_DestroyInstance(keys);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static Key *
|
|
get_key(unsigned int index)
|
|
{
|
|
return ((Key *)ARR_GetElements(keys)) + index;
|
|
}
|
|
|
|
/* ================================================== */
|
|
/* Decode key encoded in ASCII or HEX */
|
|
|
|
static int
|
|
decode_key(char *key)
|
|
{
|
|
int len = strlen(key);
|
|
|
|
if (!strncmp(key, "ASCII:", 6)) {
|
|
memmove(key, key + 6, len - 6);
|
|
return len - 6;
|
|
} else if (!strncmp(key, "HEX:", 4)) {
|
|
return UTI_HexToBytes(key + 4, key, len);
|
|
} else {
|
|
/* assume ASCII */
|
|
return len;
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
/* Compare two keys */
|
|
|
|
static int
|
|
compare_keys_by_id(const void *a, const void *b)
|
|
{
|
|
const Key *c = (const Key *) a;
|
|
const Key *d = (const Key *) b;
|
|
|
|
if (c->id < d->id) {
|
|
return -1;
|
|
} else if (c->id > d->id) {
|
|
return +1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
KEY_Reload(void)
|
|
{
|
|
unsigned int i, line_number, key_length, cmac_key_length;
|
|
FILE *in;
|
|
char line[2048], *key_file, *key_value;
|
|
const char *key_type;
|
|
HSH_Algorithm hash_algorithm;
|
|
CMC_Algorithm cmac_algorithm;
|
|
int hash_id;
|
|
Key key;
|
|
|
|
free_keys();
|
|
|
|
key_file = CNF_GetKeysFile();
|
|
line_number = 0;
|
|
|
|
if (!key_file)
|
|
return;
|
|
|
|
if (!UTI_CheckFilePermissions(key_file, 0771))
|
|
;
|
|
|
|
in = UTI_OpenFile(NULL, key_file, NULL, 'r', 0);
|
|
if (!in) {
|
|
LOG(LOGS_WARN, "Could not open keyfile %s", key_file);
|
|
return;
|
|
}
|
|
|
|
while (fgets(line, sizeof (line), in)) {
|
|
line_number++;
|
|
|
|
CPS_NormalizeLine(line);
|
|
if (!*line)
|
|
continue;
|
|
|
|
memset(&key, 0, sizeof (key));
|
|
|
|
if (!CPS_ParseKey(line, &key.id, &key_type, &key_value)) {
|
|
LOG(LOGS_WARN, "Could not parse key at line %u in file %s", line_number, key_file);
|
|
continue;
|
|
}
|
|
|
|
key_length = decode_key(key_value);
|
|
if (key_length == 0) {
|
|
LOG(LOGS_WARN, "Could not decode key %"PRIu32, key.id);
|
|
continue;
|
|
}
|
|
|
|
hash_algorithm = UTI_HashNameToAlgorithm(key_type);
|
|
cmac_algorithm = UTI_CmacNameToAlgorithm(key_type);
|
|
|
|
if (hash_algorithm != 0) {
|
|
hash_id = HSH_GetHashId(hash_algorithm);
|
|
if (hash_id < 0) {
|
|
LOG(LOGS_WARN, "Unsupported %s in key %"PRIu32, "hash function", key.id);
|
|
continue;
|
|
}
|
|
key.class = NTP_MAC;
|
|
key.type = hash_algorithm;
|
|
key.length = key_length;
|
|
key.data.ntp_mac.value = MallocArray(unsigned char, key_length);
|
|
memcpy(key.data.ntp_mac.value, key_value, key_length);
|
|
key.data.ntp_mac.hash_id = hash_id;
|
|
} else if (cmac_algorithm != 0) {
|
|
cmac_key_length = CMC_GetKeyLength(cmac_algorithm);
|
|
if (cmac_key_length == 0) {
|
|
LOG(LOGS_WARN, "Unsupported %s in key %"PRIu32, "cipher", key.id);
|
|
continue;
|
|
} else if (cmac_key_length != key_length) {
|
|
LOG(LOGS_WARN, "Invalid length of %s key %"PRIu32" (expected %u bits)",
|
|
key_type, key.id, 8 * cmac_key_length);
|
|
continue;
|
|
}
|
|
|
|
key.class = CMAC;
|
|
key.type = cmac_algorithm;
|
|
key.length = key_length;
|
|
key.data.cmac = CMC_CreateInstance(cmac_algorithm, (unsigned char *)key_value,
|
|
key_length);
|
|
assert(key.data.cmac);
|
|
} else {
|
|
LOG(LOGS_WARN, "Invalid type in key %"PRIu32, key.id);
|
|
continue;
|
|
}
|
|
|
|
ARR_AppendElement(keys, &key);
|
|
}
|
|
|
|
fclose(in);
|
|
|
|
/* Sort keys into order. Note, if there's a duplicate, it is
|
|
arbitrary which one we use later - the user should have been
|
|
more careful! */
|
|
qsort(ARR_GetElements(keys), ARR_GetSize(keys), sizeof (Key), compare_keys_by_id);
|
|
|
|
LOG(LOGS_INFO, "Loaded %u symmetric keys", ARR_GetSize(keys));
|
|
|
|
/* Check for duplicates */
|
|
for (i = 1; i < ARR_GetSize(keys); i++) {
|
|
if (get_key(i - 1)->id == get_key(i)->id)
|
|
LOG(LOGS_WARN, "Detected duplicate key %"PRIu32, get_key(i - 1)->id);
|
|
}
|
|
|
|
/* Erase any passwords from stack */
|
|
memset(line, 0, sizeof (line));
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
lookup_key(uint32_t id)
|
|
{
|
|
Key specimen, *where, *keys_ptr;
|
|
int pos;
|
|
|
|
keys_ptr = ARR_GetElements(keys);
|
|
specimen.id = id;
|
|
where = (Key *)bsearch((void *)&specimen, keys_ptr, ARR_GetSize(keys),
|
|
sizeof (Key), compare_keys_by_id);
|
|
if (!where) {
|
|
return -1;
|
|
} else {
|
|
pos = where - keys_ptr;
|
|
return pos;
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static Key *
|
|
get_key_by_id(uint32_t key_id)
|
|
{
|
|
int position;
|
|
|
|
if (cache_valid && key_id == cache_key_id)
|
|
return get_key(cache_key_pos);
|
|
|
|
position = lookup_key(key_id);
|
|
|
|
if (position >= 0) {
|
|
cache_valid = 1;
|
|
cache_key_pos = position;
|
|
cache_key_id = key_id;
|
|
|
|
return get_key(position);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
KEY_KeyKnown(uint32_t key_id)
|
|
{
|
|
return get_key_by_id(key_id) != NULL;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
KEY_GetAuthLength(uint32_t key_id)
|
|
{
|
|
unsigned char buf[MAX_HASH_LENGTH];
|
|
Key *key;
|
|
|
|
key = get_key_by_id(key_id);
|
|
|
|
if (!key)
|
|
return 0;
|
|
|
|
switch (key->class) {
|
|
case NTP_MAC:
|
|
return HSH_Hash(key->data.ntp_mac.hash_id, buf, 0, buf, 0, buf, sizeof (buf));
|
|
case CMAC:
|
|
return CMC_Hash(key->data.cmac, buf, 0, buf, sizeof (buf));
|
|
default:
|
|
assert(0);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
KEY_CheckKeyLength(uint32_t key_id)
|
|
{
|
|
Key *key;
|
|
|
|
key = get_key_by_id(key_id);
|
|
|
|
if (!key)
|
|
return 0;
|
|
|
|
return key->length >= MIN_SECURE_KEY_LENGTH;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
KEY_GetKeyInfo(uint32_t key_id, int *type, int *bits)
|
|
{
|
|
Key *key;
|
|
|
|
key = get_key_by_id(key_id);
|
|
|
|
if (!key)
|
|
return 0;
|
|
|
|
*type = key->type;
|
|
*bits = 8 * key->length;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
generate_auth(Key *key, const void *data, int data_len, unsigned char *auth, int auth_len)
|
|
{
|
|
switch (key->class) {
|
|
case NTP_MAC:
|
|
return HSH_Hash(key->data.ntp_mac.hash_id, key->data.ntp_mac.value,
|
|
key->length, data, data_len, auth, auth_len);
|
|
case CMAC:
|
|
return CMC_Hash(key->data.cmac, data, data_len, auth, auth_len);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
check_auth(Key *key, const void *data, int data_len,
|
|
const unsigned char *auth, int auth_len, int trunc_len)
|
|
{
|
|
unsigned char buf[MAX_HASH_LENGTH];
|
|
int hash_len;
|
|
|
|
hash_len = generate_auth(key, data, data_len, buf, sizeof (buf));
|
|
|
|
return MIN(hash_len, trunc_len) == auth_len && UTI_IsMemoryEqual(buf, auth, auth_len);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
KEY_GenerateAuth(uint32_t key_id, const void *data, int data_len,
|
|
unsigned char *auth, int auth_len)
|
|
{
|
|
Key *key;
|
|
|
|
key = get_key_by_id(key_id);
|
|
|
|
if (!key)
|
|
return 0;
|
|
|
|
return generate_auth(key, data, data_len, auth, auth_len);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
KEY_CheckAuth(uint32_t key_id, const void *data, int data_len,
|
|
const unsigned char *auth, int auth_len, int trunc_len)
|
|
{
|
|
Key *key;
|
|
|
|
key = get_key_by_id(key_id);
|
|
|
|
if (!key)
|
|
return 0;
|
|
|
|
return check_auth(key, data, data_len, auth, auth_len, trunc_len);
|
|
}
|