From 2fa83b541c36732c1f32ba9c559fb00ff5a4fdf2 Mon Sep 17 00:00:00 2001 From: Miroslav Lichvar Date: Thu, 9 Apr 2020 10:33:55 +0200 Subject: [PATCH] nts: save and load cookies on client Save the NTS context and cookies to files in the NTS dumpdir when the client NTS instances are destroyed or the address is changed, and reload the data to avoid unnecessary NTS-KE requests when chronyd is restarted or it is switching between different addresses resolved from the NTS-KE or NTP name. --- nts_ntp_client.c | 194 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 175 insertions(+), 19 deletions(-) diff --git a/nts_ntp_client.c b/nts_ntp_client.c index 60b5caf..4b2cf31 100644 --- a/nts_ntp_client.c +++ b/nts_ntp_client.c @@ -45,6 +45,8 @@ #define MAX_TOTAL_COOKIE_LENGTH (8 * 108) +#define DUMP_IDENTIFIER "NNC0\n" + struct NNC_Instance_Record { const IPSockAddr *ntp_address; IPSockAddr nts_address; @@ -52,6 +54,7 @@ struct NNC_Instance_Record { NKC_Instance nke; SIV_Instance siv; + int load_attempt; int nke_attempts; double next_nke_attempt; double last_nke_success; @@ -68,9 +71,15 @@ struct NNC_Instance_Record { /* ================================================== */ +static void save_cookies(NNC_Instance inst); +static void load_cookies(NNC_Instance inst); + +/* ================================================== */ + static void reset_instance(NNC_Instance inst) { + inst->load_attempt = 0; inst->nke_attempts = 0; inst->next_nke_attempt = 0.0; inst->last_nke_success = 0.0; @@ -109,6 +118,8 @@ NNC_CreateInstance(IPSockAddr *nts_address, const char *name, const IPSockAddr * void NNC_DestroyInstance(NNC_Instance inst) { + save_cookies(inst); + if (inst->nke) NKC_DestroyInstance(inst->nke); if (inst->siv) @@ -121,7 +132,7 @@ NNC_DestroyInstance(NNC_Instance inst) /* ================================================== */ static int -is_nke_needed(NNC_Instance inst) +check_cookies(NNC_Instance inst) { /* Force NKE if a NAK was received since last valid auth */ if (inst->nak_response && !inst->ok_response && inst->num_cookies > 0) { @@ -134,7 +145,7 @@ is_nke_needed(NNC_Instance inst) SCH_GetLastEventMonoTime() - inst->last_nke_success > CNF_GetNtsRefresh()) inst->num_cookies = 0; - return inst->num_cookies == 0; + return inst->num_cookies > 0; } /* ================================================== */ @@ -184,13 +195,13 @@ update_next_nke_attempt(NNC_Instance inst, double now) /* ================================================== */ static int -get_nke_data(NNC_Instance inst) +get_cookies(NNC_Instance inst) { NTP_Remote_Address ntp_address; double now; int got_data; - assert(is_nke_needed(inst)); + assert(!check_cookies(inst)); now = SCH_GetLastEventMonoTime(); @@ -227,10 +238,14 @@ get_nke_data(NNC_Instance inst) NKC_DestroyInstance(inst->nke); inst->nke = NULL; - + if (!got_data) return 0; + if (inst->siv) + SIV_DestroyInstance(inst->siv); + inst->siv = NULL; + if (!set_ntp_address(inst, &ntp_address)) { inst->num_cookies = 0; return 0; @@ -238,17 +253,6 @@ get_nke_data(NNC_Instance inst) inst->cookie_index = 0; - if (inst->siv) - SIV_DestroyInstance(inst->siv); - - inst->siv = SIV_CreateInstance(inst->context.algorithm); - - if (!inst->siv) { - DEBUG_LOG("Could not initialise SIV"); - inst->num_cookies = 0; - return 0; - } - inst->nak_response = 0; inst->last_nke_success = now; @@ -261,12 +265,21 @@ get_nke_data(NNC_Instance inst) int NNC_PrepareForAuth(NNC_Instance inst) { - if (is_nke_needed(inst)) { - if (!get_nke_data(inst)) + if (!inst->load_attempt) { + load_cookies(inst); + inst->load_attempt = 1; + } + + if (!check_cookies(inst)) { + if (!get_cookies(inst)) return 0; } - if (!SIV_SetKey(inst->siv, inst->context.c2s.key, inst->context.c2s.length)) { + if (!inst->siv) + inst->siv = SIV_CreateInstance(inst->context.algorithm); + + if (!inst->siv || + !SIV_SetKey(inst->siv, inst->context.c2s.key, inst->context.c2s.length)) { DEBUG_LOG("Could not set SIV key"); return 0; } @@ -452,6 +465,8 @@ NNC_CheckResponseAuth(NNC_Instance inst, NTP_Packet *packet, void NNC_ChangeAddress(NNC_Instance inst, IPAddr *address) { + save_cookies(inst); + if (inst->nke) NKC_DestroyInstance(inst->nke); @@ -463,3 +478,144 @@ NNC_ChangeAddress(NNC_Instance inst, IPAddr *address) DEBUG_LOG("NTS reset"); } + +/* ================================================== */ + +static void +save_cookies(NNC_Instance inst) +{ + char buf[2 * NKE_MAX_COOKIE_LENGTH + 2], *dump_dir, *filename; + struct timespec now; + double context_time; + FILE *f; + int i; + + if (inst->num_cookies < 1 || !UTI_IsIPReal(&inst->nts_address.ip_addr)) + return; + + dump_dir = CNF_GetNtsDumpDir(); + if (!dump_dir) + return; + + filename = UTI_IPToString(&inst->nts_address.ip_addr); + + f = UTI_OpenFile(dump_dir, filename, ".tmp", 'w', 0600); + if (!f) + return; + + SCH_GetLastEventTime(&now, NULL, NULL); + context_time = inst->last_nke_success - SCH_GetLastEventMonoTime(); + context_time += UTI_TimespecToDouble(&now); + + if (fprintf(f, "%s%.1f\n%s %d\n%d ", + DUMP_IDENTIFIER, context_time, UTI_IPToString(&inst->ntp_address->ip_addr), + inst->ntp_address->port, (int)inst->context.algorithm) < 0 || + !UTI_BytesToHex(inst->context.s2c.key, inst->context.s2c.length, buf, sizeof (buf)) || + fprintf(f, "%s ", buf) < 0 || + !UTI_BytesToHex(inst->context.c2s.key, inst->context.c2s.length, buf, sizeof (buf)) || + fprintf(f, "%s\n", buf) < 0) + goto error; + + for (i = 0; i < inst->num_cookies; i++) { + if (!UTI_BytesToHex(inst->cookies[i].cookie, inst->cookies[i].length, buf, sizeof (buf)) || + fprintf(f, "%s\n", buf) < 0) + goto error; + } + + fclose(f); + + if (!UTI_RenameTempFile(dump_dir, filename, ".tmp", ".nts")) + ; + return; + +error: + DEBUG_LOG("Could not %s cookies for %s", "save", filename); + fclose(f); + + if (!UTI_RemoveFile(dump_dir, filename, ".nts")) + ; +} + +/* ================================================== */ + +#define MAX_WORDS 3 + +static void +load_cookies(NNC_Instance inst) +{ + char line[2 * NKE_MAX_COOKIE_LENGTH + 2], *dump_dir, *filename, *words[MAX_WORDS]; + int i, algorithm, port; + double context_time; + struct timespec now; + IPSockAddr ntp_addr; + FILE *f; + + dump_dir = CNF_GetNtsDumpDir(); + if (!dump_dir) + return; + + filename = UTI_IPToString(&inst->nts_address.ip_addr); + + f = UTI_OpenFile(dump_dir, filename, ".nts", 'r', 0); + if (!f) + return; + + /* Don't load this file again */ + if (!UTI_RemoveFile(dump_dir, filename, ".nts")) + ; + + if (inst->siv) + SIV_DestroyInstance(inst->siv); + inst->siv = NULL; + + if (!fgets(line, sizeof (line), f) || strcmp(line, DUMP_IDENTIFIER) != 0 || + !fgets(line, sizeof (line), f) || UTI_SplitString(line, words, MAX_WORDS) != 1 || + sscanf(words[0], "%lf", &context_time) != 1 || + !fgets(line, sizeof (line), f) || UTI_SplitString(line, words, MAX_WORDS) != 2 || + !UTI_StringToIP(words[0], &ntp_addr.ip_addr) || sscanf(words[1], "%d", &port) != 1 || + !fgets(line, sizeof (line), f) || UTI_SplitString(line, words, MAX_WORDS) != 3 || + sscanf(words[0], "%d", &algorithm) != 1) + goto error; + + inst->context.algorithm = algorithm; + inst->context.s2c.length = UTI_HexToBytes(words[1], inst->context.s2c.key, + sizeof (inst->context.s2c.key)); + inst->context.c2s.length = UTI_HexToBytes(words[2], inst->context.c2s.key, + sizeof (inst->context.c2s.key)); + + if (inst->context.s2c.length != SIV_GetKeyLength(algorithm) || + inst->context.c2s.length != inst->context.s2c.length) + goto error; + + for (i = 0; i < NTS_MAX_COOKIES && fgets(line, sizeof (line), f); i++) { + if (UTI_SplitString(line, words, MAX_WORDS) != 1) + goto error; + + inst->cookies[i].length = UTI_HexToBytes(words[0], inst->cookies[i].cookie, + sizeof (inst->cookies[i].cookie)); + if (inst->cookies[i].length == 0) + goto error; + } + + inst->num_cookies = i; + + ntp_addr.port = port; + if (!set_ntp_address(inst, &ntp_addr)) + goto error; + + SCH_GetLastEventTime(&now, NULL, NULL); + context_time -= UTI_TimespecToDouble(&now); + if (context_time > 0) + context_time = 0; + inst->last_nke_success = context_time + SCH_GetLastEventMonoTime(); + + DEBUG_LOG("Loaded %d cookies for %s", i, filename); + return; + +error: + DEBUG_LOG("Could not %s cookies for %s", "load", filename); + fclose(f); + + memset(&inst->context, 0, sizeof (inst->context)); + inst->num_cookies = 0; +}