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.
This commit is contained in:
parent
72f99033fe
commit
a420ed57a1
12 changed files with 2378 additions and 4 deletions
107
conf.c
107
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;
|
||||
}
|
||||
|
|
10
conf.h
10
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 */
|
||||
|
|
28
configure
vendored
28
configure
vendored
|
@ -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,12 +930,21 @@ 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);'
|
||||
|
@ -933,6 +952,7 @@ if true && \
|
|||
add_def HAVE_NETTLE_SIV_CMAC
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $use_pthread = "1" ]; then
|
||||
MYCFLAGS="$MYCFLAGS -pthread"
|
||||
|
@ -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"
|
||||
|
|
6
main.c
6
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();
|
||||
|
|
69
nts_ke.h
Normal file
69
nts_ke.h
Normal file
|
@ -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
|
389
nts_ke_client.c
Normal file
389
nts_ke_client.c
Normal file
|
@ -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;
|
||||
}
|
58
nts_ke_client.h
Normal file
58
nts_ke_client.h
Normal file
|
@ -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
|
785
nts_ke_server.c
Normal file
785
nts_ke_server.c
Normal file
|
@ -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;
|
||||
}
|
42
nts_ke_server.h
Normal file
42
nts_ke_server.h
Normal file
|
@ -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
|
779
nts_ke_session.c
Normal file
779
nts_ke_session.c
Normal file
|
@ -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 <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);
|
||||
}
|
83
nts_ke_session.h
Normal file
83
nts_ke_session.h
Normal file
|
@ -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
|
26
stubs.c
26
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 */
|
||||
|
|
Loading…
Reference in a new issue