From 42fbf4168612a5e320e5e7b03013d2f5425ea285 Mon Sep 17 00:00:00 2001 From: Miroslav Lichvar Date: Thu, 3 Oct 2024 14:25:53 +0200 Subject: [PATCH] nts: make server and client AEAD algorithms configurable Add ntsaeads directive to specify a list of AEAD algorithms enabled for NTS. The list is shared between the server and client. For the client it also specifies the order of priority. The default is "30 15", matching the previously hardcoded preference of AES-128-GCM-SIV (30) over AES-SIV-CMAC-256 (15). --- conf.c | 39 +++++++++++++++++++++++++ conf.h | 2 ++ doc/chrony.conf.adoc | 65 +++++++++++++++++++++++++++++++++++++++++ nts_ke_client.c | 36 ++++++++++++++--------- nts_ke_server.c | 11 ++++--- test/simulation/139-nts | 27 +++++++++++++++++ 6 files changed, 163 insertions(+), 17 deletions(-) diff --git a/conf.c b/conf.c index a06423e..778cd27 100644 --- a/conf.c +++ b/conf.c @@ -57,6 +57,7 @@ static int parse_string(char *line, char **result); static int parse_int(char *line, int *result); static int parse_double(char *line, double *result); static int parse_null(char *line); +static int parse_ints(char *line, ARR_Instance array); static void parse_allow_deny(char *line, ARR_Instance restrictions, int allow); static void parse_authselectmode(char *); @@ -261,7 +262,10 @@ static char *user; /* Address refresh interval */ static int refresh = 1209600; /* 2 weeks */ +#define DEFAULT_NTS_AEADS "30 15" + /* NTS server and client configuration */ +static ARR_Instance nts_aeads; /* array of int */ static char *nts_dump_dir = NULL; static char *nts_ntp_server = NULL; static ARR_Instance nts_server_cert_files; /* array of (char *) */ @@ -397,6 +401,8 @@ check_number_of_args(char *line, int num) void CNF_Initialise(int r, int client_only) { + char buf[10]; + restarted = r; hwts_interfaces = ARR_CreateInstance(sizeof (CNF_HwTsInterface)); @@ -410,6 +416,9 @@ CNF_Initialise(int r, int client_only) ntp_restrictions = ARR_CreateInstance(sizeof (AllowDeny)); cmd_restrictions = ARR_CreateInstance(sizeof (AllowDeny)); + nts_aeads = ARR_CreateInstance(sizeof (int)); + snprintf(buf, sizeof (buf), DEFAULT_NTS_AEADS); + parse_ints(buf, nts_aeads); nts_server_cert_files = ARR_CreateInstance(sizeof (char *)); nts_server_key_files = ARR_CreateInstance(sizeof (char *)); nts_trusted_certs_paths = ARR_CreateInstance(sizeof (char *)); @@ -469,6 +478,7 @@ CNF_Finalise(void) ARR_DestroyInstance(ntp_restrictions); ARR_DestroyInstance(cmd_restrictions); + ARR_DestroyInstance(nts_aeads); ARR_DestroyInstance(nts_server_cert_files); ARR_DestroyInstance(nts_server_key_files); ARR_DestroyInstance(nts_trusted_certs_paths); @@ -679,6 +689,8 @@ CNF_ParseLine(const char *filename, int number, char *line) no_system_cert = parse_null(p); } else if (!strcasecmp(command, "ntpsigndsocket")) { parse_string(p, &ntp_signd_socket); + } else if (!strcasecmp(command, "ntsaeads")) { + parse_ints(p, nts_aeads); } else if (!strcasecmp(command, "ntsratelimit")) { parse_ratelimit(p, &nts_ratelimit_enabled, &nts_ratelimit_interval, &nts_ratelimit_burst, &nts_ratelimit_leak, NULL); @@ -806,6 +818,25 @@ parse_null(char *line) /* ================================================== */ +static int +parse_ints(char *line, ARR_Instance array) +{ + char *s; + int v; + + ARR_SetSize(array, 0); + + while (*line) { + s = line; + line = CPS_SplitWord(line); + parse_int(s, &v); + ARR_AppendElement(array, &v); + } + return 1; +} + +/* ================================================== */ + static void parse_source(char *line, char *type, int fatal) { @@ -2599,6 +2630,14 @@ CNF_GetRefresh(void) /* ================================================== */ +ARR_Instance +CNF_GetNtsAeads(void) +{ + return nts_aeads; +} + +/* ================================================== */ + char * CNF_GetNtsDumpDir(void) { diff --git a/conf.h b/conf.h index cc0f17e..8136275 100644 --- a/conf.h +++ b/conf.h @@ -29,6 +29,7 @@ #define GOT_CONF_H #include "addressing.h" +#include "array.h" #include "reference.h" #include "sources.h" @@ -163,6 +164,7 @@ extern int CNF_GetPtpDomain(void); extern int CNF_GetRefresh(void); +extern ARR_Instance CNF_GetNtsAeads(void); extern char *CNF_GetNtsDumpDir(void); extern char *CNF_GetNtsNtpServer(void); extern int CNF_GetNtsServerCertAndKeyFiles(const char ***certs, const char ***keys); diff --git a/doc/chrony.conf.adoc b/doc/chrony.conf.adoc index 2c993db..4b7e28d 100644 --- a/doc/chrony.conf.adoc +++ b/doc/chrony.conf.adoc @@ -833,6 +833,34 @@ changes in the frequency and offset of the clock. The offsets in the <> reports (and the _tracking.log_ and _statistics.log_ files) may be smaller than the actual offsets. +[[ntsaeads1]]*ntsaeads* _ID_...:: +This directive specifies a list of IDs of Authenticated Encryption with +Associated Data (AEAD) algorithms enabled for NTS authentication of NTP +messages. The algorithms are specified in decreasing order of priority. +Algorithms that are not supported by the installed version of the crypto +library (Nettle, GnuTLS) are ignored. ++ +The following IDs are supported: ++ +* 15: AES-SIV-CMAC-256 +* 30: AES-128-GCM-SIV +{blank}:: ++ +The default list of IDs is _30 15_. AES-128-GCM-SIV is prefered over +AES-SIV-CMAC-256 for shorter keys, which makes NTS cookies shorter and improves +reliability of NTS in networks that block or limit rate of longer NTP messages. ++ +The ID of the used algorithm is reported for each server by the +<> command. ++ +An example of the directive is: ++ +---- +ntsaeads 15 +---- ++ +This list is used also by the <>. + [[ntsdumpdir1]]*ntsdumpdir* _directory_:: This directive specifies a directory for the client to save NTS cookies it received from the server in order to avoid making an NTS-KE request when @@ -1779,6 +1807,43 @@ per process that the NTS server will accept. The default value is 100. The maximum practical value is half of the system *FD_SETSIZE* constant (usually 1024). +[[ntsaeads2]]*ntsaeads* _ID_...:: +This directive specifies a list of IDs of Authenticated Encryption with +Associated Data (AEAD) algorithms enabled for NTS authentication of NTP +messages. *chronyd* as a server uses the first enabled algorithm from the list +provided by the client. Algorithms that are not supported by the installed +version of the crypto library (Nettle, GnuTLS) are ignored. ++ +The following IDs are supported: ++ +* 15: AES-SIV-CMAC-256 +* 30: AES-128-GCM-SIV +{blank}:: ++ +The default list of IDs is _30 15_. AES-128-GCM-SIV is prefered over +AES-SIV-CMAC-256 for shorter keys, which makes NTS cookies shorter and improves +reliability of NTS in networks that block or limit rate of longer NTP messages. ++ +An example of the directive is: ++ +---- +ntsaeads 15 +---- ++ +This list is used also by the <>. ++ +Note the the NTS specification (RFC 8915) requires servers to support +AES-SIV-CMAC-256, i.e. 15 should be always included in the specified list. ++ +The AES-128-GCM-SIV keys used by *chronyd* do not comply to RFC 8915 for +compatibility with older *chrony* clients unless the use of compliant keys is +negotiated with an +https://chrony-project.org/doc/spec/nts-compliant-128gcm.html[NTS-KE record]. +Support for this record was added in version 4.6.1. As a client, *chronyd* can +interoperate with a server that uses compliant keys, but does not support the +negotiation, if it responds to incorrectly authenticated requests with an NTS +NAK. + [[ntsdumpdir2]]*ntsdumpdir* _directory_:: This directive specifies a directory where *chronyd* operating as an NTS server can save the keys which encrypt NTS cookies provided to clients. The keys are diff --git a/nts_ke_client.c b/nts_ke_client.c index dfd5101..1fcbc3e 100644 --- a/nts_ke_client.c +++ b/nts_ke_client.c @@ -100,12 +100,14 @@ name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *arg /* ================================================== */ +#define MAX_AEAD_ALGORITHMS 4 + static int prepare_request(NKC_Instance inst) { NKSN_Instance session = inst->session; - uint16_t data[2]; - int i, length; + uint16_t data[MAX_AEAD_ALGORITHMS]; + int i, aead_algorithm, length; NKSN_BeginMessage(session); @@ -113,11 +115,12 @@ prepare_request(NKC_Instance inst) if (!NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, data, sizeof (data[0]))) return 0; - length = 0; - if (SIV_GetKeyLength(AEAD_AES_128_GCM_SIV) > 0) - data[length++] = htons(AEAD_AES_128_GCM_SIV); - if (SIV_GetKeyLength(AEAD_AES_SIV_CMAC_256) > 0) - data[length++] = htons(AEAD_AES_SIV_CMAC_256); + for (i = length = 0; i < ARR_GetSize(CNF_GetNtsAeads()) && length < MAX_AEAD_ALGORITHMS; + i++) { + aead_algorithm = *(int *)ARR_GetElement(CNF_GetNtsAeads(), i); + if (SIV_GetKeyLength(aead_algorithm) > 0) + data[length++] = htons(aead_algorithm); + } if (!NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, data, length * sizeof (data[0]))) return 0; @@ -177,15 +180,22 @@ process_response(NKC_Instance inst) next_protocol = NKE_NEXT_PROTOCOL_NTPV4; break; case NKE_RECORD_AEAD_ALGORITHM: - if (length != 2 || (ntohs(data[0]) != AEAD_AES_SIV_CMAC_256 && - ntohs(data[0]) != AEAD_AES_128_GCM_SIV) || - SIV_GetKeyLength(ntohs(data[0])) <= 0) { - DEBUG_LOG("Unexpected NTS-KE AEAD algorithm"); + if (length != 2) { + DEBUG_LOG("Unexpected AEAD algorithm"); error = 1; break; } - aead_algorithm = ntohs(data[0]); - inst->context.algorithm = aead_algorithm; + for (i = 0; i < ARR_GetSize(CNF_GetNtsAeads()); i++) { + if (ntohs(data[0]) == *(int *)ARR_GetElement(CNF_GetNtsAeads(), i) && + SIV_GetKeyLength(ntohs(data[0])) > 0) { + aead_algorithm = ntohs(data[0]); + inst->context.algorithm = aead_algorithm; + } + } + if (aead_algorithm < 0) { + DEBUG_LOG("Unexpected AEAD algorithm"); + error = 1; + } break; case NKE_RECORD_COMPLIANT_128GCM_EXPORT: if (length != 0) { diff --git a/nts_ke_server.c b/nts_ke_server.c index eeeece3..5f10bc1 100644 --- a/nts_ke_server.c +++ b/nts_ke_server.c @@ -426,8 +426,8 @@ process_request(NKSN_Instance session) int next_protocol_records = 0, aead_algorithm_records = 0; int next_protocol_values = 0, aead_algorithm_values = 0; int next_protocol = -1, aead_algorithm = -1, error = -1; + int i, j, critical, type, length; int compliant_128gcm = 0; - int i, critical, type, length; uint16_t data[NKE_MAX_RECORD_BODY_LENGTH / sizeof (uint16_t)]; assert(NKE_MAX_RECORD_BODY_LENGTH % sizeof (uint16_t) == 0); @@ -462,9 +462,12 @@ process_request(NKSN_Instance session) for (i = 0; i < MIN(length, sizeof (data)) / 2; i++) { aead_algorithm_values++; - /* Use the first supported algorithm */ - if (aead_algorithm < 0 && SIV_GetKeyLength(ntohs(data[i])) > 0) - aead_algorithm = ntohs(data[i]); + /* Use the first enabled and supported algorithm */ + for (j = 0; j < ARR_GetSize(CNF_GetNtsAeads()); j++) { + if (ntohs(data[i]) == *(int *)ARR_GetElement(CNF_GetNtsAeads(), j) && + aead_algorithm < 0 && SIV_GetKeyLength(ntohs(data[i])) > 0) + aead_algorithm = ntohs(data[i]); + } } break; case NKE_RECORD_COMPLIANT_128GCM_EXPORT: diff --git a/test/simulation/139-nts b/test/simulation/139-nts index f1d2de3..27689b8 100755 --- a/test/simulation/139-nts +++ b/test/simulation/139-nts @@ -313,4 +313,31 @@ check_sync && test_fail check_file_messages " 3 1 .* 123 " 0 0 log.packets || test_fail check_file_messages " 3 2 .* 123 " 0 0 log.packets || test_fail +for server_aead in "" "15" "30"; do + for client_aead in "" "15" "30"; do + server_conf=" + ntsaeads $server_aead + ntsserverkey tmp/server1.key + ntsservercert tmp/server1.crt + ntsprocesses 0" + client_conf=" + nosystemcert + ntsaeads $client_aead + ntstrustedcerts tmp/server1.crt + ntstrustedcerts tmp/server2.crt" + client_server_conf="" + + run_test || test_fail + check_chronyd_exit || test_fail + if [ -n "$server_aead" ] && [ "$server_aead" == "$client_aead" ] && + ( [ "$server_aead" != "30" ] || check_config_h '.*_SIV_GCM 1' ); then + check_source_selection || test_fail + check_sync || test_fail + else + check_source_selection && test_fail + check_sync && test_fail + fi + done +done + test_pass