In the NTS-NTP client instance, maintain a local copy of the NTP address instead of using a pointer to the NCR's address, which may change at unexpected times. Also, change the NNC_CreateInstance() to accept only the NTP port to make it clear the initial NTP address is the same as the NTS-KE address and to make it consistent with NNC_ChangeAddress(), which accepts only one address.
494 lines
13 KiB
C
494 lines
13 KiB
C
/*
|
|
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, uint16_t ntp_port)
|
|
{
|
|
NAU_Instance instance = create_instance(NTP_AUTH_NTS);
|
|
|
|
instance->nts = NNC_CreateInstance(nts_address, name, ntp_port);
|
|
|
|
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);
|
|
}
|
|
}
|