diff options
Diffstat (limited to '')
-rw-r--r-- | ntp_auth.c | 494 |
1 files changed, 494 insertions, 0 deletions
diff --git a/ntp_auth.c b/ntp_auth.c new file mode 100644 index 0000000..ee0611c --- /dev/null +++ b/ntp_auth.c @@ -0,0 +1,494 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Miroslav Lichvar 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. + * + ********************************************************************** + + ======================================================================= + + NTP authentication + */ + +#include "config.h" + +#include "sysincl.h" + +#include "keys.h" +#include "logging.h" +#include "memory.h" +#include "ntp_auth.h" +#include "ntp_ext.h" +#include "ntp_signd.h" +#include "nts_ntp.h" +#include "nts_ntp_client.h" +#include "nts_ntp_server.h" +#include "srcparams.h" +#include "util.h" + +/* Structure to hold authentication configuration and state */ + +struct NAU_Instance_Record { + NTP_AuthMode mode; /* Authentication mode of NTP packets */ + uint32_t key_id; /* Identifier of a symmetric key */ + NNC_Instance nts; /* Client NTS state */ +}; + +/* ================================================== */ + +static int +generate_symmetric_auth(uint32_t key_id, NTP_Packet *packet, NTP_PacketInfo *info) +{ + int auth_len, max_auth_len; + + if (info->length + NTP_MIN_MAC_LENGTH > sizeof (*packet)) { + DEBUG_LOG("Packet too long"); + return 0; + } + + /* Truncate long MACs in NTPv4 packets to allow deterministic parsing + of extension fields (RFC 7822) */ + max_auth_len = (info->version == 4 ? NTP_MAX_V4_MAC_LENGTH : NTP_MAX_MAC_LENGTH) - 4; + max_auth_len = MIN(max_auth_len, sizeof (*packet) - info->length - 4); + + auth_len = KEY_GenerateAuth(key_id, packet, info->length, + (unsigned char *)packet + info->length + 4, max_auth_len); + if (auth_len < NTP_MIN_MAC_LENGTH - 4) { + DEBUG_LOG("Could not generate auth data with key %"PRIu32, key_id); + return 0; + } + + *(uint32_t *)((unsigned char *)packet + info->length) = htonl(key_id); + + info->auth.mac.start = info->length; + info->auth.mac.length = 4 + auth_len; + info->auth.mac.key_id = key_id; + info->length += info->auth.mac.length; + + return 1; +} + +/* ================================================== */ + +static int +check_symmetric_auth(NTP_Packet *packet, NTP_PacketInfo *info) +{ + int trunc_len; + + if (info->auth.mac.length < NTP_MIN_MAC_LENGTH) + return 0; + + trunc_len = info->version == 4 && info->auth.mac.length <= NTP_MAX_V4_MAC_LENGTH ? + NTP_MAX_V4_MAC_LENGTH : NTP_MAX_MAC_LENGTH; + + if (!KEY_CheckAuth(info->auth.mac.key_id, packet, info->auth.mac.start, + (unsigned char *)packet + info->auth.mac.start + 4, + info->auth.mac.length - 4, trunc_len - 4)) + return 0; + + return 1; +} + +/* ================================================== */ + +static int +is_zero_data(unsigned char *data, int length) +{ + int i; + + for (i = 0; i < length; i++) + if (data[i] != 0) + return 0; + return 1; +} + +/* ================================================== */ + +static NAU_Instance +create_instance(NTP_AuthMode mode) +{ + NAU_Instance instance; + + instance = MallocNew(struct NAU_Instance_Record); + instance->mode = mode; + instance->key_id = INACTIVE_AUTHKEY; + instance->nts = NULL; + + assert(sizeof (instance->key_id) == 4); + + return instance; +} + +/* ================================================== */ + +NAU_Instance +NAU_CreateNoneInstance(void) +{ + return create_instance(NTP_AUTH_NONE); +} + +/* ================================================== */ + +NAU_Instance +NAU_CreateSymmetricInstance(uint32_t key_id) +{ + NAU_Instance instance = create_instance(NTP_AUTH_SYMMETRIC); + + instance->key_id = key_id; + + if (!KEY_KeyKnown(key_id)) + LOG(LOGS_WARN, "Key %"PRIu32" is %s", key_id, "missing"); + else if (!KEY_CheckKeyLength(key_id)) + LOG(LOGS_WARN, "Key %"PRIu32" is %s", key_id, "too short"); + + return instance; +} + +/* ================================================== */ + +NAU_Instance +NAU_CreateNtsInstance(IPSockAddr *nts_address, const char *name, const IPSockAddr *ntp_address) +{ + NAU_Instance instance = create_instance(NTP_AUTH_NTS); + + instance->nts = NNC_CreateInstance(nts_address, name, ntp_address); + + return instance; +} + +/* ================================================== */ + +void +NAU_DestroyInstance(NAU_Instance instance) +{ + if (instance->mode == NTP_AUTH_NTS) + NNC_DestroyInstance(instance->nts); + Free(instance); +} + +/* ================================================== */ + +int +NAU_IsAuthEnabled(NAU_Instance instance) +{ + return instance->mode != NTP_AUTH_NONE; +} + +/* ================================================== */ + +int +NAU_GetSuggestedNtpVersion(NAU_Instance instance) +{ + /* If the MAC in NTPv4 packets would be truncated, prefer NTPv3 for + compatibility with older chronyd servers */ + if (instance->mode == NTP_AUTH_SYMMETRIC && + KEY_GetAuthLength(instance->key_id) + sizeof (instance->key_id) > NTP_MAX_V4_MAC_LENGTH) + return 3; + + return NTP_VERSION; +} + +/* ================================================== */ + +int +NAU_PrepareRequestAuth(NAU_Instance instance) +{ + switch (instance->mode) { + case NTP_AUTH_NTS: + if (!NNC_PrepareForAuth(instance->nts)) + return 0; + break; + default: + break; + } + + return 1; +} + +/* ================================================== */ + +int +NAU_GenerateRequestAuth(NAU_Instance instance, NTP_Packet *request, NTP_PacketInfo *info) +{ + switch (instance->mode) { + case NTP_AUTH_NONE: + break; + case NTP_AUTH_SYMMETRIC: + if (!generate_symmetric_auth(instance->key_id, request, info)) + return 0; + break; + case NTP_AUTH_NTS: + if (!NNC_GenerateRequestAuth(instance->nts, request, info)) + return 0; + break; + default: + assert(0); + } + + info->auth.mode = instance->mode; + + return 1; +} + +/* ================================================== */ + +int +NAU_ParsePacket(NTP_Packet *packet, NTP_PacketInfo *info) +{ + int parsed, remainder, ef_length, ef_type; + unsigned char *data; + + data = (void *)packet; + parsed = NTP_HEADER_LENGTH; + remainder = info->length - parsed; + + info->ext_fields = 0; + + /* Check if this is a plain NTP packet with no extension fields or MAC */ + if (remainder <= 0) + return 1; + + assert(remainder % 4 == 0); + + /* In NTPv3 and older packets don't have extension fields. Anything after + the header is assumed to be a MAC. */ + if (info->version <= 3) { + info->auth.mode = NTP_AUTH_SYMMETRIC; + info->auth.mac.start = parsed; + info->auth.mac.length = remainder; + info->auth.mac.key_id = ntohl(*(uint32_t *)(data + parsed)); + + /* Check if it is an MS-SNTP authenticator field or extended authenticator + field with zeroes as digest */ + if (info->version == 3 && info->auth.mac.key_id != 0) { + if (remainder == 20 && is_zero_data(data + parsed + 4, remainder - 4)) + info->auth.mode = NTP_AUTH_MSSNTP; + else if (remainder == 72 && is_zero_data(data + parsed + 8, remainder - 8)) + info->auth.mode = NTP_AUTH_MSSNTP_EXT; + } + + return 1; + } + + /* Check for a crypto NAK */ + if (remainder == 4 && ntohl(*(uint32_t *)(data + parsed)) == 0) { + info->auth.mode = NTP_AUTH_SYMMETRIC; + info->auth.mac.start = parsed; + info->auth.mac.length = remainder; + info->auth.mac.key_id = 0; + return 1; + } + + /* Parse the rest of the NTPv4 packet */ + + while (remainder > 0) { + /* Check if the remaining data is a MAC */ + if (remainder >= NTP_MIN_MAC_LENGTH && remainder <= NTP_MAX_V4_MAC_LENGTH) + break; + + /* Check if this is a valid NTPv4 extension field and skip it */ + if (!NEF_ParseField(packet, info->length, parsed, &ef_length, &ef_type, NULL, NULL)) { + DEBUG_LOG("Invalid format"); + return 0; + } + + assert(ef_length > 0 && ef_length % 4 == 0); + + switch (ef_type) { + case NTP_EF_NTS_UNIQUE_IDENTIFIER: + case NTP_EF_NTS_COOKIE: + case NTP_EF_NTS_COOKIE_PLACEHOLDER: + case NTP_EF_NTS_AUTH_AND_EEF: + info->auth.mode = NTP_AUTH_NTS; + break; + default: + DEBUG_LOG("Unknown extension field type=%x", (unsigned int)ef_type); + } + + info->ext_fields++; + parsed += ef_length; + remainder = info->length - parsed; + } + + if (remainder == 0) { + /* No MAC */ + return 1; + } else if (remainder >= NTP_MIN_MAC_LENGTH) { + info->auth.mode = NTP_AUTH_SYMMETRIC; + info->auth.mac.start = parsed; + info->auth.mac.length = remainder; + info->auth.mac.key_id = ntohl(*(uint32_t *)(data + parsed)); + return 1; + } + + DEBUG_LOG("Invalid format"); + return 0; +} + +/* ================================================== */ + +int +NAU_CheckRequestAuth(NTP_Packet *request, NTP_PacketInfo *info, uint32_t *kod) +{ + *kod = 0; + + switch (info->auth.mode) { + case NTP_AUTH_NONE: + break; + case NTP_AUTH_SYMMETRIC: + if (!check_symmetric_auth(request, info)) + return 0; + break; + case NTP_AUTH_MSSNTP: + /* MS-SNTP requests are not authenticated */ + break; + case NTP_AUTH_MSSNTP_EXT: + /* Not supported yet */ + return 0; + case NTP_AUTH_NTS: + if (!NNS_CheckRequestAuth(request, info, kod)) + return 0; + break; + default: + return 0; + } + + return 1; +} + +/* ================================================== */ + +int +NAU_GenerateResponseAuth(NTP_Packet *request, NTP_PacketInfo *request_info, + NTP_Packet *response, NTP_PacketInfo *response_info, + NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr, + uint32_t kod) +{ + switch (request_info->auth.mode) { + case NTP_AUTH_NONE: + break; + case NTP_AUTH_SYMMETRIC: + if (!generate_symmetric_auth(request_info->auth.mac.key_id, response, response_info)) + return 0; + break; + case NTP_AUTH_MSSNTP: + /* Sign the packet asynchronously by ntp_signd */ + if (!NSD_SignAndSendPacket(request_info->auth.mac.key_id, response, response_info, + remote_addr, local_addr)) + return 0; + /* Don't send the original packet */ + return 0; + case NTP_AUTH_NTS: + if (!NNS_GenerateResponseAuth(request, request_info, response, response_info, kod)) + return 0; + break; + default: + DEBUG_LOG("Could not authenticate response auth_mode=%d", (int)request_info->auth.mode); + return 0; + } + + response_info->auth.mode = request_info->auth.mode; + + return 1; +} + +/* ================================================== */ + +int +NAU_CheckResponseAuth(NAU_Instance instance, NTP_Packet *response, NTP_PacketInfo *info) +{ + /* The authentication must match the expected mode */ + if (info->auth.mode != instance->mode) + return 0; + + switch (info->auth.mode) { + case NTP_AUTH_NONE: + break; + case NTP_AUTH_SYMMETRIC: + /* Check if it is authenticated with the specified key */ + if (info->auth.mac.key_id != instance->key_id) + return 0; + /* and that the MAC is valid */ + if (!check_symmetric_auth(response, info)) + return 0; + break; + case NTP_AUTH_NTS: + if (!NNC_CheckResponseAuth(instance->nts, response, info)) + return 0; + break; + default: + return 0; + } + + return 1; +} + +/* ================================================== */ + +void +NAU_ChangeAddress(NAU_Instance instance, IPAddr *address) +{ + switch (instance->mode) { + case NTP_AUTH_NONE: + case NTP_AUTH_SYMMETRIC: + break; + case NTP_AUTH_NTS: + NNC_ChangeAddress(instance->nts, address); + break; + default: + assert(0); + } +} + +/* ================================================== */ + +void +NAU_DumpData(NAU_Instance instance) +{ + switch (instance->mode) { + case NTP_AUTH_NTS: + NNC_DumpData(instance->nts); + break; + default: + break; + } +} + +/* ================================================== */ + +void +NAU_GetReport(NAU_Instance instance, RPT_AuthReport *report) +{ + memset(report, 0, sizeof (*report)); + + report->mode = instance->mode; + report->last_ke_ago = -1; + + switch (instance->mode) { + case NTP_AUTH_NONE: + break; + case NTP_AUTH_SYMMETRIC: + report->key_id = instance->key_id; + KEY_GetKeyInfo(instance->key_id, &report->key_type, &report->key_length); + break; + case NTP_AUTH_NTS: + NNC_GetReport(instance->nts, report); + break; + default: + assert(0); + } +} |