Check the mode instead of the nts pointer to make it clear the pointer is not expected to be NULL in an NTS instance (unless the NTS support is stubbed).
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, 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);
|
|
}
|
|
}
|