summaryrefslogtreecommitdiffstats
path: root/ntp_auth.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--ntp_auth.c494
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);
+ }
+}