]> git.ipfire.org Git - thirdparty/chrony.git/commitdiff
nts: add NTS-NTP server and client
authorMiroslav Lichvar <mlichvar@redhat.com>
Tue, 4 Feb 2020 14:15:03 +0000 (15:15 +0100)
committerMiroslav Lichvar <mlichvar@redhat.com>
Thu, 5 Mar 2020 15:02:15 +0000 (16:02 +0100)
Add support for the NTS NTP extension fields.

12 files changed:
conf.c
conf.h
configure
main.c
nts_ntp.h [new file with mode: 0644]
nts_ntp_auth.c [new file with mode: 0644]
nts_ntp_auth.h [new file with mode: 0644]
nts_ntp_client.c [new file with mode: 0644]
nts_ntp_client.h [new file with mode: 0644]
nts_ntp_server.c [new file with mode: 0644]
nts_ntp_server.h [new file with mode: 0644]
stubs.c

diff --git a/conf.c b/conf.c
index 6d79c04c3698d93aff54fb3680c1945c57cf3b50..8bc59a4cf1eb46e1202d3077a4cb2ed2b8f64c1c 100644 (file)
--- a/conf.c
+++ b/conf.c
@@ -230,6 +230,7 @@ static char *nts_server_key_file = NULL;
 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;
 
@@ -556,6 +557,8 @@ CNF_ParseLine(const char *filename, int number, char *line)
     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")) {
@@ -2113,6 +2116,14 @@ CNF_GetNtsServerConnections(void)
 
 /* ================================================== */
 
+int
+CNF_GetNtsRefresh(void)
+{
+  return nts_refresh;
+}
+
+/* ================================================== */
+
 int
 CNF_GetNtsRotate(void)
 {
diff --git a/conf.h b/conf.h
index cc3479104175c33a9c5e9bbdfa6150b45e0c4cb5..697f111514dc59ce9660aa465b504c67d611e60e 100644 (file)
--- a/conf.h
+++ b/conf.h
@@ -145,6 +145,7 @@ extern char *CNF_GetNtsServerKeyFile(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);
index 5af221e1116e8e1c209fb9ff16a54634a078ddd4..4a701189893ef8244a62c77216c2e0896f487255 100755 (executable)
--- a/configure
+++ b/configure
@@ -939,6 +939,7 @@ if [ $feat_ntp = "1" ] && [ $feat_nts = "1" ] && [ $try_gnutls = "1" ] && \
     '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"
diff --git a/main.c b/main.c
index b08d7bd4be907f60cb74aaea40b6c35ed681a9c2..c5181ee977d4f4531bcc8400346d877404741030 100644 (file)
--- a/main.c
+++ b/main.c
@@ -40,6 +40,7 @@
 #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"
@@ -117,6 +118,7 @@ MAI_CleanupAndExit(void)
   CLG_Finalise();
   NKC_Finalise();
   NKS_Finalise();
+  NNS_Finalise();
   NSD_Finalise();
   NSR_Finalise();
   SST_Finalise();
@@ -587,6 +589,7 @@ int main
   SST_Initialise();
   NSR_Initialise();
   NSD_Initialise();
+  NNS_Initialise();
   NKS_Initialise(scfilter_level);
   NKC_Initialise();
   CLG_Initialise();
diff --git a/nts_ntp.h b/nts_ntp.h
new file mode 100644 (file)
index 0000000..a34eee1
--- /dev/null
+++ b/nts_ntp.h
@@ -0,0 +1,41 @@
+/*
+  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
diff --git a/nts_ntp_auth.c b/nts_ntp_auth.c
new file mode 100644 (file)
index 0000000..8896d8e
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+  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;
+}
diff --git a/nts_ntp_auth.h b/nts_ntp_auth.h
new file mode 100644 (file)
index 0000000..856beb3
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+  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
diff --git a/nts_ntp_client.c b/nts_ntp_client.c
new file mode 100644 (file)
index 0000000..9b324c1
--- /dev/null
@@ -0,0 +1,445 @@
+/*
+  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");
+}
diff --git a/nts_ntp_client.h b/nts_ntp_client.h
new file mode 100644 (file)
index 0000000..23e7721
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+  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
diff --git a/nts_ntp_server.c b/nts_ntp_server.c
new file mode 100644 (file)
index 0000000..ecdaa5b
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+  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;
+}
diff --git a/nts_ntp_server.h b/nts_ntp_server.h
new file mode 100644 (file)
index 0000000..fea28f2
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+  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
diff --git a/stubs.c b/stubs.c
index d9dce17682e81be032cd92359ccbf8a8d1f06e54..63aea148e01c5c7f0d273c422fe8e98b61a672f1 100644 (file)
--- a/stubs.c
+++ b/stubs.c
@@ -42,6 +42,8 @@
 #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"
@@ -452,6 +454,67 @@ CMC_DestroyInstance(CMC_Instance inst)
 
 #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)
 {