nts: switch client to compliant key exporter on NTS NAK

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.
This commit is contained in:
Miroslav Lichvar 2024-09-19 14:19:12 +02:00
parent 0707865413
commit 689605b6a2
5 changed files with 48 additions and 7 deletions

View file

@ -52,6 +52,7 @@ struct NKC_Instance_Record {
int compliant_128gcm; int compliant_128gcm;
NKE_Context context; NKE_Context context;
NKE_Context alt_context;
NKE_Cookie cookies[NKE_MAX_COOKIES]; NKE_Cookie cookies[NKE_MAX_COOKIES];
int num_cookies; int num_cookies;
char server_name[NKE_MAX_RECORD_BODY_LENGTH + 2]; char server_name[NKE_MAX_RECORD_BODY_LENGTH + 2];
@ -149,6 +150,7 @@ process_response(NKC_Instance inst)
assert(sizeof (uint16_t) == 2); assert(sizeof (uint16_t) == 2);
inst->compliant_128gcm = 0; inst->compliant_128gcm = 0;
inst->alt_context.algorithm = AEAD_SIV_INVALID;
inst->num_cookies = 0; inst->num_cookies = 0;
inst->ntp_address.ip_addr.family = IPADDR_UNSPEC; inst->ntp_address.ip_addr.family = IPADDR_UNSPEC;
inst->ntp_address.port = 0; 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 /* With AES-128-GCM-SIV, set the algorithm ID in the RFC5705 key exporter
context incorrectly for compatibility with older chrony servers unless context incorrectly for compatibility with older chrony servers unless
the server confirmed support for the compliant context */ the server confirmed support for the compliant context. Generate both
if (exporter_algorithm == AEAD_AES_128_GCM_SIV && !inst->compliant_128gcm) 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; exporter_algorithm = AEAD_AES_SIV_CMAC_256;
}
if (!NKSN_GetKeys(inst->session, inst->context.algorithm, exporter_algorithm, if (!NKSN_GetKeys(inst->session, inst->context.algorithm, exporter_algorithm,
NKE_NEXT_PROTOCOL_NTPV4, &inst->context.c2s, &inst->context.s2c)) NKE_NEXT_PROTOCOL_NTPV4, &inst->context.c2s, &inst->context.s2c))
@ -456,7 +467,7 @@ NKC_IsActive(NKC_Instance inst)
/* ================================================== */ /* ================================================== */
int 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, NKE_Cookie *cookies, int *num_cookies, int max_cookies,
IPSockAddr *ntp_address) IPSockAddr *ntp_address)
{ {
@ -466,6 +477,7 @@ NKC_GetNtsData(NKC_Instance inst, NKE_Context *context,
return 0; return 0;
*context = inst->context; *context = inst->context;
*alt_context = inst->alt_context;
for (i = 0; i < inst->num_cookies && i < max_cookies; i++) for (i = 0; i < inst->num_cookies && i < max_cookies; i++)
cookies[i] = inst->cookies[i]; cookies[i] = inst->cookies[i];

View file

@ -46,7 +46,7 @@ extern int NKC_Start(NKC_Instance inst);
extern int NKC_IsActive(NKC_Instance inst); extern int NKC_IsActive(NKC_Instance inst);
/* Get the NTS data if the session was successful */ /* 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, NKE_Cookie *cookies, int *num_cookies, int max_cookies,
IPSockAddr *ntp_address); IPSockAddr *ntp_address);

View file

@ -72,6 +72,7 @@ struct NNC_Instance_Record {
double last_nke_success; double last_nke_success;
NKE_Context context; NKE_Context context;
NKE_Context alt_context;
unsigned int context_id; unsigned int context_id;
NKE_Cookie cookies[NTS_MAX_COOKIES]; NKE_Cookie cookies[NTS_MAX_COOKIES];
int num_cookies; int num_cookies;
@ -105,6 +106,7 @@ reset_instance(NNC_Instance inst)
inst->last_nke_success = 0.0; inst->last_nke_success = 0.0;
memset(&inst->context, 0, sizeof (inst->context)); memset(&inst->context, 0, sizeof (inst->context));
memset(&inst->alt_context, 0, sizeof (inst->alt_context));
inst->context_id = 0; inst->context_id = 0;
memset(inst->cookies, 0, sizeof (inst->cookies)); memset(inst->cookies, 0, sizeof (inst->cookies));
inst->num_cookies = 0; inst->num_cookies = 0;
@ -165,6 +167,21 @@ check_cookies(NNC_Instance inst)
if (inst->num_cookies > 0 && if (inst->num_cookies > 0 &&
((inst->nak_response && !inst->ok_response) || ((inst->nak_response && !inst->ok_response) ||
SCH_GetLastEventMonoTime() - inst->last_nke_success > CNF_GetNtsRefresh())) { 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; inst->num_cookies = 0;
DEBUG_LOG("Dropped cookies"); DEBUG_LOG("Dropped cookies");
} }
@ -261,7 +278,7 @@ get_cookies(NNC_Instance inst)
assert(sizeof (inst->cookies) / sizeof (inst->cookies[0]) == NTS_MAX_COOKIES); assert(sizeof (inst->cookies) / sizeof (inst->cookies[0]) == NTS_MAX_COOKIES);
/* Get the new keys, cookies and NTP address if the session was successful */ /* 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, inst->cookies, &inst->num_cookies, NTS_MAX_COOKIES,
&ntp_address); &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. */ new NTS-KE session to be started as soon as the cookies run out. */
inst->nke_attempts = 0; inst->nke_attempts = 0;
inst->next_nke_attempt = 0.0; inst->next_nke_attempt = 0.0;
inst->alt_context.algorithm = AEAD_SIV_INVALID;
return 1; return 1;
} }
@ -643,6 +661,7 @@ load_cookies(NNC_Instance inst)
sscanf(words[0], "%u", &context_id) != 1 || sscanf(words[1], "%d", &algorithm) != 1) sscanf(words[0], "%u", &context_id) != 1 || sscanf(words[1], "%d", &algorithm) != 1)
goto error; goto error;
inst->alt_context.algorithm = AEAD_SIV_INVALID;
inst->context.algorithm = algorithm; inst->context.algorithm = algorithm;
inst->context.s2c.length = UTI_HexToBytes(words[2], inst->context.s2c.key, inst->context.s2c.length = UTI_HexToBytes(words[2], inst->context.s2c.key,
sizeof (inst->context.s2c.key)); sizeof (inst->context.s2c.key));
@ -687,6 +706,7 @@ error:
fclose(f); fclose(f);
memset(&inst->context, 0, sizeof (inst->context)); memset(&inst->context, 0, sizeof (inst->context));
memset(&inst->alt_context, 0, sizeof (inst->alt_context));
inst->num_cookies = 0; inst->num_cookies = 0;
} }

1
siv.h
View file

@ -36,6 +36,7 @@
/* Identifiers of SIV algorithms following the IANA AEAD registry */ /* Identifiers of SIV algorithms following the IANA AEAD registry */
typedef enum { typedef enum {
AEAD_SIV_INVALID = 0,
AEAD_AES_SIV_CMAC_256 = 15, AEAD_AES_SIV_CMAC_256 = 15,
AEAD_AES_SIV_CMAC_384 = 16, AEAD_AES_SIV_CMAC_384 = 16,
AEAD_AES_SIV_CMAC_512 = 17, AEAD_AES_SIV_CMAC_512 = 17,

View file

@ -33,7 +33,7 @@
#define NKC_IsActive(inst) (random() % 2) #define NKC_IsActive(inst) (random() % 2)
#define NKC_GetRetryFactor(inst) (1) #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, NKE_Cookie *cookies, int *num_cookies, int max_cookies,
IPSockAddr *ntp_address); IPSockAddr *ntp_address);
#define NKC_GetNtsData get_nts_data #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> #include <nts_ntp_client.c>
static int 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, NKE_Cookie *cookies, int *num_cookies, int max_cookies,
IPSockAddr *ntp_address) IPSockAddr *ntp_address)
{ {
@ -60,6 +60,14 @@ get_nts_data(NKC_Instance inst, NKE_Context *context,
context->s2c.length = SIV_GetKeyLength(context->algorithm); context->s2c.length = SIV_GetKeyLength(context->algorithm);
UTI_GetRandomBytes(context->s2c.key, context->s2c.length); 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; *num_cookies = random() % max_cookies + 1;
for (i = 0; i < *num_cookies; i++) { for (i = 0; i < *num_cookies; i++) {
cookies[i].length = random() % (sizeof (cookies[i].cookie) + 1); cookies[i].length = random() % (sizeof (cookies[i].cookie) + 1);