| /* |
| 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 "local.h" |
| #include "logging.h" |
| #include "memory.h" |
| #include "ntp_core.h" |
| #include "nts_ke_session.h" |
| #include "privops.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 FUTURE_KEYS 1 |
| |
| #define DUMP_FILENAME "ntskeys" |
| #define DUMP_IDENTIFIER "NKS0\n" |
| |
| #define INVALID_SOCK_FD (-7) |
| |
| typedef struct { |
| uint32_t key_id; |
| unsigned char 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 double last_server_key_ts; |
| static int key_rotation_interval; |
| |
| static int server_sock_fd4; |
| static int server_sock_fd6; |
| |
| static int helper_sock_fd; |
| static int is_helper; |
| |
| static int initialised = 0; |
| |
| /* Array of NKSN instances */ |
| static ARR_Instance sessions; |
| static NKSN_Credentials server_credentials; |
| |
| /* ================================================== */ |
| |
| static int handle_message(void *arg); |
| |
| /* ================================================== */ |
| |
| static int |
| handle_client(int sock_fd, IPSockAddr *addr) |
| { |
| NKSN_Instance inst, *instp; |
| int i; |
| |
| /* Leave at least half of the descriptors which can handled by select() |
| to other use */ |
| if (sock_fd > FD_SETSIZE / 2) { |
| DEBUG_LOG("Rejected connection from %s (%s)", |
| UTI_IPSockAddrToString(addr), "too many descriptors"); |
| return 0; |
| } |
| |
| /* Find an unused server slot or one with an already 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, NULL, 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; |
| } |
| |
| assert(server_credentials); |
| |
| if (!NKSN_StartSession(inst, sock_fd, UTI_IPSockAddrToString(addr), |
| 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; |
| |
| /* Receive the helper request with the NTS-KE session socket. |
| With multiple helpers EAGAIN errors are expected here. */ |
| message = SCK_ReceiveMessage(fd, SCK_FLAG_MSG_DESCRIPTOR); |
| if (!message) |
| return; |
| |
| sock_fd = message->descriptor; |
| if (sock_fd < 0) { |
| /* Message with no descriptor is a shutdown command */ |
| SCH_QuitProgram(); |
| return; |
| } |
| |
| if (!initialised) { |
| DEBUG_LOG("Uninitialised helper"); |
| SCK_CloseSocket(sock_fd); |
| return; |
| } |
| |
| if (message->length != sizeof (HelperRequest)) |
| LOG_FATAL("Invalid helper request"); |
| |
| req = message->data; |
| |
| /* Extract the current server key and client address from the request */ |
| server_keys[current_server_key].id = ntohl(req->key_id); |
| assert(sizeof (server_keys[current_server_key].key) == sizeof (req->key)); |
| 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))) |
| LOG_FATAL("Could not set SIV key"); |
| |
| 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 listening_fd, int event, void *arg) |
| { |
| SCK_Message message; |
| IPSockAddr addr; |
| int log_index, sock_fd; |
| struct timespec now; |
| |
| sock_fd = SCK_AcceptConnection(listening_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_LogServiceAccess(CLG_NTSKE, &addr.ip_addr, &now); |
| if (log_index >= 0 && CLG_LimitServiceRate(CLG_NTSKE, 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; |
| |
| memset(&req, 0, sizeof (req)); |
| |
| /* Include the current server key and client address in the request */ |
| req.key_id = htonl(server_keys[current_server_key].id); |
| assert(sizeof (req.key) == sizeof (server_keys[current_server_key].key)); |
| 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; |
| |
| errno = 0; |
| if (!SCK_SendMessage(helper_sock_fd, &message, SCK_FLAG_MSG_DESCRIPTOR)) { |
| /* If sending failed with EPIPE, it means all helpers closed their end of |
| the socket (e.g. due to a fatal error) */ |
| if (errno == EPIPE) |
| LOG_FATAL("NTS-KE helpers failed"); |
| 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) |
| { |
| IPSockAddr local_addr; |
| int backlog, sock_fd; |
| char *iface; |
| |
| if (!SCK_IsIpFamilyEnabled(family)) |
| return INVALID_SOCK_FD; |
| |
| CNF_GetBindAddress(family, &local_addr.ip_addr); |
| local_addr.port = CNF_GetNtsServerPort(); |
| iface = CNF_GetBindNtpInterface(); |
| |
| sock_fd = SCK_OpenTcpSocket(NULL, &local_addr, iface, 0); |
| if (sock_fd < 0) { |
| LOG(LOGS_ERR, "Could not open NTS-KE socket on %s", UTI_IPSockAddrToString(&local_addr)); |
| return INVALID_SOCK_FD; |
| } |
| |
| /* Set the maximum number of waiting connections on the socket to the maximum |
| number of concurrent sessions */ |
| backlog = MAX(CNF_GetNtsServerProcesses(), 1) * CNF_GetNtsServerConnections(); |
| |
| if (!SCK_ListenOnSocket(sock_fd, backlog)) { |
| 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_Context context; |
| NKE_Cookie cookie; |
| char *ntp_server; |
| 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 if (next_protocol < 0) { |
| if (!NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, NULL, 0)) |
| return 0; |
| } else if (aead_algorithm < 0) { |
| datum = htons(next_protocol); |
| if (!NKSN_AddRecord(session, 1, NKE_RECORD_NEXT_PROTOCOL, &datum, sizeof (datum))) |
| return 0; |
| if (!NKSN_AddRecord(session, 1, NKE_RECORD_AEAD_ALGORITHM, NULL, 0)) |
| 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; |
| } |
| |
| ntp_server = CNF_GetNtsNtpServer(); |
| if (ntp_server) { |
| if (!NKSN_AddRecord(session, 1, NKE_RECORD_NTPV4_SERVER_NEGOTIATION, |
| ntp_server, strlen(ntp_server))) |
| return 0; |
| } |
| |
| context.algorithm = aead_algorithm; |
| |
| if (!NKSN_GetKeys(session, aead_algorithm, &context.c2s, &context.s2c)) |
| return 0; |
| |
| for (i = 0; i < NKE_MAX_COOKIES; i++) { |
| if (!NKS_GenerateCookie(&context, &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_records = 0, aead_algorithm_records = 0; |
| int next_protocol_values = 0, aead_algorithm_values = 0; |
| int next_protocol = -1, aead_algorithm = -1, error = -1; |
| int i, 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 < 0) { |
| 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; |
| } |
| |
| next_protocol_records++; |
| |
| for (i = 0; i < MIN(length, sizeof (data)) / 2; i++) { |
| next_protocol_values++; |
| 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; |
| } |
| |
| aead_algorithm_records++; |
| |
| for (i = 0; i < MIN(length, sizeof (data)) / 2; i++) { |
| aead_algorithm_values++; |
| 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 (error < 0) { |
| if (next_protocol_records != 1 || next_protocol_values < 1 || |
| (next_protocol == NKE_NEXT_PROTOCOL_NTPV4 && |
| (aead_algorithm_records != 1 || aead_algorithm_values < 1))) |
| 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; |
| |
| if (index < 0 || index >= MAX_SERVER_KEYS) |
| assert(0); |
| |
| 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 (!server_keys[index].siv || |
| !SIV_SetKey(server_keys[index].siv, server_keys[index].key, key_length)) |
| LOG_FATAL("Could not set SIV key"); |
| |
| UTI_GetRandomBytes(&server_keys[index].id, sizeof (server_keys[index].id)); |
| |
| /* Encode the index in the lowest bits of the 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); |
| |
| last_server_key_ts = SCH_GetLastEventMonoTime(); |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| save_keys(void) |
| { |
| char buf[SIV_MAX_KEY_LENGTH * 2 + 1], *dump_dir; |
| int i, index, key_length; |
| double last_key_age; |
| FILE *f; |
| |
| /* Don't save the keys if rotation is disabled to enable an external |
| management of the keys (e.g. share them with another server) */ |
| if (key_rotation_interval == 0) |
| return; |
| |
| dump_dir = CNF_GetNtsDumpDir(); |
| if (!dump_dir) |
| return; |
| |
| f = UTI_OpenFile(dump_dir, DUMP_FILENAME, ".tmp", 'w', 0600); |
| if (!f) |
| return; |
| |
| key_length = SIV_GetKeyLength(SERVER_COOKIE_SIV); |
| last_key_age = SCH_GetLastEventMonoTime() - last_server_key_ts; |
| |
| if (fprintf(f, "%s%d %.1f\n", DUMP_IDENTIFIER, SERVER_COOKIE_SIV, last_key_age) < 0) |
| goto error; |
| |
| for (i = 0; i < MAX_SERVER_KEYS; i++) { |
| index = (current_server_key + i + 1 + FUTURE_KEYS) % MAX_SERVER_KEYS; |
| |
| if (key_length > sizeof (server_keys[index].key) || |
| !UTI_BytesToHex(server_keys[index].key, key_length, buf, sizeof (buf)) || |
| fprintf(f, "%08"PRIX32" %s\n", server_keys[index].id, buf) < 0) |
| goto error; |
| } |
| |
| fclose(f); |
| |
| /* Rename the temporary file, or remove it if that fails */ |
| if (!UTI_RenameTempFile(dump_dir, DUMP_FILENAME, ".tmp", NULL)) { |
| if (!UTI_RemoveFile(dump_dir, DUMP_FILENAME, ".tmp")) |
| ; |
| } |
| |
| return; |
| |
| error: |
| DEBUG_LOG("Could not %s server keys", "save"); |
| fclose(f); |
| |
| if (!UTI_RemoveFile(dump_dir, DUMP_FILENAME, NULL)) |
| ; |
| } |
| |
| /* ================================================== */ |
| |
| #define MAX_WORDS 2 |
| |
| static int |
| load_keys(void) |
| { |
| char *dump_dir, line[1024], *words[MAX_WORDS]; |
| unsigned char key[SIV_MAX_KEY_LENGTH]; |
| int i, index, key_length, algorithm; |
| double key_age; |
| FILE *f; |
| uint32_t id; |
| |
| dump_dir = CNF_GetNtsDumpDir(); |
| if (!dump_dir) |
| return 0; |
| |
| f = UTI_OpenFile(dump_dir, DUMP_FILENAME, NULL, 'r', 0); |
| if (!f) |
| return 0; |
| |
| if (!fgets(line, sizeof (line), f) || strcmp(line, DUMP_IDENTIFIER) != 0 || |
| !fgets(line, sizeof (line), f) || UTI_SplitString(line, words, MAX_WORDS) != 2 || |
| sscanf(words[0], "%d", &algorithm) != 1 || algorithm != SERVER_COOKIE_SIV || |
| sscanf(words[1], "%lf", &key_age) != 1) |
| goto error; |
| |
| key_length = SIV_GetKeyLength(SERVER_COOKIE_SIV); |
| last_server_key_ts = SCH_GetLastEventMonoTime() - MAX(key_age, 0.0); |
| |
| for (i = 0; i < MAX_SERVER_KEYS && fgets(line, sizeof (line), f); i++) { |
| if (UTI_SplitString(line, words, MAX_WORDS) != 2 || |
| sscanf(words[0], "%"PRIX32, &id) != 1) |
| goto error; |
| |
| if (UTI_HexToBytes(words[1], key, sizeof (key)) != key_length) |
| goto error; |
| |
| index = id % MAX_SERVER_KEYS; |
| |
| server_keys[index].id = id; |
| assert(sizeof (server_keys[index].key) == sizeof (key)); |
| memcpy(server_keys[index].key, key, key_length); |
| |
| if (!SIV_SetKey(server_keys[index].siv, server_keys[index].key, key_length)) |
| LOG_FATAL("Could not set SIV key"); |
| |
| DEBUG_LOG("Loaded key %"PRIX32, id); |
| |
| current_server_key = (index + MAX_SERVER_KEYS - FUTURE_KEYS) % MAX_SERVER_KEYS; |
| } |
| |
| fclose(f); |
| |
| return 1; |
| |
| error: |
| DEBUG_LOG("Could not %s server keys", "load"); |
| fclose(f); |
| |
| return 0; |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| key_timeout(void *arg) |
| { |
| current_server_key = (current_server_key + 1) % MAX_SERVER_KEYS; |
| generate_key((current_server_key + FUTURE_KEYS) % MAX_SERVER_KEYS); |
| save_keys(); |
| |
| SCH_AddTimeoutByDelay(key_rotation_interval, key_timeout, NULL); |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| run_helper(uid_t uid, gid_t gid, int scfilter_level) |
| { |
| LOG_Severity log_severity; |
| |
| /* Finish minimal initialisation and run using the scheduler loop |
| similarly to the main process */ |
| |
| DEBUG_LOG("Helper started"); |
| |
| /* Suppress a log message about disabled clock control */ |
| log_severity = LOG_GetMinSeverity(); |
| LOG_SetMinSeverity(LOGS_ERR); |
| |
| SYS_Initialise(0); |
| LOG_SetMinSeverity(log_severity); |
| |
| if (!geteuid() && (uid || gid)) |
| SYS_DropRoot(uid, gid, SYS_NTSKE_HELPER); |
| |
| NKS_Initialise(); |
| |
| UTI_SetQuitSignalsHandler(helper_signal, 1); |
| if (scfilter_level != 0) |
| SYS_EnableSystemCallFilter(scfilter_level, SYS_NTSKE_HELPER); |
| |
| SCH_MainLoop(); |
| |
| DEBUG_LOG("Helper exiting"); |
| |
| NKS_Finalise(); |
| SCK_Finalise(); |
| SYS_Finalise(); |
| SCH_Finalise(); |
| LCL_Finalise(); |
| PRV_Finalise(); |
| CNF_Finalise(); |
| LOG_Finalise(); |
| |
| UTI_ResetGetRandomFunctions(); |
| |
| exit(0); |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NKS_PreInitialise(uid_t uid, gid_t gid, int scfilter_level) |
| { |
| int i, processes, sock_fd1, sock_fd2; |
| const char **certs, **keys; |
| char prefix[16]; |
| pid_t pid; |
| |
| helper_sock_fd = INVALID_SOCK_FD; |
| is_helper = 0; |
| |
| if (CNF_GetNtsServerCertAndKeyFiles(&certs, &keys) <= 0) |
| return; |
| |
| processes = CNF_GetNtsServerProcesses(); |
| if (processes <= 0) |
| return; |
| |
| /* Start helper processes to perform (computationally expensive) NTS-KE |
| sessions with clients on sockets forwarded from the main process */ |
| |
| sock_fd1 = SCK_OpenUnixSocketPair(0, &sock_fd2); |
| if (sock_fd1 < 0) |
| LOG_FATAL("Could not open socket pair"); |
| |
| for (i = 0; i < processes; i++) { |
| pid = fork(); |
| |
| if (pid < 0) |
| LOG_FATAL("fork() failed : %s", strerror(errno)); |
| |
| if (pid > 0) |
| continue; |
| |
| is_helper = 1; |
| |
| UTI_ResetGetRandomFunctions(); |
| |
| snprintf(prefix, sizeof (prefix), "nks#%d:", i + 1); |
| LOG_SetDebugPrefix(prefix); |
| LOG_CloseParentFd(); |
| |
| SCK_CloseSocket(sock_fd1); |
| SCH_AddFileHandler(sock_fd2, SCH_FILE_INPUT, handle_helper_request, NULL); |
| |
| run_helper(uid, gid, scfilter_level); |
| } |
| |
| SCK_CloseSocket(sock_fd2); |
| helper_sock_fd = sock_fd1; |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NKS_Initialise(void) |
| { |
| const char **certs, **keys; |
| int i, n_certs_keys; |
| double key_delay; |
| |
| server_sock_fd4 = INVALID_SOCK_FD; |
| server_sock_fd6 = INVALID_SOCK_FD; |
| |
| n_certs_keys = CNF_GetNtsServerCertAndKeyFiles(&certs, &keys); |
| if (n_certs_keys <= 0) |
| return; |
| |
| if (helper_sock_fd == INVALID_SOCK_FD) { |
| server_credentials = NKSN_CreateServerCertCredentials(certs, keys, n_certs_keys); |
| if (!server_credentials) |
| return; |
| } else { |
| server_credentials = NULL; |
| } |
| |
| sessions = ARR_CreateInstance(sizeof (NKSN_Instance)); |
| for (i = 0; i < CNF_GetNtsServerConnections(); i++) |
| *(NKSN_Instance *)ARR_GetNewElement(sessions) = NULL; |
| |
| /* Generate random keys, even if they will be replaced by reloaded keys, |
| or unused (in the helper) */ |
| 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; |
| |
| if (!is_helper) { |
| server_sock_fd4 = open_socket(IPADDR_INET4); |
| server_sock_fd6 = open_socket(IPADDR_INET6); |
| |
| key_rotation_interval = MAX(CNF_GetNtsRotate(), 0); |
| |
| /* Reload saved keys, or save the new keys */ |
| if (!load_keys()) |
| save_keys(); |
| |
| if (key_rotation_interval > 0) { |
| key_delay = key_rotation_interval - (SCH_GetLastEventMonoTime() - last_server_key_ts); |
| SCH_AddTimeoutByDelay(MAX(key_delay, 0.0), key_timeout, NULL); |
| } |
| } |
| |
| initialised = 1; |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NKS_Finalise(void) |
| { |
| int i; |
| |
| if (!initialised) |
| return; |
| |
| if (helper_sock_fd != INVALID_SOCK_FD) { |
| /* Send the helpers a request to exit */ |
| 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); |
| |
| if (!is_helper) |
| save_keys(); |
| |
| for (i = 0; i < MAX_SERVER_KEYS; i++) |
| 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); |
| |
| if (server_credentials) |
| NKSN_DestroyCertCredentials(server_credentials); |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NKS_DumpKeys(void) |
| { |
| save_keys(); |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NKS_ReloadKeys(void) |
| { |
| /* Don't load the keys if they are expected to be generated by this server |
| instance (i.e. they are already loaded) to not delay the next rotation */ |
| if (key_rotation_interval > 0) |
| return; |
| |
| load_keys(); |
| } |
| |
| /* ================================================== */ |
| |
| /* A server cookie consists of key ID, nonce, and encrypted C2S+S2C keys */ |
| |
| int |
| NKS_GenerateCookie(NKE_Context *context, 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; |
| } |
| |
| /* The algorithm is hardcoded for now */ |
| if (context->algorithm != AEAD_AES_SIV_CMAC_256) { |
| DEBUG_LOG("Unexpected SIV algorithm"); |
| return 0; |
| } |
| |
| if (context->c2s.length < 0 || context->c2s.length > NKE_MAX_KEY_LENGTH || |
| context->s2c.length < 0 || context->s2c.length > NKE_MAX_KEY_LENGTH) { |
| DEBUG_LOG("Invalid key length"); |
| return 0; |
| } |
| |
| key = &server_keys[current_server_key]; |
| |
| header = (ServerCookieHeader *)cookie->cookie; |
| |
| header->key_id = htonl(key->id); |
| UTI_GetRandomBytes(header->nonce, sizeof (header->nonce)); |
| |
| plaintext_length = context->c2s.length + context->s2c.length; |
| assert(plaintext_length <= sizeof (plaintext)); |
| memcpy(plaintext, context->c2s.key, context->c2s.length); |
| memcpy(plaintext + context->c2s.length, context->s2c.key, context->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_Context *context) |
| { |
| unsigned char plaintext[2 * NKE_MAX_KEY_LENGTH], *ciphertext; |
| int ciphertext_length, plaintext_length, tag_length; |
| ServerCookieHeader *header; |
| ServerKey *key; |
| uint32_t key_id; |
| |
| if (!initialised) { |
| DEBUG_LOG("NTS server disabled"); |
| return 0; |
| } |
| |
| if (cookie->length <= (int)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_id = ntohl(header->key_id); |
| key = &server_keys[key_id % MAX_SERVER_KEYS]; |
| if (key_id != key->id) { |
| DEBUG_LOG("Unknown key %"PRIX32, 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; |
| } |
| |
| context->algorithm = AEAD_AES_SIV_CMAC_256; |
| |
| context->c2s.length = plaintext_length / 2; |
| context->s2c.length = plaintext_length / 2; |
| assert(context->c2s.length <= sizeof (context->c2s.key)); |
| |
| memcpy(context->c2s.key, plaintext, context->c2s.length); |
| memcpy(context->s2c.key, plaintext + context->c2s.length, context->s2c.length); |
| |
| return 1; |
| } |