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