diff options
Diffstat (limited to 'nts_ntp_server.c')
-rw-r--r-- | nts_ntp_server.c | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/nts_ntp_server.c b/nts_ntp_server.c new file mode 100644 index 0000000..be69a2b --- /dev/null +++ b/nts_ntp_server.c @@ -0,0 +1,309 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2020, 2022 + * + * 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. + * + ********************************************************************** + + ======================================================================= + + Server NTS-NTP authentication + */ + +#include "config.h" + +#include "sysincl.h" + +#include "nts_ntp_server.h" + +#include "conf.h" +#include "logging.h" +#include "memory.h" +#include "ntp.h" +#include "ntp_ext.h" +#include "nts_ke_server.h" +#include "nts_ntp.h" +#include "nts_ntp_auth.h" +#include "siv.h" +#include "util.h" + +#define MAX_SERVER_SIVS 2 + +struct NtsServer { + SIV_Instance sivs[MAX_SERVER_SIVS]; + SIV_Algorithm siv_algorithms[MAX_SERVER_SIVS]; + unsigned char nonce[NTS_MIN_UNPADDED_NONCE_LENGTH]; + NKE_Cookie cookies[NTS_MAX_COOKIES]; + int num_cookies; + int siv_index; + NTP_int64 req_tx; +}; + +/* The server instance handling all requests */ +struct NtsServer *server; + +/* ================================================== */ + +void +NNS_Initialise(void) +{ + const char **certs, **keys; + int i; + + /* Create an NTS-NTP server instance only if NTS-KE server is enabled */ + if (CNF_GetNtsServerCertAndKeyFiles(&certs, &keys) <= 0) { + server = NULL; + return; + } + + server = Malloc(sizeof (struct NtsServer)); + + server->siv_algorithms[0] = AEAD_AES_SIV_CMAC_256; + server->siv_algorithms[1] = AEAD_AES_128_GCM_SIV; + assert(MAX_SERVER_SIVS == 2); + + for (i = 0; i < 2; i++) + server->sivs[i] = SIV_CreateInstance(server->siv_algorithms[i]); + + /* AES-SIV-CMAC-256 is required on servers */ + if (!server->sivs[0]) + LOG_FATAL("Missing AES-SIV-CMAC-256"); +} + +/* ================================================== */ + +void +NNS_Finalise(void) +{ + int i; + + if (!server) + return; + + for (i = 0; i < MAX_SERVER_SIVS; i++) { + if (server->sivs[i]) + SIV_DestroyInstance(server->sivs[i]); + } + Free(server); + server = NULL; +} + +/* ================================================== */ + +int +NNS_CheckRequestAuth(NTP_Packet *packet, NTP_PacketInfo *info, uint32_t *kod) +{ + int ef_type, ef_body_length, ef_length, has_uniq_id = 0, has_auth = 0, has_cookie = 0; + int i, plaintext_length, parsed, requested_cookies, cookie_length = -1, auth_start = 0; + unsigned char plaintext[NTP_MAX_EXTENSIONS_LENGTH]; + NKE_Context context; + NKE_Cookie cookie; + SIV_Instance siv; + void *ef_body; + + *kod = 0; + + if (!server) + return 0; + + server->num_cookies = 0; + server->siv_index = -1; + server->req_tx = packet->transmit_ts; + + if (info->ext_fields == 0 || info->mode != MODE_CLIENT) + return 0; + + requested_cookies = 0; + + for (parsed = NTP_HEADER_LENGTH; parsed < info->length; parsed += ef_length) { + if (!NEF_ParseField(packet, info->length, parsed, + &ef_length, &ef_type, &ef_body, &ef_body_length)) + /* This is not expected as the packet already passed NAU_ParsePacket() */ + return 0; + + switch (ef_type) { + case NTP_EF_NTS_UNIQUE_IDENTIFIER: + has_uniq_id = 1; + break; + case NTP_EF_NTS_COOKIE: + if (has_cookie || ef_body_length > sizeof (cookie.cookie)) { + DEBUG_LOG("Unexpected cookie/length"); + return 0; + } + cookie.length = ef_body_length; + memcpy(cookie.cookie, ef_body, ef_body_length); + has_cookie = 1; + /* Fall through */ + case NTP_EF_NTS_COOKIE_PLACEHOLDER: + requested_cookies++; + + if (cookie_length >= 0 && cookie_length != ef_body_length) { + DEBUG_LOG("Invalid cookie/placeholder length"); + return 0; + } + cookie_length = ef_body_length; + break; + case NTP_EF_NTS_AUTH_AND_EEF: + if (parsed + ef_length != info->length) { + DEBUG_LOG("Auth not last EF"); + return 0; + } + + auth_start = parsed; + has_auth = 1; + break; + default: + break; + } + } + + if (!has_uniq_id || !has_cookie || !has_auth) { + DEBUG_LOG("Missing an NTS EF"); + return 0; + } + + if (!NKS_DecodeCookie(&cookie, &context)) { + *kod = NTP_KOD_NTS_NAK; + return 0; + } + + /* Find the SIV instance needed for authentication */ + for (i = 0; i < MAX_SERVER_SIVS && context.algorithm != server->siv_algorithms[i]; i++) + ; + if (i == MAX_SERVER_SIVS || !server->sivs[i]) { + DEBUG_LOG("Unexpected SIV"); + return 0; + } + server->siv_index = i; + siv = server->sivs[i]; + + if (!SIV_SetKey(siv, context.c2s.key, context.c2s.length)) { + DEBUG_LOG("Could not set C2S key"); + return 0; + } + + if (!NNA_DecryptAuthEF(packet, info, siv, auth_start, + plaintext, sizeof (plaintext), &plaintext_length)) { + *kod = NTP_KOD_NTS_NAK; + return 0; + } + + for (parsed = 0; parsed < plaintext_length; parsed += ef_length) { + if (!NEF_ParseSingleField(plaintext, plaintext_length, parsed, + &ef_length, &ef_type, &ef_body, &ef_body_length)) { + DEBUG_LOG("Could not parse encrypted EF"); + return 0; + } + + switch (ef_type) { + case NTP_EF_NTS_COOKIE_PLACEHOLDER: + if (cookie_length != ef_body_length) { + DEBUG_LOG("Invalid cookie/placeholder length"); + return 0; + } + requested_cookies++; + break; + default: + break; + } + } + + if (!SIV_SetKey(siv, context.s2c.key, context.s2c.length)) { + DEBUG_LOG("Could not set S2C key"); + return 0; + } + + /* Prepare data for NNS_GenerateResponseAuth() to minimise the time spent + there (when the TX timestamp is already set) */ + + UTI_GetRandomBytes(server->nonce, sizeof (server->nonce)); + + assert(sizeof (server->cookies) / sizeof (server->cookies[0]) == NTS_MAX_COOKIES); + for (i = 0; i < NTS_MAX_COOKIES && i < requested_cookies; i++) + if (!NKS_GenerateCookie(&context, &server->cookies[i])) + return 0; + + server->num_cookies = i; + + return 1; +} + +/* ================================================== */ + +int +NNS_GenerateResponseAuth(NTP_Packet *request, NTP_PacketInfo *req_info, + NTP_Packet *response, NTP_PacketInfo *res_info, + uint32_t kod) +{ + int i, ef_type, ef_body_length, ef_length, parsed; + void *ef_body; + unsigned char plaintext[NTP_MAX_EXTENSIONS_LENGTH]; + int plaintext_length; + + if (!server || req_info->mode != MODE_CLIENT || res_info->mode != MODE_SERVER) + return 0; + + /* Make sure this is a response to the request from the last call + of NNS_CheckRequestAuth() */ + if (UTI_CompareNtp64(&server->req_tx, &request->transmit_ts) != 0) + assert(0); + + for (parsed = NTP_HEADER_LENGTH; parsed < req_info->length; parsed += ef_length) { + if (!NEF_ParseField(request, req_info->length, parsed, + &ef_length, &ef_type, &ef_body, &ef_body_length)) + /* This is not expected as the packet already passed parsing */ + return 0; + + switch (ef_type) { + case NTP_EF_NTS_UNIQUE_IDENTIFIER: + /* Copy the ID from the request */ + if (!NEF_AddField(response, res_info, ef_type, ef_body, ef_body_length)) + return 0; + default: + break; + } + } + + /* NTS NAK response does not have any other fields */ + if (kod) + return 1; + + for (i = 0, plaintext_length = 0; i < server->num_cookies; i++) { + if (!NEF_SetField(plaintext, sizeof (plaintext), plaintext_length, + NTP_EF_NTS_COOKIE, server->cookies[i].cookie, + server->cookies[i].length, &ef_length)) + return 0; + + plaintext_length += ef_length; + assert(plaintext_length <= sizeof (plaintext)); + } + + server->num_cookies = 0; + + if (server->siv_index < 0) + return 0; + + /* Generate an authenticator field which will make the length + of the response equal to the length of the request */ + if (!NNA_GenerateAuthEF(response, res_info, server->sivs[server->siv_index], + server->nonce, sizeof (server->nonce), + plaintext, plaintext_length, + req_info->length - res_info->length)) + return 0; + + return 1; +} |