| /** |
| * @file session_client_tls.c |
| * @author Radek Krejci <rkrejci@cesnet.cz> |
| * @author Michal Vasko <mvasko@cesnet.cz> |
| * @brief libnetconf2 - TLS specific session client transport functions |
| * |
| * This source is compiled only with libssl. |
| * |
| * @copyright |
| * Copyright (c) 2015 CESNET, z.s.p.o. |
| * |
| * This source code is licensed under BSD 3-Clause License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * https://opensource.org/licenses/BSD-3-Clause |
| */ |
| |
| #define _GNU_SOURCE /* pthread_rwlock_t, strdup */ |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <signal.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <libyang/libyang.h> |
| |
| #include "compat.h" |
| #include "config.h" |
| #include "log_p.h" |
| #include "session_client.h" |
| #include "session_client_ch.h" |
| #include "session_p.h" |
| #include "session_wrapper.h" |
| |
| struct nc_client_context *nc_client_context_location(void); |
| |
| #define client_opts nc_client_context_location()->opts |
| #define tls_opts nc_client_context_location()->tls_opts |
| #define tls_ch_opts nc_client_context_location()->tls_ch_opts |
| |
| void |
| _nc_client_tls_destroy_opts(struct nc_client_tls_opts *opts) |
| { |
| free(opts->cert_path); |
| free(opts->key_path); |
| free(opts->ca_file); |
| free(opts->ca_dir); |
| free(opts->crl_file); |
| free(opts->crl_dir); |
| memset(opts, 0, sizeof *opts); |
| } |
| |
| void |
| nc_client_tls_destroy_opts(void) |
| { |
| _nc_client_tls_destroy_opts(&tls_opts); |
| _nc_client_tls_destroy_opts(&tls_ch_opts); |
| } |
| |
| static int |
| _nc_client_tls_set_cert_key_paths(const char *client_cert, const char *client_key, struct nc_client_tls_opts *opts) |
| { |
| NC_CHECK_ARG_RET(NULL, client_cert, -1); |
| |
| free(opts->cert_path); |
| free(opts->key_path); |
| |
| opts->cert_path = strdup(client_cert); |
| NC_CHECK_ERRMEM_RET(!opts->cert_path, -1); |
| |
| if (client_key) { |
| opts->key_path = strdup(client_key); |
| NC_CHECK_ERRMEM_RET(!opts->key_path, -1); |
| } else { |
| opts->key_path = NULL; |
| } |
| |
| return 0; |
| } |
| |
| API int |
| nc_client_tls_set_cert_key_paths(const char *client_cert, const char *client_key) |
| { |
| return _nc_client_tls_set_cert_key_paths(client_cert, client_key, &tls_opts); |
| } |
| |
| API int |
| nc_client_tls_ch_set_cert_key_paths(const char *client_cert, const char *client_key) |
| { |
| return _nc_client_tls_set_cert_key_paths(client_cert, client_key, &tls_ch_opts); |
| } |
| |
| static void |
| _nc_client_tls_get_cert_key_paths(const char **client_cert, const char **client_key, struct nc_client_tls_opts *opts) |
| { |
| if (!client_cert && !client_key) { |
| ERRARG(NULL, "client_cert and client_key"); |
| return; |
| } |
| |
| if (client_cert) { |
| *client_cert = opts->cert_path; |
| } |
| if (client_key) { |
| *client_key = opts->key_path; |
| } |
| } |
| |
| API void |
| nc_client_tls_get_cert_key_paths(const char **client_cert, const char **client_key) |
| { |
| _nc_client_tls_get_cert_key_paths(client_cert, client_key, &tls_opts); |
| } |
| |
| API void |
| nc_client_tls_ch_get_cert_key_paths(const char **client_cert, const char **client_key) |
| { |
| _nc_client_tls_get_cert_key_paths(client_cert, client_key, &tls_ch_opts); |
| } |
| |
| static int |
| _nc_client_tls_set_trusted_ca_paths(const char *ca_file, const char *ca_dir, struct nc_client_tls_opts *opts) |
| { |
| if (!ca_file && !ca_dir) { |
| ERRARG(NULL, "ca_file and ca_dir"); |
| return -1; |
| } |
| |
| free(opts->ca_file); |
| free(opts->ca_dir); |
| |
| if (ca_file) { |
| opts->ca_file = strdup(ca_file); |
| NC_CHECK_ERRMEM_RET(!opts->ca_file, -1); |
| } else { |
| opts->ca_file = NULL; |
| } |
| |
| if (ca_dir) { |
| opts->ca_dir = strdup(ca_dir); |
| NC_CHECK_ERRMEM_RET(!opts->ca_dir, -1); |
| } else { |
| opts->ca_dir = NULL; |
| } |
| |
| return 0; |
| } |
| |
| API int |
| nc_client_tls_set_trusted_ca_paths(const char *ca_file, const char *ca_dir) |
| { |
| return _nc_client_tls_set_trusted_ca_paths(ca_file, ca_dir, &tls_opts); |
| } |
| |
| API int |
| nc_client_tls_ch_set_trusted_ca_paths(const char *ca_file, const char *ca_dir) |
| { |
| return _nc_client_tls_set_trusted_ca_paths(ca_file, ca_dir, &tls_ch_opts); |
| } |
| |
| static void |
| _nc_client_tls_get_trusted_ca_paths(const char **ca_file, const char **ca_dir, struct nc_client_tls_opts *opts) |
| { |
| if (!ca_file && !ca_dir) { |
| ERRARG(NULL, "ca_file and ca_dir"); |
| return; |
| } |
| |
| if (ca_file) { |
| *ca_file = opts->ca_file; |
| } |
| if (ca_dir) { |
| *ca_dir = opts->ca_dir; |
| } |
| } |
| |
| API void |
| nc_client_tls_get_trusted_ca_paths(const char **ca_file, const char **ca_dir) |
| { |
| _nc_client_tls_get_trusted_ca_paths(ca_file, ca_dir, &tls_opts); |
| } |
| |
| API void |
| nc_client_tls_ch_get_trusted_ca_paths(const char **ca_file, const char **ca_dir) |
| { |
| _nc_client_tls_get_trusted_ca_paths(ca_file, ca_dir, &tls_ch_opts); |
| } |
| |
| static int |
| _nc_client_tls_set_crl_paths(const char *crl_file, const char *crl_dir, struct nc_client_tls_opts *opts) |
| { |
| if (!crl_file && !crl_dir) { |
| ERRARG(NULL, "crl_file and crl_dir"); |
| return -1; |
| } |
| |
| free(opts->crl_file); |
| free(opts->crl_dir); |
| |
| if (crl_file) { |
| opts->crl_file = strdup(crl_file); |
| NC_CHECK_ERRMEM_RET(!opts->crl_file, -1); |
| } else { |
| opts->crl_file = NULL; |
| } |
| |
| if (crl_dir) { |
| opts->crl_dir = strdup(crl_dir); |
| NC_CHECK_ERRMEM_RET(!opts->crl_dir, -1); |
| } else { |
| opts->crl_dir = NULL; |
| } |
| |
| return 0; |
| } |
| |
| API int |
| nc_client_tls_set_crl_paths(const char *crl_file, const char *crl_dir) |
| { |
| return _nc_client_tls_set_crl_paths(crl_file, crl_dir, &tls_opts); |
| } |
| |
| API int |
| nc_client_tls_ch_set_crl_paths(const char *crl_file, const char *crl_dir) |
| { |
| return _nc_client_tls_set_crl_paths(crl_file, crl_dir, &tls_ch_opts); |
| } |
| |
| static void |
| _nc_client_tls_get_crl_paths(const char **crl_file, const char **crl_dir, struct nc_client_tls_opts *opts) |
| { |
| if (!crl_file && !crl_dir) { |
| ERRARG(NULL, "crl_file and crl_dir"); |
| return; |
| } |
| |
| if (crl_file) { |
| *crl_file = opts->crl_file; |
| } |
| if (crl_dir) { |
| *crl_dir = opts->crl_dir; |
| } |
| } |
| |
| API void |
| nc_client_tls_get_crl_paths(const char **crl_file, const char **crl_dir) |
| { |
| _nc_client_tls_get_crl_paths(crl_file, crl_dir, &tls_opts); |
| } |
| |
| API void |
| nc_client_tls_ch_get_crl_paths(const char **crl_file, const char **crl_dir) |
| { |
| _nc_client_tls_get_crl_paths(crl_file, crl_dir, &tls_ch_opts); |
| } |
| |
| API int |
| nc_client_tls_ch_add_bind_listen(const char *address, uint16_t port) |
| { |
| return nc_client_ch_add_bind_listen(address, port, NULL, NC_TI_TLS); |
| } |
| |
| API int |
| nc_client_tls_ch_add_bind_hostname_listen(const char *address, uint16_t port, const char *hostname) |
| { |
| return nc_client_ch_add_bind_listen(address, port, hostname, NC_TI_TLS); |
| } |
| |
| API int |
| nc_client_tls_ch_del_bind(const char *address, uint16_t port) |
| { |
| return nc_client_ch_del_bind(address, port, NC_TI_TLS); |
| } |
| |
| static int |
| nc_client_tls_connect_check(int connect_ret, void *tls_session, const char *peername) |
| { |
| uint32_t verify; |
| char *err; |
| |
| /* check certificate verification result */ |
| verify = nc_tls_get_verify_result_wrap(tls_session); |
| if (!verify && (connect_ret == 1)) { |
| VRB(NULL, "Server certificate verified (domain \"%s\").", peername); |
| } else if (verify) { |
| err = nc_tls_verify_error_string_wrap(verify); |
| ERR(NULL, "Server certificate error (%s).", err); |
| free(err); |
| } |
| |
| /* check TLS connection result */ |
| if (connect_ret != 1) { |
| nc_client_tls_print_connect_err_wrap(connect_ret, peername, tls_session); |
| } |
| |
| return connect_ret; |
| } |
| |
| static void * |
| nc_client_tls_session_new(int sock, const char *host, int timeout, struct nc_client_tls_opts *opts, void **out_tls_cfg, struct nc_tls_ctx *tls_ctx) |
| { |
| int ret = 0, sock_tmp = sock; |
| struct timespec ts_timeout; |
| void *tls_session, *tls_cfg, *cli_cert, *cli_pkey, *cert_store, *crl_store; |
| |
| tls_session = tls_cfg = cli_cert = cli_pkey = cert_store = crl_store = NULL; |
| |
| /* prepare TLS context from which a session will be created */ |
| tls_cfg = nc_tls_config_new_wrap(NC_CLIENT); |
| if (!tls_cfg) { |
| goto fail; |
| } |
| |
| /* opaque CA/CRL certificate store */ |
| cert_store = nc_tls_cert_store_new_wrap(); |
| if (!cert_store) { |
| goto fail; |
| } |
| |
| /* load client's key and certificate */ |
| if (nc_client_tls_load_cert_key_wrap(opts->cert_path, opts->key_path, &cli_cert, &cli_pkey)) { |
| goto fail; |
| } |
| |
| /* load trusted CA certificates */ |
| if (nc_client_tls_load_trusted_certs_wrap(cert_store, opts->ca_file, opts->ca_dir)) { |
| goto fail; |
| } |
| |
| if (opts->crl_file || opts->crl_dir) { |
| /* opaque CRL store */ |
| crl_store = nc_tls_crl_store_new_wrap(); |
| if (!crl_store) { |
| goto fail; |
| } |
| |
| /* load CRLs into the crl store */ |
| if (nc_client_tls_load_crl_wrap(crl_store, opts->crl_file, opts->crl_dir)) { |
| goto fail; |
| } |
| } |
| |
| /* set client's verify mode flags */ |
| nc_client_tls_set_verify_wrap(tls_cfg); |
| |
| /* init TLS context and store data which may be needed later in it */ |
| if (nc_tls_init_ctx_wrap(sock, cli_cert, cli_pkey, cert_store, crl_store, tls_ctx)) { |
| goto fail; |
| } |
| |
| /* memory is managed by context now */ |
| cli_cert = cli_pkey = cert_store = crl_store = NULL; |
| |
| /* setup config from ctx */ |
| if (nc_tls_setup_config_from_ctx_wrap(tls_ctx, NC_CLIENT, tls_cfg)) { |
| goto fail; |
| } |
| |
| /* session from config */ |
| tls_session = nc_tls_session_new_wrap(tls_cfg); |
| if (!tls_session) { |
| goto fail; |
| } |
| |
| /* set session fd */ |
| nc_server_tls_set_fd_wrap(tls_session, sock, tls_ctx); |
| |
| sock = -1; |
| |
| /* set session hostname to check against in the server cert */ |
| if (nc_client_tls_set_hostname_wrap(tls_session, host)) { |
| goto fail; |
| } |
| |
| /* handshake */ |
| if (timeout > -1) { |
| nc_timeouttime_get(&ts_timeout, timeout); |
| } |
| while ((ret = nc_client_tls_handshake_step_wrap(tls_session, sock_tmp)) == 0) { |
| usleep(NC_TIMEOUT_STEP); |
| if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) { |
| ERR(NULL, "SSL connect timeout."); |
| goto fail; |
| } |
| } |
| |
| /* check if handshake was ok */ |
| if (nc_client_tls_connect_check(ret, tls_session, host) != 1) { |
| goto fail; |
| } |
| |
| *out_tls_cfg = tls_cfg; |
| return tls_session; |
| |
| fail: |
| if (sock > -1) { |
| close(sock); |
| } |
| |
| nc_tls_session_destroy_wrap(tls_session); |
| nc_tls_cert_destroy_wrap(cli_cert); |
| nc_tls_privkey_destroy_wrap(cli_pkey); |
| nc_tls_cert_store_destroy_wrap(cert_store); |
| nc_tls_crl_store_destroy_wrap(crl_store); |
| nc_tls_config_destroy_wrap(tls_cfg); |
| return NULL; |
| } |
| |
| API struct nc_session * |
| nc_connect_tls(const char *host, unsigned short port, struct ly_ctx *ctx) |
| { |
| struct nc_session *session = NULL; |
| int sock; |
| char *ip_host = NULL; |
| void *tls_cfg = NULL; |
| struct nc_tls_ctx tls_ctx = {0}; |
| |
| if (!tls_opts.cert_path) { |
| ERR(NULL, "Client certificate not set."); |
| return NULL; |
| } else if (!tls_opts.ca_file && !tls_opts.ca_dir) { |
| ERR(NULL, "Certificate authority certificates not set."); |
| return NULL; |
| } |
| |
| /* process parameters */ |
| if (!host || (host[0] == '\0')) { |
| host = "localhost"; |
| } |
| |
| if (!port) { |
| port = NC_PORT_TLS; |
| } |
| |
| /* prepare session structure */ |
| session = nc_new_session(NC_CLIENT, 0); |
| NC_CHECK_ERRMEM_RET(!session, NULL); |
| session->status = NC_STATUS_STARTING; |
| |
| /* create and assign socket */ |
| sock = nc_sock_connect(host, port, -1, &client_opts.ka, NULL, &ip_host); |
| if (sock == -1) { |
| ERR(NULL, "Unable to connect to %s:%u (%s).", host, port, strerror(errno)); |
| goto fail; |
| } |
| |
| /* fill the session */ |
| session->ti_type = NC_TI_TLS; |
| if (!(session->ti.tls.session = nc_client_tls_session_new(sock, host, NC_TRANSPORT_TIMEOUT, &tls_opts, &tls_cfg, &tls_ctx))) { |
| goto fail; |
| } |
| session->ti.tls.config = tls_cfg; |
| |
| /* memory belongs to session */ |
| memcpy(&session->ti.tls.ctx, &tls_ctx, sizeof tls_ctx); |
| memset(&tls_ctx, 0, sizeof tls_ctx); |
| |
| if (nc_client_session_new_ctx(session, ctx) != EXIT_SUCCESS) { |
| goto fail; |
| } |
| ctx = session->ctx; |
| |
| /* NETCONF handshake */ |
| if (nc_handshake_io(session) != NC_MSG_HELLO) { |
| goto fail; |
| } |
| session->status = NC_STATUS_RUNNING; |
| |
| if (nc_ctx_check_and_fill(session) == -1) { |
| goto fail; |
| } |
| |
| /* store information into session */ |
| session->host = ip_host; |
| session->port = port; |
| session->username = strdup("certificate-based"); |
| |
| return session; |
| |
| fail: |
| free(ip_host); |
| nc_session_free(session, NULL); |
| nc_tls_ctx_destroy_wrap(&tls_ctx); |
| return NULL; |
| } |
| |
| API struct nc_session * |
| nc_connect_libssl(void *UNUSED(tls), struct ly_ctx *UNUSED(ctx)) |
| { |
| ERR(NULL, "nc_connect_libssl() is deprecated, do not use it."); |
| return NULL; |
| } |
| |
| struct nc_session * |
| nc_accept_callhome_tls_sock(int sock, const char *host, uint16_t port, struct ly_ctx *ctx, int timeout, const char *peername) |
| { |
| struct nc_session *session = NULL; |
| void *tls_cfg = NULL; |
| struct nc_tls_ctx tls_ctx = {0}; |
| |
| /* prepare session structure */ |
| session = nc_new_session(NC_CLIENT, 0); |
| NC_CHECK_ERRMEM_RET(!session, NULL); |
| session->status = NC_STATUS_STARTING; |
| |
| /* fill the session */ |
| session->ti_type = NC_TI_TLS; |
| if (!(session->ti.tls.session = nc_client_tls_session_new(sock, peername, timeout, &tls_ch_opts, &tls_cfg, &tls_ctx))) { |
| goto fail; |
| } |
| session->ti.tls.config = tls_cfg; |
| |
| /* memory belongs to session */ |
| memcpy(&session->ti.tls.ctx, &tls_ctx, sizeof tls_ctx); |
| memset(&tls_ctx, 0, sizeof tls_ctx); |
| |
| if (nc_client_session_new_ctx(session, ctx) != EXIT_SUCCESS) { |
| goto fail; |
| } |
| ctx = session->ctx; |
| |
| /* NETCONF handshake */ |
| if (nc_handshake_io(session) != NC_MSG_HELLO) { |
| goto fail; |
| } |
| session->status = NC_STATUS_RUNNING; |
| |
| if (nc_ctx_check_and_fill(session) == -1) { |
| goto fail; |
| } |
| |
| session->flags |= NC_SESSION_CALLHOME; |
| |
| /* store information into session */ |
| session->host = strdup(host); |
| session->port = port; |
| session->username = strdup("certificate-based"); |
| |
| return session; |
| |
| fail: |
| nc_session_free(session, NULL); |
| nc_tls_ctx_destroy_wrap(&tls_ctx); |
| return NULL; |
| } |