Add support for the NTS NTP extension fields.
static int nts_server_port = 11443;
static int nts_server_processes = 1;
static int nts_server_connections = 100;
+static int nts_refresh = 2419200; /* 4 weeks */
static int nts_rotate = 604800; /* 1 week */
static char *nts_trusted_cert_file = NULL;
parse_int(p, &nts_server_port);
} else if (!strcasecmp(command, "ntsprocesses")) {
parse_int(p, &nts_server_processes);
+ } else if (!strcasecmp(command, "ntsrefresh")) {
+ parse_int(p, &nts_refresh);
} else if (!strcasecmp(command, "ntsrotate")) {
parse_int(p, &nts_rotate);
} else if (!strcasecmp(command, "ntsservercert")) {
/* ================================================== */
+int
+CNF_GetNtsRefresh(void)
+{
+ return nts_refresh;
+}
+
+/* ================================================== */
+
int
CNF_GetNtsRotate(void)
{
extern int CNF_GetNtsServerPort(void);
extern int CNF_GetNtsServerProcesses(void);
extern int CNF_GetNtsServerConnections(void);
+extern int CNF_GetNtsRefresh(void);
extern int CNF_GetNtsRotate(void);
extern char *CNF_GetNtsTrustedCertFile(void);
extern int CNF_GetNoSystemCert(void);
'gnutls_init(NULL, 0);'
then
EXTRA_OBJECTS="$EXTRA_OBJECTS nts_ke_client.o nts_ke_server.o nts_ke_session.o"
+ EXTRA_OBJECTS="$EXTRA_OBJECTS nts_ntp_auth.o nts_ntp_client.o nts_ntp_server.o"
EXTRA_OBJECTS="$EXTRA_OBJECTS siv_nettle.o"
LIBS="$LIBS $test_link"
MYCPPFLAGS="$MYCPPFLAGS $test_cflags"
#include "ntp_core.h"
#include "nts_ke_client.h"
#include "nts_ke_server.h"
+#include "nts_ntp_server.h"
#include "socket.h"
#include "sources.h"
#include "sourcestats.h"
CLG_Finalise();
NKC_Finalise();
NKS_Finalise();
+ NNS_Finalise();
NSD_Finalise();
NSR_Finalise();
SST_Finalise();
SST_Initialise();
NSR_Initialise();
NSD_Initialise();
+ NNS_Initialise();
NKS_Initialise(scfilter_level);
NKC_Initialise();
CLG_Initialise();
--- /dev/null
+/*
+ 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Header file for the NTS-NTP protocol
+ */
+
+#ifndef GOT_NTS_NTP_H
+#define GOT_NTS_NTP_H
+
+#define NTP_EF_NTS_UNIQUE_IDENTIFIER 0x0104
+#define NTP_EF_NTS_COOKIE 0x0204
+#define NTP_EF_NTS_COOKIE_PLACEHOLDER 0x0304
+#define NTP_EF_NTS_AUTH_AND_EEF 0x0404
+
+#define NTP_KOD_NTS_NAK 0x4e54534e
+
+#define NTS_MIN_UNIQ_ID_LENGTH 32
+#define NTS_MIN_UNPADDED_NONCE_LENGTH 16
+#define NTS_MAX_COOKIES 8
+
+#endif
--- /dev/null
+/*
+ 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ NTS Authenticator and Encrypted Extension Fields extension field
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "nts_ntp_auth.h"
+
+#include "logging.h"
+#include "ntp_ext.h"
+#include "nts_ntp.h"
+#include "siv.h"
+#include "util.h"
+
+struct AuthHeader {
+ uint16_t nonce_length;
+ uint16_t ciphertext_length;
+};
+
+/* ================================================== */
+
+static int
+get_padding_length(int length)
+{
+ return length % 4U ? 4 - length % 4U : 0;
+}
+
+/* ================================================== */
+
+static int
+get_padded_length(int length)
+{
+ return length + get_padding_length(length);
+}
+
+/* ================================================== */
+
+int
+NNA_GenerateAuthEF(NTP_Packet *packet, NTP_PacketInfo *info, SIV_Instance siv,
+ const unsigned char *nonce, int nonce_length,
+ const unsigned char *plaintext, int plaintext_length,
+ int min_ef_length)
+{
+ int auth_length, ciphertext_length, assoc_length;
+ int nonce_padding, ciphertext_padding, additional_padding;
+ unsigned char *ciphertext, *body;
+ struct AuthHeader *header;
+
+ assert(sizeof (*header) == 4);
+
+ if (nonce_length <= 0 || plaintext_length < 0) {
+ DEBUG_LOG("Invalid nonce/plaintext length");
+ return 0;
+ }
+
+ assoc_length = info->length;
+ ciphertext_length = SIV_GetTagLength(siv) + plaintext_length;
+ nonce_padding = get_padding_length(nonce_length);
+ ciphertext_padding = get_padding_length(ciphertext_length);
+ min_ef_length = get_padded_length(min_ef_length);
+
+ auth_length = sizeof (*header) + nonce_length + nonce_padding +
+ ciphertext_length + ciphertext_padding;
+ additional_padding = MAX(min_ef_length - auth_length - 4, 0);
+ additional_padding = MAX(NTS_MIN_UNPADDED_NONCE_LENGTH - nonce_length - nonce_padding,
+ additional_padding);
+ auth_length += additional_padding;
+
+ if (!NEF_AddBlankField(packet, info, NTP_EF_NTS_AUTH_AND_EEF, auth_length,
+ (void **)&header)) {
+ DEBUG_LOG("Could not add EF");
+ return 0;
+ }
+
+ header->nonce_length = htons(nonce_length);
+ header->ciphertext_length = htons(ciphertext_length);
+
+ body = (unsigned char *)(header + 1);
+ ciphertext = body + nonce_length + nonce_padding;
+
+ memcpy(body, nonce, nonce_length);
+ memset(body + nonce_length, 0, nonce_padding);
+
+ if (!SIV_Encrypt(siv, nonce, nonce_length, packet, assoc_length,
+ plaintext, plaintext_length, ciphertext, ciphertext_length)) {
+ DEBUG_LOG("SIV encrypt failed");
+ return 0;
+ }
+
+ memset(ciphertext + ciphertext_length, 0, ciphertext_padding + additional_padding);
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+NNA_DecryptAuthEF(NTP_Packet *packet, NTP_PacketInfo *info, SIV_Instance siv, int ef_start,
+ unsigned char *plaintext, int buffer_length, int *plaintext_length)
+{
+ unsigned int siv_tag_length, nonce_length, ciphertext_length;
+ unsigned char *nonce, *ciphertext;
+ int ef_type, ef_body_length;
+ void *ef_body;
+ struct AuthHeader *header;
+
+ if (!NEF_ParseField(packet, info->length, ef_start,
+ NULL, &ef_type, &ef_body, &ef_body_length))
+ return 0;
+
+ if (ef_type != NTP_EF_NTS_AUTH_AND_EEF)
+ return 0;
+
+ header = ef_body;
+
+ nonce_length = ntohs(header->nonce_length);
+ ciphertext_length = ntohs(header->ciphertext_length);
+
+ if (get_padded_length(nonce_length) +
+ get_padded_length(ciphertext_length) > ef_body_length)
+ return 0;
+
+ nonce = (unsigned char *)(header + 1);
+ ciphertext = (unsigned char *)(header + 1) + get_padded_length(nonce_length);
+
+ siv_tag_length = SIV_GetTagLength(siv);
+
+ if (nonce_length < 1 ||
+ ciphertext_length < siv_tag_length ||
+ ciphertext_length - siv_tag_length > buffer_length) {
+ DEBUG_LOG("Unexpected nonce/ciphertext length");
+ return 0;
+ }
+
+ if (ef_body_length < sizeof (*header) +
+ NTS_MIN_UNPADDED_NONCE_LENGTH + get_padded_length(ciphertext_length)) {
+ DEBUG_LOG("Missing padding");
+ return 0;
+ }
+
+ *plaintext_length = ciphertext_length - siv_tag_length;
+
+ if (!SIV_Decrypt(siv, nonce, nonce_length, packet, info->length - ef_body_length - 4,
+ ciphertext, ciphertext_length, plaintext, *plaintext_length)) {
+ DEBUG_LOG("SIV decrypt failed");
+ return 0;
+ }
+
+ return 1;
+}
--- /dev/null
+/*
+ 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Header for NTS Authenticator and Encrypted Extension Fields
+ extension field
+ */
+
+#ifndef GOT_NTS_NTP_AUTH_H
+#define GOT_NTS_NTP_AUTH_H
+
+#include "ntp.h"
+#include "siv.h"
+
+extern int NNA_GenerateAuthEF(NTP_Packet *packet, NTP_PacketInfo *info, SIV_Instance siv,
+ const unsigned char *nonce, int nonce_length,
+ const unsigned char *plaintext, int plaintext_length,
+ int min_ef_length);
+
+extern int NNA_DecryptAuthEF(NTP_Packet *packet, NTP_PacketInfo *info, SIV_Instance siv,
+ int ef_start, unsigned char *plaintext, int buffer_length,
+ int *plaintext_length);
+
+#endif
--- /dev/null
+/*
+ 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");
+}
--- /dev/null
+/*
+ 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Header file for client NTS-NTP authentication
+ */
+
+#ifndef GOT_NTS_NTP_CLIENT_H
+#define GOT_NTS_NTP_CLIENT_H
+
+#include "addressing.h"
+#include "ntp.h"
+
+typedef struct NNC_Instance_Record *NNC_Instance;
+
+extern NNC_Instance NNC_CreateInstance(IPSockAddr *nts_address, const char *name,
+ const IPSockAddr *ntp_address);
+extern void NNC_DestroyInstance(NNC_Instance inst);
+extern int NNC_PrepareForAuth(NNC_Instance inst);
+extern int NNC_GenerateRequestAuth(NNC_Instance inst, NTP_Packet *packet,
+ NTP_PacketInfo *info);
+extern int NNC_CheckResponseAuth(NNC_Instance inst, NTP_Packet *packet,
+ NTP_PacketInfo *info);
+
+extern void NNC_ChangeAddress(NNC_Instance inst, IPAddr *address);
+
+#endif
--- /dev/null
+/*
+ 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Server NTS-NTP authentication
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "nts_ntp_server.h"
+
+#include "conf.h"
+#include "logging.h"
+#include "memory.h"
+#include "ntp.h"
+#include "ntp_ext.h"
+#include "nts_ke_server.h"
+#include "nts_ntp.h"
+#include "nts_ntp_auth.h"
+#include "siv.h"
+#include "util.h"
+
+struct NtsServer {
+ SIV_Instance siv;
+ unsigned char nonce[NTS_MIN_UNPADDED_NONCE_LENGTH];
+ NKE_Cookie cookies[NTS_MAX_COOKIES];
+ int num_cookies;
+ NTP_int64 req_tx;
+};
+
+/* The server instance handling all requests */
+struct NtsServer *server;
+
+/* ================================================== */
+
+void
+NNS_Initialise(void)
+{
+ /* Create an NTS-NTP server instance only if NTS-KE server is enabled */
+ if (!CNF_GetNtsServerCertFile() || !CNF_GetNtsServerKeyFile()) {
+ server = NULL;
+ return;
+ }
+
+ server = Malloc(sizeof (struct NtsServer));
+ server->siv = SIV_CreateInstance(AEAD_AES_SIV_CMAC_256);
+}
+
+/* ================================================== */
+
+void
+NNS_Finalise(void)
+{
+ if (!server)
+ return;
+
+ SIV_DestroyInstance(server->siv);
+ Free(server);
+ server = NULL;
+}
+
+/* ================================================== */
+
+int
+NNS_CheckRequestAuth(NTP_Packet *packet, NTP_PacketInfo *info, uint32_t *kod)
+{
+ int ef_type, ef_body_length, ef_length, has_uniq_id = 0, has_auth = 0, has_cookie = 0;
+ int i, plaintext_length, parsed, requested_cookies, cookie_length = -1, auth_start = 0;
+ unsigned char plaintext[NTP_MAX_EXTENSIONS_LENGTH];
+ NKE_Cookie cookie;
+ NKE_Key c2s, s2c;
+ void *ef_body;
+
+ if (!server)
+ return 0;
+
+ *kod = 0;
+
+ server->num_cookies = 0;
+ server->req_tx = packet->transmit_ts;
+
+ if (info->ext_fields == 0 || info->mode != MODE_CLIENT)
+ return 0;
+
+ requested_cookies = 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:
+ has_uniq_id = 1;
+ break;
+ case NTP_EF_NTS_COOKIE:
+ if (has_cookie || ef_body_length > sizeof (cookie.cookie))
+ return 0;
+ cookie.length = ef_body_length;
+ memcpy(cookie.cookie, ef_body, ef_body_length);
+ has_cookie = 1;
+ /* Fall through */
+ case NTP_EF_NTS_COOKIE_PLACEHOLDER:
+ requested_cookies++;
+
+ if (cookie_length >= 0 && cookie_length != ef_body_length) {
+ DEBUG_LOG("Invalid cookie/placeholder length");
+ return 0;
+ }
+ cookie_length = ef_body_length;
+ break;
+ case NTP_EF_NTS_AUTH_AND_EEF:
+ auth_start = parsed;
+ has_auth = 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!has_uniq_id || !has_cookie || !has_auth) {
+ DEBUG_LOG("Missing an NTS EF");
+ return 0;
+ }
+
+ if (!NKS_DecodeCookie(&cookie, &c2s, &s2c)) {
+ *kod = NTP_KOD_NTS_NAK;
+ return 0;
+ }
+
+ if (!SIV_SetKey(server->siv, c2s.key, c2s.length)) {
+ DEBUG_LOG("Could not set C2S key");
+ return 0;
+ }
+
+ if (!NNA_DecryptAuthEF(packet, info, server->siv, auth_start,
+ plaintext, sizeof (plaintext), &plaintext_length)) {
+ *kod = NTP_KOD_NTS_NAK;
+ return 0;
+ }
+
+ for (parsed = 0; parsed < plaintext_length; parsed += ef_length) {
+ if (!NEF_ParseSingleField(plaintext, plaintext_length, parsed,
+ &ef_length, &ef_type, &ef_body, &ef_body_length))
+ break;
+
+ switch (ef_type) {
+ case NTP_EF_NTS_COOKIE_PLACEHOLDER:
+ if (cookie_length != ef_body_length) {
+ DEBUG_LOG("Invalid cookie/placeholder length");
+ return 0;
+ }
+ requested_cookies++;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!SIV_SetKey(server->siv, s2c.key, s2c.length)) {
+ DEBUG_LOG("Could not set S2C key");
+ return 0;
+ }
+
+ UTI_GetRandomBytes(server->nonce, sizeof (server->nonce));
+
+ server->num_cookies = MIN(NTS_MAX_COOKIES, requested_cookies);
+ for (i = 0; i < server->num_cookies; i++)
+ if (!NKS_GenerateCookie(&c2s, &s2c, &server->cookies[i]))
+ return 0;
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+NNS_GenerateResponseAuth(NTP_Packet *request, NTP_PacketInfo *req_info,
+ NTP_Packet *response, NTP_PacketInfo *res_info,
+ uint32_t kod)
+{
+ int i, ef_type, ef_body_length, ef_length, parsed;
+ void *ef_body;
+ unsigned char plaintext[NTP_MAX_EXTENSIONS_LENGTH];
+ int plaintext_length;
+
+ if (!server || req_info->mode != MODE_CLIENT || res_info->mode != MODE_SERVER)
+ return 0;
+
+ /* Make sure this is a response to the expected request */
+ if (UTI_CompareNtp64(&server->req_tx, &request->transmit_ts) != 0)
+ assert(0);
+
+ for (parsed = NTP_HEADER_LENGTH; parsed < req_info->length; parsed += ef_length) {
+ if (!NEF_ParseField(request, req_info->length, parsed,
+ &ef_length, &ef_type, &ef_body, &ef_body_length))
+ break;
+
+ switch (ef_type) {
+ case NTP_EF_NTS_UNIQUE_IDENTIFIER:
+ /* Copy the ID from the request */
+ if (!NEF_AddField(response, res_info, ef_type, ef_body, ef_body_length))
+ return 0;
+ default:
+ break;
+ }
+ }
+
+ /* NTS NAK response does not have any other fields */
+ if (kod)
+ return 1;
+
+ for (i = 0, plaintext_length = 0; i < server->num_cookies; i++) {
+ if (!NEF_SetField(plaintext, sizeof (plaintext), plaintext_length,
+ NTP_EF_NTS_COOKIE, &server->cookies[i].cookie,
+ server->cookies[i].length, &ef_length))
+ return 0;
+
+ plaintext_length += ef_length;
+ assert(plaintext_length <= sizeof (plaintext));
+ }
+
+ server->num_cookies = 0;
+
+ if (!NNA_GenerateAuthEF(response, res_info, server->siv,
+ server->nonce, sizeof (server->nonce),
+ plaintext, plaintext_length,
+ req_info->length - res_info->length))
+ return 0;
+
+ return 1;
+}
--- /dev/null
+/*
+ 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Header file for server NTS-NTP authentication
+ */
+
+#ifndef GOT_NTS_NTP_SERVER_H
+#define GOT_NTS_NTP_SERVER_H
+
+#include "ntp.h"
+
+extern void NNS_Initialise(void);
+extern void NNS_Finalise(void);
+
+extern int NNS_CheckRequestAuth(NTP_Packet *packet, NTP_PacketInfo *info, uint32_t *kod);
+extern int NNS_GenerateResponseAuth(NTP_Packet *request, NTP_PacketInfo *req_info,
+ NTP_Packet *response, NTP_PacketInfo *res_info,
+ uint32_t kod);
+
+#endif
#include "ntp_signd.h"
#include "nts_ke_client.h"
#include "nts_ke_server.h"
+#include "nts_ntp_client.h"
+#include "nts_ntp_server.h"
#include "privops.h"
#include "refclock.h"
#include "sched.h"
#ifndef FEAT_NTS
+void
+NNS_Initialise(void)
+{
+}
+
+void
+NNS_Finalise(void)
+{
+}
+
+int
+NNS_CheckRequestAuth(NTP_Packet *packet, NTP_PacketInfo *info, uint32_t *kod)
+{
+ *kod = 0;
+ return 0;
+}
+
+int
+NNS_GenerateResponseAuth(NTP_Packet *request, NTP_PacketInfo *req_info,
+ NTP_Packet *response, NTP_PacketInfo *res_info,
+ uint32_t kod)
+{
+ return 0;
+}
+
+NNC_Instance
+NNC_CreateInstance(IPSockAddr *nts_address, const char *name, const IPSockAddr *ntp_address)
+{
+ return NULL;
+}
+
+void
+NNC_DestroyInstance(NNC_Instance inst)
+{
+}
+
+int
+NNC_PrepareForAuth(NNC_Instance inst)
+{
+ return 1;
+}
+
+int
+NNC_GenerateRequestAuth(NNC_Instance inst, NTP_Packet *packet, NTP_PacketInfo *info)
+{
+ DEBUG_LOG("NTS support disabled");
+ return 0;
+}
+
+int
+NNC_CheckResponseAuth(NNC_Instance inst, NTP_Packet *packet, NTP_PacketInfo *info)
+{
+ DEBUG_LOG("NTS support disabled");
+ return 0;
+}
+
+void
+NNC_ChangeAddress(NNC_Instance inst, IPAddr *address)
+{
+}
+
void
NKC_Initialise(void)
{