diff --git a/nts_ke_client.c b/nts_ke_client.c index 2633502..dfd5101 100644 --- a/nts_ke_client.c +++ b/nts_ke_client.c @@ -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]; diff --git a/nts_ke_client.h b/nts_ke_client.h index a1bedb6..b2c4c84 100644 --- a/nts_ke_client.h +++ b/nts_ke_client.h @@ -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); diff --git a/nts_ntp_client.c b/nts_ntp_client.c index 2f4b728..2c3464f 100644 --- a/nts_ntp_client.c +++ b/nts_ntp_client.c @@ -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 868edbd..f1ebbab 100644 --- 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, diff --git a/test/unit/nts_ntp_client.c b/test/unit/nts_ntp_client.c index 7953413..4b56732 100644 --- a/test/unit/nts_ntp_client.c +++ b/test/unit/nts_ntp_client.c @@ -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 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);