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).
This commit is contained in:
Miroslav Lichvar 2024-10-03 14:25:53 +02:00
parent 79a790e6b5
commit 42fbf41686
6 changed files with 163 additions and 17 deletions

39
conf.c
View file

@ -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)
{

2
conf.h
View file

@ -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);

View file

@ -833,6 +833,34 @@ changes in the frequency and offset of the clock. The offsets in the
<<chronyc.adoc#sourcestats,*sourcestats*>> 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
<<chronyc.adoc#authdata,*authdata*>> command.
+
An example of the directive is:
+
----
ntsaeads 15
----
+
This list is used also by the <<ntsaeads2,NTS server>>.
[[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 <<ntsaeads1,NTS client>>.
+
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

View file

@ -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) {

View file

@ -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:

View file

@ -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