/* Name of the user to which will be dropped root privileges. */
static char *user;
+/* NTS cache dir, certificates, private key, and port */
+static char *nts_cachedir = NULL;
+static char *nts_server_cert_file = NULL;
+static char *nts_server_key_file = NULL;
+static int nts_server_port = 11443;
+static int nts_server_processes = 1;
+static int nts_server_connections = 100;
+static int nts_rotate = 604800; /* 1 week */
+static char *nts_trusted_cert_file = NULL;
+
+/* Flag disabling use of system trusted certificates */
+static int no_system_cert = 0;
+
/* Array of CNF_HwTsInterface */
static ARR_Instance hwts_interfaces;
Free(mail_user_on_change);
Free(tempcomp_sensor_file);
Free(tempcomp_point_file);
+ Free(nts_cachedir);
+ Free(nts_server_cert_file);
+ Free(nts_server_key_file);
+ Free(nts_trusted_cert_file);
}
/* ================================================== */
parse_double(p, &max_drift);
} else if (!strcasecmp(command, "maxjitter")) {
parse_double(p, &max_jitter);
+ } else if (!strcasecmp(command, "maxntsconnections")) {
+ parse_int(p, &nts_server_connections);
} else if (!strcasecmp(command, "maxsamples")) {
parse_int(p, &max_samples);
} else if (!strcasecmp(command, "maxslewrate")) {
parse_int(p, &min_sources);
} else if (!strcasecmp(command, "noclientlog")) {
no_client_log = parse_null(p);
+ } else if (!strcasecmp(command, "nosystemcert")) {
+ no_system_cert = parse_null(p);
} else if (!strcasecmp(command, "ntpsigndsocket")) {
parse_string(p, &ntp_signd_socket);
+ } else if (!strcasecmp(command, "ntstrustedcerts")) {
+ parse_string(p, &nts_trusted_cert_file);
+ } else if (!strcasecmp(command, "ntscachedir")) {
+ parse_string(p, &nts_cachedir);
+ } else if (!strcasecmp(command, "ntsport")) {
+ parse_int(p, &nts_server_port);
+ } else if (!strcasecmp(command, "ntsprocesses")) {
+ parse_int(p, &nts_server_processes);
+ } else if (!strcasecmp(command, "ntsrotate")) {
+ parse_int(p, &nts_rotate);
+ } else if (!strcasecmp(command, "ntsservercert")) {
+ parse_string(p, &nts_server_cert_file);
+ } else if (!strcasecmp(command, "ntsserverkey")) {
+ parse_string(p, &nts_server_key_file);
} else if (!strcasecmp(command, "peer")) {
parse_source(p, NTP_PEER, 0);
} else if (!strcasecmp(command, "pidfile")) {
*iface = (CNF_HwTsInterface *)ARR_GetElement(hwts_interfaces, index);
return 1;
}
+
+/* ================================================== */
+
+char *
+CNF_GetNtsCacheDir(void)
+{
+ return nts_cachedir;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetNtsServerCertFile(void)
+{
+ return nts_server_cert_file;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetNtsServerKeyFile(void)
+{
+ return nts_server_key_file;
+}
+
+/* ================================================== */
+
+int
+CNF_GetNtsServerPort(void)
+{
+ return nts_server_port;
+}
+
+/* ================================================== */
+
+int
+CNF_GetNtsServerProcesses(void)
+{
+ return nts_server_processes;
+}
+
+/* ================================================== */
+
+int
+CNF_GetNtsServerConnections(void)
+{
+ return nts_server_connections;
+}
+
+/* ================================================== */
+
+int
+CNF_GetNtsRotate(void)
+{
+ return nts_rotate;
+}
+
+/* ================================================== */
+
+char *
+CNF_GetNtsTrustedCertFile(void)
+{
+ return nts_trusted_cert_file;
+}
+
+/* ================================================== */
+
+int
+CNF_GetNoSystemCert(void)
+{
+ return no_system_cert;
+}
extern int CNF_GetHwTsInterface(unsigned int index, CNF_HwTsInterface **iface);
+extern char *CNF_GetNtsCacheDir(void);
+extern char *CNF_GetNtsServerCertFile(void);
+extern char *CNF_GetNtsServerKeyFile(void);
+extern int CNF_GetNtsServerPort(void);
+extern int CNF_GetNtsServerProcesses(void);
+extern int CNF_GetNtsServerConnections(void);
+extern int CNF_GetNtsRotate(void);
+extern char *CNF_GetNtsTrustedCertFile(void);
+extern int CNF_GetNoSystemCert(void);
+
#endif /* GOT_CONF_H */
--without-nettle Don't use nettle even if it is available
--without-nss Don't use NSS even if it is available
--without-tomcrypt Don't use libtomcrypt even if it is available
+ --disable-nts Disable NTS support
+ --without-gnutls Don't use gnutls even if it is available
--disable-cmdmon Disable command and monitoring support
--disable-ntp Disable NTP support
--disable-refclock Disable reference clock support
try_nettle=1
try_nss=1
try_tomcrypt=1
+feat_nts=1
+try_gnutls=1
feat_rtc=1
try_rtc=0
feat_droproot=1
--without-tomcrypt )
try_tomcrypt=0
;;
+ --disable-nts )
+ feat_nts=0
+ ;;
+ --without-gnutls )
+ try_gnutls=0
+ ;;
--host-system=* )
OPERATINGSYSTEM=`echo $option | sed -e 's/^.*=//;'`
;;
EXTRA_CLI_OBJECTS="$EXTRA_CLI_OBJECTS $HASH_OBJ"
LIBS="$LIBS $HASH_LINK"
-if true && \
+if [ $feat_ntp = "1" ] && [ $feat_nts = "1" ] && [ $try_gnutls = "1" ] && \
echo "$HASH_LINK" | grep 'nettle' > /dev/null; then
-
+ test_cflags="`pkg_config --cflags gnutls`"
+ test_link="`pkg_config --libs gnutls`"
+ if test_code 'gnutls' 'gnutls/gnutls.h' \
+ "$test_cflags" "$test_link" \
+ 'gnutls_init(NULL, 0);'
+ then
+ EXTRA_OBJECTS="$EXTRA_OBJECTS nts_ke_client.o nts_ke_server.o nts_ke_session.o"
EXTRA_OBJECTS="$EXTRA_OBJECTS siv_nettle.o"
- add_def HAVE_SIV
+ LIBS="$LIBS $test_link"
+ MYCPPFLAGS="$MYCPPFLAGS $test_cflags"
+ add_def FEAT_NTS
+ add_def HAVE_SIV
if test_code 'SIV in nettle' \
'nettle/siv-cmac.h' "" "$LIBS" \
'siv_cmac_aes128_set_key(NULL, NULL);'
then
add_def HAVE_NETTLE_SIV_CMAC
fi
+ fi
fi
if [ $use_pthread = "1" ]; then
common_features="`get_features SECHASH IPV6 DEBUG`"
chronyc_features="`get_features READLINE`"
-chronyd_features="`get_features CMDMON NTP REFCLOCK RTC PRIVDROP SCFILTER SIGND ASYNCDNS`"
+chronyd_features="`get_features CMDMON NTP REFCLOCK RTC PRIVDROP SCFILTER SIGND ASYNCDNS NTS`"
add_def CHRONYC_FEATURES "\"$chronyc_features $common_features\""
add_def CHRONYD_FEATURES "\"$chronyd_features $common_features\""
echo "Features : $chronyd_features $chronyc_features $common_features"
#include "ntp_signd.h"
#include "ntp_sources.h"
#include "ntp_core.h"
+#include "nts_ke_client.h"
+#include "nts_ke_server.h"
#include "socket.h"
#include "sources.h"
#include "sourcestats.h"
TMC_Finalise();
MNL_Finalise();
CLG_Finalise();
+ NKC_Finalise();
+ NKS_Finalise();
NSD_Finalise();
NSR_Finalise();
SST_Finalise();
SST_Initialise();
NSR_Initialise();
NSD_Initialise();
+ NKS_Initialise(scfilter_level);
+ NKC_Initialise();
CLG_Initialise();
MNL_Initialise();
TMC_Initialise();
--- /dev/null
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Header file for the NTS Key Establishment protocol
+ */
+
+#ifndef GOT_NTS_KE_H
+#define GOT_NTS_KE_H
+
+#include "siv.h"
+
+#define NKE_RECORD_CRITICAL_BIT (1U << 15)
+#define NKE_RECORD_END_OF_MESSAGE 0
+#define NKE_RECORD_NEXT_PROTOCOL 1
+#define NKE_RECORD_ERROR 2
+#define NKE_RECORD_WARNING 3
+#define NKE_RECORD_AEAD_ALGORITHM 4
+#define NKE_RECORD_COOKIE 5
+#define NKE_RECORD_NTPV4_SERVER_NEGOTIATION 6
+#define NKE_RECORD_NTPV4_PORT_NEGOTIATION 7
+
+#define NKE_NEXT_PROTOCOL_NTPV4 0
+
+#define NKE_ERROR_UNRECOGNIZED_CRITICAL_RECORD 0
+#define NKE_ERROR_BAD_REQUEST 1
+#define NKE_ERROR_INTERNAL_SERVER_ERROR 2
+
+#define NKE_ALPN_NAME "ntske/1"
+#define NKE_EXPORTER_LABEL "EXPORTER-network-time-security/1"
+#define NKE_EXPORTER_CONTEXT_C2S "\x0\x0\x0\xf\x0"
+#define NKE_EXPORTER_CONTEXT_S2C "\x0\x0\x0\xf\x1"
+
+#define NKE_MAX_MESSAGE_LENGTH 16384
+#define NKE_MAX_RECORD_BODY_LENGTH 256
+#define NKE_MAX_COOKIE_LENGTH 256
+#define NKE_MAX_COOKIES 8
+#define NKE_MAX_KEY_LENGTH SIV_MAX_KEY_LENGTH
+
+typedef struct {
+ int length;
+ unsigned char key[NKE_MAX_KEY_LENGTH];
+} NKE_Key;
+
+typedef struct {
+ int length;
+ unsigned char cookie[NKE_MAX_COOKIE_LENGTH];
+} NKE_Cookie;
+
+#endif
--- /dev/null
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ NTS-KE client
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "nts_ke_client.h"
+
+#include "conf.h"
+#include "logging.h"
+#include "memory.h"
+#include "nameserv_async.h"
+#include "nts_ke_session.h"
+#include "siv.h"
+#include "socket.h"
+#include "util.h"
+
+#define CLIENT_TIMEOUT 16.0
+
+struct NKC_Instance_Record {
+ char *name;
+ IPSockAddr address;
+ NKSN_Instance session;
+ int destroying;
+ int got_response;
+ int resolving_name;
+
+ SIV_Algorithm siv_algorithm;
+ NKE_Key c2s, s2c;
+ NKE_Cookie cookies[NKE_MAX_COOKIES];
+ int num_cookies;
+ char server_name[NKE_MAX_RECORD_BODY_LENGTH + 1];
+ IPSockAddr ntp_address;
+};
+
+/* ================================================== */
+
+static void *client_credentials;
+
+/* ================================================== */
+
+static void
+name_resolve_handler(DNS_Status status, int n_addrs, IPAddr *ip_addrs, void *arg)
+{
+ NKC_Instance inst = arg;
+ int i;
+
+ inst->resolving_name = 0;
+
+ if (inst->destroying) {
+ NKC_DestroyInstance(inst);
+ return;
+ }
+
+ if (status != DNS_Success || n_addrs < 1) {
+ LOG(LOGS_ERR, "Could not resolve NTP server %s from %s", inst->server_name, inst->name);
+ /* Force restart */
+ inst->got_response = 0;
+ return;
+ }
+
+ inst->ntp_address.ip_addr = ip_addrs[0];
+
+ /* Prefer an address of the same family as NTS-KE */
+ for (i = 0; i < n_addrs; i++) {
+ DEBUG_LOG("%s resolved to %s", inst->server_name, UTI_IPToString(&ip_addrs[i]));
+ if (ip_addrs[i].family == inst->address.ip_addr.family) {
+ inst->ntp_address.ip_addr = ip_addrs[i];
+ break;
+ }
+ }
+}
+
+/* ================================================== */
+
+static int
+prepare_request(NKC_Instance inst)
+{
+ NKSN_Instance session = inst->session;
+ uint16_t datum;
+
+ NKSN_BeginMessage(session);
+
+ datum = htons(NKE_NEXT_PROTOCOL_NTPV4);
+ if (!NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, &datum, sizeof (datum)))
+ return 0;
+
+ datum = htons(AEAD_AES_SIV_CMAC_256);
+ if (!NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, &datum, sizeof (datum)))
+ return 0;
+
+ if (!NKSN_EndMessage(session))
+ return 0;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_response(NKC_Instance inst)
+{
+ int next_protocol = -1, aead_algorithm = -1, error = 0;
+ int i, critical, type, length;
+ uint16_t data[NKE_MAX_COOKIE_LENGTH / sizeof (uint16_t)];
+
+ assert(NKE_MAX_COOKIE_LENGTH % sizeof (uint16_t) == 0);
+ assert(sizeof (uint16_t) == 2);
+
+ inst->num_cookies = 0;
+ inst->ntp_address.ip_addr.family = IPADDR_UNSPEC;
+ inst->ntp_address.port = 0;
+ inst->server_name[0] = '\0';
+
+ while (!error) {
+ if (!NKSN_GetRecord(inst->session, &critical, &type, &length, &data, sizeof (data)))
+ break;
+
+ switch (type) {
+ case NKE_RECORD_NEXT_PROTOCOL:
+ if (!critical || length != 2 || ntohs(data[0]) != NKE_NEXT_PROTOCOL_NTPV4) {
+ DEBUG_LOG("Unexpected NTS-KE next protocol");
+ error = 1;
+ break;
+ }
+ next_protocol = NKE_NEXT_PROTOCOL_NTPV4;
+ break;
+ case NKE_RECORD_AEAD_ALGORITHM:
+ if (length != 2 || ntohs(data[0]) != AEAD_AES_SIV_CMAC_256) {
+ DEBUG_LOG("Unexpected NTS-KE AEAD algorithm");
+ error = 1;
+ break;
+ }
+ aead_algorithm = AEAD_AES_SIV_CMAC_256;
+ inst->siv_algorithm = aead_algorithm;
+ break;
+ case NKE_RECORD_ERROR:
+ if (length == 2)
+ DEBUG_LOG("NTS-KE error %d", ntohs(data[0]));
+ error = 1;
+ break;
+ case NKE_RECORD_WARNING:
+ if (length == 2)
+ DEBUG_LOG("NTS-KE warning %d", ntohs(data[0]));
+ error = 1;
+ break;
+ case NKE_RECORD_COOKIE:
+ DEBUG_LOG("Got cookie #%d length=%d", inst->num_cookies + 1, length);
+ assert(NKE_MAX_COOKIE_LENGTH == sizeof (inst->cookies[inst->num_cookies].cookie));
+ if (length <= NKE_MAX_COOKIE_LENGTH && inst->num_cookies < NKE_MAX_COOKIES) {
+ inst->cookies[inst->num_cookies].length = length;
+ memcpy(inst->cookies[inst->num_cookies].cookie, data, length);
+ inst->num_cookies++;
+ }
+ break;
+ case NKE_RECORD_NTPV4_SERVER_NEGOTIATION:
+ if (length < 1 || length >= sizeof (inst->server_name)) {
+ DEBUG_LOG("Invalid server name");
+ error = 1;
+ break;
+ }
+
+ memcpy(inst->server_name, data, length);
+ inst->server_name[length] = '\0';
+
+ /* Make sure the name is printable and has no spaces */
+ for (i = 0; i < length && isgraph(inst->server_name[i]); i++)
+ ;
+ if (i != length) {
+ DEBUG_LOG("Invalid server name");
+ error = 1;
+ break;
+ }
+
+ DEBUG_LOG("Negotiated server %s", inst->server_name);
+ break;
+ case NKE_RECORD_NTPV4_PORT_NEGOTIATION:
+ if (length != 2) {
+ DEBUG_LOG("Invalid port");
+ error = 1;
+ break;
+ }
+ inst->ntp_address.port = ntohs(data[0]);
+ DEBUG_LOG("Negotiated port %d", inst->ntp_address.port);
+ break;
+ default:
+ DEBUG_LOG("Unknown record type=%d length=%d critical=%d", type, length, critical);
+ if (critical)
+ error = 1;
+ }
+ }
+
+ DEBUG_LOG("NTS-KE response: error=%d next=%d aead=%d",
+ error, next_protocol, aead_algorithm);
+
+ if (error || inst->num_cookies == 0 ||
+ next_protocol != NKE_NEXT_PROTOCOL_NTPV4 ||
+ aead_algorithm != AEAD_AES_SIV_CMAC_256)
+ return 0;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+handle_message(void *arg)
+{
+ NKC_Instance inst = arg;
+
+ if (!process_response(inst)) {
+ LOG(LOGS_ERR, "Received invalid NTS-KE response from %s", inst->name);
+ return 0;
+ }
+
+ if (!NKSN_GetKeys(inst->session, inst->siv_algorithm, &inst->c2s, &inst->s2c))
+ return 0;
+
+ if (inst->server_name[0] != '\0') {
+ if (inst->resolving_name)
+ return 0;
+ if (!UTI_StringToIP(inst->server_name, &inst->ntp_address.ip_addr)) {
+ DNS_Name2IPAddressAsync(inst->server_name, name_resolve_handler, inst);
+ inst->resolving_name = 1;
+ }
+ }
+
+ inst->got_response = 1;
+
+ return 1;
+}
+
+/* ================================================== */
+
+void
+NKC_Initialise(void)
+{
+ client_credentials = NULL;
+}
+
+/* ================================================== */
+
+void
+NKC_Finalise(void)
+{
+ if (client_credentials)
+ NKSN_DestroyCertCredentials(client_credentials);
+}
+
+/* ================================================== */
+
+NKC_Instance
+NKC_CreateInstance(IPSockAddr *address, const char *name)
+{
+ NKC_Instance inst;
+
+ inst = MallocNew(struct NKC_Instance_Record);
+
+ inst->address = *address;
+ inst->name = Strdup(name);
+ inst->session = NKSN_CreateInstance(0, inst->name, handle_message, inst);
+ inst->resolving_name = 0;
+ inst->destroying = 0;
+ inst->got_response = 0;
+
+ /* Create the credentials with the first client instance and share them
+ with other instances */
+ if (!client_credentials)
+ client_credentials = NKSN_CreateCertCredentials(NULL, NULL, CNF_GetNtsTrustedCertFile());
+
+ return inst;
+}
+
+/* ================================================== */
+
+void
+NKC_DestroyInstance(NKC_Instance inst)
+{
+ /* If the resolver is running, destroy the instance later when finished */
+ if (inst->resolving_name) {
+ inst->destroying = 1;
+ return;
+ }
+
+ NKSN_DestroyInstance(inst->session);
+
+ Free(inst->name);
+ Free(inst);
+}
+
+/* ================================================== */
+
+int
+NKC_Start(NKC_Instance inst)
+{
+ IPSockAddr local_addr;
+ int sock_fd;
+
+ assert(!NKC_IsActive(inst));
+
+ if (!client_credentials) {
+ DEBUG_LOG("Missing client credentials");
+ return 0;
+ }
+
+ /* Follow the bindacqaddress setting */
+ CNF_GetBindAcquisitionAddress(inst->address.ip_addr.family, &local_addr.ip_addr);
+ if (local_addr.ip_addr.family != inst->address.ip_addr.family)
+ SCK_GetAnyLocalIPAddress(inst->address.ip_addr.family, &local_addr.ip_addr);
+
+ local_addr.port = 0;
+
+ sock_fd = SCK_OpenTcpSocket(&inst->address, &local_addr, 0);
+ if (sock_fd < 0)
+ return 0;
+
+ /* Start a NTS-KE session */
+ if (!NKSN_StartSession(inst->session, sock_fd, client_credentials, CLIENT_TIMEOUT)) {
+ SCK_CloseSocket(sock_fd);
+ return 0;
+ }
+
+ /* Send a request */
+ if (!prepare_request(inst)) {
+ DEBUG_LOG("Could not prepare NTS-KE request");
+ NKSN_StopSession(inst->session);
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+NKC_IsActive(NKC_Instance inst)
+{
+ return !NKSN_IsStopped(inst->session) || inst->resolving_name;
+}
+
+/* ================================================== */
+
+int
+NKC_GetNtsData(NKC_Instance inst,
+ SIV_Algorithm *siv_algorithm, NKE_Key *c2s, NKE_Key *s2c,
+ NKE_Cookie *cookies, int *num_cookies, int max_cookies,
+ IPSockAddr *ntp_address)
+{
+ int i;
+
+ if (!inst->got_response || inst->resolving_name)
+ return 0;
+
+ *siv_algorithm = inst->siv_algorithm;
+ *c2s = inst->c2s;
+ *s2c = inst->s2c;
+
+ for (i = 0; i < inst->num_cookies && i < max_cookies; i++)
+ cookies[i] = inst->cookies[i];
+ *num_cookies = i;
+
+ *ntp_address = inst->ntp_address;
+
+ return i;
+}
--- /dev/null
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Header file for the NTS-KE client
+ */
+
+#ifndef GOT_NTS_KE_CLIENT_H
+#define GOT_NTS_KE_CLIENT_H
+
+#include "addressing.h"
+#include "nts_ke.h"
+
+typedef struct NKC_Instance_Record *NKC_Instance;
+
+/* Init and fini functions */
+extern void NKC_Initialise(void);
+extern void NKC_Finalise(void);
+
+/* Create a client NTS-KE instance */
+extern NKC_Instance NKC_CreateInstance(IPSockAddr *address, const char *name);
+
+/* Destroy an instance */
+extern void NKC_DestroyInstance(NKC_Instance inst);
+
+/* Connect to the server, start an NTS-KE session, send an NTS-KE request, and
+ process the response (asynchronously) */
+extern int NKC_Start(NKC_Instance inst);
+
+/* Check if the client is still running */
+extern int NKC_IsActive(NKC_Instance inst);
+
+/* Get the NTS data if the session was successful */
+extern int NKC_GetNtsData(NKC_Instance inst,
+ SIV_Algorithm *siv_algorithm, NKE_Key *c2s, NKE_Key *s2c,
+ NKE_Cookie *cookies, int *num_cookies, int max_cookies,
+ IPSockAddr *ntp_address);
+
+#endif
--- /dev/null
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ NTS-KE server
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "nts_ke_server.h"
+
+#include "array.h"
+#include "conf.h"
+#include "clientlog.h"
+#include "logging.h"
+#include "memory.h"
+#include "ntp_core.h"
+#include "nts_ke_session.h"
+#include "siv.h"
+#include "socket.h"
+#include "sched.h"
+#include "sys.h"
+#include "util.h"
+
+#define SERVER_TIMEOUT 2.0
+
+#define SERVER_COOKIE_SIV AEAD_AES_SIV_CMAC_256
+#define SERVER_COOKIE_NONCE_LENGTH 16
+
+#define KEY_ID_INDEX_BITS 2
+#define MAX_SERVER_KEYS (1U << KEY_ID_INDEX_BITS)
+
+#define MIN_KEY_ROTATE_INTERVAL 1.0
+
+#define INVALID_SOCK_FD (-7)
+
+typedef struct {
+ uint32_t key_id;
+ uint8_t nonce[SERVER_COOKIE_NONCE_LENGTH];
+} ServerCookieHeader;
+
+typedef struct {
+ uint32_t id;
+ unsigned char key[SIV_MAX_KEY_LENGTH];
+ SIV_Instance siv;
+} ServerKey;
+
+typedef struct {
+ uint32_t key_id;
+ unsigned char key[SIV_MAX_KEY_LENGTH];
+ IPAddr client_addr;
+ uint16_t client_port;
+ uint16_t _pad;
+} HelperRequest;
+
+/* ================================================== */
+
+static ServerKey server_keys[MAX_SERVER_KEYS];
+static int current_server_key;
+
+static int server_sock_fd4;
+static int server_sock_fd6;
+
+static int helper_sock_fd;
+
+static int initialised = 0;
+
+/* Array of NKSN instances */
+static ARR_Instance sessions;
+static void *server_credentials;
+
+/* ================================================== */
+
+static int handle_message(void *arg);
+
+/* ================================================== */
+
+static int
+handle_client(int sock_fd, IPSockAddr *addr)
+{
+ NKSN_Instance inst, *instp;
+ int i;
+
+ if (sock_fd > FD_SETSIZE / 2) {
+ DEBUG_LOG("Rejected connection from %s (%s)",
+ UTI_IPSockAddrToString(addr), "too many descriptors");
+ return 0;
+ }
+
+ /* Find a slot which is free or has a stopped session */
+ for (i = 0, inst = NULL; i < ARR_GetSize(sessions); i++) {
+ instp = ARR_GetElement(sessions, i);
+ if (!*instp) {
+ /* NULL handler arg will be replaced with the session instance */
+ inst = NKSN_CreateInstance(1, UTI_IPSockAddrToString(addr), handle_message, NULL);
+ *instp = inst;
+ break;
+ } else if (NKSN_IsStopped(*instp)) {
+ inst = *instp;
+ break;
+ }
+ }
+
+ if (!inst) {
+ DEBUG_LOG("Rejected connection from %s (%s)",
+ UTI_IPSockAddrToString(addr), "too many connections");
+ return 0;
+ }
+
+ if (!NKSN_StartSession(inst, sock_fd, server_credentials, SERVER_TIMEOUT))
+ return 0;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+handle_helper_request(int fd, int event, void *arg)
+{
+ SCK_Message message;
+ HelperRequest *req;
+ IPSockAddr client_addr;
+ int sock_fd;
+
+ if (!SCK_ReceiveMessage(fd, &message, SCK_FLAG_MSG_DESCRIPTOR))
+ return;
+
+ sock_fd = message.descriptor;
+ if (sock_fd < 0) {
+ /* Message with no descriptor is a shutdown command */
+ SCH_QuitProgram();
+ return;
+ }
+
+ if (message.length != sizeof (HelperRequest)) {
+ DEBUG_LOG("Unexpected message length");
+ SCK_CloseSocket(sock_fd);
+ return;
+ }
+
+ req = message.data;
+
+ /* Extract the server key and client address from the request */
+ server_keys[current_server_key].id = ntohl(req->key_id);
+ memcpy(server_keys[current_server_key].key, req->key,
+ sizeof (server_keys[current_server_key].key));
+ UTI_IPNetworkToHost(&req->client_addr, &client_addr.ip_addr);
+ client_addr.port = ntohs(req->client_port);
+
+ if (!SIV_SetKey(server_keys[current_server_key].siv, server_keys[current_server_key].key,
+ SIV_GetKeyLength(SERVER_COOKIE_SIV)))
+ assert(0);
+
+ if (!handle_client(sock_fd, &client_addr)) {
+ SCK_CloseSocket(sock_fd);
+ return;
+ }
+
+ DEBUG_LOG("Accepted helper request fd=%d", sock_fd);
+}
+
+/* ================================================== */
+
+static void
+accept_connection(int server_fd, int event, void *arg)
+{
+ SCK_Message message;
+ IPSockAddr addr;
+ int log_index, sock_fd;
+ struct timespec now;
+
+ sock_fd = SCK_AcceptConnection(server_fd, &addr);
+ if (sock_fd < 0)
+ return;
+
+ if (!NCR_CheckAccessRestriction(&addr.ip_addr)) {
+ DEBUG_LOG("Rejected connection from %s (%s)",
+ UTI_IPSockAddrToString(&addr), "access denied");
+ SCK_CloseSocket(sock_fd);
+ return;
+ }
+
+ SCH_GetLastEventTime(&now, NULL, NULL);
+ log_index = CLG_LogNTPAccess(&addr.ip_addr, &now);
+ if (log_index >= 0 && CLG_LimitNTPResponseRate(log_index)) {
+ DEBUG_LOG("Rejected connection from %s (%s)",
+ UTI_IPSockAddrToString(&addr), "rate limit");
+ SCK_CloseSocket(sock_fd);
+ return;
+ }
+
+ /* Pass the socket to a helper process if enabled. Otherwise, handle the
+ client in the main process. */
+ if (helper_sock_fd != INVALID_SOCK_FD) {
+ HelperRequest req;
+
+ /* Include the current server key and client address in the request */
+ memset(&req, 0, sizeof (req));
+ req.key_id = htonl(server_keys[current_server_key].id);
+ memcpy(req.key, server_keys[current_server_key].key, sizeof (req.key));
+ UTI_IPHostToNetwork(&addr.ip_addr, &req.client_addr);
+ req.client_port = htons(addr.port);
+
+ SCK_InitMessage(&message, SCK_ADDR_UNSPEC);
+ message.data = &req;
+ message.length = sizeof (req);
+ message.descriptor = sock_fd;
+
+ if (!SCK_SendMessage(helper_sock_fd, &message, SCK_FLAG_MSG_DESCRIPTOR)) {
+ SCK_CloseSocket(sock_fd);
+ return;
+ }
+
+ SCK_CloseSocket(sock_fd);
+ } else {
+ if (!handle_client(sock_fd, &addr)) {
+ SCK_CloseSocket(sock_fd);
+ return;
+ }
+ }
+
+ DEBUG_LOG("Accepted connection from %s fd=%d", UTI_IPSockAddrToString(&addr), sock_fd);
+}
+
+/* ================================================== */
+
+static int
+open_socket(int family, int port)
+{
+ IPSockAddr local_addr;
+ int sock_fd;
+
+ if (!SCK_IsFamilySupported(family))
+ return INVALID_SOCK_FD;
+
+ CNF_GetBindAddress(family, &local_addr.ip_addr);
+
+ if (local_addr.ip_addr.family != family)
+ SCK_GetAnyLocalIPAddress(family, &local_addr.ip_addr);
+
+ local_addr.port = port;
+
+ sock_fd = SCK_OpenTcpSocket(NULL, &local_addr, 0);
+ if (sock_fd < 0) {
+ LOG(LOGS_ERR, "Could not open NTS-KE socket on %s", UTI_IPSockAddrToString(&local_addr));
+ return INVALID_SOCK_FD;
+ }
+
+ if (!SCK_ListenOnSocket(sock_fd, CNF_GetNtsServerConnections())) {
+ SCK_CloseSocket(sock_fd);
+ return INVALID_SOCK_FD;
+ }
+
+ SCH_AddFileHandler(sock_fd, SCH_FILE_INPUT, accept_connection, NULL);
+
+ return sock_fd;
+}
+
+/* ================================================== */
+
+static void
+helper_signal(int x)
+{
+ SCH_QuitProgram();
+}
+
+/* ================================================== */
+
+static int
+prepare_response(NKSN_Instance session, int error, int next_protocol, int aead_algorithm)
+{
+ NKE_Cookie cookie;
+ NKE_Key c2s, s2c;
+ uint16_t datum;
+ int i;
+
+ DEBUG_LOG("NTS KE response: error=%d next=%d aead=%d", error, next_protocol, aead_algorithm);
+
+ NKSN_BeginMessage(session);
+
+ if (error >= 0) {
+ datum = htons(error);
+ if (!NKSN_AddRecord(session, 1, NKE_RECORD_ERROR, &datum, sizeof (datum)))
+ return 0;
+ } else {
+ datum = htons(next_protocol);
+ if (!NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, &datum, sizeof (datum)))
+ return 0;
+
+ datum = htons(aead_algorithm);
+ if (!NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, &datum, sizeof (datum)))
+ return 0;
+
+ if (CNF_GetNTPPort() != NTP_PORT) {
+ datum = htons(CNF_GetNTPPort());
+ if (!NKSN_AddRecord(session, 1, NKE_RECORD_NTPV4_PORT_NEGOTIATION, &datum, sizeof (datum)))
+ return 0;
+ }
+
+ /* This should be configurable */
+ if (0) {
+ const char server[] = "::1";
+ if (!NKSN_AddRecord(session, 1, NKE_RECORD_NTPV4_SERVER_NEGOTIATION, server,
+ sizeof (server) - 1))
+ return 0;
+ }
+
+ if (!NKSN_GetKeys(session, aead_algorithm, &c2s, &s2c))
+ return 0;
+
+ for (i = 0; i < NKE_MAX_COOKIES; i++) {
+ if (!NKS_GenerateCookie(&c2s, &s2c, &cookie))
+ return 0;
+ if (!NKSN_AddRecord(session, 0, NKE_RECORD_COOKIE, cookie.cookie, cookie.length))
+ return 0;
+ }
+ }
+
+ if (!NKSN_EndMessage(session))
+ return 0;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+process_request(NKSN_Instance session)
+{
+ int next_protocol = -1, aead_algorithm = -1, error = -1;
+ 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);
+ assert(sizeof (uint16_t) == 2);
+
+ while (error == -1) {
+ if (!NKSN_GetRecord(session, &critical, &type, &length, &data, sizeof (data)))
+ break;
+
+ switch (type) {
+ case NKE_RECORD_NEXT_PROTOCOL:
+ if (!critical || length < 2 || length % 2 != 0) {
+ error = NKE_ERROR_BAD_REQUEST;
+ break;
+ }
+ for (i = 0; i < MIN(length, sizeof (data)) / 2; i++) {
+ if (ntohs(data[i]) == NKE_NEXT_PROTOCOL_NTPV4)
+ next_protocol = NKE_NEXT_PROTOCOL_NTPV4;
+ }
+ break;
+ case NKE_RECORD_AEAD_ALGORITHM:
+ if (length < 2 || length % 2 != 0) {
+ error = NKE_ERROR_BAD_REQUEST;
+ break;
+ }
+ for (i = 0; i < MIN(length, sizeof (data)) / 2; i++) {
+ if (ntohs(data[i]) == AEAD_AES_SIV_CMAC_256)
+ aead_algorithm = AEAD_AES_SIV_CMAC_256;
+ }
+ break;
+ case NKE_RECORD_ERROR:
+ case NKE_RECORD_WARNING:
+ case NKE_RECORD_COOKIE:
+ error = NKE_ERROR_BAD_REQUEST;
+ break;
+ default:
+ if (critical)
+ error = NKE_ERROR_UNRECOGNIZED_CRITICAL_RECORD;
+ }
+ }
+
+ if (aead_algorithm < 0 || next_protocol < 0)
+ error = NKE_ERROR_BAD_REQUEST;
+
+ if (!prepare_response(session, error, next_protocol, aead_algorithm))
+ return 0;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+handle_message(void *arg)
+{
+ NKSN_Instance session = arg;
+
+ return process_request(session);
+}
+
+/* ================================================== */
+
+static void
+generate_key(int index)
+{
+ int key_length;
+
+ assert(index < MAX_SERVER_KEYS);
+
+ key_length = SIV_GetKeyLength(SERVER_COOKIE_SIV);
+ if (key_length > sizeof (server_keys[index].key))
+ assert(0);
+
+ UTI_GetRandomBytesUrandom(server_keys[index].key, key_length);
+ if (!SIV_SetKey(server_keys[index].siv, server_keys[index].key, key_length))
+ assert(0);
+
+ UTI_GetRandomBytes(&server_keys[index].id, sizeof (server_keys[index].id));
+
+ server_keys[index].id &= -1U << KEY_ID_INDEX_BITS;
+ server_keys[index].id |= index;
+
+ DEBUG_LOG("Generated server key %"PRIX32, server_keys[index].id);
+}
+
+/* ================================================== */
+
+static void
+save_keys(void)
+{
+ char hex_key[SIV_MAX_KEY_LENGTH * 2 + 1];
+ int i, index, key_length;
+ char *cachedir;
+ FILE *f;
+
+ cachedir = CNF_GetNtsCacheDir();
+ if (!cachedir)
+ return;
+
+ f = UTI_OpenFile(cachedir, "ntskeys", ".tmp", 'w', 0600);
+ if (!f)
+ return;
+
+ key_length = SIV_GetKeyLength(SERVER_COOKIE_SIV);
+
+ for (i = 0; i < MAX_SERVER_KEYS; i++) {
+ index = (current_server_key + i + 1) % MAX_SERVER_KEYS;
+
+ if (key_length > sizeof (server_keys[index].key) ||
+ !UTI_BytesToHex(server_keys[index].key, key_length, hex_key, sizeof (hex_key))) {
+ assert(0);
+ break;
+ }
+
+ fprintf(f, "%08"PRIX32" %s\n", server_keys[index].id, hex_key);
+ }
+
+ fclose(f);
+
+ if (!UTI_RenameTempFile(cachedir, "ntskeys", ".tmp", NULL))
+ ;
+}
+
+/* ================================================== */
+
+static void
+load_keys(void)
+{
+ int i, index, line_length, key_length, n;
+ char *cachedir, line[1024];
+ FILE *f;
+ uint32_t id;
+
+ cachedir = CNF_GetNtsCacheDir();
+ if (!cachedir)
+ return;
+
+ f = UTI_OpenFile(cachedir, "ntskeys", NULL, 'r', 0);
+ if (!f)
+ return;
+
+ key_length = SIV_GetKeyLength(SERVER_COOKIE_SIV);
+
+ for (i = 0; i < MAX_SERVER_KEYS; i++) {
+ if (!fgets(line, sizeof (line), f))
+ break;
+
+ line_length = strlen(line);
+ if (line_length < 10)
+ break;
+ /* Drop '\n' */
+ line[line_length - 1] = '\0';
+
+ if (sscanf(line, "%"PRIX32"%n", &id, &n) != 1 || line[n] != ' ')
+ break;
+
+ index = id % MAX_SERVER_KEYS;
+
+ if (UTI_HexToBytes(line + n + 1, server_keys[index].key,
+ sizeof (server_keys[index].key)) != key_length)
+ break;
+
+ server_keys[index].id = id;
+ if (!SIV_SetKey(server_keys[index].siv, server_keys[index].key, key_length))
+ assert(0);
+
+ DEBUG_LOG("Loaded key %"PRIX32, id);
+
+ current_server_key = index;
+ }
+
+ fclose(f);
+}
+
+/* ================================================== */
+
+static void
+key_timeout(void *arg)
+{
+ current_server_key = (current_server_key + 1) % MAX_SERVER_KEYS;
+ generate_key(current_server_key);
+ save_keys();
+
+ SCH_AddTimeoutByDelay(MAX(CNF_GetNtsRotate(), MIN_KEY_ROTATE_INTERVAL),
+ key_timeout, NULL);
+}
+
+/* ================================================== */
+
+static void
+start_helper(int id, int scfilter_level, int main_fd, int helper_fd)
+{
+ pid_t pid;
+
+ pid = fork();
+
+ if (pid < 0)
+ LOG_FATAL("fork() failed : %s", strerror(errno));
+
+ if (pid > 0)
+ return;
+
+ SCK_CloseSocket(main_fd);
+
+ LOG_CloseParentFd();
+ SCH_Reset();
+ SCH_AddFileHandler(helper_fd, SCH_FILE_INPUT, handle_helper_request, NULL);
+ UTI_SetQuitSignalsHandler(helper_signal, 1);
+ if (scfilter_level != 0)
+ SYS_EnableSystemCallFilter(scfilter_level, SYS_NTSKE_HELPER);
+
+ initialised = 1;
+
+ DEBUG_LOG("NTS-KE helper #%d started", id);
+
+ SCH_MainLoop();
+
+ NKS_Finalise();
+
+ DEBUG_LOG("NTS-KE helper #%d exiting", id);
+
+ exit(0);
+}
+
+/* ================================================== */
+
+void
+NKS_Initialise(int scfilter_level)
+{
+ char *cert, *key;
+ int i, processes;
+
+ server_sock_fd4 = INVALID_SOCK_FD;
+ server_sock_fd6 = INVALID_SOCK_FD;
+ helper_sock_fd = INVALID_SOCK_FD;
+
+ cert = CNF_GetNtsServerCertFile();
+ key = CNF_GetNtsServerKeyFile();
+
+ if (!cert || !key)
+ return;
+
+ server_credentials = NKSN_CreateCertCredentials(cert, key, NULL);
+ if (!server_credentials)
+ return;
+
+ sessions = ARR_CreateInstance(sizeof (NKSN_Instance));
+ for (i = 0; i < CNF_GetNtsServerConnections(); i++)
+ *(NKSN_Instance *)ARR_GetNewElement(sessions) = NULL;
+ for (i = 0; i < MAX_SERVER_KEYS; i++)
+ server_keys[i].siv = NULL;
+
+ server_sock_fd4 = open_socket(IPADDR_INET4, CNF_GetNtsServerPort());
+ server_sock_fd6 = open_socket(IPADDR_INET6, CNF_GetNtsServerPort());
+
+ for (i = 0; i < MAX_SERVER_KEYS; i++) {
+ server_keys[i].siv = SIV_CreateInstance(SERVER_COOKIE_SIV);
+ generate_key(i);
+ }
+
+ current_server_key = MAX_SERVER_KEYS - 1;
+
+ load_keys();
+
+ key_timeout(NULL);
+
+ processes = CNF_GetNtsServerProcesses();
+
+ if (processes > 0) {
+ int sock_fd1, sock_fd2;
+
+ sock_fd1 = SCK_OpenUnixSocketPair(0, &sock_fd2);
+
+ for (i = 0; i < processes; i++)
+ start_helper(i + 1, scfilter_level, sock_fd1, sock_fd2);
+
+ SCK_CloseSocket(sock_fd2);
+ helper_sock_fd = sock_fd1;
+ }
+
+ initialised = 1;
+}
+
+/* ================================================== */
+
+void
+NKS_Finalise(void)
+{
+ int i;
+
+ if (!initialised)
+ return;
+
+ if (helper_sock_fd != INVALID_SOCK_FD) {
+ for (i = 0; i < CNF_GetNtsServerProcesses(); i++) {
+ if (!SCK_Send(helper_sock_fd, "", 1, 0))
+ ;
+ }
+ SCK_CloseSocket(helper_sock_fd);
+ }
+ if (server_sock_fd4 != INVALID_SOCK_FD)
+ SCK_CloseSocket(server_sock_fd4);
+ if (server_sock_fd6 != INVALID_SOCK_FD)
+ SCK_CloseSocket(server_sock_fd6);
+
+ save_keys();
+ for (i = 0; i < MAX_SERVER_KEYS; i++) {
+ if (server_keys[i].siv != NULL)
+ SIV_DestroyInstance(server_keys[i].siv);
+ }
+
+ for (i = 0; i < ARR_GetSize(sessions); i++) {
+ NKSN_Instance session = *(NKSN_Instance *)ARR_GetElement(sessions, i);
+ if (session)
+ NKSN_DestroyInstance(session);
+ }
+ ARR_DestroyInstance(sessions);
+
+ NKSN_DestroyCertCredentials(server_credentials);
+}
+
+/* ================================================== */
+
+/* A server cookie consists of key ID, nonce, and encrypted C2S+S2C keys */
+
+int
+NKS_GenerateCookie(NKE_Key *c2s, NKE_Key *s2c, NKE_Cookie *cookie)
+{
+ unsigned char plaintext[2 * NKE_MAX_KEY_LENGTH], *ciphertext;
+ int plaintext_length, tag_length;
+ ServerCookieHeader *header;
+ ServerKey *key;
+
+ if (!initialised) {
+ DEBUG_LOG("NTS server disabled");
+ return 0;
+ }
+
+ if (c2s->length < 0 || c2s->length > NKE_MAX_KEY_LENGTH ||
+ s2c->length < 0 || s2c->length > NKE_MAX_KEY_LENGTH) {
+ DEBUG_LOG("Invalid key length");
+ return 0;
+ }
+
+ key = &server_keys[current_server_key];
+
+ header = (ServerCookieHeader *)cookie->cookie;
+
+ /* Keep the fields in the host byte order */
+ header->key_id = key->id;
+ UTI_GetRandomBytes(header->nonce, sizeof (header->nonce));
+
+ plaintext_length = c2s->length + s2c->length;
+ assert(plaintext_length <= sizeof (plaintext));
+ memcpy(plaintext, c2s->key, c2s->length);
+ memcpy(plaintext + c2s->length, s2c->key, s2c->length);
+
+ tag_length = SIV_GetTagLength(key->siv);
+ cookie->length = sizeof (*header) + plaintext_length + tag_length;
+ assert(cookie->length <= sizeof (cookie->cookie));
+ ciphertext = cookie->cookie + sizeof (*header);
+
+ if (!SIV_Encrypt(key->siv, header->nonce, sizeof (header->nonce),
+ "", 0,
+ plaintext, plaintext_length,
+ ciphertext, plaintext_length + tag_length)) {
+ DEBUG_LOG("Could not encrypt cookie");
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+NKS_DecodeCookie(NKE_Cookie *cookie, NKE_Key *c2s, NKE_Key *s2c)
+{
+ unsigned char plaintext[2 * NKE_MAX_KEY_LENGTH], *ciphertext;
+ int ciphertext_length, plaintext_length, tag_length;
+ ServerCookieHeader *header;
+ ServerKey *key;
+
+ if (!initialised) {
+ DEBUG_LOG("NTS server disabled");
+ return 0;
+ }
+
+ if (cookie->length <= sizeof (*header)) {
+ DEBUG_LOG("Invalid cookie length");
+ return 0;
+ }
+
+ header = (ServerCookieHeader *)cookie->cookie;
+ ciphertext = cookie->cookie + sizeof (*header);
+ ciphertext_length = cookie->length - sizeof (*header);
+
+ key = &server_keys[header->key_id % MAX_SERVER_KEYS];
+ if (header->key_id != key->id) {
+ DEBUG_LOG("Unknown key %"PRIX32, header->key_id);
+ return 0;
+ }
+
+ tag_length = SIV_GetTagLength(key->siv);
+ if (tag_length >= ciphertext_length) {
+ DEBUG_LOG("Invalid cookie length");
+ return 0;
+ }
+
+ plaintext_length = ciphertext_length - tag_length;
+ if (plaintext_length > sizeof (plaintext) || plaintext_length % 2 != 0) {
+ DEBUG_LOG("Invalid cookie length");
+ return 0;
+ }
+
+ if (!SIV_Decrypt(key->siv, header->nonce, sizeof (header->nonce),
+ "", 0,
+ ciphertext, ciphertext_length,
+ plaintext, plaintext_length)) {
+ DEBUG_LOG("Could not decrypt cookie");
+ return 0;
+ }
+
+ c2s->length = plaintext_length / 2;
+ s2c->length = plaintext_length / 2;
+ assert(c2s->length <= sizeof (c2s->key));
+
+ memcpy(c2s->key, plaintext, c2s->length);
+ memcpy(s2c->key, plaintext + c2s->length, s2c->length);
+
+ return 1;
+}
--- /dev/null
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Header file for the NTS-KE server
+ */
+
+#ifndef GOT_NTS_KE_SERVER_H
+#define GOT_NTS_KE_SERVER_H
+
+#include "nts_ke.h"
+
+/* Init and fini functions */
+extern void NKS_Initialise(int scfilter_level);
+extern void NKS_Finalise(void);
+
+/* Generate a new NTS cookie containing the C2S and S2C keys */
+extern int NKS_GenerateCookie(NKE_Key *c2s, NKE_Key *s2c, NKE_Cookie *cookie);
+
+/* Validate a cookie and extract the C2S and S2C keys */
+extern int NKS_DecodeCookie(NKE_Cookie *cookie, NKE_Key *c2s, NKE_Key *s2c);
+
+#endif
--- /dev/null
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ NTS-KE session used by server and client
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include "nts_ke_session.h"
+
+#include "conf.h"
+#include "logging.h"
+#include "memory.h"
+#include "siv.h"
+#include "socket.h"
+#include "sched.h"
+#include "util.h"
+
+#include <gnutls/gnutls.h>
+
+#define INVALID_SOCK_FD (-8)
+
+struct RecordHeader {
+ uint16_t type;
+ uint16_t body_length;
+};
+
+struct Message {
+ int length;
+ int sent;
+ int parsed;
+ int complete;
+ unsigned char data[NKE_MAX_MESSAGE_LENGTH];
+};
+
+typedef enum {
+ KE_WAIT_CONNECT,
+ KE_HANDSHAKE,
+ KE_SEND,
+ KE_RECEIVE,
+ KE_SHUTDOWN,
+ KE_STOPPED,
+} KeState;
+
+struct NKSN_Instance_Record {
+ int server;
+ char *name;
+ NKSN_MessageHandler handler;
+ void *handler_arg;
+
+ KeState state;
+ int sock_fd;
+ gnutls_session_t tls_session;
+ SCH_TimeoutID timeout_id;
+
+ struct Message message;
+ int new_message;
+ int ended_message;
+};
+
+/* ================================================== */
+
+static gnutls_priority_t priority_cache;
+
+static int credentials_counter = 0;
+
+/* ================================================== */
+
+static void
+reset_message(struct Message *message)
+{
+ message->length = 0;
+ message->sent = 0;
+ message->parsed = 0;
+ message->complete = 0;
+}
+
+/* ================================================== */
+
+static int
+add_record(struct Message *message, int critical, int type, const void *body, int body_length)
+{
+ struct RecordHeader header;
+
+ if (body_length < 0 || body_length > 0xffff || type < 0 || type > 0x7fff ||
+ message->length + sizeof (header) + body_length > sizeof (message->data))
+ return 0;
+
+ header.type = htons(!!critical * NKE_RECORD_CRITICAL_BIT | type);
+ header.body_length = htons(body_length);
+
+ memcpy(&message->data[message->length], &header, sizeof (header));
+ message->length += sizeof (header);
+
+ if (body_length > 0) {
+ memcpy(&message->data[message->length], body, body_length);
+ message->length += body_length;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+reset_message_parsing(struct Message *message)
+{
+ message->parsed = 0;
+}
+
+/* ================================================== */
+
+static int
+get_record(struct Message *message, int *critical, int *type, int *body_length,
+ void *body, int buffer_length)
+{
+ struct RecordHeader header;
+ int blen, rlen;
+
+ if (message->length < message->parsed + sizeof (header) ||
+ buffer_length < 0)
+ return 0;
+
+ memcpy(&header, &message->data[message->parsed], sizeof (header));
+
+ blen = ntohs(header.body_length);
+ rlen = sizeof (header) + blen;
+
+ if (message->length < message->parsed + rlen)
+ return 0;
+
+ if (critical)
+ *critical = !!(ntohs(header.type) & NKE_RECORD_CRITICAL_BIT);
+ if (type)
+ *type = ntohs(header.type) & ~NKE_RECORD_CRITICAL_BIT;
+ if (body)
+ memcpy(body, &message->data[message->parsed + sizeof (header)], MIN(buffer_length, blen));
+ if (body_length)
+ *body_length = blen;
+
+ message->parsed += rlen;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static int
+check_message_format(struct Message *message, int eof)
+{
+ int critical = 0, type = -1, length = -1, ends = 0;
+
+ reset_message_parsing(message);
+ message->complete = 0;
+
+ while (get_record(message, &critical, &type, &length, NULL, 0)) {
+ if (type == NKE_RECORD_END_OF_MESSAGE) {
+ if (!critical || length != 0 || ends > 0)
+ return 0;
+ ends++;
+ }
+ }
+
+ /* If the message cannot be fully parsed, but more data may be coming,
+ consider the format to be ok */
+ if (message->length == 0 || message->parsed < message->length)
+ return !eof;
+
+ if (type != NKE_RECORD_END_OF_MESSAGE)
+ return !eof;
+
+ message->complete = 1;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static gnutls_session_t
+create_tls_session(int server_mode, int sock_fd, const char *server_name,
+ gnutls_certificate_credentials_t credentials,
+ gnutls_priority_t priority)
+{
+ unsigned char alpn_name[sizeof (NKE_ALPN_NAME)];
+ gnutls_session_t session;
+ gnutls_datum_t alpn;
+ int r;
+
+ r = gnutls_init(&session, GNUTLS_NONBLOCK | (server_mode ? GNUTLS_SERVER : GNUTLS_CLIENT));
+ if (r < 0) {
+ LOG(LOGS_ERR, "Could not %s TLS session : %s", "create", gnutls_strerror(r));
+ return NULL;
+ }
+
+ if (!server_mode) {
+ r = gnutls_server_name_set(session, GNUTLS_NAME_DNS, server_name, strlen(server_name));
+ if (r < 0)
+ goto error;
+ gnutls_session_set_verify_cert(session, server_name, 0);
+ }
+
+ r = gnutls_priority_set(session, priority);
+ if (r < 0)
+ goto error;
+
+ r = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, credentials);
+ if (r < 0)
+ goto error;
+
+ memcpy(alpn_name, NKE_ALPN_NAME, sizeof (alpn_name));
+ alpn.data = alpn_name;
+ alpn.size = sizeof (alpn_name) - 1;
+
+ r = gnutls_alpn_set_protocols(session, &alpn, 1, 0);
+ if (r < 0)
+ goto error;
+
+ gnutls_transport_set_int(session, sock_fd);
+
+ return session;
+
+error:
+ LOG(LOGS_ERR, "Could not %s TLS session : %s", "set", gnutls_strerror(r));
+ gnutls_deinit(session);
+ return NULL;
+}
+
+/* ================================================== */
+
+static void
+stop_session(NKSN_Instance inst)
+{
+ if (inst->state == KE_STOPPED)
+ return;
+
+ inst->state = KE_STOPPED;
+
+ SCH_RemoveFileHandler(inst->sock_fd);
+ SCK_CloseSocket(inst->sock_fd);
+ inst->sock_fd = INVALID_SOCK_FD;
+
+ gnutls_deinit(inst->tls_session);
+ inst->tls_session = NULL;
+
+ SCH_RemoveTimeout(inst->timeout_id);
+ inst->timeout_id = 0;
+}
+
+/* ================================================== */
+
+static void
+session_timeout(void *arg)
+{
+ NKSN_Instance inst = arg;
+
+ LOG(inst->server ? LOGS_DEBUG : LOGS_ERR, "NTS-KE session with %s timed out", inst->name);
+
+ inst->timeout_id = 0;
+ stop_session(inst);
+}
+
+/* ================================================== */
+
+static int
+get_socket_error(int sock_fd)
+{
+ int optval;
+ socklen_t optlen = sizeof (optval);
+
+ if (getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0) {
+ DEBUG_LOG("getsockopt() failed : %s", strerror(errno));
+ return EINVAL;
+ }
+
+ return optval;
+}
+
+/* ================================================== */
+
+static int
+check_alpn(NKSN_Instance inst)
+{
+ gnutls_datum_t alpn;
+ int r;
+
+ r = gnutls_alpn_get_selected_protocol(inst->tls_session, &alpn);
+ if (r < 0 || alpn.size != sizeof (NKE_ALPN_NAME) - 1 ||
+ strncmp((const char *)alpn.data, NKE_ALPN_NAME, sizeof (NKE_ALPN_NAME) - 1))
+ return 0;
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+change_state(NKSN_Instance inst, KeState state)
+{
+ int output;
+
+ switch (state) {
+ case KE_HANDSHAKE:
+ output = !inst->server;
+ break;
+ case KE_WAIT_CONNECT:
+ case KE_SEND:
+ case KE_SHUTDOWN:
+ output = 1;
+ break;
+ case KE_RECEIVE:
+ output = 0;
+ break;
+ default:
+ assert(0);
+ }
+
+ SCH_SetFileHandlerEvent(inst->sock_fd, SCH_FILE_OUTPUT, output);
+
+ inst->state = state;
+}
+
+/* ================================================== */
+
+static int
+handle_event(NKSN_Instance inst, int event)
+{
+ struct Message *message = &inst->message;
+ int r;
+
+ DEBUG_LOG("Session event %d fd=%d state=%d", event, inst->sock_fd, (int)inst->state);
+
+ switch (inst->state) {
+ case KE_WAIT_CONNECT:
+ /* Check if connect() succeeded */
+ if (event != SCH_FILE_OUTPUT)
+ return 0;
+
+ r = get_socket_error(inst->sock_fd);
+
+ if (r) {
+ LOG(LOGS_ERR, "Could not connect to %s : %s", inst->name, strerror(r));
+ stop_session(inst);
+ return 0;
+ }
+
+ DEBUG_LOG("Connected to %s", inst->name);
+
+ change_state(inst, KE_HANDSHAKE);
+ return 0;
+
+ case KE_HANDSHAKE:
+ r = gnutls_handshake(inst->tls_session);
+
+ if (r < 0) {
+ if (gnutls_error_is_fatal(r)) {
+ LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
+ "TLS handshake with %s failed : %s", inst->name, gnutls_strerror(r));
+ stop_session(inst);
+ return 0;
+ }
+
+ /* Disable output when the handshake is trying to receive data */
+ SCH_SetFileHandlerEvent(inst->sock_fd, SCH_FILE_OUTPUT,
+ gnutls_record_get_direction(inst->tls_session));
+ return 0;
+ }
+
+ if (DEBUG) {
+ char *description = gnutls_session_get_desc(inst->tls_session);
+ DEBUG_LOG("Handshake with %s completed %s",
+ inst->name, description ? description : "");
+ gnutls_free(description);
+ }
+
+ if (!check_alpn(inst)) {
+ LOG(inst->server ? LOGS_DEBUG : LOGS_ERR, "NTS-KE not supported by %s", inst->name);
+ stop_session(inst);
+ return 0;
+ }
+
+ /* Client will send a request to the server */
+ change_state(inst, inst->server ? KE_RECEIVE : KE_SEND);
+ return 0;
+
+ case KE_SEND:
+ assert(inst->new_message && message->complete);
+
+ r = gnutls_record_send(inst->tls_session, &message->data[message->sent],
+ message->length - message->sent);
+
+ if (r < 0) {
+ if (gnutls_error_is_fatal(r)) {
+ LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
+ "Could not send NTS-KE message to %s : %s", inst->name, gnutls_strerror(r));
+ stop_session(inst);
+ }
+ return 0;
+ }
+
+ DEBUG_LOG("Sent %d bytes to %s", r, inst->name);
+
+ message->sent += r;
+ if (message->sent < message->length)
+ return 0;
+
+ /* Client will receive a response */
+ change_state(inst, inst->server ? KE_SHUTDOWN : KE_RECEIVE);
+ reset_message(&inst->message);
+ inst->new_message = 0;
+ return 0;
+
+ case KE_RECEIVE:
+ do {
+ if (message->length >= sizeof (message->data)) {
+ DEBUG_LOG("Message is too long");
+ stop_session(inst);
+ return 0;
+ }
+
+ r = gnutls_record_recv(inst->tls_session, &message->data[message->length],
+ sizeof (message->data) - message->length);
+
+ if (r < 0) {
+ /* Handle a renegotiation request on both client and server as
+ a protocol error */
+ if (gnutls_error_is_fatal(r) || r == GNUTLS_E_REHANDSHAKE) {
+ LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
+ "Could not receive NTS-KE message from %s : %s",
+ inst->name, gnutls_strerror(r));
+ stop_session(inst);
+ }
+ return 0;
+ }
+
+ DEBUG_LOG("Received %d bytes from %s", r, inst->name);
+
+ message->length += r;
+
+ } while (gnutls_record_check_pending(inst->tls_session) > 0);
+
+ if (!check_message_format(message, r == 0)) {
+ LOG(inst->server ? LOGS_DEBUG : LOGS_ERR,
+ "Received invalid NTS-KE message from %s", inst->name);
+ stop_session(inst);
+ return 0;
+ }
+
+ /* Wait for more data if the message is not complete yet */
+ if (!message->complete)
+ return 0;
+
+ /* Server will send a response to the client */
+ change_state(inst, inst->server ? KE_SEND : KE_SHUTDOWN);
+ break;
+
+ case KE_SHUTDOWN:
+ r = gnutls_bye(inst->tls_session, GNUTLS_SHUT_RDWR);
+
+ if (r < 0) {
+ if (gnutls_error_is_fatal(r)) {
+ DEBUG_LOG("Shutdown with %s failed : %s", inst->name, gnutls_strerror(r));
+ stop_session(inst);
+ return 0;
+ }
+
+ /* Disable output when the TLS shutdown is trying to receive data */
+ SCH_SetFileHandlerEvent(inst->sock_fd, SCH_FILE_OUTPUT,
+ gnutls_record_get_direction(inst->tls_session));
+ return 0;
+ }
+
+ SCK_ShutdownConnection(inst->sock_fd);
+ stop_session(inst);
+
+ DEBUG_LOG("Shutdown completed");
+ return 0;
+
+ default:
+ assert(0);
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+static void
+read_write_socket(int fd, int event, void *arg)
+{
+ NKSN_Instance inst = arg;
+
+ if (!handle_event(inst, event))
+ return;
+
+ reset_message_parsing(&inst->message);
+
+ if (!(inst->handler)(inst->handler_arg)) {
+ stop_session(inst);
+ return;
+ }
+}
+
+/* ================================================== */
+
+static int gnutls_initialised = 0;
+
+static void
+init_gnutls(void)
+{
+ int r;
+
+ if (gnutls_initialised)
+ return;
+
+ r = gnutls_global_init();
+ if (r < 0)
+ LOG_FATAL("Could not initialise %s : %s", "gnutls", gnutls_strerror(r));
+
+ /* NTS specification requires TLS1.2 or later */
+ r = gnutls_priority_init2(&priority_cache, "-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1",
+ NULL, GNUTLS_PRIORITY_INIT_DEF_APPEND);
+ if (r < 0)
+ LOG_FATAL("Could not initialise %s : %s", "priority cache", gnutls_strerror(r));
+
+ gnutls_initialised = 1;
+}
+
+/* ================================================== */
+
+static void
+deinit_gnutls(void)
+{
+ assert(gnutls_initialised);
+
+ gnutls_priority_deinit(priority_cache);
+ gnutls_global_deinit();
+ gnutls_initialised = 0;
+}
+
+/* ================================================== */
+
+void *
+NKSN_CreateCertCredentials(char *cert, char *key, char *trusted_certs)
+{
+ gnutls_certificate_credentials_t credentials = NULL;
+ int r;
+
+ init_gnutls();
+
+ r = gnutls_certificate_allocate_credentials(&credentials);
+ if (r < 0)
+ goto error;
+
+ if (cert && key) {
+ r = gnutls_certificate_set_x509_key_file(credentials, cert, key,
+ GNUTLS_X509_FMT_PEM);
+ if (r < 0)
+ goto error;
+ } else {
+ if (!CNF_GetNoSystemCert()) {
+ r = gnutls_certificate_set_x509_system_trust(credentials);
+ if (r < 0)
+ goto error;
+ }
+
+ if (trusted_certs) {
+ r = gnutls_certificate_set_x509_trust_file(credentials, trusted_certs,
+ GNUTLS_X509_FMT_PEM);
+ if (r < 0)
+ goto error;
+ }
+ }
+
+ credentials_counter++;
+
+ return credentials;
+
+error:
+ LOG(LOGS_ERR, "Could not set credentials : %s", gnutls_strerror(r));
+ if (credentials)
+ gnutls_certificate_free_credentials(credentials);
+ return NULL;
+}
+
+/* ================================================== */
+
+void
+NKSN_DestroyCertCredentials(void *credentials)
+{
+ gnutls_certificate_free_credentials(credentials);
+ credentials_counter--;
+ if (credentials_counter != 0)
+ return;
+
+ deinit_gnutls();
+}
+
+/* ================================================== */
+
+NKSN_Instance
+NKSN_CreateInstance(int server_mode, const char *name,
+ NKSN_MessageHandler handler, void *handler_arg)
+{
+ NKSN_Instance inst;
+
+ inst = MallocNew(struct NKSN_Instance_Record);
+
+ inst->server = server_mode;
+ inst->name = Strdup(name);
+ inst->handler = handler;
+ inst->handler_arg = handler_arg;
+ /* Replace NULL arg with the session itself */
+ if (!inst->handler_arg)
+ inst->handler_arg = inst;
+
+ inst->state = KE_STOPPED;
+ inst->sock_fd = INVALID_SOCK_FD;
+ inst->tls_session = NULL;
+ inst->timeout_id = 0;
+
+ return inst;
+}
+
+/* ================================================== */
+
+void
+NKSN_DestroyInstance(NKSN_Instance inst)
+{
+ stop_session(inst);
+
+ Free(inst->name);
+ Free(inst);
+}
+
+/* ================================================== */
+
+int
+NKSN_StartSession(NKSN_Instance inst, int sock_fd, void *credentials, double timeout)
+{
+ assert(inst->state == KE_STOPPED);
+
+ inst->tls_session = create_tls_session(inst->server, sock_fd,
+ inst->server ? NULL : inst->name,
+ credentials, priority_cache);
+ if (!inst->tls_session)
+ return 0;
+
+ inst->sock_fd = sock_fd;
+ SCH_AddFileHandler(sock_fd, SCH_FILE_INPUT, read_write_socket, inst);
+
+ inst->timeout_id = SCH_AddTimeoutByDelay(timeout, session_timeout, inst);
+
+ reset_message(&inst->message);
+ inst->new_message = 0;
+ inst->ended_message = 0;
+
+ change_state(inst, inst->server ? KE_HANDSHAKE : KE_WAIT_CONNECT);
+
+ return 1;
+}
+
+/* ================================================== */
+
+void
+NKSN_BeginMessage(NKSN_Instance inst)
+{
+ reset_message(&inst->message);
+ inst->new_message = 1;
+}
+
+/* ================================================== */
+
+int
+NKSN_AddRecord(NKSN_Instance inst, int critical, int type, const void *body, int body_length)
+{
+ assert(inst->new_message && !inst->message.complete);
+ assert(type != NKE_RECORD_END_OF_MESSAGE);
+
+ return add_record(&inst->message, critical, type, body, body_length);
+}
+
+/* ================================================== */
+
+int
+NKSN_EndMessage(NKSN_Instance inst)
+{
+ assert(!inst->message.complete);
+
+ if (!add_record(&inst->message, 1, NKE_RECORD_END_OF_MESSAGE, NULL, 0))
+ return 0;
+
+ inst->message.complete = 1;
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+NKSN_GetRecord(NKSN_Instance inst, int *critical, int *type, int *body_length,
+ void *body, int buffer_length)
+{
+ int type2;
+
+ assert(inst->message.complete);
+
+ if (!get_record(&inst->message, critical, &type2, body_length, body, buffer_length))
+ return 0;
+
+ if (type2 == NKE_RECORD_END_OF_MESSAGE)
+ return 0;
+
+ if (type)
+ *type = type2;
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+NKSN_GetKeys(NKSN_Instance inst, SIV_Algorithm siv, NKE_Key *c2s, NKE_Key *s2c)
+{
+ c2s->length = SIV_GetKeyLength(siv);
+ s2c->length = SIV_GetKeyLength(siv);
+ assert(c2s->length <= sizeof (c2s->key));
+ assert(s2c->length <= sizeof (s2c->key));
+
+ if (gnutls_prf_rfc5705(inst->tls_session,
+ sizeof (NKE_EXPORTER_LABEL) - 1, NKE_EXPORTER_LABEL,
+ sizeof (NKE_EXPORTER_CONTEXT_C2S) - 1, NKE_EXPORTER_CONTEXT_C2S,
+ c2s->length, (char *)c2s->key) < 0)
+ return 0;
+ if (gnutls_prf_rfc5705(inst->tls_session,
+ sizeof (NKE_EXPORTER_LABEL) - 1, NKE_EXPORTER_LABEL,
+ sizeof (NKE_EXPORTER_CONTEXT_S2C) - 1, NKE_EXPORTER_CONTEXT_S2C,
+ s2c->length, (char *)s2c->key) < 0)
+ return 0;
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+NKSN_IsStopped(NKSN_Instance inst)
+{
+ return inst->state == KE_STOPPED;
+}
+
+/* ================================================== */
+
+void
+NKSN_StopSession(NKSN_Instance inst)
+{
+ stop_session(inst);
+}
--- /dev/null
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Header file for the NTS-KE session
+ */
+
+#ifndef GOT_NTS_KE_SESSION_H
+#define GOT_NTS_KE_SESSION_H
+
+#include "nts_ke.h"
+#include "siv.h"
+
+typedef struct NKSN_Instance_Record *NKSN_Instance;
+
+/* Handler for received NTS-KE messages. A non-zero return code stops
+ the session. */
+typedef int (*NKSN_MessageHandler)(void *arg);
+
+/* Get client or server credentials using certificates of trusted CAs,
+ or a server certificate and key. The credentials may be shared between
+ different clients or servers. */
+extern void *NKSN_CreateCertCredentials(char *cert, char *key, char *trusted_certs);
+
+/* Destroy the credentials */
+extern void NKSN_DestroyCertCredentials(void *credentials);
+
+/* Create an instance */
+extern NKSN_Instance NKSN_CreateInstance(int server_mode, const char *name,
+ NKSN_MessageHandler handler, void *handler_arg);
+
+/* Destroy an instance */
+extern void NKSN_DestroyInstance(NKSN_Instance inst);
+
+/* Start a new NTS-KE session */
+extern int NKSN_StartSession(NKSN_Instance inst, int sock_fd, void *credentials,
+ double timeout);
+
+/* Begin an NTS-KE message. A request should be made right after starting
+ the session and response should be made in the message handler. */
+extern void NKSN_BeginMessage(NKSN_Instance inst);
+
+/* Add a record to the message */
+extern int NKSN_AddRecord(NKSN_Instance inst, int critical, int type,
+ const void *body, int body_length);
+
+/* Terminate the message */
+extern int NKSN_EndMessage(NKSN_Instance inst);
+
+/* Get the next record from the received message. This function should be
+ called from the message handler. */
+extern int NKSN_GetRecord(NKSN_Instance inst, int *critical, int *type, int *body_length,
+ void *body, int buffer_length);
+
+/* Export NTS keys for a specified algorithm */
+extern int NKSN_GetKeys(NKSN_Instance inst, SIV_Algorithm siv, NKE_Key *c2s, NKE_Key *s2c);
+
+/* Check if the session has stopped */
+extern int NKSN_IsStopped(NKSN_Instance inst);
+
+/* Stop the session */
+extern void NKSN_StopSession(NKSN_Instance inst);
+
+#endif
#include "ntp_io.h"
#include "ntp_sources.h"
#include "ntp_signd.h"
+#include "nts_ke_client.h"
+#include "nts_ke_server.h"
#include "privops.h"
#include "refclock.h"
#include "sched.h"
}
#endif /* !HAVE_CMAC */
+
+#ifndef FEAT_NTS
+
+void
+NKC_Initialise(void)
+{
+}
+
+void
+NKC_Finalise(void)
+{
+}
+
+void
+NKS_Initialise(int scfilter_level)
+{
+}
+
+void
+NKS_Finalise(void)
+{
+}
+
+#endif /* !FEAT_NTS */