| /* |
| chronyd/chronyc - Programs for keeping computer clocks accurate. |
| |
| ********************************************************************** |
| * Copyright (C) Miroslav Lichvar 2020-2021 |
| * |
| * 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 "local.h" |
| #include "logging.h" |
| #include "memory.h" |
| #include "siv.h" |
| #include "socket.h" |
| #include "sched.h" |
| #include "util.h" |
| |
| #include <gnutls/gnutls.h> |
| #include <gnutls/x509.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 *server_name; |
| NKSN_MessageHandler handler; |
| void *handler_arg; |
| |
| KeState state; |
| int sock_fd; |
| char *label; |
| gnutls_session_t tls_session; |
| SCH_TimeoutID timeout_id; |
| int retry_factor; |
| |
| struct Message message; |
| int new_message; |
| }; |
| |
| /* ================================================== */ |
| |
| static gnutls_priority_t priority_cache; |
| |
| static int credentials_counter = 0; |
| |
| static int clock_updates = 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; |
| |
| assert(message->length <= sizeof (message->data)); |
| |
| 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; |
| assert(blen >= 0 && rlen > 0); |
| |
| 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; |
| unsigned int flags; |
| int r; |
| |
| r = gnutls_init(&session, GNUTLS_NONBLOCK | GNUTLS_NO_TICKETS | |
| (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) { |
| assert(server_name); |
| |
| if (!UTI_IsStringIP(server_name)) { |
| r = gnutls_server_name_set(session, GNUTLS_NAME_DNS, server_name, strlen(server_name)); |
| if (r < 0) |
| goto error; |
| } |
| |
| flags = 0; |
| |
| if (clock_updates < CNF_GetNoCertTimeCheck()) { |
| flags |= GNUTLS_VERIFY_DISABLE_TIME_CHECKS | GNUTLS_VERIFY_DISABLE_TRUSTED_TIME_CHECKS; |
| DEBUG_LOG("Disabled time checks"); |
| } |
| |
| gnutls_session_set_verify_cert(session, server_name, flags); |
| } |
| |
| 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; |
| |
| Free(inst->label); |
| inst->label = NULL; |
| |
| 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->label); |
| |
| inst->timeout_id = 0; |
| stop_session(inst); |
| } |
| |
| /* ================================================== */ |
| |
| static int |
| check_alpn(NKSN_Instance inst) |
| { |
| gnutls_datum_t alpn; |
| |
| if (gnutls_alpn_get_selected_protocol(inst->tls_session, &alpn) < 0 || |
| alpn.size != sizeof (NKE_ALPN_NAME) - 1 || |
| memcmp(alpn.data, NKE_ALPN_NAME, sizeof (NKE_ALPN_NAME) - 1) != 0) |
| return 0; |
| |
| return 1; |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| set_input_output(NKSN_Instance inst, int output) |
| { |
| SCH_SetFileHandlerEvent(inst->sock_fd, SCH_FILE_INPUT, !output); |
| SCH_SetFileHandlerEvent(inst->sock_fd, SCH_FILE_OUTPUT, output); |
| } |
| |
| /* ================================================== */ |
| |
| 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); |
| } |
| |
| set_input_output(inst, 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; |
| |
| /* Get the socket error */ |
| if (!SCK_GetIntOption(inst->sock_fd, SOL_SOCKET, SO_ERROR, &r)) |
| r = EINVAL; |
| |
| if (r != 0) { |
| LOG(LOGS_ERR, "Could not connect to %s : %s", inst->label, strerror(r)); |
| stop_session(inst); |
| return 0; |
| } |
| |
| DEBUG_LOG("Connected to %s", inst->label); |
| |
| 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)) { |
| gnutls_datum_t cert_error; |
| |
| /* Get a description of verification errors */ |
| if (r != GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR || |
| gnutls_certificate_verification_status_print( |
| gnutls_session_get_verify_cert_status(inst->tls_session), |
| gnutls_certificate_type_get(inst->tls_session), &cert_error, 0) < 0) |
| cert_error.data = NULL; |
| |
| LOG(inst->server ? LOGS_DEBUG : LOGS_ERR, |
| "TLS handshake with %s failed : %s%s%s", inst->label, gnutls_strerror(r), |
| cert_error.data ? " " : "", cert_error.data ? (const char *)cert_error.data : ""); |
| |
| if (cert_error.data) |
| gnutls_free(cert_error.data); |
| |
| stop_session(inst); |
| |
| /* Increase the retry interval if the handshake did not fail due |
| to the other end closing the connection */ |
| if (r != GNUTLS_E_PULL_ERROR && r != GNUTLS_E_PREMATURE_TERMINATION) |
| inst->retry_factor = NKE_RETRY_FACTOR2_TLS; |
| |
| return 0; |
| } |
| |
| /* Disable output when the handshake is trying to receive data */ |
| set_input_output(inst, gnutls_record_get_direction(inst->tls_session)); |
| return 0; |
| } |
| |
| inst->retry_factor = NKE_RETRY_FACTOR2_TLS; |
| |
| if (DEBUG) { |
| char *description = gnutls_session_get_desc(inst->tls_session); |
| DEBUG_LOG("Handshake with %s completed %s", |
| inst->label, description ? description : ""); |
| gnutls_free(description); |
| } |
| |
| if (!check_alpn(inst)) { |
| LOG(inst->server ? LOGS_DEBUG : LOGS_ERR, "NTS-KE not supported by %s", inst->label); |
| 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); |
| assert(message->length <= sizeof (message->data) && message->length > message->sent); |
| |
| 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->label, gnutls_strerror(r)); |
| stop_session(inst); |
| } |
| return 0; |
| } |
| |
| DEBUG_LOG("Sent %d bytes to %s", r, inst->label); |
| |
| 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->label, gnutls_strerror(r)); |
| stop_session(inst); |
| } |
| return 0; |
| } |
| |
| DEBUG_LOG("Received %d bytes from %s", r, inst->label); |
| |
| 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->label); |
| 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); |
| |
| /* Return success to process the received message */ |
| return 1; |
| |
| 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->label, gnutls_strerror(r)); |
| stop_session(inst); |
| return 0; |
| } |
| |
| /* Disable output when the TLS shutdown is trying to receive data */ |
| set_input_output(inst, 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 0; |
| } |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| read_write_socket(int fd, int event, void *arg) |
| { |
| NKSN_Instance inst = arg; |
| |
| if (!handle_event(inst, event)) |
| return; |
| |
| /* A valid message was received. Call the handler to process the message, |
| and prepare a response if it is a server. */ |
| |
| reset_message_parsing(&inst->message); |
| |
| if (!(inst->handler)(inst->handler_arg)) { |
| stop_session(inst); |
| return; |
| } |
| } |
| |
| /* ================================================== */ |
| |
| static time_t |
| get_time(time_t *t) |
| { |
| struct timespec now; |
| |
| LCL_ReadCookedTime(&now, NULL); |
| if (t) |
| *t = now.tv_sec; |
| |
| return now.tv_sec; |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| handle_step(struct timespec *raw, struct timespec *cooked, double dfreq, |
| double doffset, LCL_ChangeType change_type, void *anything) |
| { |
| if (change_type != LCL_ChangeUnknownStep && clock_updates < INT_MAX) |
| clock_updates++; |
| } |
| |
| /* ================================================== */ |
| |
| 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)); |
| |
| /* Prepare a priority cache for server and client NTS-KE sessions |
| (the NTS specification requires TLS1.3 or later) */ |
| r = gnutls_priority_init2(&priority_cache, |
| "-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-TLS1.2:-VERS-DTLS-ALL", |
| NULL, GNUTLS_PRIORITY_INIT_DEF_APPEND); |
| if (r < 0) |
| LOG_FATAL("Could not initialise %s : %s", "priority cache", gnutls_strerror(r)); |
| |
| /* Use our clock instead of the system clock in certificate verification */ |
| gnutls_global_set_time_function(get_time); |
| |
| gnutls_initialised = 1; |
| DEBUG_LOG("Initialised"); |
| |
| LCL_AddParameterChangeHandler(handle_step, NULL); |
| } |
| |
| /* ================================================== */ |
| |
| static void |
| deinit_gnutls(void) |
| { |
| if (!gnutls_initialised || credentials_counter > 0) |
| return; |
| |
| LCL_RemoveParameterChangeHandler(handle_step, NULL); |
| |
| gnutls_priority_deinit(priority_cache); |
| gnutls_global_deinit(); |
| gnutls_initialised = 0; |
| DEBUG_LOG("Deinitialised"); |
| } |
| |
| /* ================================================== */ |
| |
| static NKSN_Credentials |
| create_credentials(const char **certs, const char **keys, int n_certs_keys, |
| const char **trusted_certs, uint32_t *trusted_certs_ids, |
| int n_trusted_certs, uint32_t trusted_cert_set) |
| { |
| gnutls_certificate_credentials_t credentials = NULL; |
| int i, r; |
| |
| init_gnutls(); |
| |
| r = gnutls_certificate_allocate_credentials(&credentials); |
| if (r < 0) |
| goto error; |
| |
| if (certs && keys) { |
| if (trusted_certs || trusted_certs_ids) |
| assert(0); |
| |
| for (i = 0; i < n_certs_keys; i++) { |
| r = gnutls_certificate_set_x509_key_file(credentials, certs[i], keys[i], |
| GNUTLS_X509_FMT_PEM); |
| if (r < 0) |
| goto error; |
| } |
| } else { |
| if (certs || keys || n_certs_keys > 0) |
| assert(0); |
| |
| if (trusted_cert_set == 0 && !CNF_GetNoSystemCert()) { |
| r = gnutls_certificate_set_x509_system_trust(credentials); |
| if (r < 0) |
| goto error; |
| } |
| |
| if (trusted_certs && trusted_certs_ids) { |
| for (i = 0; i < n_trusted_certs; i++) { |
| struct stat buf; |
| |
| if (trusted_certs_ids[i] != trusted_cert_set) |
| continue; |
| |
| if (stat(trusted_certs[i], &buf) == 0 && S_ISDIR(buf.st_mode)) |
| r = gnutls_certificate_set_x509_trust_dir(credentials, trusted_certs[i], |
| GNUTLS_X509_FMT_PEM); |
| else |
| r = gnutls_certificate_set_x509_trust_file(credentials, trusted_certs[i], |
| GNUTLS_X509_FMT_PEM); |
| if (r < 0) |
| goto error; |
| |
| DEBUG_LOG("Added %d trusted certs from %s", r, trusted_certs[i]); |
| } |
| } |
| } |
| |
| credentials_counter++; |
| |
| return (NKSN_Credentials)credentials; |
| |
| error: |
| LOG(LOGS_ERR, "Could not set credentials : %s", gnutls_strerror(r)); |
| if (credentials) |
| gnutls_certificate_free_credentials(credentials); |
| deinit_gnutls(); |
| return NULL; |
| } |
| |
| /* ================================================== */ |
| |
| NKSN_Credentials |
| NKSN_CreateServerCertCredentials(const char **certs, const char **keys, int n_certs_keys) |
| { |
| return create_credentials(certs, keys, n_certs_keys, NULL, NULL, 0, 0); |
| } |
| |
| /* ================================================== */ |
| |
| NKSN_Credentials |
| NKSN_CreateClientCertCredentials(const char **certs, uint32_t *ids, |
| int n_certs_ids, uint32_t trusted_cert_set) |
| { |
| return create_credentials(NULL, NULL, 0, certs, ids, n_certs_ids, trusted_cert_set); |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NKSN_DestroyCertCredentials(NKSN_Credentials credentials) |
| { |
| gnutls_certificate_free_credentials((gnutls_certificate_credentials_t)credentials); |
| credentials_counter--; |
| deinit_gnutls(); |
| } |
| |
| /* ================================================== */ |
| |
| NKSN_Instance |
| NKSN_CreateInstance(int server_mode, const char *server_name, |
| NKSN_MessageHandler handler, void *handler_arg) |
| { |
| NKSN_Instance inst; |
| |
| inst = MallocNew(struct NKSN_Instance_Record); |
| |
| inst->server = server_mode; |
| inst->server_name = server_name ? Strdup(server_name) : NULL; |
| inst->handler = handler; |
| inst->handler_arg = handler_arg; |
| /* Replace a NULL argument with the session itself */ |
| if (!inst->handler_arg) |
| inst->handler_arg = inst; |
| |
| inst->state = KE_STOPPED; |
| inst->sock_fd = INVALID_SOCK_FD; |
| inst->label = NULL; |
| inst->tls_session = NULL; |
| inst->timeout_id = 0; |
| inst->retry_factor = NKE_RETRY_FACTOR2_CONNECT; |
| |
| return inst; |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NKSN_DestroyInstance(NKSN_Instance inst) |
| { |
| stop_session(inst); |
| |
| Free(inst->server_name); |
| Free(inst); |
| } |
| |
| /* ================================================== */ |
| |
| int |
| NKSN_StartSession(NKSN_Instance inst, int sock_fd, const char *label, |
| NKSN_Credentials credentials, double timeout) |
| { |
| assert(inst->state == KE_STOPPED); |
| |
| inst->tls_session = create_tls_session(inst->server, sock_fd, inst->server_name, |
| (gnutls_certificate_credentials_t)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->label = Strdup(label); |
| inst->timeout_id = SCH_AddTimeoutByDelay(timeout, session_timeout, inst); |
| inst->retry_factor = NKE_RETRY_FACTOR2_CONNECT; |
| |
| reset_message(&inst->message); |
| inst->new_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); |
| |
| /* Terminate the message */ |
| 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 (body_length) |
| *body_length = 0; |
| |
| if (!get_record(&inst->message, critical, &type2, body_length, body, buffer_length)) |
| return 0; |
| |
| /* Hide the end-of-message record */ |
| 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) |
| { |
| int length = SIV_GetKeyLength(siv); |
| |
| if (length <= 0 || length > sizeof (c2s->key) || length > sizeof (s2c->key)) { |
| DEBUG_LOG("Invalid algorithm"); |
| return 0; |
| } |
| |
| 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, |
| length, (char *)c2s->key) < 0 || |
| gnutls_prf_rfc5705(inst->tls_session, |
| sizeof (NKE_EXPORTER_LABEL) - 1, NKE_EXPORTER_LABEL, |
| sizeof (NKE_EXPORTER_CONTEXT_S2C) - 1, NKE_EXPORTER_CONTEXT_S2C, |
| length, (char *)s2c->key) < 0) { |
| DEBUG_LOG("Could not export key"); |
| return 0; |
| } |
| |
| c2s->length = length; |
| s2c->length = length; |
| |
| return 1; |
| } |
| |
| /* ================================================== */ |
| |
| int |
| NKSN_IsStopped(NKSN_Instance inst) |
| { |
| return inst->state == KE_STOPPED; |
| } |
| |
| /* ================================================== */ |
| |
| void |
| NKSN_StopSession(NKSN_Instance inst) |
| { |
| stop_session(inst); |
| } |
| |
| /* ================================================== */ |
| |
| int |
| NKSN_GetRetryFactor(NKSN_Instance inst) |
| { |
| return inst->retry_factor; |
| } |