chrony/nts_ntp_client.c
Miroslav Lichvar 6043632f80 nts: add NTS-NTP server and client
Add support for the NTS NTP extension fields.
2020-03-05 16:02:15 +01:00

445 lines
11 KiB
C

/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) Miroslav Lichvar 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.
*
**********************************************************************
=======================================================================
Client NTS-NTP authentication
*/
#include "config.h"
#include "sysincl.h"
#include "nts_ntp_client.h"
#include "conf.h"
#include "logging.h"
#include "memory.h"
#include "ntp.h"
#include "ntp_ext.h"
#include "ntp_sources.h"
#include "nts_ke_client.h"
#include "nts_ntp.h"
#include "nts_ntp_auth.h"
#include "sched.h"
#include "siv.h"
#include "util.h"
#define MAX_TOTAL_COOKIE_LENGTH (8 * 108)
#define MIN_NKE_RETRY_INTERVAL 1000
struct NNC_Instance_Record {
const IPSockAddr *ntp_address;
IPSockAddr nts_address;
char *name;
SIV_Instance siv_c2s;
SIV_Instance siv_s2c;
NKC_Instance nke;
struct timespec last_nke_attempt;
struct timespec last_nke_success;
NKE_Cookie cookies[NTS_MAX_COOKIES];
int num_cookies;
int cookie_index;
int nak_response;
int ok_response;
unsigned char nonce[NTS_MIN_UNPADDED_NONCE_LENGTH];
unsigned char uniq_id[NTS_MIN_UNIQ_ID_LENGTH];
};
/* ================================================== */
static void
reset_instance(NNC_Instance inst)
{
UTI_ZeroTimespec(&inst->last_nke_attempt);
UTI_ZeroTimespec(&inst->last_nke_success);
inst->num_cookies = 0;
inst->cookie_index = 0;
inst->nak_response = 0;
inst->ok_response = 1;
memset(inst->nonce, 0, sizeof (inst->nonce));
memset(inst->uniq_id, 0, sizeof (inst->uniq_id));
}
/* ================================================== */
NNC_Instance
NNC_CreateInstance(IPSockAddr *nts_address, const char *name, const IPSockAddr *ntp_address)
{
NNC_Instance inst;
inst = MallocNew(struct NNC_Instance_Record);
inst->ntp_address = ntp_address;
inst->nts_address = *nts_address;
inst->name = name ? Strdup(name) : NULL;
inst->siv_c2s = NULL;
inst->siv_s2c = NULL;
inst->nke = NULL;
reset_instance(inst);
return inst;
}
/* ================================================== */
void
NNC_DestroyInstance(NNC_Instance inst)
{
if (inst->nke)
NKC_DestroyInstance(inst->nke);
if (inst->siv_c2s)
SIV_DestroyInstance(inst->siv_c2s);
if (inst->siv_s2c)
SIV_DestroyInstance(inst->siv_s2c);
Free(inst->name);
Free(inst);
}
/* ================================================== */
static int
is_nke_needed(NNC_Instance inst)
{
struct timespec now;
/* Force NKE if a NAK was received since last valid auth */
if (inst->nak_response && !inst->ok_response && inst->num_cookies > 0) {
inst->num_cookies = 0;
DEBUG_LOG("Dropped cookies");
}
/* Force NKE if the keys encrypting the cookies are too old */
if (inst->num_cookies > 0) {
SCH_GetLastEventTime(&now, NULL, NULL);
if (fabs(UTI_DiffTimespecsToDouble(&inst->last_nke_success, &now)) > CNF_GetNtsRefresh())
inst->num_cookies = 0;
}
return inst->num_cookies == 0;
}
/* ================================================== */
static int
set_ntp_address(NNC_Instance inst, NTP_Remote_Address *negotiated_address)
{
NTP_Remote_Address old_address, new_address;
old_address = *inst->ntp_address;
new_address = *negotiated_address;
if (new_address.ip_addr.family == IPADDR_UNSPEC)
new_address.ip_addr = old_address.ip_addr;
if (new_address.port == 0)
new_address.port = old_address.port;
if (UTI_CompareIPs(&old_address.ip_addr, &new_address.ip_addr, NULL) == 0 &&
old_address.port == new_address.port)
/* Nothing to do */
return 1;
if (NSR_UpdateSourceNtpAddress(&old_address, &new_address) != NSR_Success) {
LOG(LOGS_ERR, "Could not change %s to negotiated address %s",
UTI_IPToString(&old_address.ip_addr), UTI_IPToString(&new_address.ip_addr));
return 0;
}
return 1;
}
/* ================================================== */
static int
get_nke_data(NNC_Instance inst)
{
NTP_Remote_Address ntp_address;
SIV_Algorithm siv;
NKE_Key c2s, s2c;
struct timespec now;
int got_data;
assert(is_nke_needed(inst));
if (!inst->nke) {
SCH_GetLastEventTime(&now, NULL, NULL);
if (fabs(UTI_DiffTimespecsToDouble(&inst->last_nke_attempt, &now)) <
MIN_NKE_RETRY_INTERVAL) {
DEBUG_LOG("Limiting NTS-KE request rate");
return 0;
}
if (!inst->name) {
LOG(LOGS_ERR, "Missing name of %s for NTS-KE",
UTI_IPToString(&inst->nts_address.ip_addr));
return 0;
}
inst->nke = NKC_CreateInstance(&inst->nts_address, inst->name);
if (!NKC_Start(inst->nke))
return 0;
inst->last_nke_attempt = now;
}
if (NKC_IsActive(inst->nke))
return 0;
got_data = NKC_GetNtsData(inst->nke, &siv, &c2s, &s2c,
inst->cookies, &inst->num_cookies, NTS_MAX_COOKIES,
&ntp_address);
NKC_DestroyInstance(inst->nke);
inst->nke = NULL;
if (!got_data)
return 0;
if (!set_ntp_address(inst, &ntp_address)) {
inst->num_cookies = 0;
return 0;
}
inst->cookie_index = 0;
if (inst->siv_c2s)
SIV_DestroyInstance(inst->siv_c2s);
if (inst->siv_s2c)
SIV_DestroyInstance(inst->siv_s2c);
inst->siv_c2s = SIV_CreateInstance(siv);
inst->siv_s2c = SIV_CreateInstance(siv);
if (!inst->siv_c2s || !inst->siv_s2c ||
!SIV_SetKey(inst->siv_c2s, c2s.key, c2s.length) ||
!SIV_SetKey(inst->siv_s2c, s2c.key, s2c.length)) {
DEBUG_LOG("Could not initialise SIV");
inst->num_cookies = 0;
return 0;
}
inst->nak_response = 0;
SCH_GetLastEventTime(&inst->last_nke_success, NULL, NULL);
return 1;
}
/* ================================================== */
int
NNC_PrepareForAuth(NNC_Instance inst)
{
if (is_nke_needed(inst)) {
if (!get_nke_data(inst))
return 0;
}
UTI_GetRandomBytes(&inst->uniq_id, sizeof (inst->uniq_id));
UTI_GetRandomBytes(&inst->nonce, sizeof (inst->nonce));
return 1;
}
/* ================================================== */
int
NNC_GenerateRequestAuth(NNC_Instance inst, NTP_Packet *packet,
NTP_PacketInfo *info)
{
NKE_Cookie *cookie;
int i, req_cookies;
if (inst->num_cookies == 0 || !inst->siv_c2s)
return 0;
if (info->mode != MODE_CLIENT)
return 0;
cookie = &inst->cookies[inst->cookie_index];
req_cookies = MIN(NTS_MAX_COOKIES - inst->num_cookies + 1,
MAX_TOTAL_COOKIE_LENGTH / (cookie->length + 4));
if (!NEF_AddField(packet, info, NTP_EF_NTS_UNIQUE_IDENTIFIER,
&inst->uniq_id, sizeof (inst->uniq_id)))
return 0;
if (!NEF_AddField(packet, info, NTP_EF_NTS_COOKIE,
cookie->cookie, cookie->length))
return 0;
for (i = 0; i < req_cookies - 1; i++) {
if (!NEF_AddField(packet, info, NTP_EF_NTS_COOKIE_PLACEHOLDER,
cookie->cookie, cookie->length))
return 0;
}
if (!NNA_GenerateAuthEF(packet, info, inst->siv_c2s, inst->nonce, sizeof (inst->nonce),
(const unsigned char *)"", 0, NTP_MAX_V4_MAC_LENGTH + 4))
return 0;
inst->num_cookies--;
inst->cookie_index = (inst->cookie_index + 1) % NTS_MAX_COOKIES;
inst->ok_response = 0;
return 1;
}
/* ================================================== */
static int
extract_cookies(NNC_Instance inst, unsigned char *plaintext, int length)
{
int ef_type, ef_body_length, ef_length, parsed, index, acceptable, saved;
void *ef_body;
acceptable = saved = 0;
for (parsed = 0; parsed < length; parsed += ef_length) {
if (!NEF_ParseSingleField(plaintext, length, parsed,
&ef_length, &ef_type, &ef_body, &ef_body_length))
break;
if (ef_type != NTP_EF_NTS_COOKIE)
continue;
if (ef_length < NTP_MIN_EF_LENGTH || ef_body_length > sizeof (inst->cookies[0].cookie)) {
DEBUG_LOG("Unexpected cookie length %d", ef_body_length);
continue;
}
acceptable++;
if (inst->num_cookies >= NTS_MAX_COOKIES)
continue;
index = (inst->cookie_index + inst->num_cookies) % NTS_MAX_COOKIES;
memcpy(inst->cookies[index].cookie, ef_body, ef_body_length);
inst->cookies[index].length = ef_body_length;
inst->num_cookies++;
saved++;
}
DEBUG_LOG("Extracted %d cookies (saved %d)", acceptable, saved);
return acceptable > 0;
}
/* ================================================== */
int
NNC_CheckResponseAuth(NNC_Instance inst, NTP_Packet *packet,
NTP_PacketInfo *info)
{
int ef_type, ef_body_length, ef_length, parsed, plaintext_length;
int has_valid_uniq_id = 0, has_valid_auth = 0;
unsigned char plaintext[NTP_MAX_EXTENSIONS_LENGTH];
void *ef_body;
if (info->ext_fields == 0 || info->mode != MODE_SERVER)
return 0;
/* Accept only one response per request */
if (inst->ok_response)
return 0;
if (!inst->siv_s2c)
return 0;
for (parsed = NTP_HEADER_LENGTH; parsed < info->length; parsed += ef_length) {
if (!NEF_ParseField(packet, info->length, parsed,
&ef_length, &ef_type, &ef_body, &ef_body_length))
break;
switch (ef_type) {
case NTP_EF_NTS_UNIQUE_IDENTIFIER:
if (ef_body_length != sizeof (inst->uniq_id) ||
memcmp(ef_body, inst->uniq_id, sizeof (inst->uniq_id)) != 0) {
DEBUG_LOG("Invalid uniq id");
return 0;
}
has_valid_uniq_id = 1;
break;
case NTP_EF_NTS_COOKIE:
DEBUG_LOG("Unencrypted cookie");
break;
case NTP_EF_NTS_AUTH_AND_EEF:
if (parsed + ef_length != info->length) {
DEBUG_LOG("Auth not last EF");
return 0;
}
if (!NNA_DecryptAuthEF(packet, info, inst->siv_s2c, parsed,
plaintext, sizeof (plaintext), &plaintext_length))
return 0;
has_valid_auth = 1;
break;
default:
break;
}
}
if (!has_valid_uniq_id || !has_valid_auth) {
if (has_valid_uniq_id && packet->stratum == NTP_INVALID_STRATUM &&
ntohl(packet->reference_id) == NTP_KOD_NTS_NAK) {
DEBUG_LOG("NTS NAK");
inst->nak_response = 1;
return 0;
}
DEBUG_LOG("Missing NTS EF");
return 0;
}
if (!extract_cookies(inst, plaintext, plaintext_length))
return 0;
inst->ok_response = 1;
/* At this point we know the client interoperates with the server. Allow a
new NTS-KE session to be started as soon as the cookies run out. */
UTI_ZeroTimespec(&inst->last_nke_attempt);
return 1;
}
/* ================================================== */
void
NNC_ChangeAddress(NNC_Instance inst, IPAddr *address)
{
if (inst->nke)
NKC_DestroyInstance(inst->nke);
inst->nke = NULL;
inst->num_cookies = 0;
inst->nts_address.ip_addr = *address;
reset_instance(inst);
DEBUG_LOG("NTS reset");
}