]> git.ipfire.org Git - thirdparty/chrony.git/commitdiff
nts: switch client to compliant key exporter on NTS NAK
authorMiroslav Lichvar <mlichvar@redhat.com>
Thu, 19 Sep 2024 12:19:12 +0000 (14:19 +0200)
committerMiroslav Lichvar <mlichvar@redhat.com>
Thu, 3 Oct 2024 13:02:03 +0000 (15:02 +0200)
Implement a fallback for the NTS-NTP client to switch to the compliant
AES-128-GCM-SIV exporter context when the server is using the compliant
context, but does not support the new NTS-KE record negotiating its use,
assuming it can respond with an NTS NAK to the request authenticated
with the incorrect key.

Export both sets of keys when processing the NTS-KE response. If an NTS
NAK is the only valid response from the server after the last NTS-KE
session, switch to the keys exported with the compliant context for the
following requests instead of dropping all cookies and restarting
NTS-KE. Don't switch back to the original keys if an NTS NAK is received
again.

nts_ke_client.c
nts_ke_client.h
nts_ntp_client.c
siv.h
test/unit/nts_ntp_client.c

index 26335021e48a1cd98d18cba3e4222a32d38c5c2c..dfd5101e88368a3e5239644a91a31acb03c2bcb7 100644 (file)
@@ -52,6 +52,7 @@ struct NKC_Instance_Record {
 
   int compliant_128gcm;
   NKE_Context context;
+  NKE_Context alt_context;
   NKE_Cookie cookies[NKE_MAX_COOKIES];
   int num_cookies;
   char server_name[NKE_MAX_RECORD_BODY_LENGTH + 2];
@@ -149,6 +150,7 @@ process_response(NKC_Instance inst)
   assert(sizeof (uint16_t) == 2);
 
   inst->compliant_128gcm = 0;
+  inst->alt_context.algorithm = AEAD_SIV_INVALID;
   inst->num_cookies = 0;
   inst->ntp_address.ip_addr.family = IPADDR_UNSPEC;
   inst->ntp_address.port = 0;
@@ -286,9 +288,18 @@ handle_message(void *arg)
 
   /* With AES-128-GCM-SIV, set the algorithm ID in the RFC5705 key exporter
      context incorrectly for compatibility with older chrony servers unless
-     the server confirmed support for the compliant context */
-  if (exporter_algorithm == AEAD_AES_128_GCM_SIV && !inst->compliant_128gcm)
+     the server confirmed support for the compliant context.  Generate both
+     sets of keys in case the server uses the compliant context, but does not
+     support the negotiation record, assuming it will respond with an NTS NAK
+     to a request authenticated with the noncompliant key. */
+  if (exporter_algorithm == AEAD_AES_128_GCM_SIV && !inst->compliant_128gcm) {
+    inst->alt_context.algorithm = inst->context.algorithm;
+    if (!NKSN_GetKeys(inst->session, inst->alt_context.algorithm, exporter_algorithm,
+                      NKE_NEXT_PROTOCOL_NTPV4, &inst->alt_context.c2s, &inst->alt_context.s2c))
+      return 0;
+
     exporter_algorithm = AEAD_AES_SIV_CMAC_256;
+  }
 
   if (!NKSN_GetKeys(inst->session, inst->context.algorithm, exporter_algorithm,
                     NKE_NEXT_PROTOCOL_NTPV4, &inst->context.c2s, &inst->context.s2c))
@@ -456,7 +467,7 @@ NKC_IsActive(NKC_Instance inst)
 /* ================================================== */
 
 int
-NKC_GetNtsData(NKC_Instance inst, NKE_Context *context,
+NKC_GetNtsData(NKC_Instance inst, NKE_Context *context, NKE_Context *alt_context,
                NKE_Cookie *cookies, int *num_cookies, int max_cookies,
                IPSockAddr *ntp_address)
 {
@@ -466,6 +477,7 @@ NKC_GetNtsData(NKC_Instance inst, NKE_Context *context,
     return 0;
 
   *context = inst->context;
+  *alt_context = inst->alt_context;
 
   for (i = 0; i < inst->num_cookies && i < max_cookies; i++)
     cookies[i] = inst->cookies[i];
index a1bedb693ddbfcb2b2b427d19373211837186491..b2c4c84f61a9276be30487c7961a26ea9da0aeb5 100644 (file)
@@ -46,7 +46,7 @@ extern int NKC_Start(NKC_Instance inst);
 extern int NKC_IsActive(NKC_Instance inst);
 
 /* Get the NTS data if the session was successful */
-extern int NKC_GetNtsData(NKC_Instance inst, NKE_Context *context,
+extern int NKC_GetNtsData(NKC_Instance inst, NKE_Context *context, NKE_Context *alt_context,
                           NKE_Cookie *cookies, int *num_cookies, int max_cookies,
                           IPSockAddr *ntp_address);
 
index 2f4b72835c89eeec0cbe56da73f02be78230d93c..2c3464fe8c3a8cab71d7921bb1bce555fe50589c 100644 (file)
@@ -72,6 +72,7 @@ struct NNC_Instance_Record {
   double last_nke_success;
 
   NKE_Context context;
+  NKE_Context alt_context;
   unsigned int context_id;
   NKE_Cookie cookies[NTS_MAX_COOKIES];
   int num_cookies;
@@ -105,6 +106,7 @@ reset_instance(NNC_Instance inst)
   inst->last_nke_success = 0.0;
 
   memset(&inst->context, 0, sizeof (inst->context));
+  memset(&inst->alt_context, 0, sizeof (inst->alt_context));
   inst->context_id = 0;
   memset(inst->cookies, 0, sizeof (inst->cookies));
   inst->num_cookies = 0;
@@ -165,6 +167,21 @@ check_cookies(NNC_Instance inst)
   if (inst->num_cookies > 0 &&
       ((inst->nak_response && !inst->ok_response) ||
        SCH_GetLastEventMonoTime() - inst->last_nke_success > CNF_GetNtsRefresh())) {
+
+    /* Before dropping the cookies, check whether there is an alternate set of
+       keys available (exported with the compliant context for AES-128-GCM-SIV)
+       and the NAK was the only valid response after the last NTS-KE session,
+       indicating we use incorrect keys and switching to the other set of keys
+       for the following NTP requests might work */
+    if (inst->alt_context.algorithm != AEAD_SIV_INVALID &&
+        inst->alt_context.algorithm == inst->context.algorithm &&
+        inst->nke_attempts > 0 && inst->nak_response && !inst->ok_response) {
+      inst->context = inst->alt_context;
+      inst->alt_context.algorithm = AEAD_SIV_INVALID;
+      DEBUG_LOG("Switched to compliant keys");
+      return 1;
+    }
+
     inst->num_cookies = 0;
     DEBUG_LOG("Dropped cookies");
   }
@@ -261,7 +278,7 @@ get_cookies(NNC_Instance inst)
   assert(sizeof (inst->cookies) / sizeof (inst->cookies[0]) == NTS_MAX_COOKIES);
 
   /* Get the new keys, cookies and NTP address if the session was successful */
-  got_data = NKC_GetNtsData(inst->nke, &inst->context,
+  got_data = NKC_GetNtsData(inst->nke, &inst->context, &inst->alt_context,
                             inst->cookies, &inst->num_cookies, NTS_MAX_COOKIES,
                             &ntp_address);
 
@@ -520,6 +537,7 @@ NNC_CheckResponseAuth(NNC_Instance inst, NTP_Packet *packet,
      new NTS-KE session to be started as soon as the cookies run out. */
   inst->nke_attempts = 0;
   inst->next_nke_attempt = 0.0;
+  inst->alt_context.algorithm = AEAD_SIV_INVALID;
 
   return 1;
 }
@@ -643,6 +661,7 @@ load_cookies(NNC_Instance inst)
         sscanf(words[0], "%u", &context_id) != 1 || sscanf(words[1], "%d", &algorithm) != 1)
     goto error;
 
+  inst->alt_context.algorithm = AEAD_SIV_INVALID;
   inst->context.algorithm = algorithm;
   inst->context.s2c.length = UTI_HexToBytes(words[2], inst->context.s2c.key,
                                             sizeof (inst->context.s2c.key));
@@ -687,6 +706,7 @@ error:
   fclose(f);
 
   memset(&inst->context, 0, sizeof (inst->context));
+  memset(&inst->alt_context, 0, sizeof (inst->alt_context));
   inst->num_cookies = 0;
 }
 
diff --git a/siv.h b/siv.h
index 868edbd49f9bd876fc5907fb6989b0e72577292e..f1ebbab6ce3776b1afe61530a72cd62e6c5e9873 100644 (file)
--- a/siv.h
+++ b/siv.h
@@ -36,6 +36,7 @@
 
 /* Identifiers of SIV algorithms following the IANA AEAD registry */
 typedef enum {
+  AEAD_SIV_INVALID = 0,
   AEAD_AES_SIV_CMAC_256 = 15,
   AEAD_AES_SIV_CMAC_384 = 16,
   AEAD_AES_SIV_CMAC_512 = 17,
index 7953413d01e3bbabaa183f781e41dbf10d37247a..4b56732446a71ab64b20c5e34be4ea7ef9edc54e 100644 (file)
@@ -33,7 +33,7 @@
 #define NKC_IsActive(inst) (random() % 2)
 #define NKC_GetRetryFactor(inst) (1)
 
-static int get_nts_data(NKC_Instance inst, NKE_Context *context,
+static int get_nts_data(NKC_Instance inst, NKE_Context *context, NKE_Context *alt_context,
                         NKE_Cookie *cookies, int *num_cookies, int max_cookies,
                         IPSockAddr *ntp_address);
 #define NKC_GetNtsData get_nts_data
@@ -41,7 +41,7 @@ static int get_nts_data(NKC_Instance inst, NKE_Context *context,
 #include <nts_ntp_client.c>
 
 static int
-get_nts_data(NKC_Instance inst, NKE_Context *context,
+get_nts_data(NKC_Instance inst, NKE_Context *context, NKE_Context *alt_context,
              NKE_Cookie *cookies, int *num_cookies, int max_cookies,
              IPSockAddr *ntp_address)
 {
@@ -60,6 +60,14 @@ get_nts_data(NKC_Instance inst, NKE_Context *context,
   context->s2c.length = SIV_GetKeyLength(context->algorithm);
   UTI_GetRandomBytes(context->s2c.key, context->s2c.length);
 
+  if (random() % 2) {
+    *alt_context = *context;
+    UTI_GetRandomBytes(alt_context->c2s.key, alt_context->c2s.length);
+    UTI_GetRandomBytes(alt_context->s2c.key, alt_context->s2c.length);
+  } else {
+    alt_context->algorithm = AEAD_SIV_INVALID;
+  }
+
   *num_cookies = random() % max_cookies + 1;
   for (i = 0; i < *num_cookies; i++) {
     cookies[i].length = random() % (sizeof (cookies[i].cookie) + 1);