From a420ed57a10f7abebae4ac76c01ccde4ad893cf9 Mon Sep 17 00:00:00 2001 From: Miroslav Lichvar Date: Tue, 4 Feb 2020 15:10:14 +0100 Subject: [PATCH] nts: add NTS-KE server and client Add a client and server implementing the Network Time Security (NTS) Key Establishment. Use the GnuTLS library for TLS. --- conf.c | 107 +++++++ conf.h | 10 + configure | 28 +- main.c | 6 + nts_ke.h | 69 +++++ nts_ke_client.c | 389 +++++++++++++++++++++++ nts_ke_client.h | 58 ++++ nts_ke_server.c | 785 +++++++++++++++++++++++++++++++++++++++++++++++ nts_ke_server.h | 42 +++ nts_ke_session.c | 779 ++++++++++++++++++++++++++++++++++++++++++++++ nts_ke_session.h | 83 +++++ stubs.c | 26 ++ 12 files changed, 2378 insertions(+), 4 deletions(-) create mode 100644 nts_ke.h create mode 100644 nts_ke_client.c create mode 100644 nts_ke_client.h create mode 100644 nts_ke_server.c create mode 100644 nts_ke_server.h create mode 100644 nts_ke_session.c create mode 100644 nts_ke_session.h diff --git a/conf.c b/conf.c index a5525ce..6d79c04 100644 --- a/conf.c +++ b/conf.c @@ -223,6 +223,19 @@ static char *leapsec_tz = NULL; /* 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; @@ -390,6 +403,10 @@ CNF_Finalise(void) 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); } /* ================================================== */ @@ -513,6 +530,8 @@ CNF_ParseLine(const char *filename, int number, char *line) 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")) { @@ -525,8 +544,24 @@ CNF_ParseLine(const char *filename, int number, char *line) 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")) { @@ -2027,3 +2062,75 @@ CNF_GetHwTsInterface(unsigned int index, CNF_HwTsInterface **iface) *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; +} diff --git a/conf.h b/conf.h index 43217fc..cc34791 100644 --- a/conf.h +++ b/conf.h @@ -139,4 +139,14 @@ typedef struct { 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 */ diff --git a/configure b/configure index 597fd4d..5af221e 100755 --- a/configure +++ b/configure @@ -89,6 +89,8 @@ For better control, use the options below. --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 @@ -203,6 +205,8 @@ feat_sechash=1 try_nettle=1 try_nss=1 try_tomcrypt=1 +feat_nts=1 +try_gnutls=1 feat_rtc=1 try_rtc=0 feat_droproot=1 @@ -373,6 +377,12 @@ do --without-tomcrypt ) try_tomcrypt=0 ;; + --disable-nts ) + feat_nts=0 + ;; + --without-gnutls ) + try_gnutls=0 + ;; --host-system=* ) OPERATINGSYSTEM=`echo $option | sed -e 's/^.*=//;'` ;; @@ -920,18 +930,28 @@ EXTRA_OBJECTS="$EXTRA_OBJECTS $HASH_OBJ" 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 @@ -1003,7 +1023,7 @@ add_def MAIL_PROGRAM "\"$mail_program\"" 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" diff --git a/main.c b/main.c index b32cd52..b08d7bd 100644 --- a/main.c +++ b/main.c @@ -38,6 +38,8 @@ #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" @@ -113,6 +115,8 @@ MAI_CleanupAndExit(void) TMC_Finalise(); MNL_Finalise(); CLG_Finalise(); + NKC_Finalise(); + NKS_Finalise(); NSD_Finalise(); NSR_Finalise(); SST_Finalise(); @@ -583,6 +587,8 @@ int main SST_Initialise(); NSR_Initialise(); NSD_Initialise(); + NKS_Initialise(scfilter_level); + NKC_Initialise(); CLG_Initialise(); MNL_Initialise(); TMC_Initialise(); diff --git a/nts_ke.h b/nts_ke.h new file mode 100644 index 0000000..f00fcef --- /dev/null +++ b/nts_ke.h @@ -0,0 +1,69 @@ +/* + 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 diff --git a/nts_ke_client.c b/nts_ke_client.c new file mode 100644 index 0000000..090d713 --- /dev/null +++ b/nts_ke_client.c @@ -0,0 +1,389 @@ +/* + 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; +} diff --git a/nts_ke_client.h b/nts_ke_client.h new file mode 100644 index 0000000..8d58775 --- /dev/null +++ b/nts_ke_client.h @@ -0,0 +1,58 @@ +/* + 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 diff --git a/nts_ke_server.c b/nts_ke_server.c new file mode 100644 index 0000000..14bb621 --- /dev/null +++ b/nts_ke_server.c @@ -0,0 +1,785 @@ +/* + 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; +} diff --git a/nts_ke_server.h b/nts_ke_server.h new file mode 100644 index 0000000..558dd11 --- /dev/null +++ b/nts_ke_server.h @@ -0,0 +1,42 @@ +/* + 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 diff --git a/nts_ke_session.c b/nts_ke_session.c new file mode 100644 index 0000000..d9b0d4b --- /dev/null +++ b/nts_ke_session.c @@ -0,0 +1,779 @@ +/* + 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 + +#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); +} diff --git a/nts_ke_session.h b/nts_ke_session.h new file mode 100644 index 0000000..cbfb7f5 --- /dev/null +++ b/nts_ke_session.h @@ -0,0 +1,83 @@ +/* + 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 diff --git a/stubs.c b/stubs.c index 41b8dcc..d9dce17 100644 --- a/stubs.c +++ b/stubs.c @@ -40,6 +40,8 @@ #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" @@ -447,3 +449,27 @@ CMC_DestroyInstance(CMC_Instance inst) } #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 */