diff --git a/CMakeLists.txt b/CMakeLists.txt index c39d9775..5da3806a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,7 @@ option(ENABLE_EXAMPLES "Build examples" ON) option(ENABLE_COVERAGE "Build code coverage report from tests" OFF) option(ENABLE_SSH "Enable NETCONF over SSH support (via libssh)" ON) option(ENABLE_TLS "Enable NETCONF over TLS support (via OpenSSL)" ON) +# option(ENABLE_SSH_TLS "Enable NETCONF over SSH and TLS support (via libssh and OpenSSL)" ON) option(ENABLE_DNSSEC "Enable support for SSHFP retrieval using DNSSEC for SSH (requires OpenSSL and libval)" OFF) set(READ_INACTIVE_TIMEOUT 20 CACHE STRING "Maximum number of seconds waiting for new data once some data have arrived") set(READ_ACTIVE_TIMEOUT 300 CACHE STRING "Maximum number of seconds for receiving a full message") @@ -115,19 +116,21 @@ set(libsrc src/server_config.c src/server_config_ks.c src/server_config_ts.c - src/config_new_ssh.c) + src/config_new.c) if(ENABLE_SSH) list(APPEND libsrc src/session_client_ssh.c - src/session_server_ssh.c) + src/session_server_ssh.c + src/config_new_ssh.c) set(SSH_MACRO "#ifndef NC_ENABLED_SSH\n#define NC_ENABLED_SSH\n#endif") endif() if(ENABLE_TLS) list(APPEND libsrc src/session_client_tls.c - src/session_server_tls.c) + src/session_server_tls.c + src/config_new_tls.c) set(TLS_MACRO "#ifndef NC_ENABLED_TLS\n#define NC_ENABLED_TLS\n#endif") endif() @@ -145,7 +148,7 @@ set(headers # files to generate doxygen from set(doxy_files - src/libnetconf.h + doc/libnetconf.doc src/log.h src/netconf.h src/session.h @@ -233,14 +236,11 @@ check_function_exists(pthread_rwlockattr_setkind_np HAVE_PTHREAD_RWLOCKATTR_SETK # dependencies - openssl if(ENABLE_TLS OR ENABLE_DNSSEC OR ENABLE_SSH) - find_package(OpenSSL REQUIRED) + find_package(OpenSSL 3.0.0 REQUIRED) if(ENABLE_TLS) message(STATUS "OpenSSL found, required for TLS") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNC_ENABLED_TLS") endif() - if(OPENSSL_VERSION VERSION_LESS 1.1.1) - message(WARNING "OpenSSL version ${OPENSSL_VERSION} is no longer maintained, consider an update.") - endif() target_link_libraries(netconf2 ${OPENSSL_LIBRARIES}) include_directories(${OPENSSL_INCLUDE_DIR}) @@ -248,10 +248,7 @@ endif() # dependencies - libssh if(ENABLE_SSH) - find_package(LibSSH 0.7.1 REQUIRED) - if(LIBSSH_VERSION VERSION_EQUAL 0.9.3 OR LIBSSH_VERSION VERSION_EQUAL 0.9.4) - message(FATAL_ERROR "LibSSH ${LIBSSH_VERSION} includes regression bugs and libnetconf2 will NOT work properly, try to use another version") - endif() + find_package(LibSSH 0.9.5 REQUIRED) target_link_libraries(netconf2 ${LIBSSH_LIBRARIES}) list(APPEND CMAKE_REQUIRED_LIBRARIES ${LIBSSH_LIBRARIES}) diff --git a/Doxyfile.in b/Doxyfile.in index 324e670f..445709fc 100644 --- a/Doxyfile.in +++ b/Doxyfile.in @@ -2184,7 +2184,7 @@ HIDE_UNDOC_RELATIONS = YES # set to NO # The default value is: NO. -HAVE_DOT = @HAVE_DOT@ +HAVE_DOT = YES # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed # to run in parallel. When set to 0 doxygen will base this on the number of diff --git a/src/libnetconf.h b/doc/libnetconf.doc similarity index 96% rename from src/libnetconf.h rename to doc/libnetconf.doc index a09c1983..83d0d450 100644 --- a/src/libnetconf.h +++ b/doc/libnetconf.doc @@ -1,32 +1,3 @@ -/** - * @file libnetconf.h - * @author Radek Krejci - * @author Michal Vasko - * @brief libnetconf2 main internal header. - * - * @copyright - * Copyright (c) 2015 - 2021 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 - */ - -#ifndef NC_LIBNETCONF_H_ -#define NC_LIBNETCONF_H_ - -#include "config.h" -#include "log_p.h" -#include "messages_p.h" -#include "netconf.h" -#include "session_p.h" - -/* Tests whether string is empty or non-empty. */ -#define strisempty(str) ((str)[0] == '\0') -#define strnonempty(str) ((str)[0] != '\0') - /** * @mainpage About * @@ -664,5 +635,3 @@ * @defgroup server Server * @brief NETCONF server functionality. */ - -#endif /* NC_LIBNETCONF_H_ */ diff --git a/examples/server.c b/examples/server.c index 2ca22155..bfa6ece8 100644 --- a/examples/server.c +++ b/examples/server.c @@ -238,19 +238,19 @@ init(struct ly_ctx **context, struct nc_pollsession **ps, const char *path, NC_T /* this is where the YANG configuration data gets generated, * start by creating hostkey configuration data */ - rc = nc_server_config_new_ssh_hostkey(hostkey_path, NULL, *context, "endpt", "hostkey", &config); + rc = nc_server_config_new_ssh_hostkey(*context, "endpt", "hostkey", hostkey_path, NULL, &config); if (rc) { ERR_MSG_CLEANUP("Error creating new hostkey configuration data.\n"); } /* create address and port configuration data */ - rc = nc_server_config_new_ssh_address_port(SSH_ADDRESS, SSH_PORT, *context, "endpt", &config); + rc = nc_server_config_new_address_port(*context, "endpt", NC_TI_LIBSSH, SSH_ADDRESS, SSH_PORT, &config); if (rc) { ERR_MSG_CLEANUP("Error creating new address and port configuration data.\n"); } /* create client authentication configuration data */ - rc = nc_server_config_new_ssh_client_auth_password(SSH_PASSWORD, *context, "endpt", SSH_USERNAME, &config); + rc = nc_server_config_new_ssh_client_auth_password(*context, "endpt", SSH_USERNAME, SSH_PASSWORD, &config); if (rc) { ERR_MSG_CLEANUP("Error creating client authentication configuration data.\n"); } diff --git a/src/config_new.c b/src/config_new.c new file mode 100644 index 00000000..17b227fc --- /dev/null +++ b/src/config_new.c @@ -0,0 +1,693 @@ +/** + * @file config_new.c + * @author Roman Janota + * @brief libnetconf2 server new configuration creation functions + * + * @copyright + * Copyright (c) 2023 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 + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "compat.h" +#include "config_new.h" +#include "log_p.h" +#include "session.h" +#include "session_p.h" + +int +nc_config_new_check_add_operation(const struct ly_ctx *ctx, struct lyd_node *top) +{ + if (lyd_find_meta(top->meta, NULL, "yang:operation")) { + /* it already has operation attribute */ + return 0; + } + + /* give the top level container create operation */ + if (lyd_new_meta(ctx, top, NULL, "yang:operation", "create", 0, NULL)) { + return 1; + } + + return 0; +} + +const char * +nc_config_new_privkey_format_to_identityref(NC_PRIVKEY_FORMAT format) +{ + switch (format) { + case NC_PRIVKEY_FORMAT_RSA: + return "ietf-crypto-types:rsa-private-key-format"; + case NC_PRIVKEY_FORMAT_EC: + return "ietf-crypto-types:ec-private-key-format"; + case NC_PRIVKEY_FORMAT_X509: + return "libnetconf2-netconf-server:subject-private-key-info-format"; + case NC_PRIVKEY_FORMAT_OPENSSH: + return "libnetconf2-netconf-server:openssh-private-key-format"; + default: + ERR(NULL, "Private key type not supported."); + return NULL; + } +} + +int +nc_server_config_new_read_certificate(const char *cert_path, char **cert) +{ + int ret = 0, cert_len; + X509 *x509 = NULL; + FILE *f = NULL; + BIO *bio = NULL; + char *c = NULL; + + *cert = NULL; + + f = fopen(cert_path, "r"); + if (!f) { + ERR(NULL, "Unable to open certificate file \"%s\".", cert_path); + ret = 1; + goto cleanup; + } + + /* load the cert into memory */ + x509 = PEM_read_X509(f, NULL, NULL, NULL); + if (!x509) { + ret = -1; + goto cleanup; + } + + bio = BIO_new(BIO_s_mem()); + if (!bio) { + ret = -1; + goto cleanup; + } + + ret = PEM_write_bio_X509(bio, x509); + if (!ret) { + ret = -1; + goto cleanup; + } + + cert_len = BIO_pending(bio); + if (cert_len <= 0) { + ret = -1; + goto cleanup; + } + + c = malloc(cert_len + 1); + if (!c) { + ERRMEM; + ret = 1; + goto cleanup; + } + + /* read the cert from bio */ + ret = BIO_read(bio, c, cert_len); + if (ret <= 0) { + ret = -1; + goto cleanup; + } + c[cert_len] = '\0'; + + /* strip the cert of the header and footer */ + *cert = strdup(c + strlen(NC_PEM_CERTIFICATE_HEADER)); + if (!*cert) { + ERRMEM; + ret = 1; + goto cleanup; + } + + (*cert)[strlen(*cert) - strlen(NC_PEM_CERTIFICATE_FOOTER)] = '\0'; + + ret = 0; + +cleanup: + if (ret == -1) { + ERR(NULL, "Error getting certificate from file \"%s\" (OpenSSL Error): \"%s\".", cert_path, ERR_reason_error_string(ERR_get_error())); + ret = 1; + } + if (f) { + fclose(f); + } + + BIO_free(bio); + X509_free(x509); + free(c); + return ret; +} + +static int +nc_server_config_new_read_ssh2_pubkey(FILE *f, char **pubkey) +{ + char *buffer = NULL; + size_t size = 0, pubkey_len = 0; + void *tmp; + ssize_t read; + int ret = 0; + + while ((read = getline(&buffer, &size, f)) > 0) { + if (!strncmp(buffer, "----", 4)) { + continue; + } + + if (!strncmp(buffer, "Comment:", 8)) { + continue; + } + + if (buffer[read - 1] == '\n') { + read--; + } + + tmp = realloc(*pubkey, pubkey_len + read + 1); + if (!tmp) { + ERRMEM; + ret = 1; + goto cleanup; + } + + *pubkey = tmp; + memcpy(*pubkey + pubkey_len, buffer, read); + pubkey_len += read; + } + + if (!pubkey_len) { + ERR(NULL, "Unexpected public key format."); + ret = 1; + goto cleanup; + } + + (*pubkey)[pubkey_len] = '\0'; + +cleanup: + free(buffer); + return ret; +} + +static int +nc_server_config_new_read_pubkey_openssl(FILE *f, char **pubkey) +{ + int ret = 0; + EVP_PKEY *pkey; + BIO *bio; + char *key = NULL; + int pub_len; + + /* read the pubkey from file */ + pkey = PEM_read_PUBKEY(f, NULL, NULL, NULL); + if (!pkey) { + ret = -1; + goto cleanup; + } + + bio = BIO_new(BIO_s_mem()); + if (!bio) { + ret = -1; + goto cleanup; + } + + /* write the pubkey into bio */ + ret = PEM_write_bio_PUBKEY(bio, pkey); + if (!ret) { + ret = -1; + goto cleanup; + } + + pub_len = BIO_pending(bio); + if (pub_len <= 0) { + ret = -1; + goto cleanup; + } + + /* get pubkey's length */ + key = malloc(pub_len + 1); + if (!key) { + ERRMEM; + ret = 1; + goto cleanup; + } + + /* read the public key from bio */ + ret = BIO_read(bio, key, pub_len); + if (ret <= 0) { + ret = -1; + goto cleanup; + } + key[pub_len] = '\0'; + + /* strip the pubkey of the header and footer */ + *pubkey = strdup(key + strlen(NC_SUBJECT_PUBKEY_INFO_HEADER)); + if (!*pubkey) { + ERRMEM; + ret = 1; + goto cleanup; + } + + (*pubkey)[strlen(*pubkey) - strlen(NC_SUBJECT_PUBKEY_INFO_FOOTER)] = '\0'; + + ret = 0; +cleanup: + if (ret == -1) { + ERR(NULL, "Error getting public key from file (OpenSSL Error): \"%s\".", ERR_reason_error_string(ERR_get_error())); + ret = 1; + } + + BIO_free(bio); + EVP_PKEY_free(pkey); + free(key); + + return ret; +} + +#ifdef NC_ENABLED_SSH + +static int +nc_server_config_new_read_pubkey_libssh(const char *pubkey_path, char **pubkey) +{ + int ret = 0; + ssh_key pub_sshkey = NULL; + + ret = ssh_pki_import_pubkey_file(pubkey_path, &pub_sshkey); + if (ret) { + ERR(NULL, "Importing public key from file \"%s\" failed.", pubkey_path); + return ret; + } + + ret = ssh_pki_export_pubkey_base64(pub_sshkey, pubkey); + if (ret) { + ERR(NULL, "Exporting public key to base64 failed."); + } + + ssh_key_free(pub_sshkey); + return ret; +} + +#endif /* NC_ENABLED_SSH */ + +int +nc_server_config_new_get_pubkey(const char *pubkey_path, char **pubkey, NC_PUBKEY_FORMAT *pubkey_type) +{ + int ret = 0; + FILE *f = NULL; + char *header = NULL; + size_t len = 0; + + NC_CHECK_ARG_RET(NULL, pubkey, pubkey_type, 1); + + *pubkey = NULL; + + f = fopen(pubkey_path, "r"); + if (!f) { + ERR(NULL, "Unable to open file \"%s\".", pubkey_path); + ret = 1; + goto cleanup; + } + + if (getline(&header, &len, f) < 0) { + ERR(NULL, "Error reading header from file \"%s\".", pubkey_path); + ret = 1; + goto cleanup; + } + rewind(f); + + if (!strncmp(header, NC_SUBJECT_PUBKEY_INFO_HEADER, strlen(NC_SUBJECT_PUBKEY_INFO_HEADER))) { + /* it's subject public key info public key */ + ret = nc_server_config_new_read_pubkey_openssl(f, pubkey); + *pubkey_type = NC_PUBKEY_FORMAT_X509; + } else if (!strncmp(header, NC_SSH2_PUBKEY_HEADER, strlen(NC_SSH2_PUBKEY_HEADER))) { + /* it's ssh2 public key */ + ret = nc_server_config_new_read_ssh2_pubkey(f, pubkey); + *pubkey_type = NC_PUBKEY_FORMAT_SSH2; + } +#ifdef NC_ENABLED_SSH + else { + /* it's probably OpenSSH public key */ + ret = nc_server_config_new_read_pubkey_libssh(pubkey_path, pubkey); + *pubkey_type = NC_PUBKEY_FORMAT_SSH2; + } +#endif /* NC_ENABLED_SSH */ + + if (ret) { + ERR(NULL, "Error getting public key from file \"%s\".", pubkey_path); + goto cleanup; + } + +cleanup: + if (f) { + fclose(f); + } + + free(header); + + return ret; +} + +static int +nc_server_config_new_get_privkey_openssl(FILE *f, char **privkey, EVP_PKEY **priv_pkey) +{ + int ret = 0, priv_len; + BIO *bio = NULL; + + NC_CHECK_ARG_RET(NULL, privkey, priv_pkey, 1); + + /* read private key from file */ + *priv_pkey = PEM_read_PrivateKey(f, NULL, NULL, NULL); + if (!*priv_pkey) { + ret = -1; + goto cleanup; + } + + bio = BIO_new(BIO_s_mem()); + if (!bio) { + ret = -1; + goto cleanup; + } + + /* write the private key in to bio */ + ret = PEM_write_bio_PrivateKey(bio, *priv_pkey, NULL, NULL, 0, NULL, NULL); + if (!ret) { + ret = -1; + goto cleanup; + } + + priv_len = BIO_pending(bio); + if (priv_len <= 0) { + ret = -1; + goto cleanup; + } + + /* get private key's length */ + *privkey = malloc(priv_len + 1); + if (!*privkey) { + ERRMEM; + ret = 1; + goto cleanup; + } + + /* read the private key from bio */ + ret = BIO_read(bio, *privkey, priv_len); + if (ret <= 0) { + ret = -1; + goto cleanup; + } + (*privkey)[priv_len] = '\0'; + + ret = 0; +cleanup: + if (ret < 0) { + ERR(NULL, "Getting private key from file failed (%s).", ERR_reason_error_string(ERR_get_error())); + } + BIO_free(bio); + return ret; +} + +static int +nc_server_config_new_privkey_to_pubkey_openssl(EVP_PKEY *priv_pkey, char **pubkey) +{ + int ret = 0, pub_len; + BIO *bio = NULL; + + bio = BIO_new(BIO_s_mem()); + if (!bio) { + ret = -1; + goto cleanup; + } + + /* write the pubkey into bio */ + ret = PEM_write_bio_PUBKEY(bio, priv_pkey); + if (!ret) { + ret = -1; + goto cleanup; + } + + /* get the length of the pubkey */ + pub_len = BIO_pending(bio); + if (pub_len <= 0) { + ret = -1; + goto cleanup; + } + + *pubkey = malloc(pub_len + 1); + if (!*pubkey) { + ERRMEM; + ret = 1; + goto cleanup; + } + + /* read the pubkey from the bio */ + ret = BIO_read(bio, *pubkey, pub_len); + if (ret <= 0) { + ret = -1; + goto cleanup; + } + (*pubkey)[pub_len] = '\0'; + + ret = 0; + +cleanup: + if (ret < 0) { + ERR(NULL, "Converting private to public key failed (%s).", ERR_reason_error_string(ERR_get_error())); + } + BIO_free(bio); + return ret; +} + +static int +nc_server_config_new_privkey_to_pubkey_libssh(const ssh_key priv_sshkey, char **pubkey) +{ + int ret; + ssh_key pub_sshkey = NULL; + + ret = ssh_pki_export_privkey_to_pubkey(priv_sshkey, &pub_sshkey); + if (ret) { + ERR(NULL, "Exporting privkey to pubkey failed."); + return ret; + } + + ret = ssh_pki_export_pubkey_base64(pub_sshkey, pubkey); + if (ret) { + ERR(NULL, "Exporting pubkey to base64 failed."); + } + + ssh_key_free(pub_sshkey); + return ret; +} + +static int +nc_server_config_new_privkey_to_pubkey(EVP_PKEY *priv_pkey, const ssh_key priv_sshkey, NC_PRIVKEY_FORMAT privkey_type, char **pubkey, NC_PUBKEY_FORMAT *pubkey_type) +{ + switch (privkey_type) { +#ifdef NC_ENABLED_SSH + case NC_PRIVKEY_FORMAT_RSA: + case NC_PRIVKEY_FORMAT_EC: + case NC_PRIVKEY_FORMAT_OPENSSH: + *pubkey_type = NC_PUBKEY_FORMAT_SSH2; + return nc_server_config_new_privkey_to_pubkey_libssh(priv_sshkey, pubkey); +#endif /* NC_ENABLED_SSH */ + case NC_PRIVKEY_FORMAT_X509: + *pubkey_type = NC_PUBKEY_FORMAT_X509; + return nc_server_config_new_privkey_to_pubkey_openssl(priv_pkey, pubkey); + default: + break; + } + + return 1; +} + +#ifdef NC_ENABLED_SSH + +static int +nc_server_config_new_get_privkey_libssh(const char *privkey_path, char **privkey, ssh_key *priv_sshkey) +{ + int ret; + + *priv_sshkey = NULL; + + ret = ssh_pki_import_privkey_file(privkey_path, NULL, NULL, NULL, priv_sshkey); + if (ret) { + ERR(NULL, "Importing privkey from file \"%s\" failed.", privkey_path); + return ret; + } + + ret = ssh_pki_export_privkey_base64(*priv_sshkey, NULL, NULL, NULL, privkey); + if (ret) { + ERR(NULL, "Exporting privkey from file \"%s\" to base64 failed.", privkey_path); + } + + return ret; +} + +#endif /* NC_ENABLED_SSH */ + +int +nc_server_config_new_get_keys(const char *privkey_path, const char *pubkey_path, + char **privkey, char **pubkey, NC_PRIVKEY_FORMAT *privkey_type, NC_PUBKEY_FORMAT *pubkey_type) +{ + int ret = 0; + EVP_PKEY *priv_pkey = NULL; + ssh_key priv_sshkey = NULL; + FILE *f_privkey = NULL; + char *header = NULL; + size_t len = 0; + + NC_CHECK_ARG_RET(NULL, privkey_path, privkey, pubkey, privkey_type, 1); + + *privkey = NULL; + *pubkey = NULL; + + /* get private key first */ + f_privkey = fopen(privkey_path, "r"); + if (!f_privkey) { + ERR(NULL, "Unable to open file \"%s\".", privkey_path); + ret = 1; + goto cleanup; + } + + if (getline(&header, &len, f_privkey) < 0) { + ERR(NULL, "Error reading header from file \"%s\".", privkey_path); + ret = 1; + goto cleanup; + } + rewind(f_privkey); + + if (!strncmp(header, NC_PKCS8_PRIVKEY_HEADER, strlen(NC_PKCS8_PRIVKEY_HEADER))) { + /* it's PKCS8 (X.509) private key */ + *privkey_type = NC_PRIVKEY_FORMAT_X509; + ret = nc_server_config_new_get_privkey_openssl(f_privkey, privkey, &priv_pkey); + } +#ifdef NC_ENABLED_SSH + else if (!strncmp(header, NC_OPENSSH_PRIVKEY_HEADER, strlen(NC_OPENSSH_PRIVKEY_HEADER))) { + /* it's OpenSSH private key */ + *privkey_type = NC_PRIVKEY_FORMAT_OPENSSH; + ret = nc_server_config_new_get_privkey_libssh(privkey_path, privkey, &priv_sshkey); + } else if (!strncmp(header, NC_PKCS1_RSA_PRIVKEY_HEADER, strlen(NC_PKCS1_RSA_PRIVKEY_HEADER))) { + /* it's RSA privkey in PKCS1 format */ + *privkey_type = NC_PRIVKEY_FORMAT_RSA; + ret = nc_server_config_new_get_privkey_libssh(privkey_path, privkey, &priv_sshkey); + } else if (!strncmp(header, NC_SEC1_EC_PRIVKEY_HEADER, strlen(NC_SEC1_EC_PRIVKEY_HEADER))) { + /* it's EC privkey in SEC1 format */ + *privkey_type = NC_PRIVKEY_FORMAT_EC; + ret = nc_server_config_new_get_privkey_libssh(privkey_path, privkey, &priv_sshkey); + } +#endif /* NC_ENABLED_SSH */ + else { + ERR(NULL, "Private key format not supported."); + ret = 1; + goto cleanup; + } + + if (ret) { + goto cleanup; + } + + if (pubkey_path) { + ret = nc_server_config_new_get_pubkey(pubkey_path, pubkey, pubkey_type); + } else { + ret = nc_server_config_new_privkey_to_pubkey(priv_pkey, priv_sshkey, *privkey_type, pubkey, pubkey_type); + } + + if (ret) { + ERR(NULL, "Getting public key failed."); + goto cleanup; + } + +cleanup: + if (f_privkey) { + fclose(f_privkey); + } + + free(header); + + ssh_key_free(priv_sshkey); + EVP_PKEY_free(priv_pkey); + + return ret; +} + +API int +nc_server_config_new_address_port(const struct ly_ctx *ctx, const char *endpt_name, NC_TRANSPORT_IMPL transport, + const char *address, const char *port, struct lyd_node **config) +{ + int ret = 0; + char *tree_path = NULL; + struct lyd_node *new_tree, *port_node; + + NC_CHECK_ARG_RET(NULL, address, port, ctx, endpt_name, config, 1); + + /* prepare path for instertion of leaves later */ +#ifdef NC_ENABLED_SSH + if (transport == NC_TI_LIBSSH) { + asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/ssh/tcp-server-parameters", endpt_name); + } +#endif +#ifdef NC_ENABLED_TLS + if (transport == NC_TI_OPENSSL) { + asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/tls/tcp-server-parameters", endpt_name); + } +#endif + if (!tree_path) { + ERR(NULL, "Transport not supported or memory allocation error."); + ret = 1; + goto cleanup; + } + + /* create all the nodes in the path */ + ret = lyd_new_path(*config, ctx, tree_path, NULL, LYD_NEW_PATH_UPDATE, &new_tree); + if (ret) { + goto cleanup; + } + if (!*config) { + *config = new_tree; + } + + if (!new_tree) { + /* no new nodes were created */ + ret = lyd_find_path(*config, tree_path, 0, &new_tree); + } else { + /* config was NULL */ + ret = lyd_find_path(new_tree, tree_path, 0, &new_tree); + } + if (ret) { + ERR(NULL, "Unable to find tcp-server-parameters container."); + goto cleanup; + } + + ret = lyd_new_term(new_tree, NULL, "local-address", address, 0, NULL); + if (ret) { + goto cleanup; + } + + ret = lyd_find_path(new_tree, "local-port", 0, &port_node); + if (!ret) { + ret = lyd_change_term(port_node, port); + } else if (ret == LY_ENOTFOUND) { + ret = lyd_new_term(new_tree, NULL, "local-port", port, 0, NULL); + } + + if (ret && (ret != LY_EEXIST) && (ret != LY_ENOT)) { + /* only fail if there was actually an error */ + goto cleanup; + } + + /* Add all default nodes */ + ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL); + if (ret) { + goto cleanup; + } +cleanup: + free(tree_path); + return ret; +} diff --git a/src/config_new_ssh.h b/src/config_new.h similarity index 66% rename from src/config_new_ssh.h rename to src/config_new.h index 0f03da66..747b648a 100644 --- a/src/config_new_ssh.h +++ b/src/config_new.h @@ -1,7 +1,7 @@ /** - * @file config_new_ssh.h + * @file config_new.h * @author Roman Janota - * @brief libnetconf2 server new configuration creation + * @brief libnetconf2 server new configuration creation header * * @copyright * Copyright (c) 2023 CESNET, z.s.p.o. @@ -13,8 +13,8 @@ * https://opensource.org/licenses/BSD-3-Clause */ -#ifndef NC_CONFIG_NEW_SSH_H_ -#define NC_CONFIG_NEW_SSH_H_ +#ifndef NC_CONFIG_NEW_H_ +#define NC_CONFIG_NEW_H_ #include @@ -57,6 +57,12 @@ extern "C" { /* public key's SubjectPublicKeyInfo format footer */ #define NC_SUBJECT_PUBKEY_INFO_FOOTER "\n-----END PUBLIC KEY-----\n" +/* certificate's PEM format header */ +#define NC_PEM_CERTIFICATE_HEADER "-----BEGIN CERTIFICATE-----\n" + +/* certificate's PEM format footer */ +#define NC_PEM_CERTIFICATE_FOOTER "\n-----END CERTIFICATE-----\n" + typedef enum { NC_ALG_HOSTKEY, NC_ALG_KEY_EXCHANGE, @@ -64,8 +70,19 @@ typedef enum { NC_ALG_MAC } NC_ALG_TYPE; +int nc_server_config_new_get_keys(const char *privkey_path, const char *pubkey_path, + char **privkey, char **pubkey, NC_PRIVKEY_FORMAT *privkey_type, NC_PUBKEY_FORMAT *pubkey_type); + +int nc_server_config_new_get_pubkey(const char *pubkey_path, char **pubkey, NC_PUBKEY_FORMAT *pubkey_type); + +int nc_server_config_new_read_certificate(const char *cert_path, char **cert); + +int nc_config_new_check_add_operation(const struct ly_ctx *ctx, struct lyd_node *top); + +const char * nc_config_new_privkey_format_to_identityref(NC_PRIVKEY_FORMAT format); + #ifdef __cplusplus } #endif -#endif /* NC_CONFIG_NEW_SSH_H_ */ +#endif /* NC_CONFIG_NEW_H_ */ diff --git a/src/config_new_ssh.c b/src/config_new_ssh.c index dd18b96f..a64a8a14 100644 --- a/src/config_new_ssh.c +++ b/src/config_new_ssh.c @@ -1,7 +1,7 @@ /** * @file config_new_ssh.c * @author Roman Janota - * @brief libnetconf2 server new configuration creation functions + * @brief libnetconf2 server new SSH configuration creation functions * * @copyright * Copyright (c) 2023 CESNET, z.s.p.o. @@ -15,493 +15,41 @@ #define _GNU_SOURCE -#include #include -#include #include #include #include #include -#include -#include -#include -#include -#include +#include #include "compat.h" -#include "config_new_ssh.h" -#include "libnetconf.h" +#include "config.h" +#include "config_new.h" +#include "log_p.h" #include "server_config.h" -#include "session_server.h" +#include "session_p.h" #if !defined (HAVE_CRYPT_R) extern pthread_mutex_t crypt_lock; #endif -static int -nc_server_config_new_ssh_read_ssh2_pubkey(FILE *f, char **pubkey) -{ - char *buffer = NULL; - size_t size = 0, pubkey_len = 0; - void *tmp; - ssize_t read; - int ret = 0; - - while ((read = getline(&buffer, &size, f)) > 0) { - if (!strncmp(buffer, "----", 4)) { - continue; - } - - if (!strncmp(buffer, "Comment:", 8)) { - continue; - } - - if (buffer[read - 1] == '\n') { - read--; - } - - tmp = realloc(*pubkey, pubkey_len + read + 1); - if (!tmp) { - ERRMEM; - ret = 1; - goto cleanup; - } - - *pubkey = tmp; - memcpy(*pubkey + pubkey_len, buffer, read); - pubkey_len += read; - } - - if (!pubkey_len) { - ERR(NULL, "Unexpected public key format."); - ret = 1; - goto cleanup; - } - - (*pubkey)[pubkey_len] = '\0'; - -cleanup: - free(buffer); - return ret; -} - -static int -nc_server_config_new_ssh_read_pubkey_openssl(FILE *f, char **pubkey) -{ - int ret = 0; - EVP_PKEY *pkey; - BIO *bio; - char *key = NULL; - int pub_len; - - /* read the pubkey from file */ - pkey = PEM_read_PUBKEY(f, NULL, NULL, NULL); - if (!pkey) { - ret = -1; - goto cleanup; - } - - bio = BIO_new(BIO_s_mem()); - if (!bio) { - ret = -1; - goto cleanup; - } - - /* write the pubkey into bio */ - ret = PEM_write_bio_PUBKEY(bio, pkey); - if (!ret) { - ret = -1; - goto cleanup; - } - - pub_len = BIO_pending(bio); - if (pub_len <= 0) { - ret = -1; - goto cleanup; - } - - /* get pubkey's length */ - key = malloc(pub_len + 1); - if (!key) { - ERRMEM; - ret = 1; - goto cleanup; - } - - /* read the public key from bio */ - ret = BIO_read(bio, key, pub_len); - if (ret <= 0) { - ret = -1; - goto cleanup; - } - key[pub_len] = '\0'; - - /* strip the pubkey of the header and footer */ - *pubkey = strdup(key + strlen(NC_SUBJECT_PUBKEY_INFO_HEADER)); - if (!*pubkey) { - ERRMEM; - ret = 1; - goto cleanup; - } - - (*pubkey)[strlen(*pubkey) - strlen(NC_SUBJECT_PUBKEY_INFO_FOOTER)] = '\0'; - - ret = 0; -cleanup: - if (ret == -1) { - ERR(NULL, "Error getting public key from file (OpenSSL Error): \"%s\".", ERR_reason_error_string(ERR_get_error())); - ret = 1; - } - - BIO_free(bio); - EVP_PKEY_free(pkey); - free(key); - - return ret; -} - -static int -nc_server_config_new_ssh_read_pubkey_libssh(const char *pubkey_path, char **pubkey) -{ - int ret = 0; - ssh_key pub_sshkey = NULL; - - ret = ssh_pki_import_pubkey_file(pubkey_path, &pub_sshkey); - if (ret) { - ERR(NULL, "Importing public key from file \"%s\" failed.", pubkey_path); - return ret; - } - - ret = ssh_pki_export_pubkey_base64(pub_sshkey, pubkey); - if (ret) { - ERR(NULL, "Exporting public key to base64 failed."); - } - - ssh_key_free(pub_sshkey); - return ret; -} - -static int -nc_server_config_new_ssh_get_pubkey(const char *pubkey_path, char **pubkey, NC_SSH_PUBKEY_TYPE *pubkey_type) -{ - int ret = 0; - FILE *f = NULL; - char *header = NULL; - size_t len = 0; - - NC_CHECK_ARG_RET(NULL, pubkey, pubkey_type, 1); - - *pubkey = NULL; - - f = fopen(pubkey_path, "r"); - if (!f) { - ERR(NULL, "Unable to open file \"%s\".", pubkey_path); - ret = 1; - goto cleanup; - } - - if (getline(&header, &len, f) < 0) { - ERR(NULL, "Error reading header from file \"%s\".", pubkey_path); - ret = 1; - goto cleanup; - } - rewind(f); - - if (!strncmp(header, NC_SUBJECT_PUBKEY_INFO_HEADER, strlen(NC_SUBJECT_PUBKEY_INFO_HEADER))) { - /* it's subject public key info public key */ - ret = nc_server_config_new_ssh_read_pubkey_openssl(f, pubkey); - *pubkey_type = NC_SSH_PUBKEY_X509; - } else if (!strncmp(header, NC_SSH2_PUBKEY_HEADER, strlen(NC_SSH2_PUBKEY_HEADER))) { - /* it's ssh2 public key */ - ret = nc_server_config_new_ssh_read_ssh2_pubkey(f, pubkey); - *pubkey_type = NC_SSH_PUBKEY_SSH2; - } else { - /* it's probably OpenSSH public key */ - ret = nc_server_config_new_ssh_read_pubkey_libssh(pubkey_path, pubkey); - *pubkey_type = NC_SSH_PUBKEY_SSH2; - } - - if (ret) { - ERR(NULL, "Error getting public key from file \"%s\".", pubkey_path); - goto cleanup; - } - -cleanup: - if (f) { - fclose(f); - } - - free(header); - - return ret; -} - -static int -nc_server_config_new_ssh_get_privkey_openssl(FILE *f, char **privkey, EVP_PKEY **priv_pkey) -{ - int ret = 0, priv_len; - BIO *bio = NULL; - - NC_CHECK_ARG_RET(NULL, privkey, priv_pkey, 1); - - /* read private key from file */ - *priv_pkey = PEM_read_PrivateKey(f, NULL, NULL, NULL); - if (!*priv_pkey) { - ret = -1; - goto cleanup; - } - - bio = BIO_new(BIO_s_mem()); - if (!bio) { - ret = -1; - goto cleanup; - } - - /* write the private key in to bio */ - ret = PEM_write_bio_PrivateKey(bio, *priv_pkey, NULL, NULL, 0, NULL, NULL); - if (!ret) { - ret = -1; - goto cleanup; - } - - priv_len = BIO_pending(bio); - if (priv_len <= 0) { - ret = -1; - goto cleanup; - } - - /* get private key's length */ - *privkey = malloc(priv_len + 1); - if (!*privkey) { - ERRMEM; - ret = 1; - goto cleanup; - } - - /* read the private key from bio */ - ret = BIO_read(bio, *privkey, priv_len); - if (ret <= 0) { - ret = -1; - goto cleanup; - } - (*privkey)[priv_len] = '\0'; - - ret = 0; -cleanup: - if (ret < 0) { - ERR(NULL, "Getting private key from file failed (%s).", ERR_reason_error_string(ERR_get_error())); - } - BIO_free(bio); - return ret; -} - -static int -nc_server_config_new_ssh_privkey_to_pubkey_openssl(EVP_PKEY *priv_pkey, char **pubkey) -{ - int ret = 0, pub_len; - BIO *bio = NULL; - - bio = BIO_new(BIO_s_mem()); - if (!bio) { - ret = -1; - goto cleanup; - } - - /* write the pubkey into bio */ - ret = PEM_write_bio_PUBKEY(bio, priv_pkey); - if (!ret) { - ret = -1; - goto cleanup; - } - - /* get the length of the pubkey */ - pub_len = BIO_pending(bio); - if (pub_len <= 0) { - ret = -1; - goto cleanup; - } - - *pubkey = malloc(pub_len + 1); - if (!*pubkey) { - ERRMEM; - ret = 1; - goto cleanup; - } - - /* read the pubkey from the bio */ - ret = BIO_read(bio, *pubkey, pub_len); - if (ret <= 0) { - ret = -1; - goto cleanup; - } - (*pubkey)[pub_len] = '\0'; - - ret = 0; - -cleanup: - if (ret < 0) { - ERR(NULL, "Converting private to public key failed (%s).", ERR_reason_error_string(ERR_get_error())); - } - BIO_free(bio); - return ret; -} - -static int -nc_server_config_new_ssh_privkey_to_pubkey_libssh(const ssh_key priv_sshkey, char **pubkey) -{ - int ret; - ssh_key pub_sshkey = NULL; - - ret = ssh_pki_export_privkey_to_pubkey(priv_sshkey, &pub_sshkey); - if (ret) { - ERR(NULL, "Exporting privkey to pubkey failed."); - return ret; - } - - ret = ssh_pki_export_pubkey_base64(pub_sshkey, pubkey); - if (ret) { - ERR(NULL, "Exporting pubkey to base64 failed."); - } - - ssh_key_free(pub_sshkey); - return ret; -} - -static int -nc_server_config_new_ssh_privkey_to_pubkey(EVP_PKEY *priv_pkey, const ssh_key priv_sshkey, NC_PRIVKEY_FORMAT privkey_type, char **pubkey, NC_SSH_PUBKEY_TYPE *pubkey_type) -{ - switch (privkey_type) { - case NC_PRIVKEY_FORMAT_RSA: - case NC_PRIVKEY_FORMAT_EC: - case NC_PRIVKEY_FORMAT_OPENSSH: - *pubkey_type = NC_SSH_PUBKEY_SSH2; - return nc_server_config_new_ssh_privkey_to_pubkey_libssh(priv_sshkey, pubkey); - case NC_PRIVKEY_FORMAT_PKCS8: - *pubkey_type = NC_SSH_PUBKEY_X509; - return nc_server_config_new_ssh_privkey_to_pubkey_openssl(priv_pkey, pubkey); - } - - return 1; -} - -static int -nc_server_config_new_ssh_get_privkey_libssh(const char *privkey_path, char **privkey, ssh_key *priv_sshkey) -{ - int ret; - - *priv_sshkey = NULL; - - ret = ssh_pki_import_privkey_file(privkey_path, NULL, NULL, NULL, priv_sshkey); - if (ret) { - ERR(NULL, "Importing privkey from file \"%s\" failed.", privkey_path); - return ret; - } - - ret = ssh_pki_export_privkey_base64(*priv_sshkey, NULL, NULL, NULL, privkey); - if (ret) { - ERR(NULL, "Exporting privkey from file \"%s\" to base64 failed.", privkey_path); - } - - return ret; -} - -static int -nc_server_config_new_ssh_get_keys(const char *privkey_path, const char *pubkey_path, - char **privkey, char **pubkey, NC_PRIVKEY_FORMAT *privkey_type, NC_SSH_PUBKEY_TYPE *pubkey_type) -{ - int ret = 0; - EVP_PKEY *priv_pkey = NULL; - ssh_key priv_sshkey = NULL; - FILE *f_privkey = NULL; - char *header = NULL; - size_t len = 0; - - NC_CHECK_ARG_RET(NULL, privkey_path, privkey, pubkey, privkey_type, 1); - - *privkey = NULL; - *pubkey = NULL; - - /* get private key first */ - f_privkey = fopen(privkey_path, "r"); - if (!f_privkey) { - ERR(NULL, "Unable to open file \"%s\".", privkey_path); - ret = 1; - goto cleanup; - } - - if (getline(&header, &len, f_privkey) < 0) { - ERR(NULL, "Error reading header from file \"%s\".", privkey_path); - ret = 1; - goto cleanup; - } - rewind(f_privkey); - - if (!strncmp(header, NC_PKCS8_PRIVKEY_HEADER, strlen(NC_PKCS8_PRIVKEY_HEADER))) { - /* it's PKCS8 (X.509) private key */ - *privkey_type = NC_PRIVKEY_FORMAT_PKCS8; - ret = nc_server_config_new_ssh_get_privkey_openssl(f_privkey, privkey, &priv_pkey); - } else if (!strncmp(header, NC_OPENSSH_PRIVKEY_HEADER, strlen(NC_OPENSSH_PRIVKEY_HEADER))) { - /* it's OpenSSH private key */ - *privkey_type = NC_PRIVKEY_FORMAT_OPENSSH; - ret = nc_server_config_new_ssh_get_privkey_libssh(privkey_path, privkey, &priv_sshkey); - } else if (!strncmp(header, NC_PKCS1_RSA_PRIVKEY_HEADER, strlen(NC_PKCS1_RSA_PRIVKEY_HEADER))) { - /* it's RSA privkey in PKCS1 format */ - *privkey_type = NC_PRIVKEY_FORMAT_RSA; - ret = nc_server_config_new_ssh_get_privkey_libssh(privkey_path, privkey, &priv_sshkey); - } else if (!strncmp(header, NC_SEC1_EC_PRIVKEY_HEADER, strlen(NC_SEC1_EC_PRIVKEY_HEADER))) { - /* it's EC privkey in SEC1 format */ - *privkey_type = NC_PRIVKEY_FORMAT_EC; - ret = nc_server_config_new_ssh_get_privkey_libssh(privkey_path, privkey, &priv_sshkey); - } else { - ERR(NULL, "Private key format not supported."); - ret = 1; - goto cleanup; - } - - if (ret) { - goto cleanup; - } - - if (pubkey_path) { - ret = nc_server_config_new_ssh_get_pubkey(pubkey_path, pubkey, pubkey_type); - } else { - ret = nc_server_config_new_ssh_privkey_to_pubkey(priv_pkey, priv_sshkey, *privkey_type, pubkey, pubkey_type); - } - - if (ret) { - ERR(NULL, "Getting public key failed."); - goto cleanup; - } - -cleanup: - if (f_privkey) { - fclose(f_privkey); - } - - free(header); - - ssh_key_free(priv_sshkey); - EVP_PKEY_free(priv_pkey); - - return ret; -} - API int -nc_server_config_new_ssh_hostkey(const char *privkey_path, const char *pubkey_path, const struct ly_ctx *ctx, - const char *endpt_name, const char *hostkey_name, struct lyd_node **config) +nc_server_config_new_ssh_hostkey(const struct ly_ctx *ctx, + const char *endpt_name, const char *hostkey_name, const char *privkey_path, const char *pubkey_path, struct lyd_node **config) { int ret = 0; char *pubkey = NULL, *privkey = NULL, *pubkey_stripped, *privkey_stripped; struct lyd_node *new_tree; char *tree_path = NULL; NC_PRIVKEY_FORMAT privkey_type; - NC_SSH_PUBKEY_TYPE pubkey_type; + NC_PUBKEY_FORMAT pubkey_type; + const char *privkey_identity; NC_CHECK_ARG_RET(NULL, privkey_path, config, ctx, endpt_name, hostkey_name, 1); /* get the keys as a string from the given files */ - ret = nc_server_config_new_ssh_get_keys(privkey_path, pubkey_path, &privkey, &pubkey, &privkey_type, &pubkey_type); + ret = nc_server_config_new_get_keys(privkey_path, pubkey_path, &privkey, &pubkey, &privkey_type, &pubkey_type); if (ret) { ERR(NULL, "Getting keys from file(s) failed."); goto cleanup; @@ -525,12 +73,6 @@ nc_server_config_new_ssh_hostkey(const char *privkey_path, const char *pubkey_pa *config = new_tree; } - /* give the top level container create operation */ - ret = lyd_new_meta(ctx, *config, NULL, "yang:operation", "create", 0, NULL); - if (ret) { - goto cleanup; - } - /* find the node where leaves will get inserted */ ret = lyd_find_path(*config, tree_path, 0, &new_tree); if (ret) { @@ -538,7 +80,7 @@ nc_server_config_new_ssh_hostkey(const char *privkey_path, const char *pubkey_pa } /* insert pubkey format */ - if (pubkey_type == NC_SSH_PUBKEY_SSH2) { + if (pubkey_type == NC_PUBKEY_FORMAT_SSH2) { ret = lyd_new_term(new_tree, NULL, "public-key-format", "ietf-crypto-types:ssh-public-key-format", 0, NULL); } else { ret = lyd_new_term(new_tree, NULL, "public-key-format", "ietf-crypto-types:subject-public-key-info-format", 0, NULL); @@ -550,9 +92,9 @@ nc_server_config_new_ssh_hostkey(const char *privkey_path, const char *pubkey_pa /* strip pubkey's header and footer only if it's generated from pkcs8 key (using OpenSSL), * otherwise it's already stripped */ - if (!pubkey_path && (privkey_type == NC_PRIVKEY_FORMAT_PKCS8)) { - pubkey_stripped = pubkey + strlen("-----BEGIN PUBLIC KEY-----") + 1; - pubkey_stripped[strlen(pubkey_stripped) - strlen("-----END PUBLIC KEY-----") - 2] = '\0'; + if (!pubkey_path && (privkey_type == NC_PRIVKEY_FORMAT_X509)) { + pubkey_stripped = pubkey + strlen(NC_SUBJECT_PUBKEY_INFO_HEADER); + pubkey_stripped[strlen(pubkey_stripped) - strlen(NC_SUBJECT_PUBKEY_INFO_FOOTER)] = '\0'; } else { pubkey_stripped = pubkey; } @@ -563,20 +105,15 @@ nc_server_config_new_ssh_hostkey(const char *privkey_path, const char *pubkey_pa goto cleanup; } - /* insert private key format */ - if (privkey_type == NC_PRIVKEY_FORMAT_RSA) { - ret = lyd_new_term(new_tree, NULL, "private-key-format", "ietf-crypto-types:rsa-private-key-format", 0, NULL); - } else if (privkey_type == NC_PRIVKEY_FORMAT_EC) { - ret = lyd_new_term(new_tree, NULL, "private-key-format", "ietf-crypto-types:ec-private-key-format", 0, NULL); - } else if (privkey_type == NC_PRIVKEY_FORMAT_PKCS8) { - ret = lyd_new_term(new_tree, NULL, "private-key-format", "libnetconf2-netconf-server:subject-private-key-info-format", 0, NULL); - } else if (privkey_type == NC_PRIVKEY_FORMAT_OPENSSH) { - ret = lyd_new_term(new_tree, NULL, "private-key-format", "libnetconf2-netconf-server:openssh-private-key-format", 0, NULL); - } else { - ERR(NULL, "Private key type not supported."); + /* get privkey identityref value */ + privkey_identity = nc_config_new_privkey_format_to_identityref(privkey_type); + if (!privkey_identity) { ret = 1; + goto cleanup; } + /* insert private key format */ + ret = lyd_new_term(new_tree, NULL, "private-key-format", privkey_identity, 0, NULL); if (ret) { goto cleanup; } @@ -596,81 +133,21 @@ nc_server_config_new_ssh_hostkey(const char *privkey_path, const char *pubkey_pa goto cleanup; } - /* Add all default nodes */ - ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL); + /* check if top-level container has operation and if not, add it */ + ret = nc_config_new_check_add_operation(ctx, *config); if (ret) { goto cleanup; } -cleanup: - free(privkey); - free(pubkey); - free(tree_path); - return ret; -} - -API int -nc_server_config_new_ssh_address_port(const char *address, const char *port, const struct ly_ctx *ctx, - const char *endpt_name, struct lyd_node **config) -{ - int ret = 0; - char *tree_path = NULL; - struct lyd_node *new_tree, *port_node; - - NC_CHECK_ARG_RET(NULL, address, port, ctx, endpt_name, config, 1); - - /* prepare path for instertion of leaves later */ - asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/ssh/tcp-server-parameters", endpt_name); - if (!tree_path) { - ERRMEM; - ret = 1; - goto cleanup; - } - - /* create all the nodes in the path */ - ret = lyd_new_path(*config, ctx, tree_path, NULL, LYD_NEW_PATH_UPDATE, &new_tree); - if (ret) { - goto cleanup; - } - if (!*config) { - *config = new_tree; - } - - if (!new_tree) { - /* no new nodes were created */ - ret = lyd_find_path(*config, tree_path, 0, &new_tree); - } else { - /* config was NULL */ - ret = lyd_find_path(new_tree, tree_path, 0, &new_tree); - } - if (ret) { - ERR(NULL, "Unable to find tcp-server-parameters container."); - goto cleanup; - } - - ret = lyd_new_term(new_tree, NULL, "local-address", address, 0, NULL); - if (ret) { - goto cleanup; - } - - ret = lyd_find_path(new_tree, "local-port", 0, &port_node); - if (!ret) { - ret = lyd_change_term(port_node, port); - } else if (ret == LY_ENOTFOUND) { - ret = lyd_new_term(new_tree, NULL, "local-port", port, 0, NULL); - } - - if (ret && (ret != LY_EEXIST) && (ret != LY_ENOT)) { - /* only fail if there was actually an error */ - goto cleanup; - } - /* Add all default nodes */ ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL); if (ret) { goto cleanup; } + cleanup: + free(privkey); + free(pubkey); free(tree_path); return ret; } @@ -803,6 +280,12 @@ nc_server_config_new_ssh_host_key_algs(const struct ly_ctx *ctx, const char *end goto cleanup; } + /* check if top-level container has operation and if not, add it */ + ret = nc_config_new_check_add_operation(ctx, *config); + if (ret) { + goto cleanup; + } + /* Add all default nodes */ ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL); if (ret) { @@ -836,6 +319,12 @@ nc_server_config_ssh_new_key_exchange_algs(const struct ly_ctx *ctx, const char goto cleanup; } + /* check if top-level container has operation and if not, add it */ + ret = nc_config_new_check_add_operation(ctx, *config); + if (ret) { + goto cleanup; + } + /* Add all default nodes */ ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL); if (ret) { @@ -869,6 +358,12 @@ nc_server_config_new_ssh_encryption_algs(const struct ly_ctx *ctx, const char *e goto cleanup; } + /* check if top-level container has operation and if not, add it */ + ret = nc_config_new_check_add_operation(ctx, *config); + if (ret) { + goto cleanup; + } + /* Add all default nodes */ ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL); if (ret) { @@ -902,6 +397,12 @@ nc_server_config_ssh_new_mac_algs(const struct ly_ctx *ctx, const char *endpt_na goto cleanup; } + /* check if top-level container has operation and if not, add it */ + ret = nc_config_new_check_add_operation(ctx, *config); + if (ret) { + goto cleanup; + } + /* Add all default nodes */ ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL); if (ret) { @@ -912,15 +413,15 @@ nc_server_config_ssh_new_mac_algs(const struct ly_ctx *ctx, const char *endpt_na } API int -nc_server_config_new_ssh_client_auth_pubkey(const char *pubkey_path, const struct ly_ctx *ctx, const char *endpt_name, - const char *user_name, const char *pubkey_name, struct lyd_node **config) +nc_server_config_new_ssh_client_auth_pubkey(const struct ly_ctx *ctx, const char *endpt_name, + const char *user_name, const char *pubkey_name, const char *pubkey_path, struct lyd_node **config) { int ret = 0; char *pubkey = NULL, *tree_path = NULL; struct lyd_node *new_tree; - NC_SSH_PUBKEY_TYPE pubkey_type; + NC_PUBKEY_FORMAT pubkey_type; - ret = nc_server_config_new_ssh_get_pubkey(pubkey_path, &pubkey, &pubkey_type); + ret = nc_server_config_new_get_pubkey(pubkey_path, &pubkey, &pubkey_type); if (ret) { goto cleanup; } @@ -950,7 +451,7 @@ nc_server_config_new_ssh_client_auth_pubkey(const char *pubkey_path, const struc } /* insert pubkey format */ - if (pubkey_type == NC_SSH_PUBKEY_SSH2) { + if (pubkey_type == NC_PUBKEY_FORMAT_SSH2) { ret = lyd_new_term(new_tree, NULL, "public-key-format", "ietf-crypto-types:ssh-public-key-format", 0, NULL); } else { ret = lyd_new_term(new_tree, NULL, "public-key-format", "ietf-crypto-types:subject-public-key-info-format", 0, NULL); @@ -965,6 +466,12 @@ nc_server_config_new_ssh_client_auth_pubkey(const char *pubkey_path, const struc goto cleanup; } + /* check if top-level container has operation and if not, add it */ + ret = nc_config_new_check_add_operation(ctx, *config); + if (ret) { + goto cleanup; + } + /* Add all default nodes */ ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL); if (ret) { @@ -978,8 +485,8 @@ nc_server_config_new_ssh_client_auth_pubkey(const char *pubkey_path, const struc } API int -nc_server_config_new_ssh_client_auth_password(const char *password, const struct ly_ctx *ctx, const char *endpt_name, - const char *user_name, struct lyd_node **config) +nc_server_config_new_ssh_client_auth_password(const struct ly_ctx *ctx, const char *endpt_name, + const char *user_name, const char *password, struct lyd_node **config) { int ret = 0; char *tree_path = NULL, *hashed_pw = NULL; @@ -1035,6 +542,12 @@ nc_server_config_new_ssh_client_auth_password(const char *password, const struct goto cleanup; } + /* check if top-level container has operation and if not, add it */ + ret = nc_config_new_check_add_operation(ctx, *config); + if (ret) { + goto cleanup; + } + /* Add all default nodes */ ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL); if (ret) { @@ -1084,6 +597,12 @@ nc_server_config_new_ssh_client_auth_none(const struct ly_ctx *ctx, const char * goto cleanup; } + /* check if top-level container has operation and if not, add it */ + ret = nc_config_new_check_add_operation(ctx, *config); + if (ret) { + goto cleanup; + } + /* Add all default nodes */ ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL); if (ret) { @@ -1096,9 +615,8 @@ nc_server_config_new_ssh_client_auth_none(const struct ly_ctx *ctx, const char * } API int -nc_server_config_new_ssh_client_auth_interactive(const char *pam_config_name, const char *pam_config_dir, - const struct ly_ctx *ctx, const char *endpt_name, - const char *user_name, struct lyd_node **config) +nc_server_config_new_ssh_client_auth_interactive(const struct ly_ctx *ctx, const char *endpt_name, + const char *user_name, const char *pam_config_name, const char *pam_config_dir, struct lyd_node **config) { int ret = 0; char *tree_path = NULL; @@ -1142,6 +660,12 @@ nc_server_config_new_ssh_client_auth_interactive(const char *pam_config_name, co } } + /* check if top-level container has operation and if not, add it */ + ret = nc_config_new_check_add_operation(ctx, *config); + if (ret) { + goto cleanup; + } + /* Add all default nodes */ ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL); if (ret) { diff --git a/src/config_new_tls.c b/src/config_new_tls.c new file mode 100644 index 00000000..078b73c7 --- /dev/null +++ b/src/config_new_tls.c @@ -0,0 +1,406 @@ +/** + * @file config_new_tls.c + * @author Roman Janota + * @brief libnetconf2 TLS server new configuration creation functions + * + * @copyright + * Copyright (c) 2023 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 + +#include +#include +#include +#include + +#include + +#include "compat.h" +#include "config.h" +#include "config_new.h" +#include "log_p.h" +#include "server_config.h" +#include "session.h" +#include "session_p.h" + +API int +nc_server_config_new_tls_ctn(const struct ly_ctx *ctx, const char *endpt_name, uint32_t id, const char *fingerprint, + NC_TLS_CTN_MAPTYPE map_type, const char *name, struct lyd_node **config) +{ + int ret = 0; + char *tree_path = NULL; + struct lyd_node *new_tree; + + NC_CHECK_ARG_RET(NULL, ctx, endpt_name, id, map_type, name, 1); + NC_CHECK_ARG_RET(NULL, config, 1); + + /* prepare path for instertion of leaves later */ + asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/tls/" + "netconf-server-parameters/client-identity-mappings/cert-to-name[id='%d']", endpt_name, id); + if (!tree_path) { + ERRMEM; + ret = 1; + goto cleanup; + } + + /* create all the nodes in the path */ + ret = lyd_new_path(*config, ctx, tree_path, NULL, LYD_NEW_PATH_UPDATE, &new_tree); + if (ret) { + goto cleanup; + } + if (!*config) { + *config = new_tree; + } + + if (!new_tree) { + /* no new nodes were created */ + ret = lyd_find_path(*config, tree_path, 0, &new_tree); + } else { + /* config was NULL */ + ret = lyd_find_path(new_tree, tree_path, 0, &new_tree); + } + if (ret) { + ERR(NULL, "Unable to find netconf-server-parameters container."); + goto cleanup; + } + + /* not mandatory */ + if (fingerprint) { + ret = lyd_new_term(new_tree, NULL, "fingerprint", fingerprint, 0, NULL); + if (ret) { + goto cleanup; + } + } + + /* insert map-type */ + switch (map_type) { + case NC_TLS_CTN_SPECIFIED: + ret = lyd_new_term(new_tree, NULL, "map-type", "ietf-x509-cert-to-name:specified", 0, NULL); + break; + case NC_TLS_CTN_SAN_RFC822_NAME: + ret = lyd_new_term(new_tree, NULL, "map-type", "ietf-x509-cert-to-name:san-rfc822-name", 0, NULL); + break; + case NC_TLS_CTN_SAN_DNS_NAME: + ret = lyd_new_term(new_tree, NULL, "map-type", "ietf-x509-cert-to-name:san-dns-name", 0, NULL); + break; + case NC_TLS_CTN_SAN_IP_ADDRESS: + ret = lyd_new_term(new_tree, NULL, "map-type", "ietf-x509-cert-to-name:san-ip-address", 0, NULL); + break; + case NC_TLS_CTN_SAN_ANY: + ret = lyd_new_term(new_tree, NULL, "map-type", "ietf-x509-cert-to-name:san-any", 0, NULL); + break; + case NC_TLS_CTN_COMMON_NAME: + ret = lyd_new_term(new_tree, NULL, "map-type", "ietf-x509-cert-to-name:common-name", 0, NULL); + break; + case NC_TLS_CTN_UNKNOWN: + default: + ERR(NULL, "Unknown map_type."); + ret = 1; + break; + } + if (ret) { + goto cleanup; + } + + /* insert name */ + ret = lyd_new_term(new_tree, NULL, "name", name, 0, NULL); + if (ret) { + goto cleanup; + } + + /* check if top-level container has operation and if not, add it */ + ret = nc_config_new_check_add_operation(ctx, *config); + if (ret) { + goto cleanup; + } + + /* Add all default nodes */ + ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL); + if (ret) { + goto cleanup; + } + +cleanup: + free(tree_path); + return ret; +} + +API int +nc_server_config_new_tls_server_certificate(const struct ly_ctx *ctx, const char *endpt_name, const char *pubkey_path, + const char *privkey_path, const char *certificate_path, struct lyd_node **config) +{ + int ret = 0; + char *tree_path = NULL, *privkey = NULL, *pubkey = NULL, *pubkey_stripped = NULL, *privkey_stripped, *cert = NULL; + struct lyd_node *new_tree; + NC_PRIVKEY_FORMAT privkey_type; + NC_PUBKEY_FORMAT pubkey_type; + const char *privkey_identity; + + NC_CHECK_ARG_RET(NULL, ctx, endpt_name, privkey_path, certificate_path, 1); + NC_CHECK_ARG_RET(NULL, config, 1); + + /* get the keys as a string from the given files */ + ret = nc_server_config_new_get_keys(privkey_path, pubkey_path, &privkey, &pubkey, &privkey_type, &pubkey_type); + if (ret) { + ERR(NULL, "Getting keys from file(s) failed."); + goto cleanup; + } + + ret = nc_server_config_new_read_certificate(certificate_path, &cert); + if (ret) { + ERR(NULL, "Getting certificate from file \"%s\" failed.", certificate_path); + goto cleanup; + } + + /* prepare path for instertion of leaves later */ + asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/tls/tls-server-parameters/server-identity/certificate/local-definition", endpt_name); + if (!tree_path) { + ERRMEM; + ret = 1; + goto cleanup; + } + + /* create all the nodes in the path */ + ret = lyd_new_path(*config, ctx, tree_path, NULL, LYD_NEW_PATH_UPDATE, &new_tree); + if (ret) { + goto cleanup; + } + if (!*config) { + *config = new_tree; + } + + if (!new_tree) { + /* no new nodes were created */ + ret = lyd_find_path(*config, tree_path, 0, &new_tree); + } else { + /* config was NULL */ + ret = lyd_find_path(new_tree, tree_path, 0, &new_tree); + } + if (ret) { + ERR(NULL, "Unable to find local-definition container."); + goto cleanup; + } + + /* insert pubkey format */ + if (pubkey_type == NC_PUBKEY_FORMAT_X509) { + ret = lyd_new_term(new_tree, NULL, "public-key-format", "ietf-crypto-types:public-key-info-format", 0, NULL); + } else { + ret = lyd_new_term(new_tree, NULL, "public-key-format", "ietf-crypto-types:ssh-public-key-format", 0, NULL); + } + if (ret) { + goto cleanup; + } + + /* strip pubkey's header and footer only if it's generated from pkcs8 key (using OpenSSL), + * otherwise it's already stripped + */ + if (!pubkey_path && (privkey_type == NC_PRIVKEY_FORMAT_X509)) { + pubkey_stripped = pubkey + strlen("-----BEGIN PUBLIC KEY-----") + 1; + pubkey_stripped[strlen(pubkey_stripped) - strlen("-----END PUBLIC KEY-----") - 2] = '\0'; + } else { + pubkey_stripped = pubkey; + } + + /* insert pubkey b64 */ + ret = lyd_new_term(new_tree, NULL, "public-key", pubkey_stripped, 0, NULL); + if (ret) { + goto cleanup; + } + + /* get privkey identityref value */ + privkey_identity = nc_config_new_privkey_format_to_identityref(privkey_type); + if (!privkey_identity) { + ret = 1; + goto cleanup; + } + + /* insert private key format */ + ret = lyd_new_term(new_tree, NULL, "private-key-format", privkey_identity, 0, NULL); + if (ret) { + goto cleanup; + } + + if (privkey_type == NC_PRIVKEY_FORMAT_OPENSSH) { + /* only OpenSSH private keys have different header and footer after processing */ + privkey_stripped = privkey + strlen(NC_OPENSSH_PRIVKEY_HEADER); + privkey_stripped[strlen(privkey_stripped) - strlen(NC_OPENSSH_PRIVKEY_FOOTER)] = '\0'; + } else { + /* the rest share the same header and footer */ + privkey_stripped = privkey + strlen(NC_PKCS8_PRIVKEY_HEADER); + privkey_stripped[strlen(privkey_stripped) - strlen(NC_PKCS8_PRIVKEY_FOOTER)] = '\0'; + } + + ret = lyd_new_term(new_tree, NULL, "cleartext-private-key", privkey_stripped, 0, NULL); + if (ret) { + goto cleanup; + } + + ret = lyd_new_term(new_tree, NULL, "cert-data", cert, 0, NULL); + if (ret) { + goto cleanup; + } + + /* check if top-level container has operation and if not, add it */ + ret = nc_config_new_check_add_operation(ctx, *config); + if (ret) { + goto cleanup; + } + + /* Add all default nodes */ + ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL); + if (ret) { + goto cleanup; + } + +cleanup: + free(privkey); + free(pubkey); + free(cert); + free(tree_path); + return ret; +} + +API int +nc_server_config_new_tls_client_certificate(const struct ly_ctx *ctx, const char *endpt_name, const char *cert_name, + const char *cert_path, struct lyd_node **config) +{ + int ret = 0; + struct lyd_node *new_tree; + char *tree_path = NULL, *cert = NULL; + + NC_CHECK_ARG_RET(NULL, ctx, endpt_name, cert_name, cert_path, config, 1); + + ret = nc_server_config_new_read_certificate(cert_path, &cert); + if (ret) { + ERR(NULL, "Getting certificate from file \"%s\" failed.", cert_path); + goto cleanup; + } + + /* prepare path for instertion of leaves later */ + asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/tls/tls-server-parameters/" + "client-authentication/ee-certs/local-definition/certificate[name='%s']", endpt_name, cert_name); + if (!tree_path) { + ERRMEM; + ret = 1; + goto cleanup; + } + + /* create all the nodes in the path */ + ret = lyd_new_path(*config, ctx, tree_path, NULL, LYD_NEW_PATH_UPDATE, &new_tree); + if (ret) { + goto cleanup; + } + if (!*config) { + *config = new_tree; + } + + if (!new_tree) { + /* no new nodes were created */ + ret = lyd_find_path(*config, tree_path, 0, &new_tree); + } else { + /* config was NULL */ + ret = lyd_find_path(new_tree, tree_path, 0, &new_tree); + } + if (ret) { + goto cleanup; + } + + /* insert cert-data */ + ret = lyd_new_term(new_tree, NULL, "cert-data", cert, 0, NULL); + if (ret) { + goto cleanup; + } + + /* check if top-level container has operation and if not, add it */ + ret = nc_config_new_check_add_operation(ctx, *config); + if (ret) { + goto cleanup; + } + + /* Add all default nodes */ + ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL); + if (ret) { + goto cleanup; + } + +cleanup: + free(cert); + free(tree_path); + return ret; +} + +API int +nc_server_config_new_tls_client_ca(const struct ly_ctx *ctx, const char *endpt_name, const char *cert_name, + const char *cert_path, struct lyd_node **config) +{ + int ret = 0; + struct lyd_node *new_tree; + char *tree_path = NULL, *cert = NULL; + + NC_CHECK_ARG_RET(NULL, ctx, endpt_name, cert_name, cert_path, config, 1); + + ret = nc_server_config_new_read_certificate(cert_path, &cert); + if (ret) { + ERR(NULL, "Getting certificate from file \"%s\" failed.", cert_path); + goto cleanup; + } + + /* prepare path for instertion of leaves later */ + asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/tls/tls-server-parameters/" + "client-authentication/ca-certs/local-definition/certificate[name='%s']", endpt_name, cert_name); + if (!tree_path) { + ERRMEM; + ret = 1; + goto cleanup; + } + + /* create all the nodes in the path */ + ret = lyd_new_path(*config, ctx, tree_path, NULL, LYD_NEW_PATH_UPDATE, &new_tree); + if (ret) { + goto cleanup; + } + if (!*config) { + *config = new_tree; + } + + if (!new_tree) { + /* no new nodes were created */ + ret = lyd_find_path(*config, tree_path, 0, &new_tree); + } else { + /* config was NULL */ + ret = lyd_find_path(new_tree, tree_path, 0, &new_tree); + } + if (ret) { + goto cleanup; + } + + /* insert cert-data */ + ret = lyd_new_term(new_tree, NULL, "cert-data", cert, 0, NULL); + if (ret) { + goto cleanup; + } + + /* check if top-level container has operation and if not, add it */ + ret = nc_config_new_check_add_operation(ctx, *config); + if (ret) { + goto cleanup; + } + + /* Add all default nodes */ + ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL); + if (ret) { + goto cleanup; + } + +cleanup: + free(cert); + free(tree_path); + return ret; +} diff --git a/src/io.c b/src/io.c index d9b5c54a..eb9360ae 100644 --- a/src/io.c +++ b/src/io.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -30,12 +31,18 @@ #ifdef NC_ENABLED_TLS # include +# include #endif #include #include "compat.h" -#include "libnetconf.h" +#include "config.h" +#include "log_p.h" +#include "messages_p.h" +#include "netconf.h" +#include "session.h" +#include "session_p.h" const char *nc_msgtype2str[] = { "error", diff --git a/src/log.c b/src/log.c index c87b4fb4..f5d2feec 100644 --- a/src/log.c +++ b/src/log.c @@ -17,6 +17,7 @@ #include #include +#include #include @@ -25,8 +26,9 @@ #endif #include "compat.h" -#include "libnetconf.h" +#include "config.h" #include "log.h" +#include "session_p.h" /** * @brief libnetconf verbose level variable diff --git a/src/log_p.h b/src/log_p.h index 8bd57c0b..dbde9963 100644 --- a/src/log_p.h +++ b/src/log_p.h @@ -16,7 +16,7 @@ #ifndef NC_LOG_PRIVATE_H_ #define NC_LOG_PRIVATE_H_ -#include +#include #include "compat.h" #include "log.h" @@ -51,7 +51,6 @@ extern ATOMIC_T verbose_level; #define ERRMEM ERR(NULL, "%s: memory reallocation failed (%s:%d).", __func__, __FILE__, __LINE__) #define ERRINIT ERR(NULL, "%s: libnetconf2 not initialized.", __func__) #define ERRINT ERR(NULL, "%s: internal error (%s:%d).", __func__, __FILE__, __LINE__) -#define ERRNODE(name) ERR(NULL, "%s: missing node (%s) in the YANG data.", __func__, name) #define ERRARG(session, ARG) ERR(session, "Invalid argument %s (%s()).", #ARG, __func__) #define GETMACRO1(_1, NAME, ...) NAME diff --git a/src/messages_client.c b/src/messages_client.c index e222dbef..5cd1165c 100644 --- a/src/messages_client.c +++ b/src/messages_client.c @@ -17,13 +17,17 @@ #include #include -#include #include #include #include -#include "libnetconf.h" +#include "compat.h" +#include "config.h" +#include "log_p.h" +#include "messages_client.h" +#include "messages_p.h" +#include "netconf.h" const char *rpcedit_dfltop2str[] = {NULL, "merge", "replace", "none"}; const char *rpcedit_testopt2str[] = {NULL, "test-then-set", "set", "test-only"}; diff --git a/src/messages_p.h b/src/messages_p.h index 0ea41885..5bf3d3aa 100644 --- a/src/messages_p.h +++ b/src/messages_p.h @@ -16,10 +16,13 @@ #ifndef NC_MESSAGES_P_H_ #define NC_MESSAGES_P_H_ +#include + #include #include "messages_client.h" #include "messages_server.h" +#include "netconf.h" extern const char *rpcedit_dfltop2str[]; extern const char *rpcedit_testopt2str[]; diff --git a/src/messages_server.c b/src/messages_server.c index 7864ebfc..2cc193ab 100644 --- a/src/messages_server.c +++ b/src/messages_server.c @@ -24,8 +24,11 @@ #include #include "compat.h" -#include "libnetconf.h" -#include "session_server.h" +#include "config.h" +#include "log_p.h" +#include "messages_p.h" +#include "messages_server.h" +#include "netconf.h" extern struct nc_server_opts server_opts; diff --git a/src/messages_server.h b/src/messages_server.h index a9bbbae0..a3deacbc 100644 --- a/src/messages_server.h +++ b/src/messages_server.h @@ -20,9 +20,11 @@ extern "C" { #endif -#include +#include #include +#include + #include "netconf.h" #include "session.h" diff --git a/src/netconf.h b/src/netconf.h index 48058cb2..1e25cce7 100644 --- a/src/netconf.h +++ b/src/netconf.h @@ -20,8 +20,6 @@ extern "C" { #endif -#include - /** * @addtogroup misc * @{ diff --git a/src/server_config.c b/src/server_config.c index 9ec3e433..9dc94366 100644 --- a/src/server_config.c +++ b/src/server_config.c @@ -16,15 +16,21 @@ #define _GNU_SOURCE #include +#include +#include #include #include +#include + #include "compat.h" -#include "libnetconf.h" +#include "config.h" +#include "log_p.h" #include "server_config.h" #include "server_config_p.h" -#include "session_server.h" -#include "session_server_ch.h" +#include "session_p.h" + +#ifdef NC_ENABLED_SSH /* All libssh supported host-key, key-exchange, encryption and mac algorithms as of version 0.10.90 */ @@ -54,6 +60,8 @@ static const char *supported_mac_algs[] = { "hmac-sha2-256", "hmac-sha2-512", "hmac-sha1", NULL }; +#endif /* NC_ENABLED_SSH */ + extern struct nc_server_opts server_opts; int @@ -94,6 +102,8 @@ nc_server_config_get_endpt(const struct lyd_node *node, struct nc_endpt **endpt, return 1; } +#ifdef NC_ENABLED_SSH + int nc_server_config_get_hostkey(const struct lyd_node *node, const struct nc_server_ssh_opts *opts, struct nc_hostkey **hostkey) { @@ -200,6 +210,86 @@ nc_server_config_get_pubkey(const struct lyd_node *node, const struct nc_client_ return 1; } +#endif /* NC_ENABLED_SSH */ + +#ifdef NC_ENABLED_TLS + +int +nc_server_config_get_cert(const struct lyd_node *node, struct nc_cert_grouping *auth_client, struct nc_certificate **cert) +{ + uint16_t i; + const char *cert_name; + + assert(node && auth_client); + + while (node) { + if (!strcmp(LYD_NAME(node), "certificate")) { + break; + } + node = lyd_parent(node); + } + + if (!node) { + ERR(NULL, "Node \"%s\" is not contained in a certificate subtree.", LYD_NAME(node)); + return 1; + } + + node = lyd_child(node); + assert(!strcmp(LYD_NAME(node), "name")); + cert_name = lyd_get_value(node); + + for (i = 0; i < auth_client->cert_count; i++) { + if (!strcmp(auth_client->certs[i].name, cert_name)) { + *cert = &auth_client->certs[i]; + return 0; + } + } + + ERR(NULL, "Certificate \"%s\" was not found.", cert_name); + return 1; +} + +static int +nc_server_config_get_ctn(const struct lyd_node *node, struct nc_endpt *endpt, struct nc_ctn **ctn) +{ + uint32_t id; + struct nc_ctn *iter; + + assert(node && endpt); + + node = lyd_parent(node); + while (node) { + if (!strcmp(LYD_NAME(node), "cert-to-name")) { + break; + } + node = lyd_parent(node); + } + + if (!node) { + ERR(NULL, "Node \"%s\" is not contained in a cert-to-name subtree.", LYD_NAME(node)); + return 1; + } + + node = lyd_child(node); + assert(!strcmp(LYD_NAME(node), "id")); + id = strtoul(lyd_get_value(node), NULL, 10); + + iter = endpt->opts.tls->ctn; + while (iter) { + if (iter->id == id) { + *ctn = iter; + return 0; + } + + iter = iter->next; + } + + ERR(NULL, "Cert-to-name entry with id \"%d\" was not found.", id); + return 1; +} + +#endif /* NC_ENABLED_TLS */ + int equal_parent_name(const struct lyd_node *node, uint16_t parent_count, const char *parent_name) { @@ -219,6 +309,23 @@ equal_parent_name(const struct lyd_node *node, uint16_t parent_count, const char return 0; } +NC_PRIVKEY_FORMAT +nc_server_config_get_private_key_type(const char *format) +{ + if (!strcmp(format, "rsa-private-key-format")) { + return NC_PRIVKEY_FORMAT_RSA; + } else if (!strcmp(format, "ec-private-key-format")) { + return NC_PRIVKEY_FORMAT_EC; + } else if (!strcmp(format, "subject-private-key-info-format")) { + return NC_PRIVKEY_FORMAT_X509; + } else if (!strcmp(format, "openssh-private-key-format")) { + return NC_PRIVKEY_FORMAT_OPENSSH; + } else { + ERR(NULL, "Private key format (%s) not supported.", format); + return NC_PRIVKEY_FORMAT_UNKNOWN; + } +} + int nc_server_config_realloc(const char *key_value, void **ptr, size_t size, uint16_t *count) { @@ -253,20 +360,73 @@ nc_server_config_realloc(const char *key_value, void **ptr, size_t size, uint16_ return ret; } -static void -nc_server_config_del_auth_client_pam_name(struct nc_client_auth *auth_client) +static int +is_listen(const struct lyd_node *node) { - free(auth_client->pam_config_name); - auth_client->pam_config_name = NULL; + assert(node); + + while (node) { + if (!strcmp(LYD_NAME(node), "listen")) { + break; + } + node = lyd_parent(node); + } + + return node != NULL; } -static void -nc_server_config_del_auth_client_pam_dir(struct nc_client_auth *auth_client) +// static int +// is_ch(const struct lyd_node *node) +// { +// assert(node); + +// while (node) { +// if (!strcmp(LYD_NAME(node), "call-home")) { +// break; +// } +// node = lyd_parent(node); +// } + +// return node != NULL; +// } + +#ifdef NC_ENABLED_SSH + +static int +is_ssh(const struct lyd_node *node) { - free(auth_client->pam_config_dir); - auth_client->pam_config_dir = NULL; + assert(node); + + while (node) { + if (!strcmp(LYD_NAME(node), "ssh")) { + break; + } + node = lyd_parent(node); + } + + return node != NULL; +} + +#endif /* NC_ENABLED_SSH */ + +#ifdef NC_ENABLED_TLS +static int +is_tls(const struct lyd_node *node) +{ + assert(node); + + while (node) { + if (!strcmp(LYD_NAME(node), "tls")) { + break; + } + node = lyd_parent(node); + } + + return node != NULL; } +#endif /* NC_ENABLED_TLS */ + static void nc_server_config_del_endpt_name(struct nc_endpt *endpt) { @@ -275,17 +435,33 @@ nc_server_config_del_endpt_name(struct nc_endpt *endpt) } static void -nc_server_config_del_endpt_reference(struct nc_endpt *endpt) +nc_server_config_del_local_address(struct nc_bind *bind) { - free(endpt->referenced_endpt_name); - endpt->referenced_endpt_name = NULL; + free(bind->address); + bind->address = NULL; } +#ifdef NC_ENABLED_SSH + static void -nc_server_config_del_local_address(struct nc_bind *bind) +nc_server_config_del_auth_client_pam_name(struct nc_client_auth *auth_client) { - free(bind->address); - bind->address = NULL; + free(auth_client->pam_config_name); + auth_client->pam_config_name = NULL; +} + +static void +nc_server_config_del_auth_client_pam_dir(struct nc_client_auth *auth_client) +{ + free(auth_client->pam_config_dir); + auth_client->pam_config_dir = NULL; +} + +static void +nc_server_config_del_endpt_reference(struct nc_endpt *endpt) +{ + free(endpt->referenced_endpt_name); + endpt->referenced_endpt_name = NULL; } static void @@ -298,15 +474,15 @@ nc_server_config_del_hostkey_name(struct nc_hostkey *hostkey) static void nc_server_config_del_public_key(struct nc_hostkey *hostkey) { - free(hostkey->key.pub_base64); - hostkey->key.pub_base64 = NULL; + free(hostkey->key.pubkey_data); + hostkey->key.pubkey_data = NULL; } static void nc_server_config_del_private_key(struct nc_hostkey *hostkey) { - free(hostkey->key.priv_base64); - hostkey->key.priv_base64 = NULL; + free(hostkey->key.privkey_data); + hostkey->key.privkey_data = NULL; } static void @@ -326,8 +502,8 @@ nc_server_config_del_auth_client_pubkey_name(struct nc_public_key *pubkey) static void nc_server_config_del_auth_client_pubkey_pub_base64(struct nc_public_key *pubkey) { - free(pubkey->pub_base64); - pubkey->pub_base64 = NULL; + free(pubkey->data); + pubkey->data = NULL; } static void @@ -466,6 +642,8 @@ nc_server_config_del_endpt_ssh(struct nc_endpt *endpt, struct nc_bind *bind) } } +#endif /* NC_ENABLED_SSH */ + void nc_server_config_del_unix_socket(struct nc_bind *bind, struct nc_server_unix_opts *opts) { @@ -495,6 +673,158 @@ nc_server_config_del_endpt_unix_socket(struct nc_endpt *endpt, struct nc_bind *b } } +#ifdef NC_ENABLED_TLS + +static void +nc_server_config_tls_del_public_key(struct nc_server_tls_opts *opts) +{ + free(opts->pubkey_data); + opts->pubkey_data = NULL; +} + +static void +nc_server_config_tls_del_cleartext_private_key(struct nc_server_tls_opts *opts) +{ + free(opts->privkey_data); + opts->privkey_data = NULL; +} + +static void +nc_server_config_tls_del_cert_data(struct nc_server_tls_opts *opts) +{ + free(opts->cert_data); + opts->cert_data = NULL; +} + +static void +nc_server_config_tls_del_cert_data_certificate(struct nc_certificate *cert) +{ + free(cert->data); + cert->data = NULL; +} + +static void +nc_server_config_del_fingerprint(struct nc_ctn *ctn) +{ + free(ctn->fingerprint); + ctn->fingerprint = NULL; +} + +static void +nc_server_config_del_cert(struct nc_cert_grouping *certs, struct nc_certificate *cert) +{ + free(cert->name); + cert->name = NULL; + + free(cert->data); + cert->data = NULL; + + certs->cert_count--; + if (!certs->cert_count) { + free(certs->certs); + certs->certs = NULL; + } +} + +static void +nc_server_config_tls_del_certs(struct nc_cert_grouping *ca) +{ + uint16_t i, cert_count; + + if (ca->store == NC_STORE_LOCAL) { + cert_count = ca->cert_count; + for (i = 0; i < cert_count; i++) { + nc_server_config_del_cert(ca, &ca->certs[i]); + } + } +} + +static void +nc_server_config_del_ctn(struct nc_server_tls_opts *opts, struct nc_ctn *ctn) +{ + struct nc_ctn *iter; + + free(ctn->fingerprint); + ctn->fingerprint = NULL; + + free(ctn->name); + ctn->name = NULL; + + if (opts->ctn == ctn) { + /* it's the first in the list */ + opts->ctn = ctn->next; + free(ctn); + return; + } + + iter = opts->ctn; + while (iter) { + if (iter->next == ctn) { + /* found the ctn */ + break; + } + iter = iter->next; + } + + iter->next = ctn->next; + free(ctn); +} + +static void +nc_server_config_del_ctns(struct nc_server_tls_opts *opts) +{ + struct nc_ctn *cur, *next; + + cur = opts->ctn; + while (cur) { + next = cur->next; + free(cur->fingerprint); + free(cur->name); + free(cur); + cur = next; + } + opts->ctn = NULL; +} + +static void +nc_server_config_del_tls(struct nc_bind *bind, struct nc_server_tls_opts *opts) +{ + nc_server_config_del_local_address(bind); + if (bind->sock > -1) { + close(bind->sock); + } + + if (opts->store == NC_STORE_LOCAL) { + nc_server_config_tls_del_public_key(opts); + nc_server_config_tls_del_cleartext_private_key(opts); + nc_server_config_tls_del_cert_data(opts); + } + + nc_server_config_tls_del_certs(&opts->ca_certs); + nc_server_config_tls_del_certs(&opts->ee_certs); + + nc_server_config_del_ctns(opts); + + free(opts); +} + +static void +nc_server_config_del_endpt_tls(struct nc_endpt *endpt, struct nc_bind *bind) +{ + nc_server_config_del_endpt_name(endpt); + nc_server_config_del_tls(bind, endpt->opts.tls); + + server_opts.endpt_count--; + if (!server_opts.endpt_count) { + free(server_opts.endpts); + free(server_opts.binds); + server_opts.endpts = NULL; + server_opts.binds = NULL; + } +} + +#endif /* NC_ENABLED_TLS */ + /* presence container */ int nc_server_config_listen(struct lyd_node *node, NC_OPERATION op) @@ -513,12 +843,12 @@ nc_server_config_listen(struct lyd_node *node, NC_OPERATION op) case NC_TI_LIBSSH: nc_server_config_del_endpt_ssh(&server_opts.endpts[i], &server_opts.binds[i]); break; -#endif +#endif /* NC_ENABLED_SSH */ #ifdef NC_ENABLED_TLS case NC_TI_OPENSSL: - /* todo */ + nc_server_config_del_endpt_tls(&server_opts.endpts[i], &server_opts.binds[i]); break; -#endif +#endif /* NC_ENABLED_TLS */ case NC_TI_UNIX: nc_server_config_del_endpt_unix_socket(&server_opts.endpts[i], &server_opts.binds[i]); break; @@ -606,13 +936,35 @@ nc_server_config_endpoint(const struct lyd_node *node, NC_OPERATION op) ret = 1; goto cleanup; } - nc_server_config_del_endpt_ssh(endpt, bind); + + switch (endpt->ti) { +#ifdef NC_ENABLED_SSH + case NC_TI_LIBSSH: + nc_server_config_del_endpt_ssh(endpt, bind); + break; +#endif /* NC_ENABLED_SSH */ +#ifdef NC_ENABLED_TLS + case NC_TI_OPENSSL: + nc_server_config_del_endpt_tls(endpt, bind); + break; +#endif /* NC_ENABLED_TLS */ + case NC_TI_UNIX: + nc_server_config_del_endpt_unix_socket(endpt, bind); + break; + case NC_TI_NONE: + case NC_TI_FD: + ERRINT; + ret = 1; + goto cleanup; + } } cleanup: return ret; } +#ifdef NC_ENABLED_SSH + static int nc_server_config_create_ssh(struct nc_endpt *endpt) { @@ -654,17 +1006,63 @@ nc_server_config_ssh(const struct lyd_node *node, NC_OPERATION op) return ret; } -static int -nc_server_config_set_address_port(struct nc_endpt *endpt, struct nc_bind *bind, const char *address, uint16_t port) -{ - int sock = -1, set_addr, ret = 0; +#endif /* NC_ENABLED_SSH */ - assert((address && !port) || (!address && port) || (endpt->ti == NC_TI_UNIX)); +#ifdef NC_ENABLED_TLS - if (address) { - set_addr = 1; - } else { - set_addr = 0; +static int +nc_server_config_create_tls(struct nc_endpt *endpt) +{ + endpt->ti = NC_TI_OPENSSL; + endpt->opts.tls = calloc(1, sizeof *endpt->opts.tls); + if (!endpt->opts.tls) { + ERRMEM; + return 1; + } + + return 0; +} + +static int +nc_server_config_tls(const struct lyd_node *node, NC_OPERATION op) +{ + struct nc_endpt *endpt; + struct nc_bind *bind; + int ret = 0; + + assert(!strcmp(LYD_NAME(node), "tls")); + + if (nc_server_config_get_endpt(node, &endpt, &bind)) { + ret = 1; + goto cleanup; + } + + if (op == NC_OP_CREATE) { + ret = nc_server_config_create_tls(endpt); + if (ret) { + goto cleanup; + } + } else if (op == NC_OP_DELETE) { + nc_server_config_del_tls(bind, endpt->opts.tls); + } + +cleanup: + return ret; +} + +#endif /* NC_ENABLED_TLS */ + +static int +nc_server_config_set_address_port(struct nc_endpt *endpt, struct nc_bind *bind, const char *address, uint16_t port) +{ + int sock = -1, set_addr, ret = 0; + + assert((address && !port) || (!address && port) || (endpt->ti == NC_TI_UNIX)); + + if (address) { + set_addr = 1; + } else { + set_addr = 0; } if (set_addr) { @@ -798,7 +1196,7 @@ nc_server_config_keepalives(const struct lyd_node *node, NC_OPERATION op) assert(!strcmp(LYD_NAME(node), "keepalives")); - if (equal_parent_name(node, 4, "listen")) { + if (equal_parent_name(node, 1, "tcp-server-parameters")) { if (nc_server_config_get_endpt(node, &endpt, &bind)) { ret = 1; goto cleanup; @@ -912,6 +1310,8 @@ nc_server_config_probe_interval(const struct lyd_node *node, NC_OPERATION op) return ret; } +#ifdef NC_ENABLED_SSH + static int nc_server_config_create_host_key(const struct lyd_node *node, struct nc_server_ssh_opts *opts) { @@ -963,22 +1363,38 @@ nc_server_config_host_key(const struct lyd_node *node, NC_OPERATION op) return ret; } +#endif /* NC_ENABLED_SSH */ + /* mandatory leaf */ static int nc_server_config_public_key_format(const struct lyd_node *node, NC_OPERATION op) { const char *format; + int ret = 0; + NC_PUBKEY_FORMAT pubkey_type; struct nc_endpt *endpt; + +#ifdef NC_ENABLED_SSH struct nc_client_auth *auth_client; struct nc_public_key *pubkey; struct nc_hostkey *hostkey; - int ret = 0; +#endif /* NC_ENABLED_SSH */ assert(!strcmp(LYD_NAME(node), "public-key-format")); format = ((struct lyd_node_term *)node)->value.ident->name; + if (!strcmp(format, "ssh-public-key-format")) { + pubkey_type = NC_PUBKEY_FORMAT_SSH2; + } else if (!strcmp(format, "subject-public-key-info-format")) { + pubkey_type = NC_PUBKEY_FORMAT_X509; + } else { + ERR(NULL, "Public key format (%s) not supported.", format); + ret = 1; + goto cleanup; + } - if ((equal_parent_name(node, 6, "client-authentication")) && (equal_parent_name(node, 10, "listen"))) { +#ifdef NC_ENABLED_SSH + if ((equal_parent_name(node, 6, "client-authentication")) && (is_ssh(node)) && (is_listen(node))) { if (nc_server_config_get_endpt(node, &endpt, NULL)) { ret = 1; goto cleanup; @@ -995,15 +1411,9 @@ nc_server_config_public_key_format(const struct lyd_node *node, NC_OPERATION op) } if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { - if (!strcmp(format, "ssh-public-key-format")) { - pubkey->pubkey_type = NC_SSH_PUBKEY_SSH2; - } else if (!strcmp(format, "subject-public-key-info-format")) { - pubkey->pubkey_type = NC_SSH_PUBKEY_X509; - } else { - ERR(NULL, "Public key format (%s) not supported.", format); - } + pubkey->type = pubkey_type; } - } else if ((equal_parent_name(node, 5, "server-identity")) && (equal_parent_name(node, 11, "listen"))) { + } else if (equal_parent_name(node, 5, "server-identity") && is_ssh(node) && is_listen(node)) { if (nc_server_config_get_endpt(node, &endpt, NULL)) { ret = 1; goto cleanup; @@ -1015,15 +1425,23 @@ nc_server_config_public_key_format(const struct lyd_node *node, NC_OPERATION op) } if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { - if (!strcmp(format, "ssh-public-key-format")) { - hostkey->key.pubkey_type = NC_SSH_PUBKEY_SSH2; - } else if (!strcmp(format, "subject-public-key-info-format")) { - hostkey->key.pubkey_type = NC_SSH_PUBKEY_X509; - } else { - ERR(NULL, "Public key format (%s) not supported.", format); - } + hostkey->key.pubkey_type = pubkey_type; + } + } +#endif /* NC_ENABLED_SSH */ +#ifdef NC_ENABLED_TLS + if (equal_parent_name(node, 3, "server-identity") && is_tls(node) && is_listen(node)) { + /* TLS listen server-identity */ + if (nc_server_config_get_endpt(node, &endpt, NULL)) { + ret = 1; + goto cleanup; + } + + if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { + endpt->opts.tls->pubkey_type = pubkey_type; } } +#endif /* NC_ENABLED_TLS */ cleanup: return ret; @@ -1033,10 +1451,16 @@ nc_server_config_public_key_format(const struct lyd_node *node, NC_OPERATION op) static int nc_server_config_private_key_format(const struct lyd_node *node, NC_OPERATION op) { + int ret = 0; const char *format; struct nc_endpt *endpt; + NC_PRIVKEY_FORMAT privkey_type; + +#ifdef NC_ENABLED_SSH struct nc_hostkey *hostkey; - int ret = 0; +#endif /* NC_ENABLED_SSH */ + + (void) op; assert(!strcmp(LYD_NAME(node), "private-key-format")); @@ -1045,36 +1469,65 @@ nc_server_config_private_key_format(const struct lyd_node *node, NC_OPERATION op goto cleanup; } - if (nc_server_config_get_hostkey(node, endpt->opts.ssh, &hostkey)) { + format = ((struct lyd_node_term *)node)->value.ident->name; + if (!format) { ret = 1; goto cleanup; } - format = ((struct lyd_node_term *)node)->value.ident->name; - if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { - if (!strcmp(format, "rsa-private-key-format")) { - hostkey->key.privkey_type = NC_PRIVKEY_FORMAT_RSA; - } else if (!strcmp(format, "ec-private-key-format")) { - hostkey->key.privkey_type = NC_PRIVKEY_FORMAT_EC; - } else if (!strcmp(format, "subject-private-key-info-format")) { - hostkey->key.privkey_type = NC_PRIVKEY_FORMAT_PKCS8; - } else if (!strcmp(format, "openssh-private-key-format")) { - hostkey->key.privkey_type = NC_PRIVKEY_FORMAT_OPENSSH; - } else { - ERR(NULL, "Private key format (%s) not supported.", format); + privkey_type = nc_server_config_get_private_key_type(format); + if (privkey_type == NC_PRIVKEY_FORMAT_UNKNOWN) { + ret = 1; + goto cleanup; + } + +#ifdef NC_ENABLED_SSH + if ((is_ssh(node)) && (is_listen(node))) { + /* listen ssh */ + if (nc_server_config_get_hostkey(node, endpt->opts.ssh, &hostkey)) { + ret = 1; + goto cleanup; } + + hostkey->key.privkey_type = privkey_type; + } +#endif /* NC_ENABLED_SSH */ +#ifdef NC_ENABLED_TLS + if ((is_tls(node)) && (is_listen(node))) { + /* listen tls */ + + endpt->opts.tls->privkey_type = privkey_type; } +#endif /* NC_ENABLED_TLS */ cleanup: return ret; } +#ifdef NC_ENABLED_SSH + static int nc_server_config_replace_cleartext_private_key(const struct lyd_node *node, struct nc_hostkey *hostkey) { nc_server_config_del_private_key(hostkey); - hostkey->key.priv_base64 = strdup(lyd_get_value(node)); - if (!hostkey->key.priv_base64) { + hostkey->key.privkey_data = strdup(lyd_get_value(node)); + if (!hostkey->key.privkey_data) { + ERRMEM; + return 1; + } + + return 0; +} + +#endif /* NC_ENABLED_SSH */ + +#ifdef NC_ENABLED_TLS +static int +nc_server_config_tls_replace_cleartext_private_key(const struct lyd_node *node, struct nc_server_tls_opts *opts) +{ + nc_server_config_tls_del_cleartext_private_key(opts); + opts->privkey_data = strdup(lyd_get_value(node)); + if (!opts->privkey_data) { ERRMEM; return 1; } @@ -1082,20 +1535,27 @@ nc_server_config_replace_cleartext_private_key(const struct lyd_node *node, stru return 0; } +#endif /* NC_ENABLED_TLS */ + static int nc_server_config_cleartext_private_key(const struct lyd_node *node, NC_OPERATION op) { + int ret = 0; struct nc_endpt *endpt; + +#ifdef NC_ENABLED_SSH struct nc_hostkey *hostkey; - int ret = 0; +#endif /* NC_ENABLED_SSH */ assert(!strcmp(LYD_NAME(node), "cleartext-private-key")); - if ((equal_parent_name(node, 6, "ssh")) && (equal_parent_name(node, 8, "listen"))) { - if (nc_server_config_get_endpt(node, &endpt, NULL)) { - ret = 1; - goto cleanup; - } + if (nc_server_config_get_endpt(node, &endpt, NULL)) { + ret = 1; + goto cleanup; + } + +#ifdef NC_ENABLED_SSH + if ((is_ssh(node)) && (is_listen(node))) { if (nc_server_config_get_hostkey(node, endpt->opts.ssh, &hostkey)) { ret = 1; goto cleanup; @@ -1110,11 +1570,28 @@ nc_server_config_cleartext_private_key(const struct lyd_node *node, NC_OPERATION nc_server_config_del_private_key(hostkey); } } +#endif /* NC_ENABLED_SSH */ +#ifdef NC_ENABLED_TLS + if ((is_tls(node)) && (is_listen(node))) { + /* listen tls */ + + if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { + ret = nc_server_config_tls_replace_cleartext_private_key(node, endpt->opts.tls); + if (ret) { + goto cleanup; + } + } else { + nc_server_config_tls_del_cleartext_private_key(endpt->opts.tls); + } + } +#endif /* NC_ENABLED_TLS */ cleanup: return ret; } +#ifdef NC_ENABLED_SSH + static int nc_server_config_create_keystore_reference(const struct lyd_node *node, struct nc_hostkey *hostkey) { @@ -1148,7 +1625,7 @@ nc_server_config_keystore_reference(const struct lyd_node *node, NC_OPERATION op assert(!strcmp(LYD_NAME(node), "keystore-reference")); - if ((equal_parent_name(node, 3, "server-identity")) && (equal_parent_name(node, 7, "listen"))) { + if ((equal_parent_name(node, 3, "server-identity")) && (is_ssh(node)) && (is_listen(node))) { if (nc_server_config_get_endpt(node, &endpt, NULL)) { ret = 1; goto cleanup; @@ -1191,8 +1668,8 @@ nc_server_config_replace_auth_key_public_key_leaf(const struct lyd_node *node, s { nc_server_config_del_auth_client_pubkey_pub_base64(pubkey); - pubkey->pub_base64 = strdup(lyd_get_value(node)); - if (!pubkey->pub_base64) { + pubkey->data = strdup(lyd_get_value(node)); + if (!pubkey->data) { ERRMEM; return 1; } @@ -1205,8 +1682,25 @@ nc_server_config_replace_host_key_public_key(const struct lyd_node *node, struct { nc_server_config_del_public_key(hostkey); - hostkey->key.pub_base64 = strdup(lyd_get_value(node)); - if (!hostkey->key.pub_base64) { + hostkey->key.pubkey_data = strdup(lyd_get_value(node)); + if (!hostkey->key.pubkey_data) { + ERRMEM; + return 1; + } + + return 0; +} + +#endif /* NC_ENABLED_SSH */ + +#ifdef NC_ENABLED_TLS +static int +nc_server_config_tls_replace_server_public_key(const struct lyd_node *node, struct nc_server_tls_opts *opts) +{ + nc_server_config_tls_del_public_key(opts); + + opts->pubkey_data = strdup(lyd_get_value(node)); + if (!opts->pubkey_data) { ERRMEM; return 1; } @@ -1214,24 +1708,30 @@ nc_server_config_replace_host_key_public_key(const struct lyd_node *node, struct return 0; } +#endif /* NC_ENABLED_TLS */ + static int nc_server_config_public_key(const struct lyd_node *node, NC_OPERATION op) { + int ret = 0; struct nc_endpt *endpt; + +#ifdef NC_ENABLED_SSH struct nc_hostkey *hostkey; struct nc_client_auth *auth_client; struct nc_public_key *pubkey; - int ret = 0; +#endif /* NC_ENABLED_SSH */ assert(!strcmp(LYD_NAME(node), "public-key")); - if ((equal_parent_name(node, 3, "host-key")) && (equal_parent_name(node, 8, "listen"))) { - /* server's public-key, mandatory leaf */ - if (nc_server_config_get_endpt(node, &endpt, NULL)) { - ret = 1; - goto cleanup; - } + if (nc_server_config_get_endpt(node, &endpt, NULL)) { + ret = 1; + goto cleanup; + } +#ifdef NC_ENABLED_SSH + if ((equal_parent_name(node, 3, "host-key")) && (is_ssh(node)) && (is_listen(node))) { + /* server's public-key, mandatory leaf */ if (nc_server_config_get_hostkey(node, endpt->opts.ssh, &hostkey)) { ret = 1; goto cleanup; @@ -1246,13 +1746,8 @@ nc_server_config_public_key(const struct lyd_node *node, NC_OPERATION op) goto cleanup; } } - } else if ((equal_parent_name(node, 5, "client-authentication")) && (equal_parent_name(node, 9, "listen"))) { + } else if ((equal_parent_name(node, 5, "client-authentication")) && (is_ssh(node)) && (is_listen(node))) { /* client auth pubkeys, list */ - if (nc_server_config_get_endpt(node, &endpt, NULL)) { - ret = 1; - goto cleanup; - } - if (nc_server_config_get_auth_client(node, endpt->opts.ssh, &auth_client)) { ret = 1; goto cleanup; @@ -1274,13 +1769,8 @@ nc_server_config_public_key(const struct lyd_node *node, NC_OPERATION op) nc_server_config_del_auth_client_pubkey(auth_client, pubkey); } - } else if ((equal_parent_name(node, 6, "client-authentication")) && (equal_parent_name(node, 10, "listen"))) { + } else if ((equal_parent_name(node, 6, "client-authentication")) && (is_ssh(node)) && (is_listen(node))) { /* client auth pubkey, leaf */ - if (nc_server_config_get_endpt(node, &endpt, NULL)) { - ret = 1; - goto cleanup; - } - if (nc_server_config_get_auth_client(node, endpt->opts.ssh, &auth_client)) { ret = 1; goto cleanup; @@ -1300,11 +1790,29 @@ nc_server_config_public_key(const struct lyd_node *node, NC_OPERATION op) nc_server_config_del_auth_client_pubkey_pub_base64(pubkey); } } +#endif /* NC_ENABLED_SSH */ +#ifdef NC_ENABLED_TLS + if ((equal_parent_name(node, 3, "server-identity")) && (is_tls(node)) && (is_listen(node))) { + /* tls listen server-identity */ + + if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { + /* set to local */ + endpt->opts.tls->store = NC_STORE_LOCAL; + + ret = nc_server_config_tls_replace_server_public_key(node, endpt->opts.tls); + if (ret) { + goto cleanup; + } + } + } +#endif /* NC_ENABLED_TLS */ cleanup: return ret; } +#ifdef NC_ENABLED_SSH + static int nc_server_config_create_user(const struct lyd_node *node, struct nc_server_ssh_opts *opts) { @@ -1409,7 +1917,7 @@ nc_server_config_replace_truststore_reference(const struct lyd_node *node, struc } if (i == ts->pub_bag_count) { - ERR(NULL, "Truststore \"%s\" not found.", lyd_get_value(node)); + ERR(NULL, "Public-key bag \"%s\" not found in truststore.", lyd_get_value(node)); return 1; } @@ -1418,22 +1926,54 @@ nc_server_config_replace_truststore_reference(const struct lyd_node *node, struc return 0; } +#endif /* NC_ENABLED_SSH */ + +#ifdef NC_ENABLED_TLS +static int +nc_server_config_tls_replace_truststore_reference(const struct lyd_node *node, struct nc_cert_grouping *auth_client) +{ + uint16_t i; + struct nc_truststore *ts = &server_opts.truststore; + + /* lookup name */ + for (i = 0; i < ts->cert_bag_count; i++) { + if (!strcmp(lyd_get_value(node), ts->cert_bags[i].name)) { + break; + } + } + + if (i == ts->cert_bag_count) { + ERR(NULL, "Certificate bag \"%s\" not found in truststore.", lyd_get_value(node)); + return 1; + } + + auth_client->ts_ref = &ts->cert_bags[i]; + + return 0; +} + +#endif /* NC_ENABLED_TLS */ + /* leaf */ static int nc_server_config_truststore_reference(const struct lyd_node *node, NC_OPERATION op) { + int ret = 0; struct nc_endpt *endpt; + +#ifdef NC_ENABLED_SSH struct nc_client_auth *auth_client; - int ret = 0; +#endif /* NC_ENABLED_SSH */ assert(!strcmp(LYD_NAME(node), "truststore-reference")); - if ((equal_parent_name(node, 1, "public-keys")) && (equal_parent_name(node, 8, "listen"))) { - if (nc_server_config_get_endpt(node, &endpt, NULL)) { - ret = 1; - goto cleanup; - } + if (nc_server_config_get_endpt(node, &endpt, NULL)) { + ret = 1; + goto cleanup; + } +#ifdef NC_ENABLED_SSH + if ((equal_parent_name(node, 1, "public-keys")) && (is_ssh(node)) && (is_listen(node))) { if (nc_server_config_get_auth_client(node, endpt->opts.ssh, &auth_client)) { ret = 1; goto cleanup; @@ -1451,11 +1991,41 @@ nc_server_config_truststore_reference(const struct lyd_node *node, NC_OPERATION auth_client->ts_ref = NULL; } } +#endif /* NC_ENABLED_SSH */ +#ifdef NC_ENABLED_TLS + if ((equal_parent_name(node, 1, "ca-certs")) && (is_listen(node))) { + if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { + /* set to truststore */ + endpt->opts.tls->ca_certs.store = NC_STORE_TRUSTSTORE; + + ret = nc_server_config_tls_replace_truststore_reference(node, &endpt->opts.tls->ca_certs); + if (ret) { + goto cleanup; + } + } else { + endpt->opts.tls->ca_certs.ts_ref = NULL; + } + } else if ((equal_parent_name(node, 1, "ee-certs")) && (is_listen(node))) { + if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { + /* set to truststore */ + endpt->opts.tls->ee_certs.store = NC_STORE_TRUSTSTORE; + + ret = nc_server_config_tls_replace_truststore_reference(node, &endpt->opts.tls->ee_certs); + if (ret) { + goto cleanup; + } + } else { + endpt->opts.tls->ee_certs.ts_ref = NULL; + } + } +#endif /* NC_ENABLED_TLS */ cleanup: return ret; } +#ifdef NC_ENABLED_SSH + static int nc_server_config_replace_password(const struct lyd_node *node, struct nc_client_auth *auth_client) { @@ -1863,6 +2433,8 @@ nc_server_config_mac_alg(const struct lyd_node *node, NC_OPERATION op) return ret; } +#endif /* NC_ENABLED_SSH */ + static int nc_server_config_create_unix_socket(struct nc_endpt *endpt) { @@ -1949,6 +2521,8 @@ nc_server_config_unix_socket(const struct lyd_node *node, NC_OPERATION op) return ret; } +#ifdef NC_ENABLED_SSH + /** * @brief Set all endpoint client auth references, which couldn't be set beforehand. * @@ -2072,28 +2646,476 @@ nc_server_config_endpoint_client_auth(const struct lyd_node *node, NC_OPERATION return ret; } +#endif /* NC_ENABLED_SSH */ + +#ifdef NC_ENABLED_TLS + static int -nc_server_config_parse_netconf_server(const struct lyd_node *node, NC_OPERATION op) +nc_server_config_tls_replace_cert_data(const struct lyd_node *node, struct nc_server_tls_opts *opts) { - const char *name = LYD_NAME(node); + nc_server_config_tls_del_cert_data(opts); + opts->cert_data = strdup(lyd_get_value(node)); + if (!opts->cert_data) { + ERRMEM; + return 1; + } - if (!strcmp(name, "listen")) { - if (nc_server_config_listen(NULL, op)) { - goto error; - } - } else if (!strcmp(name, "idle-timeout")) { - if (nc_server_config_idle_timeout(node, op)) { - goto error; - } - } else if (!strcmp(name, "endpoint")) { - if (nc_server_config_endpoint(node, op)) { - goto error; - } - } else if (!strcmp(name, "ssh")) { - if (nc_server_config_ssh(node, op)) { - goto error; - } - } else if (!strcmp(name, "local-address")) { + return 0; +} + +static int +nc_server_config_tls_replace_cert_data_client_auth(const struct lyd_node *node, struct nc_certificate *cert) +{ + nc_server_config_tls_del_cert_data_certificate(cert); + cert->data = strdup(lyd_get_value(node)); + if (!cert->data) { + ERRMEM; + return 1; + } + + return 0; +} + +static int +nc_server_config_cert_data(const struct lyd_node *node, NC_OPERATION op) +{ + int ret = 0; + struct nc_endpt *endpt; + struct nc_certificate *cert; + + assert(!strcmp(LYD_NAME(node), "cert-data")); + + if (nc_server_config_get_endpt(node, &endpt, NULL)) { + ret = 1; + goto cleanup; + } + + if ((equal_parent_name(node, 3, "server-identity")) && (is_listen(node))) { + if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { + ret = nc_server_config_tls_replace_cert_data(node, endpt->opts.tls); + if (ret) { + goto cleanup; + } + } + } else if ((equal_parent_name(node, 3, "ca-certs")) && (is_listen(node))) { + if (nc_server_config_get_cert(node, &endpt->opts.tls->ca_certs, &cert)) { + ret = 1; + goto cleanup; + } + + if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { + ret = nc_server_config_tls_replace_cert_data_client_auth(node, cert); + if (ret) { + goto cleanup; + } + } else { + nc_server_config_tls_del_cert_data_certificate(cert); + } + } else if ((equal_parent_name(node, 3, "ee-certs")) && (is_listen(node))) { + if (nc_server_config_get_cert(node, &endpt->opts.tls->ee_certs, &cert)) { + ret = 1; + goto cleanup; + } + + if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { + ret = nc_server_config_tls_replace_cert_data_client_auth(node, cert); + if (ret) { + goto cleanup; + } + } else { + nc_server_config_tls_del_cert_data_certificate(cert); + } + } + +cleanup: + return ret; +} + +static int +nc_server_config_tls_create_asymmetric_key_ref(const struct lyd_node *node, struct nc_endpt *endpt) +{ + uint16_t i; + struct nc_keystore *ks = &server_opts.keystore; + + /* lookup name */ + for (i = 0; i < ks->asym_key_count; i++) { + if (!strcmp(lyd_get_value(node), ks->asym_keys[i].name)) { + break; + } + } + + if (i == ks->asym_key_count) { + ERR(NULL, "Asymmetric key \"%s\" not found in the keystore.", lyd_get_value(node)); + return 1; + } + + endpt->opts.tls->key_ref = &ks->asym_keys[i]; + + return 0; +} + +static int +nc_server_config_asymmetric_key(const struct lyd_node *node, NC_OPERATION op) +{ + int ret = 0; + struct nc_endpt *endpt; + + assert(!strcmp(LYD_NAME(node), "asymmetric-key")); + + if (nc_server_config_get_endpt(node, &endpt, NULL)) { + ret = 1; + goto cleanup; + } + + if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { + /* set to keystore */ + endpt->opts.tls->store = NC_STORE_KEYSTORE; + + ret = nc_server_config_tls_create_asymmetric_key_ref(node, endpt); + if (ret) { + goto cleanup; + } + } else { + endpt->opts.tls->key_ref = NULL; + } + +cleanup: + return ret; +} + +static int +nc_server_config_tls_create_certificate_ref(const struct lyd_node *node, struct nc_endpt *endpt, struct nc_asymmetric_key *key) +{ + uint16_t i; + + /* lookup name */ + for (i = 0; i < key->cert_count; i++) { + if (!strcmp(lyd_get_value(node), key->certs[i].name)) { + break; + } + } + + if (i == key->cert_count) { + ERR(NULL, "Certificate \"%s\" not found in the asymmetric key \"%s\".", lyd_get_value(node), key->name); + return 1; + } + + endpt->opts.tls->cert_ref = &key->certs[i]; + + return 0; +} + +static struct nc_asymmetric_key * +cert_get_asymmetric_key(const struct lyd_node *node) +{ + uint16_t i; + struct nc_keystore *ks = &server_opts.keystore; + + /* starting with certificate node */ + assert(!strcmp(LYD_NAME(node), "certificate")); + + /* switch to it's only sibling, must be asymmetric-key */ + node = node->prev; + assert(!strcmp(LYD_NAME(node), "asymmetric-key")); + + /* find the given asymmetric key */ + for (i = 0; i < ks->asym_key_count; i++) { + if (!strcmp(lyd_get_value(node), ks->asym_keys[i].name)) { + return &ks->asym_keys[i]; + } + } + + /* didn't find it */ + ERR(NULL, "Asymmetric key \"%s\" not found in the keystore.", lyd_get_value(node)); + return NULL; +} + +static int +nc_server_config_create_ca_certs_certificate(const struct lyd_node *node, struct nc_server_tls_opts *opts) +{ + assert(!strcmp(LYD_NAME(node), "certificate")); + + node = lyd_child(node); + assert(!strcmp(LYD_NAME(node), "name")); + + return nc_server_config_realloc(lyd_get_value(node), (void **)&opts->ca_certs.certs, sizeof *opts->ca_certs.certs, &opts->ca_certs.cert_count); +} + +static int +nc_server_config_create_ee_certs_certificate(const struct lyd_node *node, struct nc_server_tls_opts *opts) +{ + assert(!strcmp(LYD_NAME(node), "certificate")); + + node = lyd_child(node); + assert(!strcmp(LYD_NAME(node), "name")); + + return nc_server_config_realloc(lyd_get_value(node), (void **)&opts->ee_certs.certs, sizeof *opts->ee_certs.certs, &opts->ee_certs.cert_count); +} + +static int +nc_server_config_certificate(const struct lyd_node *node, NC_OPERATION op) +{ + int ret = 0; + struct nc_endpt *endpt; + struct nc_asymmetric_key *key; + + assert(!strcmp(LYD_NAME(node), "certificate")); + + if (nc_server_config_get_endpt(node, &endpt, NULL)) { + ret = 1; + goto cleanup; + } + + if ((equal_parent_name(node, 1, "keystore-reference")) && (is_listen(node))) { + /* server-identity TLS listen */ + + if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { + /* set to keystore */ + endpt->opts.tls->store = NC_STORE_KEYSTORE; + + if (!endpt->opts.tls->key_ref) { + /* we don't have a key from which we need the cert yet */ + key = cert_get_asymmetric_key(node); + if (!key) { + ret = 1; + goto cleanup; + } + } else { + /* we have the key */ + key = endpt->opts.tls->key_ref; + } + + /* find the given cert in the key and set it */ + ret = nc_server_config_tls_create_certificate_ref(node, endpt, key); + if (ret) { + goto cleanup; + } + } else { + endpt->opts.tls->cert_ref = NULL; + } + } else if ((equal_parent_name(node, 2, "ca-certs")) && (is_listen(node))) { + /* client auth TLS listen */ + if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { + ret = nc_server_config_create_ca_certs_certificate(node, endpt->opts.tls); + if (ret) { + goto cleanup; + } + } else { + nc_server_config_tls_del_certs(&endpt->opts.tls->ca_certs); + } + } else if ((equal_parent_name(node, 2, "ee-certs")) && (is_listen(node))) { + /* client auth TLS listen */ + if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { + ret = nc_server_config_create_ee_certs_certificate(node, endpt->opts.tls); + if (ret) { + goto cleanup; + } + } else { + nc_server_config_tls_del_certs(&endpt->opts.tls->ee_certs); + } + } + +cleanup: + return ret; +} + +static int +nc_server_config_create_cert_to_name(const struct lyd_node *node, struct nc_server_tls_opts *opts) +{ + int ret = 0; + struct lyd_node *n; + struct nc_ctn *new, *iter; + const char *map_type, *name; + uint32_t id; + NC_TLS_CTN_MAPTYPE m_type; + + assert(!strcmp(LYD_NAME(node), "cert-to-name")); + + /* create new ctn */ + new = calloc(1, sizeof *new); + if (!new) { + ERRMEM; + ret = 1; + goto cleanup; + } + + /* get all the data */ + /* find the list's key */ + lyd_find_path(node, "id", 0, &n); + assert(n); + id = strtoul(lyd_get_value(n), NULL, 10); + + /* find the ctn's name */ + lyd_find_path(node, "name", 0, &n); + assert(n); + name = lyd_get_value(n); + + /* find the ctn's map-type */ + lyd_find_path(node, "map-type", 0, &n); + assert(n); + map_type = ((struct lyd_node_term *)n)->value.ident->name; + if (!strcmp(map_type, "specified")) { + m_type = NC_TLS_CTN_SPECIFIED; + } else if (!strcmp(map_type, "san-rfc822-name")) { + m_type = NC_TLS_CTN_SAN_RFC822_NAME; + } else if (!strcmp(map_type, "san-dns-name")) { + m_type = NC_TLS_CTN_SAN_DNS_NAME; + } else if (!strcmp(map_type, "san-ip-address")) { + m_type = NC_TLS_CTN_SAN_IP_ADDRESS; + } else if (!strcmp(map_type, "san-any")) { + m_type = NC_TLS_CTN_SAN_ANY; + } else if (!strcmp(map_type, "common-name")) { + m_type = NC_TLS_CTN_COMMON_NAME; + } else { + ERR(NULL, "Map-type identity \"%s\" not supported.", map_type); + ret = 1; + goto cleanup; + } + + /* find the right place for insertion */ + if (!opts->ctn) { + /* inserting the first one */ + opts->ctn = new; + } else if (opts->ctn->id > new->id) { + /* insert at the beginning */ + new->next = opts->ctn; + opts->ctn = new; + } else { + /* have to find the right place */ + for (iter = opts->ctn; iter->next && iter->next->id <= new->id; iter = iter->next) {} + if (iter->id == new->id) { + /* collision */ + new = iter; + } else { + new->next = iter->next; + iter->next = new; + } + } + + /* insert the right data */ + new->id = id; + if (new->name) { + free(new->name); + } + new->name = strdup(name); + if (!new->name) { + ERRMEM; + ret = 1; + goto cleanup; + } + new->map_type = m_type; + +cleanup: + return ret; +} + +static int +nc_server_config_cert_to_name(const struct lyd_node *node, NC_OPERATION op) +{ + int ret = 0; + struct nc_endpt *endpt; + struct lyd_node *key; + struct nc_ctn *ctn; + + assert(!strcmp(LYD_NAME(node), "cert-to-name")); + + if (nc_server_config_get_endpt(node, &endpt, NULL)) { + ret = 1; + goto cleanup; + } + + if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { + ret = nc_server_config_create_cert_to_name(node, endpt->opts.tls); + if (ret) { + goto cleanup; + } + } else { + /* find the given ctn entry */ + lyd_find_path(node, "id", 0, &key); + assert(key); + if (nc_server_config_get_ctn(node, endpt, &ctn)) { + ret = 1; + goto cleanup; + } + nc_server_config_del_ctn(endpt->opts.tls, ctn); + } + +cleanup: + return ret; +} + +static int +nc_server_config_replace_fingerprint(const struct lyd_node *node, struct nc_ctn *ctn) +{ + nc_server_config_del_fingerprint(ctn); + + ctn->fingerprint = strdup(lyd_get_value(node)); + if (!ctn->fingerprint) { + ERRMEM; + return 1; + } + + return 0; +} + +static int +nc_server_config_fingerprint(const struct lyd_node *node, NC_OPERATION op) +{ + int ret = 0; + struct nc_endpt *endpt; + struct nc_ctn *ctn; + + if (nc_server_config_get_endpt(node, &endpt, NULL)) { + ret = 1; + goto cleanup; + } + + if (nc_server_config_get_ctn(node, endpt, &ctn)) { + ret = 1; + goto cleanup; + } + + if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { + ret = nc_server_config_replace_fingerprint(node, ctn); + if (ret) { + goto cleanup; + } + } else { + nc_server_config_del_fingerprint(ctn); + } + +cleanup: + return ret; +} + +#endif /* NC_ENABLED_TLS */ + +static int +nc_server_config_parse_netconf_server(const struct lyd_node *node, NC_OPERATION op) +{ + const char *name = LYD_NAME(node); + + if (!strcmp(name, "listen")) { + if (nc_server_config_listen(NULL, op)) { + goto error; + } + } else if (!strcmp(name, "idle-timeout")) { + if (nc_server_config_idle_timeout(node, op)) { + goto error; + } + } else if (!strcmp(name, "endpoint")) { + if (nc_server_config_endpoint(node, op)) { + goto error; + } + } +#ifdef NC_ENABLED_SSH + else if (!strcmp(name, "ssh")) { + if (nc_server_config_ssh(node, op)) { + goto error; + } + } +#endif /* NC_ENABLED_SSH */ + else if (!strcmp(name, "local-address")) { if (nc_server_config_local_address(node, op)) { goto error; } @@ -2117,11 +3139,15 @@ nc_server_config_parse_netconf_server(const struct lyd_node *node, NC_OPERATION if (nc_server_config_probe_interval(node, op)) { goto error; } - } else if (!strcmp(name, "host-key")) { + } +#ifdef NC_ENABLED_SSH + else if (!strcmp(name, "host-key")) { if (nc_server_config_host_key(node, op)) { goto error; } - } else if (!strcmp(name, "public-key-format")) { + } +#endif /* NC_ENABLED_SSH */ + else if (!strcmp(name, "public-key-format")) { if (nc_server_config_public_key_format(node, op)) { goto error; } @@ -2137,7 +3163,9 @@ nc_server_config_parse_netconf_server(const struct lyd_node *node, NC_OPERATION if (nc_server_config_cleartext_private_key(node, op)) { goto error; } - } else if (!strcmp(name, "keystore-reference")) { + } +#ifdef NC_ENABLED_SSH + else if (!strcmp(name, "keystore-reference")) { if (nc_server_config_keystore_reference(node, op)) { goto error; } @@ -2153,11 +3181,15 @@ nc_server_config_parse_netconf_server(const struct lyd_node *node, NC_OPERATION if (nc_server_config_auth_timeout(node, op)) { goto error; } - } else if (!strcmp(name, "truststore-reference")) { + } +#endif /* NC_ENABLED_SSH */ + else if (!strcmp(name, "truststore-reference")) { if (nc_server_config_truststore_reference(node, op)) { goto error; } - } else if (!strcmp(name, "password")) { + } +#ifdef NC_ENABLED_SSH + else if (!strcmp(name, "password")) { if (nc_server_config_password(node, op)) { goto error; } @@ -2189,20 +3221,47 @@ nc_server_config_parse_netconf_server(const struct lyd_node *node, NC_OPERATION if (nc_server_config_mac_alg(node, op)) { goto error; } - } else if (!strcmp(name, "unix-socket")) { + } +#endif /* NC_ENABLED_SSH */ + else if (!strcmp(name, "unix-socket")) { if (nc_server_config_unix_socket(node, op)) { goto error; } - } else if (!strcmp(name, "endpoint-client-auth")) { + } +#ifdef NC_ENABLED_SSH + else if (!strcmp(name, "endpoint-client-auth")) { if (nc_server_config_endpoint_client_auth(node, op)) { goto error; } - } else if (!strcmp(name, "cert-data")) {} else if (!strcmp(name, "expiration-date")) {} else if (!strcmp(name, "asymmetric-key")) {} else if (!strcmp(name, "certificate")) {} else if (!strcmp(name, "key-format")) {} else if (!strcmp(name, - "cleartext-key")) {} else if (!strcmp(name, "hidden-key")) {} else if (!strcmp(name, "id_hint")) {} else if (!strcmp(name, "external-identity")) {} else if (!strcmp(name, "hash")) {} else if (!strcmp(name, "context")) {} else if (!strcmp(name, - "target-protocol")) {} else if (!strcmp(name, "target-kdf")) {} else if (!strcmp(name, "client-authentication")) {} else if (!strcmp(name, "ca-certs")) {} else if (!strcmp(name, "ee-certs")) {} else if (!strcmp(name, - "raw-public-keys")) {} else if (!strcmp(name, "tls12-psks")) {} else if (!strcmp(name, "tls13-epsks")) {} else if (!strcmp(name, "tls-version")) {} else if (!strcmp(name, "cipher-suite")) {} else if (!strcmp(name, - "peer-allowed-to-send")) {} else if (!strcmp(name, "test-peer-aliveness")) {} else if (!strcmp(name, "max-wait")) {} else if (!strcmp(name, "max-attempts")) {} else if (!strcmp(name, "cert-to-name")) {} else if (!strcmp(name, - "id")) {} else if (!strcmp(name, "fingerprint")) {} else if (!strcmp(name, "map-type")) {} + } +#endif /* NC_ENABLED_SSH */ +#ifdef NC_ENABLED_TLS + else if (!strcmp(name, "tls")) { + if (nc_server_config_tls(node, op)) { + goto error; + } + } else if (!strcmp(name, "cert-data")) { + if (nc_server_config_cert_data(node, op)) { + goto error; + } + } else if (!strcmp(name, "asymmetric-key")) { + if (nc_server_config_asymmetric_key(node, op)) { + goto error; + } + } else if (!strcmp(name, "certificate")) { + if (nc_server_config_certificate(node, op)) { + goto error; + } + } else if (!strcmp(name, "cert-to-name")) { + if (nc_server_config_cert_to_name(node, op)) { + goto error; + } + } else if (!strcmp(name, "fingerprint")) { + if (nc_server_config_fingerprint(node, op)) { + goto error; + } + } +#endif /* NC_ENABLED_TLS */ return 0; @@ -2359,29 +3418,6 @@ nc_server_config_load_modules(struct ly_ctx **ctx) return 1; } -API int -nc_server_config_setup_path(const struct ly_ctx *ctx, const char *path) -{ - struct lyd_node *tree = NULL; - int ret = 0; - - NC_CHECK_ARG_RET(NULL, path, 1); - - ret = lyd_parse_data_path(ctx, path, LYD_UNKNOWN, LYD_PARSE_NO_STATE | LYD_PARSE_STRICT, LYD_VALIDATE_NO_STATE, &tree); - if (ret) { - goto cleanup; - } - - ret = nc_server_config_setup_data(tree); - if (ret) { - goto cleanup; - } - -cleanup: - lyd_free_all(tree); - return ret; -} - static int nc_server_config_fill_nectonf_server(const struct lyd_node *data, NC_OPERATION op) { @@ -2399,10 +3435,12 @@ nc_server_config_fill_nectonf_server(const struct lyd_node *data, NC_OPERATION o goto cleanup; } +#ifdef NC_ENABLED_SSH if (nc_server_config_fill_endpt_client_auth()) { ret = 1; goto cleanup; } +#endif /* NC_ENABLED_SSH */ cleanup: return ret; @@ -2502,3 +3540,26 @@ nc_server_config_setup_data(const struct lyd_node *data) pthread_rwlock_unlock(&server_opts.config_lock); return ret; } + +API int +nc_server_config_setup_path(const struct ly_ctx *ctx, const char *path) +{ + struct lyd_node *tree = NULL; + int ret = 0; + + NC_CHECK_ARG_RET(NULL, path, 1); + + ret = lyd_parse_data_path(ctx, path, LYD_UNKNOWN, LYD_PARSE_NO_STATE | LYD_PARSE_STRICT, LYD_VALIDATE_NO_STATE, &tree); + if (ret) { + goto cleanup; + } + + ret = nc_server_config_setup_data(tree); + if (ret) { + goto cleanup; + } + +cleanup: + lyd_free_all(tree); + return ret; +} diff --git a/src/server_config.h b/src/server_config.h index 019db841..63b30eeb 100644 --- a/src/server_config.h +++ b/src/server_config.h @@ -4,7 +4,7 @@ * @brief libnetconf2 server configuration * * @copyright - * Copyright (c) 2015 - 2021 CESNET, z.s.p.o. + * Copyright (c) 2023 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. @@ -20,12 +20,12 @@ extern "C" { #endif -#include +#include #include -#include "netconf.h" +#include + #include "session.h" -#include "session_p.h" /** * @brief Configure server based on the given diff data. @@ -83,38 +83,39 @@ int nc_server_config_setup_path(const struct ly_ctx *ctx, const char *path); int nc_server_config_load_modules(struct ly_ctx **ctx); /** - * @brief Creates new YANG configuration data nodes for a hostkey. + * @brief Creates new YANG configuration data nodes for a local-address and local-port. * - * @param[in] privkey_path Path to a file containing a private key. - * The private key has to be in a PEM format. Only RSA and ECDSA keys are supported. - * @param[in] pubkey_path Path to a file containing a public key. If NULL, public key will be - * generated from the private key. * @param[in] ctx libyang context. * @param[in] endpt_name Arbitrary identifier of the endpoint. - * If an endpoint with this identifier already exists, it's hostkey might be changed. - * @param[in] hostkey_name Arbitrary identifier of the hostkey. - * If a hostkey with this identifier already exists, it's contents will be changed. + * @param[in] transport Either SSH or TLS transport for the given endpoint. + * @param[in] address New listening address. + * @param[in] port New listening port. + * If an endpoint with this identifier already exists, it's address and port will be overriden. * @param[in,out] config Configuration YANG data tree. If *config is NULL, it will be created. * Otherwise the new YANG data will be added to the previous data and may override it. * @return 0 on success, non-zero otherwise. */ -int nc_server_config_new_ssh_hostkey(const char *privkey_path, const char *pubkey_path, const struct ly_ctx *ctx, - const char *endpt_name, const char *hostkey_name, struct lyd_node **config); +int nc_server_config_new_address_port(const struct ly_ctx *ctx, const char *endpt_name, NC_TRANSPORT_IMPL transport, + const char *address, const char *port, struct lyd_node **config); /** - * @brief Creates new YANG configuration data nodes for a local-address and local-port. + * @brief Creates new YANG configuration data nodes for a hostkey. * - * @param[in] address New listening address. - * @param[in] port New listening port. * @param[in] ctx libyang context. * @param[in] endpt_name Arbitrary identifier of the endpoint. - * If an endpoint with this identifier already exists, it's address and port will be overriden. + * If an endpoint with this identifier already exists, it's hostkey might be changed. + * @param[in] hostkey_name Arbitrary identifier of the hostkey. + * If a hostkey with this identifier already exists, it's contents will be changed. + * @param[in] privkey_path Path to a file containing a private key. + * The private key has to be in a PEM format. Only RSA and ECDSA keys are supported. + * @param[in] pubkey_path Path to a file containing a public key. If NULL, public key will be + * generated from the private key. * @param[in,out] config Configuration YANG data tree. If *config is NULL, it will be created. * Otherwise the new YANG data will be added to the previous data and may override it. * @return 0 on success, non-zero otherwise. */ -int nc_server_config_new_ssh_address_port(const char *address, const char *port, const struct ly_ctx *ctx, - const char *endpt_name, struct lyd_node **config); +int nc_server_config_new_ssh_hostkey(const struct ly_ctx *ctx, + const char *endpt_name, const char *hostkey_name, const char *privkey_path, const char *pubkey_path, struct lyd_node **config); /** * @brief Creates new YANG configuration data nodes for host-key algorithms replacing any previous ones. @@ -191,7 +192,6 @@ int nc_server_config_new_ssh_mac_algs(const struct ly_ctx *ctx, const char *endp /** * @brief Creates new YANG configuration data nodes for a client, which supports the public key authentication method. * - * @param[in] pubkey_path Path to a file containing the user's public key. * @param[in] ctx libyang context. * @param[in] endpt_name Arbitrary identifier of the endpoint. * If an endpoint with this identifier already exists, it's user might be changed. @@ -199,30 +199,31 @@ int nc_server_config_new_ssh_mac_algs(const struct ly_ctx *ctx, const char *endp * If an user with this identifier already exists, it's contents will be changed. * @param[in] pubkey_name Arbitrary identifier of the user's public key. * If a public key with this identifier already exists for this user, it's contents will be changed. + * @param[in] pubkey_path Path to a file containing the user's public key. * @param[in,out] config Configuration YANG data tree. If *config is NULL, it will be created. * Otherwise the new YANG data will be added to the previous data and may override it. * @return 0 on success, non-zero otherwise. */ -int nc_server_config_new_ssh_client_auth_pubkey(const char *pubkey_path, const struct ly_ctx *ctx, const char *endpt_name, - const char *user_name, const char *pubkey_name, struct lyd_node **config); +int nc_server_config_new_ssh_client_auth_pubkey(const struct ly_ctx *ctx, const char *endpt_name, + const char *user_name, const char *pubkey_name, const char *pubkey_path, struct lyd_node **config); /** * @brief Creates new YANG configuration data nodes for a client, which supports the password authentication method. * * This function sets the password for the given user. * - * @param[in] password Cleartext user's password. * @param[in] ctx libyang context. * @param[in] endpt_name Arbitrary identifier of the endpoint. * If an endpoint with this identifier already exists, it's user might be changed. * @param[in] user_name Arbitrary identifier of the user. * If an user with this identifier already exists, it's contents will be changed. + * @param[in] password Cleartext user's password. * @param[in,out] config Configuration YANG data tree. If *config is NULL, it will be created. * Otherwise the new YANG data will be added to the previous data and may override it. * @return 0 on success, non-zero otherwise. */ -int nc_server_config_new_ssh_client_auth_password(const char *password, const struct ly_ctx *ctx, const char *endpt_name, - const char *user_name, struct lyd_node **config); +int nc_server_config_new_ssh_client_auth_password(const struct ly_ctx *ctx, const char *endpt_name, + const char *user_name, const char *password, struct lyd_node **config); /** * @brief Creates new YANG configuration data nodes for a client, which supports the none authentication method. @@ -242,22 +243,91 @@ int nc_server_config_new_ssh_client_auth_none(const struct ly_ctx *ctx, const ch /** * @brief Creates new YANG configuration data nodes for a client, which supports the interactive authentication method. * + * @param[in] ctx libyang context. + * @param[in] endpt_name Arbitrary identifier of the endpoint. + * If an endpoint with this identifier already exists, it's user might be changed. + * @param[in] user_name Arbitrary identifier of the user. + * If an user with this identifier already exists, it's contents will be changed. * @param[in] pam_config_name Name of the PAM configuration file. * @param[in] pam_config_name Optional. The absolute path to the directory in which the configuration file * with the name conf_name is located. A newer version (>= 1.4) of PAM library is required to be able to specify * the path. If NULL is passed, then the PAM's system directories will be searched (usually /etc/pam.d/). + * @param[in,out] config Configuration YANG data tree. If *config is NULL, it will be created. + * Otherwise the new YANG data will be added to the previous data and may override it. + * @return 0 on success, non-zero otherwise. + */ +int nc_server_config_new_ssh_client_auth_interactive(const struct ly_ctx *ctx, const char *endpt_name, + const char *user_name, const char *pam_config_name, const char *pam_config_dir, struct lyd_node **config); + +#ifdef NC_ENABLED_TLS +/** + * @brief Creates new YANG configuration data nodes for a server's certificate. + * * @param[in] ctx libyang context. * @param[in] endpt_name Arbitrary identifier of the endpoint. - * If an endpoint with this identifier already exists, it's user might be changed. - * @param[in] user_name Arbitrary identifier of the user. - * If an user with this identifier already exists, it's contents will be changed. + * If an endpoint with this identifier already exists, it's server certificate will be changed. + * @param[in] pubkey_path Optional path to the server's public key file. If not provided, + * it will be generated from the private key. + * @param[in] privkey_path Path to the server's private key file. + * @param[in] certificate_path Path to the server's certificate file. * @param[in,out] config Configuration YANG data tree. If *config is NULL, it will be created. * Otherwise the new YANG data will be added to the previous data and may override it. * @return 0 on success, non-zero otherwise. */ -int nc_server_config_new_ssh_client_auth_interactive(const char *pam_config_name, const char *pam_config_dir, - const struct ly_ctx *ctx, const char *endpt_name, - const char *user_name, struct lyd_node **config); +int nc_server_config_new_tls_server_certificate(const struct ly_ctx *ctx, const char *endpt_name, const char *pubkey_path, + const char *privkey_path, const char *certificate_path, struct lyd_node **config); + +/** + * @brief Creates new YANG configuration data nodes for a client's (end-entity) certificate. + * + * @param[in] ctx libyang context. + * @param[in] endpt_name Arbitrary identifier of the endpoint. + * If an endpoint with this identifier already exists, it's contents will be changed. + * @param[in] cert_name Arbitrary identifier of the client's certificate. + * If a client certificate with this indetifier already exists, it will be changed. + * @param[in] cert_path Path to the client's certificate file. + * @param[in,out] config Configuration YANG data tree. If *config is NULL, it will be created. + * Otherwise the new YANG data will be added to the previous data and may override it. + * @return 0 on success, non-zero otherwise. + */ +int nc_server_config_new_tls_client_certificate(const struct ly_ctx *ctx, const char *endpt_name, const char *cert_name, + const char *cert_path, struct lyd_node **config); + +/** + * @brief Creates new YANG configuration data nodes for a client certificate authority (trust-anchor) certificate. + * + * @param[in] ctx libyang context. + * @param[in] endpt_name Arbitrary identifier of the endpoint. + * If an endpoint with this identifier already exists, it's contents will be changed. + * @param[in] cert_name Arbitrary identifier of the certificate authority certificate. + * If a CA with this indetifier already exists, it will be changed. + * @param[in] cert_path Path to the CA certificate file. + * @param[in,out] config Configuration YANG data tree. If *config is NULL, it will be created. + * Otherwise the new YANG data will be added to the previous data and may override it. + * @return 0 on success, non-zero otherwise. + */ +int nc_server_config_new_tls_client_ca(const struct ly_ctx *ctx, const char *endpt_name, const char *cert_name, + const char *cert_path, struct lyd_node **config); + +/** + * @brief Creates new YANG configuration data nodes for a cert-to-name entry. + * + * @param[in] ctx libyang context. + * @param[in] endpt_name Arbitrary identifier of the endpoint. + * If an endpoint with this identifier already exists, it's contents will be changed. + * @param[in] id ID of the entry. The lower the ID, the higher the priority of the entry (it will be checked earlier). + * @param[in] fingerprint Optional fingerprint of the entry. The fingerprint should always be set, however if it is + * not set, it will match any certificate. Entry with no fingerprint should therefore be placed only as the last entry. + * @param[in] map_type Mapping username to the certificate option. + * @param[in] name Username for this cert-to-name entry. + * @param[in,out] config Configuration YANG data tree. If *config is NULL, it will be created. + * Otherwise the new YANG data will be added to the previous data and may override it. + * @return 0 on success, non-zero otherwise. + */ +int nc_server_config_new_tls_ctn(const struct ly_ctx *ctx, const char *endpt_name, uint32_t id, const char *fingerprint, + NC_TLS_CTN_MAPTYPE map_type, const char *name, struct lyd_node **config); + +#endif /* NC_ENABLED_TLS */ #ifdef __cplusplus } diff --git a/src/server_config_ks.c b/src/server_config_ks.c index dfcb3857..25b67748 100644 --- a/src/server_config_ks.c +++ b/src/server_config_ks.c @@ -16,12 +16,16 @@ #define _GNU_SOURCE #include +#include #include #include +#include + #include "compat.h" -#include "libnetconf.h" +#include "log_p.h" #include "server_config_p.h" +#include "session_p.h" extern struct nc_server_opts server_opts; @@ -117,8 +121,8 @@ nc_server_config_ks_del_asymmetric_key_cert(struct nc_asymmetric_key *key, struc free(cert->name); cert->name = NULL; - free(cert->cert_base64); - cert->cert_base64 = NULL; + free(cert->data); + cert->data = NULL; key->cert_count--; if (key->cert_count == 0) { @@ -130,22 +134,22 @@ nc_server_config_ks_del_asymmetric_key_cert(struct nc_asymmetric_key *key, struc static void nc_server_config_ks_del_public_key(struct nc_asymmetric_key *key) { - free(key->pub_base64); - key->pub_base64 = NULL; + free(key->pubkey_data); + key->pubkey_data = NULL; } static void nc_server_config_ks_del_private_key(struct nc_asymmetric_key *key) { - free(key->priv_base64); - key->priv_base64 = NULL; + free(key->privkey_data); + key->privkey_data = NULL; } static void nc_server_config_ks_del_cert_data(struct nc_certificate *cert) { - free(cert->cert_base64); - cert->cert_base64 = NULL; + free(cert->data); + cert->data = NULL; } static void @@ -252,9 +256,9 @@ nc_server_config_ks_public_key_format(const struct lyd_node *node, NC_OPERATION format = ((struct lyd_node_term *)node)->value.ident->name; if (!strcmp(format, "ssh-public-key-format")) { - key->pubkey_type = NC_SSH_PUBKEY_SSH2; + key->pubkey_type = NC_PUBKEY_FORMAT_SSH2; } else if (!strcmp(format, "subject-public-key-info-format")) { - key->pubkey_type = NC_SSH_PUBKEY_X509; + key->pubkey_type = NC_PUBKEY_FORMAT_X509; } else { ERR(NULL, "Public key format (%s) not supported.", format); } @@ -277,8 +281,8 @@ nc_server_config_ks_public_key(const struct lyd_node *node, NC_OPERATION op) /* replace the pubkey */ nc_server_config_ks_del_public_key(key); - key->pub_base64 = strdup(lyd_get_value(node)); - if (!key->pub_base64) { + key->pubkey_data = strdup(lyd_get_value(node)); + if (!key->pubkey_data) { ERRMEM; return 1; } @@ -291,6 +295,9 @@ nc_server_config_ks_private_key_format(const struct lyd_node *node, NC_OPERATION { struct nc_asymmetric_key *key; const char *format; + NC_PRIVKEY_FORMAT privkey_type; + + (void) op; assert(!strcmp(LYD_NAME(node), "private-key-format")); @@ -299,19 +306,15 @@ nc_server_config_ks_private_key_format(const struct lyd_node *node, NC_OPERATION } format = ((struct lyd_node_term *)node)->value.ident->name; - if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { - if (!strcmp(format, "rsa-private-key-format")) { - key->privkey_type = NC_PRIVKEY_FORMAT_RSA; - } else if (!strcmp(format, "ec-private-key-format")) { - key->privkey_type = NC_PRIVKEY_FORMAT_EC; - } else if (!strcmp(format, "subject-private-key-info-format")) { - key->privkey_type = NC_PRIVKEY_FORMAT_PKCS8; - } else if (!strcmp(format, "openssh-private-key-format")) { - key->privkey_type = NC_PRIVKEY_FORMAT_OPENSSH; - } else { - ERR(NULL, "Private key format (%s) not supported.", format); - } + if (!format) { + return 1; + } + + privkey_type = nc_server_config_get_private_key_type(format); + if (privkey_type == NC_PRIVKEY_FORMAT_UNKNOWN) { + return 1; } + key->privkey_type = privkey_type; return 0; } @@ -330,8 +333,8 @@ nc_server_config_ks_cleartext_private_key(const struct lyd_node *node, NC_OPERAT if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { /* replace the privkey */ nc_server_config_ks_del_private_key(key); - key->priv_base64 = strdup(lyd_get_value(node)); - if (!key->priv_base64) { + key->privkey_data = strdup(lyd_get_value(node)); + if (!key->privkey_data) { ERRMEM; return 1; } @@ -401,8 +404,8 @@ nc_server_config_ks_cert_data(const struct lyd_node *node, NC_OPERATION op) /* replace the cert data */ nc_server_config_ks_del_cert_data(cert); - cert->cert_base64 = strdup(lyd_get_value(node)); - if (!cert->cert_base64) { + cert->data = strdup(lyd_get_value(node)); + if (!cert->data) { ERRMEM; return 1; } diff --git a/src/server_config_p.h b/src/server_config_p.h index 7d5469df..7866bf99 100644 --- a/src/server_config_p.h +++ b/src/server_config_p.h @@ -22,10 +22,8 @@ extern "C" { #include #include +#include -#include "compat.h" -#include "libnetconf.h" -#include "netconf.h" #include "session_p.h" /** @@ -47,6 +45,7 @@ typedef enum { */ int nc_server_config_get_endpt(const struct lyd_node *node, struct nc_endpt **endpt, struct nc_bind **bind); +#ifdef NC_ENABLED_SSH /** * @brief Get the pointer to a hostkey structure based on node's location in the YANG data. * @@ -77,6 +76,8 @@ int nc_server_config_get_auth_client(const struct lyd_node *node, const struct n */ int nc_server_config_get_pubkey(const struct lyd_node *node, const struct nc_client_auth *auth_client, struct nc_public_key **pubkey); +#endif /* NC_ENABLED_SSH */ + /** * @brief Compares the nth-parent name. * @@ -87,6 +88,14 @@ int nc_server_config_get_pubkey(const struct lyd_node *node, const struct nc_cli */ int equal_parent_name(const struct lyd_node *node, uint16_t parent_count, const char *parent_name); +/** + * @brief Get private key type from YANG identity stored in a string. + * + * @param[in] format Value of the YANG identityref. + * @return Private key format on success, NC_PRIVKEY_FORMAT_UNKNOWN otherwise. + */ +NC_PRIVKEY_FORMAT nc_server_config_get_private_key_type(const char *format); + /** * @brief Generic realloc function for arrays of structures representing YANG lists whose first member is the key (char *) * diff --git a/src/server_config_ts.c b/src/server_config_ts.c index 9b06ada9..b0d318b8 100644 --- a/src/server_config_ts.c +++ b/src/server_config_ts.c @@ -16,12 +16,16 @@ #define _GNU_SOURCE #include +#include #include #include +#include + #include "compat.h" -#include "libnetconf.h" +#include "log_p.h" #include "server_config_p.h" +#include "session_p.h" extern struct nc_server_opts server_opts; @@ -206,15 +210,15 @@ nc_server_config_get_public_key(const struct lyd_node *node, const struct nc_pub static void nc_server_config_ts_del_cert_data(struct nc_certificate *cert) { - free(cert->cert_base64); - cert->cert_base64 = NULL; + free(cert->data); + cert->data = NULL; } static void nc_server_config_ts_del_public_key_base64(struct nc_public_key *pkey) { - free(pkey->pub_base64); - pkey->pub_base64 = NULL; + free(pkey->data); + pkey->data = NULL; } static void @@ -422,8 +426,8 @@ nc_server_config_ts_cert_data(const struct lyd_node *node, NC_OPERATION op) } nc_server_config_ts_del_cert_data(cert); - cert->cert_base64 = strdup(lyd_get_value(node)); - if (!cert->cert_base64) { + cert->data = strdup(lyd_get_value(node)); + if (!cert->data) { ERRMEM; return 1; } @@ -510,8 +514,8 @@ nc_server_config_ts_public_key(const struct lyd_node *node, NC_OPERATION op) /* replace the public key */ nc_server_config_ts_del_public_key_base64(pkey); - pkey->pub_base64 = strdup(lyd_get_value(node)); - if (!pkey->pub_base64) { + pkey->data = strdup(lyd_get_value(node)); + if (!pkey->data) { ERRMEM; ret = 1; goto cleanup; @@ -541,9 +545,9 @@ nc_server_config_ts_public_key_format(const struct lyd_node *node, NC_OPERATION format = ((struct lyd_node_term *)node)->value.ident->name; if (!strcmp(format, "ssh-public-key-format")) { - pkey->pubkey_type = NC_SSH_PUBKEY_SSH2; + pkey->type = NC_PUBKEY_FORMAT_SSH2; } else if (!strcmp(format, "subject-public-key-info-format")) { - pkey->pubkey_type = NC_SSH_PUBKEY_X509; + pkey->type = NC_PUBKEY_FORMAT_X509; } else { ERR(NULL, "Public key format (%s) not supported.", format); } diff --git a/src/session.c b/src/session.c index 17c6bea3..a3c7fb02 100644 --- a/src/session.c +++ b/src/session.c @@ -18,8 +18,6 @@ #include #include #include -#include -#include #include #include #include @@ -30,9 +28,10 @@ #include #include "compat.h" -#include "libnetconf.h" -#include "session.h" -#include "session_server.h" +#include "config.h" +#include "log_p.h" +#include "netconf.h" +#include "session_p.h" #ifdef NC_ENABLED_SSH @@ -107,37 +106,21 @@ nc_realtime_get(struct timespec *ts) } } -/** - * @brief Convert key type to string. - * - * @param[in] type Type of the key. - * @return String literal representing the key type or NULL. - */ const char * -nc_keytype2str(NC_SSH_KEY_TYPE type) +nc_privkey_format_to_str(NC_PRIVKEY_FORMAT format) { - switch (type) { - case NC_SSH_KEY_UNKNOWN: - return "unknown"; - case NC_SSH_KEY_DSA: - return "DSA"; - case NC_SSH_KEY_RSA: + switch (format) { + case NC_PRIVKEY_FORMAT_RSA: return "RSA"; - case NC_SSH_KEY_ECDSA: + case NC_PRIVKEY_FORMAT_EC: return "EC"; - case NC_SSH_KEY_ECDSA_P256: - return "ECDSA_P256"; - case NC_SSH_KEY_ECDSA_P384: - return "ECDSA_P384"; - case NC_SSH_KEY_ECDSA_P521: - return "ECDSA_P521"; - case NC_SSH_KEY_ED25519: + case NC_PRIVKEY_FORMAT_X509: return NULL; + case NC_PRIVKEY_FORMAT_OPENSSH: + return "OPENSSH"; default: - break; + return NULL; } - - return NULL; } int diff --git a/src/session.h b/src/session.h index aab4cac6..eb3b36fa 100644 --- a/src/session.h +++ b/src/session.h @@ -109,20 +109,6 @@ typedef enum { NC_CH_RANDOM } NC_CH_START_WITH; -/** - * @brief Enumeration of SSH key types. - */ -typedef enum { - NC_SSH_KEY_UNKNOWN = 0, - NC_SSH_KEY_DSA, - NC_SSH_KEY_RSA, - NC_SSH_KEY_ECDSA, /**< only for private key */ - NC_SSH_KEY_ECDSA_P256, - NC_SSH_KEY_ECDSA_P384, - NC_SSH_KEY_ECDSA_P521, - NC_SSH_KEY_ED25519 -} NC_SSH_KEY_TYPE; - /** * @brief NETCONF session object */ diff --git a/src/session_client.c b/src/session_client.c index b0b0d950..e0a68401 100644 --- a/src/session_client.c +++ b/src/session_client.c @@ -42,9 +42,12 @@ #include #include "compat.h" -#include "libnetconf.h" -#include "messages_client.h" +#include "config.h" +#include "log_p.h" +#include "messages_p.h" #include "session_client.h" +#include "session_client_ch.h" +#include "session_p.h" #include "../modules/ietf_netconf@2013-09-29_yang.h" #include "../modules/ietf_netconf_monitoring@2010-10-04_yang.h" diff --git a/src/session_client_ssh.c b/src/session_client_ssh.c index c3c8d1f9..500fdbe4 100644 --- a/src/session_client_ssh.c +++ b/src/session_client_ssh.c @@ -45,9 +45,11 @@ #include #include "compat.h" -#include "libnetconf.h" +#include "config.h" +#include "log_p.h" #include "session_client.h" #include "session_client_ch.h" +#include "session_p.h" struct nc_client_context *nc_client_context_location(void); @@ -1690,7 +1692,7 @@ nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx) char *known_hosts_path = NULL; /* process parameters */ - if (!host || strisempty(host)) { + if (!host || (host[0] == '\0')) { host = "localhost"; } diff --git a/src/session_client_tls.c b/src/session_client_tls.c index 94b21b42..26b22e42 100644 --- a/src/session_client_tls.c +++ b/src/session_client_tls.c @@ -28,9 +28,11 @@ #include #include -#include "libnetconf.h" +#include "config.h" +#include "log_p.h" #include "session_client.h" #include "session_client_ch.h" +#include "session_p.h" #if OPENSSL_VERSION_NUMBER < 0x10100000L #define X509_STORE_CTX_get_by_subject X509_STORE_get_by_subject @@ -666,7 +668,7 @@ nc_connect_tls(const char *host, unsigned short port, struct ly_ctx *ctx) } /* process parameters */ - if (!host || strisempty(host)) { + if (!host || (host[0] == '\0')) { host = "localhost"; } diff --git a/src/session_p.h b/src/session_p.h index 2bfc2c2e..839d24fb 100644 --- a/src/session_p.h +++ b/src/session_p.h @@ -21,15 +21,12 @@ #include #include -#include #include +#include #include "compat.h" -#include "libnetconf.h" -#include "messages_client.h" -#include "netconf.h" -#include "session.h" +#include "config.h" #include "session_client.h" /** @@ -55,12 +52,12 @@ typedef enum { } NC_STORE_TYPE; /** - * Enumeration of SSH public key representation types. + * Enumeration of SSH public key formats. */ typedef enum { - NC_SSH_PUBKEY_SSH2, /**< begins with BEGIN SSH2 PUBLICKEY, see RFC 4716 */ - NC_SSH_PUBKEY_X509 /**< begins with BEGIN PUBLICKEY, see RFC 5280 sec. 4.1.2.7 */ -} NC_SSH_PUBKEY_TYPE; + NC_PUBKEY_FORMAT_SSH2, /**< begins with BEGIN SSH2 PUBLICKEY, see RFC 4716 */ + NC_PUBKEY_FORMAT_X509 /**< begins with BEGIN PUBLICKEY, see RFC 5280 sec. 4.1.2.7 */ +} NC_PUBKEY_FORMAT; /** * Enumeration of private key file formats. @@ -68,16 +65,17 @@ typedef enum { typedef enum { NC_PRIVKEY_FORMAT_RSA, /**< PKCS1 RSA format */ NC_PRIVKEY_FORMAT_EC, /**< SEC1 EC format */ - NC_PRIVKEY_FORMAT_PKCS8, /**< PKCS8 format */ - NC_PRIVKEY_FORMAT_OPENSSH /**< OpenSSH format */ + NC_PRIVKEY_FORMAT_X509, /**< X509 (PKCS8) format */ + NC_PRIVKEY_FORMAT_OPENSSH, /**< OpenSSH format */ + NC_PRIVKEY_FORMAT_UNKNOWN /**< Unknown format */ } NC_PRIVKEY_FORMAT; /** * @brief A basic certificate. */ struct nc_certificate { - char *name; /**< Arbitrary name of the certificate. */ - char *cert_base64; /**< Base-64 encoded certificate. */ + char *name; /**< Arbitrary name of the certificate. */ + char *data; /**< Base-64 encoded certificate. */ }; struct nc_certificate_bag { @@ -92,10 +90,10 @@ struct nc_certificate_bag { struct nc_asymmetric_key { char *name; /**< Arbitrary name of the key. */ - NC_SSH_PUBKEY_TYPE pubkey_type; /**< Type of the public key. */ - char *pub_base64; /**< Base-64 encoded public key. */ - NC_PRIVKEY_FORMAT privkey_type; /**< Type of the private key. */ - char *priv_base64; /**< Base-64 encoded private key. */ + NC_PUBKEY_FORMAT pubkey_type; /**< Type of the public key. */ + char *pubkey_data; /**< Base-64 encoded public key. */ + NC_PRIVKEY_FORMAT privkey_type; /**< Type of the private key. */ + char *privkey_data; /**< Base-64 encoded private key. */ struct nc_certificate *certs; /**< The certificates associated with this key. */ uint16_t cert_count; /**< Number of certificates associated with this key. */ @@ -105,17 +103,17 @@ struct nc_asymmetric_key { * @brief A symmetric key. */ struct nc_symmetric_key { - char *name; /**< Arbitrary name of the key. */ - char *base64; /**< Base-64 encoded key. */ + char *name; /**< Arbitrary name of the key. */ + char *data; /**< Base-64 encoded key. */ }; /** * @brief A public key. */ struct nc_public_key { - char *name; /**< Arbitrary name of the public key. */ - NC_SSH_PUBKEY_TYPE pubkey_type; /**< Type of the public key. */ - char *pub_base64; /**< Base-64 encoded public key. */ + char *name; /**< Arbitrary name of the public key. */ + NC_PUBKEY_FORMAT type; /**< Type of the public key. */ + char *data; /**< Base-64 encoded public key. */ }; struct nc_public_key_bag { @@ -147,10 +145,6 @@ struct nc_keystore { #ifdef NC_ENABLED_SSH -# include -# include -# include - /* seconds */ # define NC_SSH_TIMEOUT 10 /* number of all supported authentication methods */ @@ -223,9 +217,6 @@ struct nc_server_ssh_opts { #ifdef NC_ENABLED_TLS -# include -# include - /* ACCESS unlocked */ struct nc_client_tls_opts { char *cert_path; @@ -241,22 +232,57 @@ struct nc_client_tls_opts { X509_STORE *crl_store; }; -/* ACCESS locked, separate locks */ +/** + * @brief Certificate grouping (either local-definition or truststore reference). + */ +struct nc_cert_grouping { + NC_STORE_TYPE store; /**< Specifies how/where the certificates are stored. */ + union { + struct { + struct nc_certificate *certs; /**< Local-defined certificates */ + uint16_t cert_count; /**< Certificate count */ + }; + struct nc_certificate_bag *ts_ref; /**< Referenced trustore certificate bag */ + }; +}; + +/** + * @brief Cert-to-name entries. + */ +struct nc_ctn { + uint32_t id; /**< ID of the entry, the lower the higher priority */ + char *fingerprint; /**< Fingerprint of the entry */ + NC_TLS_CTN_MAPTYPE map_type; /**< Specifies how to get the username from the certificate */ + char *name; /**< Username for this entry */ + struct nc_ctn *next; /**< Linked-list reference to the next entry */ +}; + +/** + * @brief Server options for configuring the TLS transport protocol. + */ struct nc_server_tls_opts { - char *server_cert; - char **trusted_cert_lists; - uint16_t trusted_cert_list_count; - char *trusted_ca_file; - char *trusted_ca_dir; - X509_STORE *crl_store; + NC_STORE_TYPE store; /**< Specifies how/where the server identity is stored. */ + union { + struct { + NC_PUBKEY_FORMAT pubkey_type; /**< Server public key type */ + char *pubkey_data; /**< Server's public key */ - struct nc_ctn { - uint32_t id; - char *fingerprint; - NC_TLS_CTN_MAPTYPE map_type; - char *name; - struct nc_ctn *next; - } *ctn; + NC_PRIVKEY_FORMAT privkey_type; /**< Server private key type */ + char *privkey_data; /**< Server's private key */ + + char *cert_data; /**< Server's certificate */ + }; + + struct { + struct nc_asymmetric_key *key_ref; /**< Reference to the server's key */ + struct nc_certificate *cert_ref; /**< Reference to the concrete server's certificate */ + }; + }; + + struct nc_cert_grouping ca_certs; /**< Client certificate authorities */ + struct nc_cert_grouping ee_certs; /**< Client end-entity certificates */ + + struct nc_ctn *ctn; /**< Cert-to-name entries */ }; #endif /* NC_ENABLED_TLS */ @@ -389,21 +415,6 @@ struct nc_server_opts { #endif #ifdef NC_ENABLED_TLS int (*user_verify_clb)(const struct nc_session *session); - - int (*server_cert_clb)(const char *name, void *user_data, char **cert_path, char **cert_data, char **privkey_path, - char **privkey_data, NC_SSH_KEY_TYPE *privkey_type); - void *server_cert_data; - void (*server_cert_data_free)(void *data); - - int (*server_cert_chain_clb)(const char *name, void *user_data, char ***cert_paths, int *cert_path_count, - char ***cert_data, int *cert_data_count); - void *server_cert_chain_data; - void (*server_cert_chain_data_free)(void *data); - - int (*trusted_cert_list_clb)(const char *name, void *user_data, char ***cert_paths, int *cert_path_count, - char ***cert_data, int *cert_data_count); - void *trusted_cert_list_data; - void (*trusted_cert_list_data_free)(void *data); #endif pthread_rwlock_t config_lock; @@ -723,7 +734,7 @@ int32_t nc_timeouttime_cur_diff(const struct timespec *ts); */ void nc_realtime_get(struct timespec *ts); -const char *nc_keytype2str(NC_SSH_KEY_TYPE type); +const char *nc_privkey_format_to_str(NC_PRIVKEY_FORMAT format); int nc_sock_configure_keepalive(int sock, struct nc_keepalives *ka); @@ -980,7 +991,7 @@ struct nc_session *nc_accept_callhome_tls_sock(int sock, const char *host, uint1 * @param[in] timeout Transport operations timeout in msec. * @return 1 on success, 0 on timeout, -1 on error. */ -int nc_accept_tls_session(struct nc_session *session, int sock, int timeout); +int nc_accept_tls_session(struct nc_session *session, struct nc_server_tls_opts *opts, int sock, int timeout); void nc_server_tls_clear_opts(struct nc_server_tls_opts *opts); diff --git a/src/session_server.c b/src/session_server.c index e7d9e23e..6ab8e5a8 100644 --- a/src/session_server.c +++ b/src/session_server.c @@ -29,14 +29,20 @@ #include #include #include +#include #include #include #include #include #include "compat.h" -#include "libnetconf.h" +#include "config.h" +#include "log_p.h" +#include "messages_p.h" +#include "messages_server.h" #include "server_config_p.h" +#include "session.h" +#include "session_p.h" #include "session_server.h" #include "session_server_ch.h" @@ -2153,7 +2159,7 @@ nc_accept(int timeout, const struct ly_ctx *ctx, struct nc_session **session) #ifdef NC_ENABLED_TLS if (server_opts.endpts[bind_idx].ti == NC_TI_OPENSSL) { (*session)->data = server_opts.endpts[bind_idx].opts.tls; - ret = nc_accept_tls_session(*session, sock, NC_TRANSPORT_TIMEOUT); + ret = nc_accept_tls_session(*session, server_opts.endpts[bind_idx].opts.tls, sock, NC_TRANSPORT_TIMEOUT); if (ret < 0) { msgtype = NC_MSG_ERROR; goto cleanup; @@ -2608,7 +2614,7 @@ nc_connect_ch_endpt(struct nc_ch_endpt *endpt, nc_server_ch_session_acquire_ctx_ #ifdef NC_ENABLED_TLS if (endpt->ti == NC_TI_OPENSSL) { (*session)->data = endpt->opts.tls; - ret = nc_accept_tls_session(*session, sock, NC_TRANSPORT_TIMEOUT); + ret = nc_accept_tls_session(*session, endpt->opts.tls, sock, NC_TRANSPORT_TIMEOUT); (*session)->data = NULL; if (ret < 0) { diff --git a/src/session_server.h b/src/session_server.h index 631fd06f..516ae34d 100644 --- a/src/session_server.h +++ b/src/session_server.h @@ -400,29 +400,6 @@ void nc_ps_clear(struct nc_pollsession *ps, int all, void (*data_free)(void *)); * @{ */ -/** - * @brief Add a new endpoint. - * - * Before the endpoint can accept any connections, its address and port must - * be set via nc_server_endpt_set_address() and nc_server_endpt_set_port(). - * - * @param[in] name Arbitrary unique endpoint name. - * @param[in] ti Transport protocol to use. - * @return 0 on success, -1 on error. - */ -int nc_server_add_endpt(const char *name, NC_TRANSPORT_IMPL ti); - -/** - * @brief Stop listening on and remove an endpoint. - * - * @param[in] name Endpoint name. NULL matches all endpoints. - * @param[in] ti Endpoint transport protocol. NULL matches any protocol. - * Redundant to set if @p name is set, endpoint names are - * unique disregarding their protocol. - * @return 0 on success, -1 on not finding any match. - */ -int nc_server_del_endpt(const char *name, NC_TRANSPORT_IMPL ti); - /** * @brief Get the number of currently configured listening endpoints. * Note that an ednpoint without address and/or port will be included @@ -440,35 +417,6 @@ int nc_server_endpt_count(void); */ int nc_server_is_endpt(const char *name); -/** - * @brief Change endpoint listening address. - * - * On error the previous listening socket (if any) is left untouched. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] address New listening address. - * @return 0 on success, -1 on error. - */ -int nc_server_endpt_set_address(const char *endpt_name, const char *address); - -char * nc_server_endpt_get_address(const char *endpt_name); - -#if defined (NC_ENABLED_SSH) || defined (NC_ENABLED_TLS) - -/** - * @brief Change endpoint listening port. - * - * This is only valid on SSH/TLS transport endpoint. - * On error the previous listening socket (if any) is left untouched. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] port New listening port. - * @return 0 on success, -1 on error. - */ -int nc_server_endpt_set_port(const char *endpt_name, uint16_t port); - -#endif - /** * @brief Change endpoint permissions. * @@ -483,26 +431,6 @@ int nc_server_endpt_set_port(const char *endpt_name, uint16_t port); */ int nc_server_endpt_set_perms(const char *endpt_name, mode_t mode, uid_t uid, gid_t gid); -/** - * @brief Change endpoint keepalives state. Affects only new connections. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] enable Whether to enable or disable keepalives. - * @return 0 on success, -1 on error. - */ -int nc_server_endpt_enable_keepalives(const char *endpt_name, int enable); - -/** - * @brief Change endpoint keepalives parameters. Affects only new connections. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] idle_time Keepalive idle time in seconds, 1 by default, -1 to keep previous value. - * @param[in] max_probes Keepalive max probes sent, 10 by default, -1 to keep previous value. - * @param[in] probe_interval Keepalive probe interval in seconds, 5 by default, -1 to keep previous value. - * @return 0 on success, -1 on error. - */ -int nc_server_endpt_set_keepalives(const char *endpt_name, int idle_time, int max_probes, int probe_interval); - /** @} Server */ /** @@ -571,39 +499,6 @@ NC_MSG_TYPE nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_sessio * @{ */ -/** - * @brief Add an authorized client SSH public key. This public key can be used for - * publickey authentication (for any SSH connection, even Call Home) afterwards. - * - * @param[in] pubkey_base64 Authorized public key binary content encoded in base64. - * @param[in] type Authorized public key SSH type. - * @param[in] username Username that the client with the public key must use. - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_add_authkey(const char *pubkey_base64, NC_SSH_KEY_TYPE type, const char *username); - -/** - * @brief Add an authorized client SSH public key. This public key can be used for - * publickey authentication (for any SSH connection, even Call Home) afterwards. - * - * @param[in] pubkey_path Path to the public key. - * @param[in] username Username that the client with the public key must use. - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_add_authkey_path(const char *pubkey_path, const char *username); - -/** - * @brief Remove an authorized client SSH public key. - * - * @param[in] pubkey_path Path to an authorized public key. NULL matches all the keys. - * @param[in] pubkey_base64 Authorized public key content. NULL matches any key. - * @param[in] type Authorized public key type. 0 matches all types. - * @param[in] username Username for an authorized public key. NULL matches all the usernames. - * @return 0 on success, -1 on not finding any match. - */ -int nc_server_ssh_del_authkey(const char *pubkey_path, const char *pubkey_base64, NC_SSH_KEY_TYPE type, - const char *username); - /** * @brief Set the callback for SSH password authentication. If none is set, local system users are used. * @@ -637,101 +532,6 @@ void nc_server_ssh_set_interactive_auth_clb(int (*interactive_auth_clb)(const st void nc_server_ssh_set_pubkey_auth_clb(int (*pubkey_auth_clb)(const struct nc_session *session, ssh_key key, void *user_data), void *user_data, void (*free_user_data)(void *user_data)); -/** - * @brief Set the callback for retrieving host keys. Any RSA, DSA, and ECDSA keys can be added. However, - * a maximum of one key of each type will be used during SSH authentication, later keys replacing - * the earlier ones. - * - * @param[in] hostkey_clb Callback that should return the key itself. Zero return indicates success, non-zero - * an error. On success exactly ONE of @p privkey_path or @p privkey_data is expected - * to be set. The one set will be freed. - * - @p privkey_path expects a PEM file, - * - @p privkey_data expects a base-64 encoded ANS.1 DER data, - * - @p privkey_type type of the key in @p privkey_data. Use ::NC_SSH_KEY_UNKNOWN for - * PKCS#8 key that includes the information about the key in its data. - * @param[in] user_data Optional arbitrary user data that will be passed to @p hostkey_clb. - * @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data. - */ -void nc_server_ssh_set_hostkey_clb(int (*hostkey_clb)(const char *name, void *user_data, char **privkey_path, - char **privkey_data, NC_SSH_KEY_TYPE *privkey_type), void *user_data, void (*free_user_data)(void *user_data)); - -/** - * @brief Add endpoint SSH host keys the server will identify itself with. Only the name is set, the key itself - * wil be retrieved using a callback. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] name Arbitrary name of the host key. - * @param[in] idx Optional index where to add the key. -1 adds at the end. - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_endpt_add_hostkey(const char *endpt_name, const char *name, int16_t idx); - -/** - * @brief Delete endpoint SSH host key. Their order is preserved. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] name Name of the host key. NULL matches all the keys, but if @p idx != -1 then this must be NULL. - * @param[in] idx Index of the hostkey. -1 matches all indices, but if @p name != NULL then this must be -1. - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_endpt_del_hostkey(const char *endpt_name, const char *name, int16_t idx); - -/** - * @brief Move endpoint SSH host key. - * - * @param[in] endpt_name Exisitng endpoint name. - * @param[in] key_mov Name of the host key that will be moved. - * @param[in] key_after Name of the key that will preceed @p key_mov. NULL if @p key_mov is to be moved at the beginning. - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_endpt_mov_hostkey(const char *endpt_name, const char *key_mov, const char *key_after); - -/** - * @brief Modify endpoint SSH host key. - * - * @param[in] endpt_name Exisitng endpoint name. - * @param[in] name Name of an existing host key. - * @param[in] new_name New name of the host key @p name. - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_endpt_mod_hostkey(const char *endpt_name, const char *name, const char *new_name); - -/** - * @brief Set endpoint accepted SSH authentication methods. All (publickey, password, interactive) - * are supported by default. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] auth_methods Accepted authentication methods bit field of NC_SSH_AUTH_TYPE. - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_endpt_set_auth_methods(const char *endpt_name, int auth_methods); - -/** - * @brief Get endpoint accepted SSH authentication methods. - * - * @param[in] endpt_name Existing endpoint name. - * @return Accepted authentication methods bit field of NC_SSH_AUTH_TYPE. - */ -int nc_server_ssh_endpt_get_auth_methods(const char *endpt_name); - -/** - * @brief Set endpoint SSH authentication attempts of every client. 3 by default. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] auth_attempts Failed authentication attempts before a client is dropped. - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_endpt_set_auth_attempts(const char *endpt_name, uint16_t auth_attempts); - -/** - * @brief Set endpoint SSH authentication timeout. 30 seconds by default. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] auth_timeout Number of seconds before an unauthenticated client is dropped. - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_endpt_set_auth_timeout(const char *endpt_name, uint16_t auth_timeout); - /** @} Server SSH */ #endif /* NC_ENABLED_SSH */ @@ -746,148 +546,6 @@ int nc_server_ssh_endpt_set_auth_timeout(const char *endpt_name, uint16_t auth_t * @{ */ -/** - * @brief Set the server TLS certificate. Only the name is set, the certificate itself - * wil be retrieved using a callback. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] name Arbitrary certificate name. - * @return 0 on success, -1 on error. - */ -int nc_server_tls_endpt_set_server_cert(const char *endpt_name, const char *name); - -/** - * @brief Set the callback for retrieving server certificate and matching private key. - * - * @param[in] cert_clb Callback that should return the certificate and the key itself. Zero return indicates success, - * non-zero an error. On success exactly ONE of @p cert_path or @p cert_data and ONE of - * @p privkey_path and @p privkey_data is expected to be set. Those set will be freed. - * - @p cert_path expects a PEM file, - * - @p cert_data expects a base-64 encoded ASN.1 DER data, - * - @p privkey_path expects a PEM file, - * - @p privkey_data expects a base-64 encoded ANS.1 DER data, - * - @p privkey_type type of the key in @p privkey_data. - * @param[in] user_data Optional arbitrary user data that will be passed to @p cert_clb. - * @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data. - */ -void nc_server_tls_set_server_cert_clb(int (*cert_clb)(const char *name, void *user_data, char **cert_path, char **cert_data, - char **privkey_path, char **privkey_data, NC_SSH_KEY_TYPE *privkey_type), void *user_data, - void (*free_user_data)(void *user_data)); - -/** - * @brief Set the callback for retrieving server certificate chain - * - * @param[in] cert_chain_clb Callback that should return all the certificates of the chain. Zero return indicates success, - * non-zero an error. On success, @p cert_paths and @p cert_data are expected to be set or left - * NULL. Both will be (deeply) freed. - * - @p cert_paths expect an array of PEM files, - * - @p cert_path_count number of @p cert_paths array members, - * - @p cert_data expect an array of base-64 encoded ASN.1 DER cert data, - * - @p cert_data_count number of @p cert_data array members. - * @param[in] user_data Optional arbitrary user data that will be passed to @p cert_clb. - * @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data. - */ -void nc_server_tls_set_server_cert_chain_clb(int (*cert_chain_clb)(const char *name, void *user_data, char ***cert_paths, - int *cert_path_count, char ***cert_data, int *cert_data_count), void *user_data, void (*free_user_data)(void *user_data)); - -/** - * @brief Add a trusted certificate list. Can be both a CA or a client one. Can be - * safely used together with nc_server_tls_endpt_set_trusted_ca_paths(). - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] name Arbitary name identifying this certificate list. - * @return 0 on success, -1 on error. - */ -int nc_server_tls_endpt_add_trusted_cert_list(const char *endpt_name, const char *name); - -/** - * @brief Set the callback for retrieving trusted certificates. - * - * @param[in] cert_list_clb Callback that should return all the certificates of a list. Zero return indicates success, - * non-zero an error. On success, @p cert_paths and @p cert_data are expected to be set or left - * NULL. Both will be (deeply) freed. - * - @p cert_paths expect an array of PEM files, - * - @p cert_path_count number of @p cert_paths array members, - * - @p cert_data expect an array of base-64 encoded ASN.1 DER cert data, - * - @p cert_data_count number of @p cert_data array members. - * @param[in] user_data Optional arbitrary user data that will be passed to @p cert_clb. - * @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data. - */ -void nc_server_tls_set_trusted_cert_list_clb(int (*cert_list_clb)(const char *name, void *user_data, char ***cert_paths, - int *cert_path_count, char ***cert_data, int *cert_data_count), void *user_data, void (*free_user_data)(void *user_data)); - -/** - * @brief Remove a trusted certificate. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] name Name of the certificate list to delete. NULL deletes all the lists. - * @return 0 on success, -1 on not found. - */ -int nc_server_tls_endpt_del_trusted_cert_list(const char *endpt_name, const char *name); - -/** - * @brief Set trusted Certificate Authority certificate locations. There can only be - * one file and one directory, they are replaced if already set. Can be safely - * used with nc_server_tls_endpt_add_trusted_cert() or its _path variant. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] ca_file Path to a trusted CA cert store file in PEM format. Can be NULL. - * @param[in] ca_dir Path to a trusted CA cert store hashed directory (c_rehash utility - * can be used to create hashes) with PEM files. Can be NULL. - * @return 0 on success, -1 on error. - */ -int nc_server_tls_endpt_set_trusted_ca_paths(const char *endpt_name, const char *ca_file, const char *ca_dir); - -/** - * @brief Set Certificate Revocation List locations. There can only be one file - * and one directory, they are replaced if already set. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] crl_file Path to a CRL store file in PEM format. Can be NULL. - * @param[in] crl_dir Path to a CRL store hashed directory (c_rehash utility - * can be used to create hashes) with PEM files. Can be NULL. - * @return 0 on success, -1 on error. - */ -int nc_server_tls_endpt_set_crl_paths(const char *endpt_name, const char *crl_file, const char *crl_dir); - -/** - * @brief Destroy and clean CRLs. Certificates, private keys, and CTN entries are - * not affected. - * - * @param[in] endpt_name Existing endpoint name. - */ -void nc_server_tls_endpt_clear_crls(const char *endpt_name); - -/** - * @brief Add a cert-to-name entry. - * - * It is possible to add an entry step-by-step, specifying first only @p ip and in later calls - * @p fingerprint, @p map_type, and optionally @p name spearately. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] id Priority of the entry. It must be unique. If already exists, the entry with this id - * is modified. - * @param[in] fingerprint Matching certificate fingerprint. If NULL, kept temporarily unset. - * @param[in] map_type Type of username-certificate mapping. If 0, kept temporarily unset. - * @param[in] name Specific username used only if @p map_type == NC_TLS_CTN_SPECIFED. - * @return 0 on success, -1 on error. - */ -int nc_server_tls_endpt_add_ctn(const char *endpt_name, uint32_t id, const char *fingerprint, - NC_TLS_CTN_MAPTYPE map_type, const char *name); - -/** - * @brief Remove a cert-to-name entry. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] id Priority of the entry. -1 matches all the priorities. - * @param[in] fingerprint Fingerprint fo the entry. NULL matches all the fingerprints. - * @param[in] map_type Mapping type of the entry. 0 matches all the mapping types. - * @param[in] name Specific username for the entry. NULL matches all the usernames. - * @return 0 on success, -1 on not finding any match. - */ -int nc_server_tls_endpt_del_ctn(const char *endpt_name, int64_t id, const char *fingerprint, - NC_TLS_CTN_MAPTYPE map_type, const char *name); - /** * @brief Get a cert-to-name entry. * diff --git a/src/session_server_ch.h b/src/session_server_ch.h index e8c508ba..bf59ce4a 100644 --- a/src/session_server_ch.h +++ b/src/session_server_ch.h @@ -22,6 +22,7 @@ extern "C" { #include #include +#include #include "netconf.h" #include "session.h" diff --git a/src/session_server_ssh.c b/src/session_server_ssh.c index 01e6de51..c22b0c24 100644 --- a/src/session_server_ssh.c +++ b/src/session_server_ssh.c @@ -27,19 +27,17 @@ #include #endif -#include -#include -#include -#if OPENSSL_VERSION_NUMBER >= 0x30000000L -#include -#endif // OPENSSL_VERSION_NUMBER >= 0x30000000L -#include - #include #include #include -#include +#include +#include +#include +#include +#include +#include #include +#include #include #include #include @@ -48,9 +46,9 @@ #include #include "compat.h" -#include "libnetconf.h" -#include "session_server.h" -#include "session_server_ch.h" +#include "log_p.h" +#include "session.h" +#include "session_p.h" #if !defined (HAVE_CRYPT_R) pthread_mutex_t crypt_lock = PTHREAD_MUTEX_INITIALIZER; @@ -147,66 +145,6 @@ nc_server_ssh_set_pubkey_auth_clb(int (*pubkey_auth_clb)(const struct nc_session server_opts.pubkey_auth_data_free = free_user_data; } -static int -nc_server_ssh_set_auth_attempts(uint16_t auth_attempts, struct nc_server_ssh_opts *opts) -{ - NC_CHECK_ARG_RET(NULL, auth_attempts, -1); - - opts->auth_attempts = auth_attempts; - return 0; -} - -API int -nc_server_ssh_ch_client_endpt_set_auth_attempts(const char *client_name, const char *endpt_name, uint16_t auth_attempts) -{ - int ret; - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client); - if (!endpt) { - return -1; - } - - ret = nc_server_ssh_set_auth_attempts(auth_attempts, endpt->opts.ssh); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return ret; -} - -static int -nc_server_ssh_set_auth_timeout(uint16_t auth_timeout, struct nc_server_ssh_opts *opts) -{ - NC_CHECK_ARG_RET(NULL, auth_timeout, -1); - - opts->auth_timeout = auth_timeout; - return 0; -} - -API int -nc_server_ssh_ch_client_endpt_set_auth_timeout(const char *client_name, const char *endpt_name, uint16_t auth_timeout) -{ - int ret; - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client); - if (!endpt) { - return -1; - } - - ret = nc_server_ssh_set_auth_timeout(auth_timeout, endpt->opts.ssh); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return ret; -} - /** * @brief Compare hashed password with a cleartext password for a match. * @@ -627,8 +565,8 @@ nc_server_ssh_decode_base64(const char *base64, char **buffer) * The data is in the form of: 4 bytes = data length, then data of data length * and the data is in network byte order. The key has to be in the SSH2 format. */ -static NC_SSH_KEY_TYPE -nc_server_ssh_get_pubkey_type(const char *buffer) +static const char * +nc_server_ssh_get_pubkey_type(const char *buffer, uint32_t *len) { uint32_t type_len; @@ -636,24 +574,11 @@ nc_server_ssh_get_pubkey_type(const char *buffer) memcpy(&type_len, buffer, sizeof type_len); /* type_len now stores the length of the key type */ type_len = ntohl(type_len); - /* move 4 bytes in the buffer so we can compare */ - buffer += sizeof type_len; + *len = type_len; - if (!strncmp(buffer, "ssh-dss", type_len)) { - return NC_SSH_KEY_DSA; - } else if (!strncmp(buffer, "ssh-rsa", type_len)) { - return NC_SSH_KEY_RSA; - } else if (!strncmp(buffer, "ecdsa-sha2-nistp256", type_len)) { - return NC_SSH_KEY_ECDSA_P256; - } else if (!strncmp(buffer, "ecdsa-sha2-nistp384", type_len)) { - return NC_SSH_KEY_ECDSA_P384; - } else if (!strncmp(buffer, "ecdsa-sha2-nistp521", type_len)) { - return NC_SSH_KEY_ECDSA_P521; - } else if (!strncmp(buffer, "ssh-ed25519", type_len)) { - return NC_SSH_KEY_ED25519; - } else { - return NC_SSH_KEY_UNKNOWN; - } + /* move 4 bytes in the buffer, this is where the type should be */ + buffer += sizeof type_len; + return buffer; } /* @@ -902,7 +827,8 @@ nc_server_ssh_create_ssh_pubkey(const char *base64, ssh_key *key) { int ret = 0; char *bin = NULL; - NC_SSH_KEY_TYPE pub_type = NC_SSH_KEY_UNKNOWN; + const char *pub_type = NULL; + uint32_t pub_type_len = 0; if (!key && !base64) { ERRINT; @@ -920,44 +846,33 @@ nc_server_ssh_create_ssh_pubkey(const char *base64, ssh_key *key) } /* get the key type and try to import it if possible */ - pub_type = nc_server_ssh_get_pubkey_type(bin); - switch (pub_type) { - case NC_SSH_KEY_DSA: + pub_type = nc_server_ssh_get_pubkey_type(bin, &pub_type_len); + if (!pub_type) { + ret = 1; + goto cleanup; + } else if (!strncmp(pub_type, "ssh-dss", pub_type_len)) { ERR(NULL, "DSA keys are not supported."); ret = 1; - break; - - case NC_SSH_KEY_RSA: + goto cleanup; + } else if (!strncmp(pub_type, "ssh-rsa", pub_type_len)) { ret = ssh_pki_import_pubkey_base64(base64, SSH_KEYTYPE_RSA, key); - break; - - case NC_SSH_KEY_ECDSA_P256: + } else if (!strncmp(pub_type, "ecdsa-sha2-nistp256", pub_type_len)) { ret = ssh_pki_import_pubkey_base64(base64, SSH_KEYTYPE_ECDSA_P256, key); - break; - - case NC_SSH_KEY_ECDSA_P384: + } else if (!strncmp(pub_type, "ecdsa-sha2-nistp384", pub_type_len)) { ret = ssh_pki_import_pubkey_base64(base64, SSH_KEYTYPE_ECDSA_P384, key); - break; - - case NC_SSH_KEY_ECDSA_P521: + } else if (!strncmp(pub_type, "ecdsa-sha2-nistp521", pub_type_len)) { ret = ssh_pki_import_pubkey_base64(base64, SSH_KEYTYPE_ECDSA_P521, key); - break; - - case NC_SSH_KEY_ED25519: + } else if (!strncmp(pub_type, "ssh-ed25519", pub_type_len)) { ret = ssh_pki_import_pubkey_base64(base64, SSH_KEYTYPE_ED25519, key); - break; - - case NC_SSH_KEY_UNKNOWN: - ret = 1; - break; - default: + } else { + ERR(NULL, "Public key type not recognised."); ret = 1; - break; + goto cleanup; } cleanup: if (ret != SSH_OK) { - ERR(NULL, "Error importing %s public key.", nc_keytype2str(pub_type)); + ERR(NULL, "Error importing public key."); } free(bin); return ret; @@ -989,7 +904,7 @@ auth_pubkey_compare_key(ssh_key key, struct nc_client_auth *auth_client) /* try to compare all of the client's keys with the key received in the SSH message */ for (i = 0; i < pubkey_count; i++) { /* create the SSH key from the data */ - if (nc_server_ssh_create_ssh_pubkey(pubkeys[i].pub_base64, &new_key)) { + if (nc_server_ssh_create_ssh_pubkey(pubkeys[i].data, &new_key)) { ssh_key_free(new_key); continue; } @@ -1442,23 +1357,6 @@ nc_accept_ssh_session_open_netconf_channel(struct nc_session *session, struct nc return 0; } -static const char * -nc_privkey_format_to_str(NC_PRIVKEY_FORMAT format) -{ - switch (format) { - case NC_PRIVKEY_FORMAT_RSA: - return "RSA"; - case NC_PRIVKEY_FORMAT_EC: - return "EC"; - case NC_PRIVKEY_FORMAT_PKCS8: - return NULL; - case NC_PRIVKEY_FORMAT_OPENSSH: - return "OPENSSH"; - default: - return NULL; - } -} - /** * @brief Set hostkeys to be used for an SSH bind. * @@ -1479,10 +1377,10 @@ nc_ssh_bind_add_hostkeys(ssh_bind sbind, struct nc_server_ssh_opts *opts, uint16 privkey_path = privkey_data = NULL; if (opts->hostkeys[i].store == NC_STORE_LOCAL) { - privkey_data = opts->hostkeys[i].key.priv_base64; + privkey_data = opts->hostkeys[i].key.privkey_data; privkey_path = base64der_privkey_to_tmp_file(privkey_data, nc_privkey_format_to_str(opts->hostkeys[i].key.privkey_type)); } else { - privkey_data = opts->hostkeys[i].ks_ref->priv_base64; + privkey_data = opts->hostkeys[i].ks_ref->privkey_data; privkey_path = base64der_privkey_to_tmp_file(privkey_data, nc_privkey_format_to_str(opts->hostkeys[i].ks_ref->privkey_type)); } diff --git a/src/session_server_tls.c b/src/session_server_tls.c index ceeeb37d..e02456df 100644 --- a/src/session_server_tls.c +++ b/src/session_server_tls.c @@ -1,10 +1,11 @@ /** * @file session_server_tls.c * @author Michal Vasko + * @author Roman Janota * @brief libnetconf2 TLS server session manipulation functions * * @copyright - * Copyright (c) 2015 - 2021 CESNET, z.s.p.o. + * Copyright (c) 2015 - 2023 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. @@ -16,6 +17,7 @@ #define _GNU_SOURCE #include +#include #include #include @@ -26,13 +28,10 @@ #include #include "compat.h" -#include "libnetconf.h" -#include "session_server.h" -#include "session_server_ch.h" - -#if OPENSSL_VERSION_NUMBER < 0x10100000L -#define X509_STORE_CTX_get_by_subject X509_STORE_get_by_subject -#endif +#include "config.h" +#include "log_p.h" +#include "session.h" +#include "session_p.h" struct nc_server_tls_opts tls_ch_opts; pthread_mutex_t tls_ch_opts_lock = PTHREAD_MUTEX_INITIALIZER; @@ -41,38 +40,38 @@ extern struct nc_server_opts server_opts; static pthread_key_t verify_key; static pthread_once_t verify_once = PTHREAD_ONCE_INIT; -static char * -asn1time_to_str(const ASN1_TIME *t) -{ - char *cp; - BIO *bio; - int n; - - if (!t) { - return NULL; - } - bio = BIO_new(BIO_s_mem()); - if (!bio) { - return NULL; - } - ASN1_TIME_print(bio, t); - n = BIO_pending(bio); - cp = malloc(n + 1); - if (!cp) { - ERRMEM; - BIO_free(bio); - return NULL; - } - n = BIO_read(bio, cp, n); - if (n < 0) { - BIO_free(bio); - free(cp); - return NULL; - } - cp[n] = '\0'; - BIO_free(bio); - return cp; -} +// static char * +// asn1time_to_str(const ASN1_TIME *t) +// { +// char *cp; +// BIO *bio; +// int n; + +// if (!t) { +// return NULL; +// } +// bio = BIO_new(BIO_s_mem()); +// if (!bio) { +// return NULL; +// } +// ASN1_TIME_print(bio, t); +// n = BIO_pending(bio); +// cp = malloc(n + 1); +// if (!cp) { +// ERRMEM; +// BIO_free(bio); +// return NULL; +// } +// n = BIO_read(bio, cp, n); +// if (n < 0) { +// BIO_free(bio); +// free(cp); +// return NULL; +// } +// cp[n] = '\0'; +// BIO_free(bio); +// return cp; +// } static void digest_to_str(const unsigned char *digest, unsigned int dig_len, char **str) @@ -124,21 +123,21 @@ base64der_to_cert(const char *in) } /* return NULL - either errno or SSL error */ -static X509 * -pem_to_cert(const char *path) -{ - FILE *fp; - X509 *out; - - fp = fopen(path, "r"); - if (!fp) { - return NULL; - } - - out = PEM_read_X509(fp, NULL, NULL, NULL); - fclose(fp); - return out; -} +// static X509 * +// pem_to_cert(const char *path) +// { +// FILE *fp; +// X509 *out; + +// fp = fopen(path, "r"); +// if (!fp) { +// return NULL; +// } + +// out = PEM_read_X509(fp, NULL, NULL, NULL); +// fclose(fp); +// return out; +// } static EVP_PKEY * base64der_to_privatekey(const char *in, const char *key_str) @@ -233,11 +232,7 @@ nc_tls_ctn_get_username_from_cert(X509 *client_cert, NC_TLS_CTN_MAPTYPE map_type /* rfc822Name (email) */ if (((map_type == NC_TLS_CTN_SAN_ANY) || (map_type == NC_TLS_CTN_SAN_RFC822_NAME)) && (san_name->type == GEN_EMAIL)) { -#if OPENSSL_VERSION_NUMBER < 0x10100000L // < 1.1.0 - *username = strdup((char *)ASN1_STRING_data(san_name->d.rfc822Name)); -#else *username = strdup((char *)ASN1_STRING_get0_data(san_name->d.rfc822Name)); -#endif if (!*username) { ERRMEM; return 1; @@ -248,11 +243,7 @@ nc_tls_ctn_get_username_from_cert(X509 *client_cert, NC_TLS_CTN_MAPTYPE map_type /* dNSName */ if (((map_type == NC_TLS_CTN_SAN_ANY) || (map_type == NC_TLS_CTN_SAN_DNS_NAME)) && (san_name->type == GEN_DNS)) { -#if OPENSSL_VERSION_NUMBER < 0x10100000L // < 1.1.0 - *username = strdup((char *)ASN1_STRING_data(san_name->d.dNSName)); -#else *username = strdup((char *)ASN1_STRING_get0_data(san_name->d.dNSName)); -#endif if (!*username) { ERRMEM; return 1; @@ -335,11 +326,20 @@ nc_tls_cert_to_name(struct nc_ctn *ctn_first, X509 *cert, NC_TLS_CTN_MAPTYPE *ma for (ctn = ctn_first; ctn; ctn = ctn->next) { /* first make sure the entry is valid */ - if (!ctn->fingerprint || !ctn->map_type || ((ctn->map_type == NC_TLS_CTN_SPECIFIED) && !ctn->name)) { + if (!ctn->map_type || ((ctn->map_type == NC_TLS_CTN_SPECIFIED) && !ctn->name)) { VRB(NULL, "Cert verify CTN: entry with id %u not valid, skipping.", ctn->id); continue; } + /* if ctn has no fingerprint, it will match any certificate */ + if (!ctn->fingerprint) { + *map_type = ctn->map_type; + if (ctn->map_type == NC_TLS_CTN_SPECIFIED) { + *name = ctn->name; + } + break; + } + /* MD5 */ if (!strncmp(ctn->fingerprint, "01", 2)) { if (!digest_md5) { @@ -487,29 +487,29 @@ nc_tls_cert_to_name(struct nc_ctn *ctn_first, X509 *cert, NC_TLS_CTN_MAPTYPE *ma return ret; } -#if OPENSSL_VERSION_NUMBER >= 0x10100000L // >= 1.1.0 - static int nc_tlsclb_verify(int preverify_ok, X509_STORE_CTX *x509_ctx) { - X509_STORE_CTX *store_ctx; - X509_OBJECT *obj; X509_NAME *subject; X509_NAME *issuer; X509 *cert; - X509_CRL *crl; - X509_REVOKED *revoked; + struct nc_cert_grouping *ee_certs; STACK_OF(X509) * cert_stack; - EVP_PKEY *pubkey; struct nc_session *session; struct nc_server_tls_opts *opts; - const ASN1_INTEGER *serial; - int i, n, rc, depth; + int i, rc, depth; char *cp; const char *username = NULL; NC_TLS_CTN_MAPTYPE map_type = 0; - const ASN1_TIME *last_update = NULL, *next_update = NULL; + + // X509_STORE_CTX *store_ctx; + // X509_OBJECT *obj; + // X509_CRL *crl; + // X509_REVOKED *revoked; + // EVP_PKEY *pubkey; + // const ASN1_INTEGER *serial; + // const ASN1_TIME *last_update = NULL, *next_update = NULL; /* get the thread session */ session = pthread_getspecific(verify_key); @@ -528,23 +528,35 @@ nc_tlsclb_verify(int preverify_ok, X509_STORE_CTX *x509_ctx) sk_X509_pop_free(cert_stack, X509_free); } - /* standard certificate verification failed, so a trusted client cert must match to continue */ + /* standard certificate verification failed, so an end-entity client cert must match to continue */ if (!preverify_ok) { - subject = X509_get_subject_name(session->opts.server.client_cert); - cert_stack = X509_STORE_CTX_get1_certs(x509_ctx, subject); - if (cert_stack) { - for (i = 0; i < sk_X509_num(cert_stack); ++i) { - if (cert_pubkey_match(session->opts.server.client_cert, sk_X509_value(cert_stack, i))) { - /* we are just overriding the failed standard certificate verification (preverify_ok == 0), - * this callback will be called again with the same current certificate and preverify_ok == 1 */ - VRB(NULL, "Cert verify: fail (%s), but the client certificate is trusted, continuing.", - X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509_ctx))); - X509_STORE_CTX_set_error(x509_ctx, X509_V_OK); - sk_X509_pop_free(cert_stack, X509_free); - return 1; - } + /* get the store from the current context */ + X509_STORE *store = X509_STORE_CTX_get0_store(x509_ctx); + + if (!store) { + ERR(NULL, "Error getting store from context (%s).", ERR_reason_error_string(ERR_get_error())); + return 0; + } + + /* get the data from the store */ + ee_certs = X509_STORE_get_ex_data(store, 1); + if (!ee_certs) { + ERR(NULL, "Error getting data from store (%s).", ERR_reason_error_string(ERR_get_error())); + return 0; + } + + for (i = 0; i < ee_certs->cert_count; i++) { + cert = base64der_to_cert(ee_certs->certs[i].data); + rc = cert_pubkey_match(session->opts.server.client_cert, cert); + X509_free(cert); + if (rc) { + /* we are just overriding the failed standard certificate verification (preverify_ok == 0), + * this callback will be called again with the same current certificate and preverify_ok == 1 */ + VRB(NULL, "Cert verify: fail (%s), but the end-entity certificate is trusted, continuing.", + X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509_ctx))); + X509_STORE_CTX_set_error(x509_ctx, X509_V_OK); + return 1; } - sk_X509_pop_free(cert_stack, X509_free); } ERR(NULL, "Cert verify: fail (%s).", X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509_ctx))); @@ -567,87 +579,87 @@ nc_tlsclb_verify(int preverify_ok, X509_STORE_CTX *x509_ctx) OPENSSL_free(cp); /* check for revocation if set */ - if (opts->crl_store) { - /* try to retrieve a CRL corresponding to the _subject_ of - * the current certificate in order to verify it's integrity */ - store_ctx = X509_STORE_CTX_new(); - obj = X509_OBJECT_new(); - X509_STORE_CTX_init(store_ctx, opts->crl_store, NULL, NULL); - rc = X509_STORE_CTX_get_by_subject(store_ctx, X509_LU_CRL, subject, obj); - X509_STORE_CTX_free(store_ctx); - crl = X509_OBJECT_get0_X509_CRL(obj); - if ((rc > 0) && crl) { - cp = X509_NAME_oneline(subject, NULL, 0); - VRB(NULL, "Cert verify CRL: issuer: %s.", cp); - OPENSSL_free(cp); - - last_update = X509_CRL_get0_lastUpdate(crl); - next_update = X509_CRL_get0_nextUpdate(crl); - cp = asn1time_to_str(last_update); - VRB(NULL, "Cert verify CRL: last update: %s.", cp); - free(cp); - cp = asn1time_to_str(next_update); - VRB(NULL, "Cert verify CRL: next update: %s.", cp); - free(cp); - - /* verify the signature on this CRL */ - pubkey = X509_get_pubkey(cert); - if (X509_CRL_verify(crl, pubkey) <= 0) { - ERR(NULL, "Cert verify CRL: invalid signature."); - X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE); - X509_OBJECT_free(obj); - if (pubkey) { - EVP_PKEY_free(pubkey); - } - return 0; - } - if (pubkey) { - EVP_PKEY_free(pubkey); - } - - /* check date of CRL to make sure it's not expired */ - if (!next_update) { - ERR(NULL, "Cert verify CRL: invalid nextUpdate field."); - X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD); - X509_OBJECT_free(obj); - return 0; - } - if (X509_cmp_current_time(next_update) < 0) { - ERR(NULL, "Cert verify CRL: expired - revoking all certificates."); - X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CRL_HAS_EXPIRED); - X509_OBJECT_free(obj); - return 0; - } - X509_OBJECT_free(obj); - } - - /* try to retrieve a CRL corresponding to the _issuer_ of - * the current certificate in order to check for revocation */ - store_ctx = X509_STORE_CTX_new(); - obj = X509_OBJECT_new(); - X509_STORE_CTX_init(store_ctx, opts->crl_store, NULL, NULL); - rc = X509_STORE_CTX_get_by_subject(store_ctx, X509_LU_CRL, issuer, obj); - X509_STORE_CTX_free(store_ctx); - crl = X509_OBJECT_get0_X509_CRL(obj); - if ((rc > 0) && crl) { - /* check if the current certificate is revoked by this CRL */ - n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl)); - for (i = 0; i < n; i++) { - revoked = sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i); - serial = X509_REVOKED_get0_serialNumber(revoked); - if (ASN1_INTEGER_cmp(serial, X509_get_serialNumber(cert)) == 0) { - cp = X509_NAME_oneline(issuer, NULL, 0); - ERR(NULL, "Cert verify CRL: certificate with serial %ld (0x%lX) revoked per CRL from issuer %s.", - serial, serial, cp); - OPENSSL_free(cp); - X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CERT_REVOKED); - X509_OBJECT_free(obj); - return 0; - } - } - X509_OBJECT_free(obj); - } - } + // if (opts->crl_store) { + /// * try to retrieve a CRL corresponding to the _subject_ of + // * the current certificate in order to verify it's integrity */ + // store_ctx = X509_STORE_CTX_new(); + // obj = X509_OBJECT_new(); + // X509_STORE_CTX_init(store_ctx, opts->crl_store, NULL, NULL); + // rc = X509_STORE_CTX_get_by_subject(store_ctx, X509_LU_CRL, subject, obj); + // X509_STORE_CTX_free(store_ctx); + // crl = X509_OBJECT_get0_X509_CRL(obj); + // if ((rc > 0) && crl) { + // cp = X509_NAME_oneline(subject, NULL, 0); + // VRB(NULL, "Cert verify CRL: issuer: %s.", cp); + // OPENSSL_free(cp); + + // last_update = X509_CRL_get0_lastUpdate(crl); + // next_update = X509_CRL_get0_nextUpdate(crl); + // cp = asn1time_to_str(last_update); + // VRB(NULL, "Cert verify CRL: last update: %s.", cp); + // free(cp); + // cp = asn1time_to_str(next_update); + // VRB(NULL, "Cert verify CRL: next update: %s.", cp); + // free(cp); + + /// * verify the signature on this CRL */ + // pubkey = X509_get_pubkey(cert); + // if (X509_CRL_verify(crl, pubkey) <= 0) { + // ERR(NULL, "Cert verify CRL: invalid signature."); + // X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE); + // X509_OBJECT_free(obj); + // if (pubkey) { + // EVP_PKEY_free(pubkey); + // } + // return 0; + // } + // if (pubkey) { + // EVP_PKEY_free(pubkey); + // } + + /// * check date of CRL to make sure it's not expired */ + // if (!next_update) { + // ERR(NULL, "Cert verify CRL: invalid nextUpdate field."); + // X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD); + // X509_OBJECT_free(obj); + // return 0; + // } + // if (X509_cmp_current_time(next_update) < 0) { + // ERR(NULL, "Cert verify CRL: expired - revoking all certificates."); + // X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CRL_HAS_EXPIRED); + // X509_OBJECT_free(obj); + // return 0; + // } + // X509_OBJECT_free(obj); + // } + + /// * try to retrieve a CRL corresponding to the _issuer_ of + // * the current certificate in order to check for revocation */ + // store_ctx = X509_STORE_CTX_new(); + // obj = X509_OBJECT_new(); + // X509_STORE_CTX_init(store_ctx, opts->crl_store, NULL, NULL); + // rc = X509_STORE_CTX_get_by_subject(store_ctx, X509_LU_CRL, issuer, obj); + // X509_STORE_CTX_free(store_ctx); + // crl = X509_OBJECT_get0_X509_CRL(obj); + // if ((rc > 0) && crl) { + /// * check if the current certificate is revoked by this CRL */ + // n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl)); + // for (i = 0; i < n; i++) { + // revoked = sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i); + // serial = X509_REVOKED_get0_serialNumber(revoked); + // if (ASN1_INTEGER_cmp(serial, X509_get_serialNumber(cert)) == 0) { + // cp = X509_NAME_oneline(issuer, NULL, 0); + // ERR(NULL, "Cert verify CRL: certificate with serial %ld (0x%lX) revoked per CRL from issuer %s.", + // serial, serial, cp); + // OPENSSL_free(cp); + // X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CERT_REVOKED); + // X509_OBJECT_free(obj); + // return 0; + // } + // } + // X509_OBJECT_free(obj); + // } + // } /* cert-to-name already successful */ if (session->username) { @@ -701,243 +713,51 @@ nc_tlsclb_verify(int preverify_ok, X509_STORE_CTX *x509_ctx) return 0; } -#else - static int -nc_tlsclb_verify(int preverify_ok, X509_STORE_CTX *x509_ctx) +nc_server_tls_get_ctn(uint32_t *id, char **fingerprint, NC_TLS_CTN_MAPTYPE *map_type, char **name, + struct nc_server_tls_opts *opts) { - X509_STORE_CTX store_ctx; - X509_OBJECT obj; - X509_NAME *subject; - X509_NAME *issuer; - X509 *cert; - X509_CRL *crl; - X509_REVOKED *revoked; - - STACK_OF(X509) * cert_stack; - EVP_PKEY *pubkey; - struct nc_session *session; - struct nc_server_tls_opts *opts; - long serial; - int i, n, rc, depth; - char *cp; - const char *username = NULL; - NC_TLS_CTN_MAPTYPE map_type = 0; - ASN1_TIME *last_update = NULL, *next_update = NULL; - - /* get the thread session */ - session = pthread_getspecific(verify_key); - if (!session) { - ERRINT; - return 0; - } - - opts = session->data; + struct nc_ctn *ctn; + int ret = -1; - /* get the last certificate, that is the peer (client) certificate */ - if (!session->opts.server.client_cert) { - cert_stack = X509_STORE_CTX_get1_chain(x509_ctx); - while ((cert = sk_X509_pop(cert_stack))) { - X509_free(session->opts.server.client_cert); - session->opts.server.client_cert = cert; + for (ctn = opts->ctn; ctn; ctn = ctn->next) { + if (id && *id && (*id != ctn->id)) { + continue; } - sk_X509_pop_free(cert_stack, X509_free); - } - - /* standard certificate verification failed, so a trusted client cert must match to continue */ - if (!preverify_ok) { - subject = X509_get_subject_name(session->opts.server.client_cert); - cert_stack = X509_STORE_get1_certs(x509_ctx, subject); - if (cert_stack) { - for (i = 0; i < sk_X509_num(cert_stack); ++i) { - if (cert_pubkey_match(session->opts.server.client_cert, sk_X509_value(cert_stack, i))) { - /* we are just overriding the failed standard certificate verification (preverify_ok == 0), - * this callback will be called again with the same current certificate and preverify_ok == 1 */ - VRB(session, "Cert verify: fail (%s), but the client certificate is trusted, continuing.", - X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509_ctx))); - X509_STORE_CTX_set_error(x509_ctx, X509_V_OK); - sk_X509_pop_free(cert_stack, X509_free); - return 1; - } - } - sk_X509_pop_free(cert_stack, X509_free); + if (fingerprint && *fingerprint && (!ctn->fingerprint || strcmp(*fingerprint, ctn->fingerprint))) { + continue; } - - ERR(session, "Cert verify: fail (%s).", X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509_ctx))); - return 0; - } - - /* print cert verify info */ - depth = X509_STORE_CTX_get_error_depth(x509_ctx); - VRB(session, "Cert verify: depth %d.", depth); - - cert = X509_STORE_CTX_get_current_cert(x509_ctx); - subject = X509_get_subject_name(cert); - issuer = X509_get_issuer_name(cert); - - cp = X509_NAME_oneline(subject, NULL, 0); - VRB(session, "Cert verify: subject: %s.", cp); - OPENSSL_free(cp); - cp = X509_NAME_oneline(issuer, NULL, 0); - VRB(session, "Cert verify: issuer: %s.", cp); - OPENSSL_free(cp); - - /* check for revocation if set */ - if (opts->crl_store) { - /* try to retrieve a CRL corresponding to the _subject_ of - * the current certificate in order to verify it's integrity */ - memset((char *)&obj, 0, sizeof(obj)); - X509_STORE_CTX_init(&store_ctx, opts->crl_store, NULL, NULL); - rc = X509_STORE_CTX_get_by_subject(&store_ctx, X509_LU_CRL, subject, &obj); - X509_STORE_CTX_cleanup(&store_ctx); - crl = obj.data.crl; - if ((rc > 0) && crl) { - cp = X509_NAME_oneline(subject, NULL, 0); - VRB(session, "Cert verify CRL: issuer: %s.", cp); - OPENSSL_free(cp); - - last_update = X509_CRL_get_lastUpdate(crl); - next_update = X509_CRL_get_nextUpdate(crl); - cp = asn1time_to_str(last_update); - VRB(session, "Cert verify CRL: last update: %s.", cp); - free(cp); - cp = asn1time_to_str(next_update); - VRB(session, "Cert verify CRL: next update: %s.", cp); - free(cp); - - /* verify the signature on this CRL */ - pubkey = X509_get_pubkey(cert); - if (X509_CRL_verify(crl, pubkey) <= 0) { - ERR(session, "Cert verify CRL: invalid signature."); - X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE); - X509_OBJECT_free_contents(&obj); - if (pubkey) { - EVP_PKEY_free(pubkey); - } - return 0; - } - if (pubkey) { - EVP_PKEY_free(pubkey); - } - - /* check date of CRL to make sure it's not expired */ - if (!next_update) { - ERR(session, "Cert verify CRL: invalid nextUpdate field."); - X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD); - X509_OBJECT_free_contents(&obj); - return 0; - } - if (X509_cmp_current_time(next_update) < 0) { - ERR(session, "Cert verify CRL: expired - revoking all certificates."); - X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CRL_HAS_EXPIRED); - X509_OBJECT_free_contents(&obj); - return 0; - } - X509_OBJECT_free_contents(&obj); + if (map_type && *map_type && (!ctn->map_type || (*map_type != ctn->map_type))) { + continue; } - - /* try to retrieve a CRL corresponding to the _issuer_ of - * the current certificate in order to check for revocation */ - memset((char *)&obj, 0, sizeof(obj)); - X509_STORE_CTX_init(&store_ctx, opts->crl_store, NULL, NULL); - rc = X509_STORE_CTX_get_by_subject(&store_ctx, X509_LU_CRL, issuer, &obj); - X509_STORE_CTX_cleanup(&store_ctx); - crl = obj.data.crl; - if ((rc > 0) && crl) { - /* check if the current certificate is revoked by this CRL */ - n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl)); - for (i = 0; i < n; i++) { - revoked = sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i); - if (ASN1_INTEGER_cmp(revoked->serialNumber, X509_get_serialNumber(cert)) == 0) { - serial = ASN1_INTEGER_get(revoked->serialNumber); - cp = X509_NAME_oneline(issuer, NULL, 0); - ERR(session, "Cert verify CRL: certificate with serial %ld (0x%lX) revoked per CRL from issuer %s.", - serial, serial, cp); - OPENSSL_free(cp); - X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CERT_REVOKED); - X509_OBJECT_free_contents(&obj); - return 0; - } - } - X509_OBJECT_free_contents(&obj); + if (name && *name && (!ctn->name || strcmp(*name, ctn->name))) { + continue; } - } - - /* cert-to-name already successful */ - if (session->username) { - return 1; - } - /* cert-to-name */ - rc = nc_tls_cert_to_name(opts->ctn, cert, &map_type, &username); - - if (rc) { - if (rc == -1) { - /* fatal error */ - depth = 0; + /* first match, good enough */ + if (id && !(*id)) { + *id = ctn->id; } - /* rc == 1 is a normal CTN fail (no match found) */ - goto fail; - } - - /* cert-to-name match, now to extract the specific field from the peer cert */ - if (map_type == NC_TLS_CTN_SPECIFIED) { - session->username = strdup(username); - } else { - rc = nc_tls_ctn_get_username_from_cert(session->opts.server.client_cert, map_type, &cp); - if (rc) { - if (rc == -1) { - depth = 0; - } - goto fail; + if (fingerprint && !(*fingerprint) && ctn->fingerprint) { + *fingerprint = strdup(ctn->fingerprint); } - session->username = cp; - } - - VRB(session, "Cert verify CTN: new client username recognized as \"%s\".", session->username); - - if (server_opts.user_verify_clb && !server_opts.user_verify_clb(session)) { - VRB(session, "Cert verify: user verify callback revoked authorization."); - X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_APPLICATION_VERIFICATION); - return 0; - } - - return 1; - -fail: - if (depth > 0) { - VRB(session, "Cert verify CTN: cert fail, cert-to-name will continue on the next cert in chain."); - return 1; - } - - VRB(session, "Cert-to-name unsuccessful, dropping the new client."); - X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_APPLICATION_VERIFICATION); - return 0; -} - -#endif - -static int -nc_server_tls_set_server_cert(const char *name, struct nc_server_tls_opts *opts) -{ - if (!name) { - if (opts->server_cert) { - free(opts->server_cert); + if (map_type && !(*map_type) && ctn->map_type) { + *map_type = ctn->map_type; + } + if (name && !(*name) && ctn->name) { + *name = strdup(ctn->name); } - opts->server_cert = NULL; - return 0; - } - if (opts->server_cert) { - free(opts->server_cert); + ret = 0; + break; } - opts->server_cert = strdup(name); - return 0; + return ret; } API int -nc_server_tls_endpt_set_server_cert(const char *endpt_name, const char *name) +nc_server_tls_endpt_get_ctn(const char *endpt_name, uint32_t *id, char **fingerprint, NC_TLS_CTN_MAPTYPE *map_type, + char **name) { int ret; struct nc_endpt *endpt; @@ -949,7 +769,7 @@ nc_server_tls_endpt_set_server_cert(const char *endpt_name, const char *name) if (!endpt) { return -1; } - ret = nc_server_tls_set_server_cert(name, endpt->opts.tls); + ret = nc_server_tls_get_ctn(id, fingerprint, map_type, name, endpt->opts.tls); /* UNLOCK */ pthread_rwlock_unlock(&server_opts.config_lock); @@ -957,7 +777,8 @@ nc_server_tls_endpt_set_server_cert(const char *endpt_name, const char *name) } API int -nc_server_tls_ch_client_endpt_set_server_cert(const char *client_name, const char *endpt_name, const char *name) +nc_server_tls_ch_client_endpt_get_ctn(const char *client_name, const char *endpt_name, uint32_t *id, char **fingerprint, + NC_TLS_CTN_MAPTYPE *map_type, char **name) { int ret; struct nc_ch_client *client; @@ -969,7 +790,7 @@ nc_server_tls_ch_client_endpt_set_server_cert(const char *client_name, const cha return -1; } - ret = nc_server_tls_set_server_cert(name, endpt->opts.tls); + ret = nc_server_tls_get_ctn(id, fingerprint, map_type, name, endpt->opts.tls); /* UNLOCK */ nc_server_ch_client_unlock(client); @@ -977,883 +798,136 @@ nc_server_tls_ch_client_endpt_set_server_cert(const char *client_name, const cha return ret; } -API void -nc_server_tls_set_server_cert_clb(int (*cert_clb)(const char *name, void *user_data, char **cert_path, char **cert_data, - char **privkey_path, char **privkey_data, NC_SSH_KEY_TYPE *privkey_type), void *user_data, - void (*free_user_data)(void *user_data)) +API const X509 * +nc_session_get_client_cert(const struct nc_session *session) { - if (!cert_clb) { - ERRARG(NULL, "cert_clb"); - return; + if (!session || (session->side != NC_SERVER)) { + ERRARG(session, "session"); + return NULL; } - server_opts.server_cert_clb = cert_clb; - server_opts.server_cert_data = user_data; - server_opts.server_cert_data_free = free_user_data; + return session->opts.server.client_cert; } API void -nc_server_tls_set_server_cert_chain_clb(int (*cert_chain_clb)(const char *name, void *user_data, char ***cert_paths, - int *cert_path_count, char ***cert_data, int *cert_data_count), void *user_data, void (*free_user_data)(void *user_data)) +nc_server_tls_set_verify_clb(int (*verify_clb)(const struct nc_session *session)) { - if (!cert_chain_clb) { - ERRARG(NULL, "cert_chain_clb"); - return; - } + server_opts.user_verify_clb = verify_clb; +} - server_opts.server_cert_chain_clb = cert_chain_clb; - server_opts.server_cert_chain_data = user_data; - server_opts.server_cert_chain_data_free = free_user_data; +static void +nc_tls_make_verify_key(void) +{ + pthread_key_create(&verify_key, NULL); } static int -nc_server_tls_add_trusted_cert_list(const char *name, struct nc_server_tls_opts *opts) +nc_tls_ctx_set_server_cert_key(SSL_CTX *tls_ctx, struct nc_server_tls_opts *opts) { - NC_CHECK_ARG_RET(NULL, name, -1); + char *privkey_data = NULL; + int ret = 0; + NC_PRIVKEY_FORMAT privkey_type; + X509 *cert = NULL; + EVP_PKEY *pkey = NULL; - ++opts->trusted_cert_list_count; - opts->trusted_cert_lists = nc_realloc(opts->trusted_cert_lists, - opts->trusted_cert_list_count * sizeof *opts->trusted_cert_lists); - if (!opts->trusted_cert_lists) { - ERRMEM; - return -1; - } - opts->trusted_cert_lists[opts->trusted_cert_list_count - 1] = strdup(name); + NC_CHECK_ARG_RET(NULL, tls_ctx, opts, -1); - return 0; -} + /* load the certificate */ + if (opts->store == NC_STORE_LOCAL) { + /* local definition */ + cert = base64der_to_cert(opts->cert_data); + } else { + /* keystore */ + cert = base64der_to_cert(opts->cert_ref->data); + } + if (!cert) { + ERR(NULL, "Converting certificate data to certificate format failed."); + ret = -1; + goto cleanup; + } -API int -nc_server_tls_endpt_add_trusted_cert_list(const char *endpt_name, const char *name) -{ - int ret; - struct nc_endpt *endpt; + ret = SSL_CTX_use_certificate(tls_ctx, cert); + if (ret != 1) { + ERR(NULL, "Loading the server certificate failed (%s).", ERR_reason_error_string(ERR_get_error())); + ret = -1; + goto cleanup; + } - NC_CHECK_ARG_RET(NULL, endpt_name, -1); + /* load the private key */ + if (opts->store == NC_STORE_LOCAL) { + /* local definition */ + privkey_data = opts->privkey_data; + privkey_type = opts->privkey_type; + } else { + /* keystore */ + privkey_data = opts->key_ref->privkey_data; + privkey_type = opts->key_ref->privkey_type; + } + pkey = base64der_to_privatekey(privkey_data, nc_privkey_format_to_str(privkey_type)); + if (!pkey) { + ERR(NULL, "Converting private key data to private key format failed."); + ret = -1; + goto cleanup; + } - /* LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_OPENSSL, NULL); - if (!endpt) { - return -1; + ret = SSL_CTX_use_PrivateKey(tls_ctx, pkey); + if (ret != 1) { + ERR(NULL, "Loading the server private key failed (%s).", ERR_reason_error_string(ERR_get_error())); + ret = -1; + goto cleanup; } - ret = nc_server_tls_add_trusted_cert_list(name, endpt->opts.tls); - /* UNLOCK */ - pthread_rwlock_unlock(&server_opts.config_lock); + ret = 0; + +cleanup: + X509_free(cert); + EVP_PKEY_free(pkey); return ret; } -API int -nc_server_tls_ch_client_endpt_add_trusted_cert_list(const char *client_name, const char *endpt_name, const char *name) +static int +tls_store_add_trusted_cert(X509_STORE *cert_store, const char *cert_data) { - int ret; - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; + X509 *cert; - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_OPENSSL, &client); - if (!endpt) { - return -1; - } - - ret = nc_server_tls_add_trusted_cert_list(name, endpt->opts.tls); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return ret; -} - -API void -nc_server_tls_set_trusted_cert_list_clb(int (*cert_list_clb)(const char *name, void *user_data, char ***cert_paths, - int *cert_path_count, char ***cert_data, int *cert_data_count), - void *user_data, void (*free_user_data)(void *user_data)) -{ - if (!cert_list_clb) { - ERRARG(NULL, "cert_list_clb"); - return; - } - - server_opts.trusted_cert_list_clb = cert_list_clb; - server_opts.trusted_cert_list_data = user_data; - server_opts.trusted_cert_list_data_free = free_user_data; -} - -static int -nc_server_tls_del_trusted_cert_list(const char *name, struct nc_server_tls_opts *opts) -{ - uint16_t i; - - if (!name) { - for (i = 0; i < opts->trusted_cert_list_count; ++i) { - free(opts->trusted_cert_lists[i]); - } - free(opts->trusted_cert_lists); - opts->trusted_cert_lists = NULL; - opts->trusted_cert_list_count = 0; - return 0; - } else { - for (i = 0; i < opts->trusted_cert_list_count; ++i) { - if (!strcmp(opts->trusted_cert_lists[i], name)) { - free(opts->trusted_cert_lists[i]); - - --opts->trusted_cert_list_count; - if (i < opts->trusted_cert_list_count - 1) { - memmove(opts->trusted_cert_lists + i, opts->trusted_cert_lists + i + 1, - (opts->trusted_cert_list_count - i) * sizeof *opts->trusted_cert_lists); - } - return 0; - } - } - } - - ERR(NULL, "Certificate list \"%s\" not found.", name); - return -1; -} - -API int -nc_server_tls_endpt_del_trusted_cert_list(const char *endpt_name, const char *name) -{ - int ret; - struct nc_endpt *endpt; - - NC_CHECK_ARG_RET(NULL, endpt_name, -1); - - /* LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_OPENSSL, NULL); - if (!endpt) { - return -1; - } - ret = nc_server_tls_del_trusted_cert_list(name, endpt->opts.tls); - /* UNLOCK */ - pthread_rwlock_unlock(&server_opts.config_lock); - - return ret; -} - -API int -nc_server_tls_ch_client_endpt_del_trusted_cert_list(const char *client_name, const char *endpt_name, const char *name) -{ - int ret; - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_OPENSSL, &client); - if (!endpt) { - return -1; - } - - ret = nc_server_tls_del_trusted_cert_list(name, endpt->opts.tls); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return ret; -} - -static int -nc_server_tls_set_trusted_ca_paths(const char *ca_file, const char *ca_dir, struct nc_server_tls_opts *opts) -{ - if (!ca_file && !ca_dir) { - ERRARG(NULL, "ca_file and ca_dir"); - return -1; - } - - if (ca_file) { - free(opts->trusted_ca_file); - opts->trusted_ca_file = strdup(ca_file); - } - - if (ca_dir) { - free(opts->trusted_ca_dir); - opts->trusted_ca_dir = strdup(ca_dir); - } - - return 0; -} - -API int -nc_server_tls_endpt_set_trusted_ca_paths(const char *endpt_name, const char *ca_file, const char *ca_dir) -{ - int ret; - struct nc_endpt *endpt; - - NC_CHECK_ARG_RET(NULL, endpt_name, -1); - - /* LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_OPENSSL, NULL); - if (!endpt) { - return -1; - } - ret = nc_server_tls_set_trusted_ca_paths(ca_file, ca_dir, endpt->opts.tls); - /* UNLOCK */ - pthread_rwlock_unlock(&server_opts.config_lock); - - return ret; -} - -API int -nc_server_tls_ch_client_endpt_set_trusted_ca_paths(const char *client_name, const char *endpt_name, const char *ca_file, - const char *ca_dir) -{ - int ret; - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_OPENSSL, &client); - if (!endpt) { - return -1; - } - - ret = nc_server_tls_set_trusted_ca_paths(ca_file, ca_dir, endpt->opts.tls); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return ret; -} - -static int -nc_server_tls_set_crl_paths(const char *crl_file, const char *crl_dir, struct nc_server_tls_opts *opts) -{ - X509_LOOKUP *lookup; - - if (!crl_file && !crl_dir) { - ERRARG(NULL, "crl_file and crl_dir"); - return -1; - } - - if (!opts->crl_store) { - opts->crl_store = X509_STORE_new(); - } - - if (crl_file) { - lookup = X509_STORE_add_lookup(opts->crl_store, X509_LOOKUP_file()); - if (!lookup) { - ERR(NULL, "Failed to add a lookup method."); - goto fail; - } - - if (X509_LOOKUP_load_file(lookup, crl_file, X509_FILETYPE_PEM) != 1) { - ERR(NULL, "Failed to add a revocation lookup file (%s).", ERR_reason_error_string(ERR_get_error())); - goto fail; - } - } - - if (crl_dir) { - lookup = X509_STORE_add_lookup(opts->crl_store, X509_LOOKUP_hash_dir()); - if (!lookup) { - ERR(NULL, "Failed to add a lookup method."); - goto fail; - } - - if (X509_LOOKUP_add_dir(lookup, crl_dir, X509_FILETYPE_PEM) != 1) { - ERR(NULL, "Failed to add a revocation lookup directory (%s).", ERR_reason_error_string(ERR_get_error())); - goto fail; - } - } - - return 0; - -fail: - return -1; -} - -API int -nc_server_tls_endpt_set_crl_paths(const char *endpt_name, const char *crl_file, const char *crl_dir) -{ - int ret; - struct nc_endpt *endpt; - - NC_CHECK_ARG_RET(NULL, endpt_name, -1); - - /* LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_OPENSSL, NULL); - if (!endpt) { - return -1; - } - ret = nc_server_tls_set_crl_paths(crl_file, crl_dir, endpt->opts.tls); - /* UNLOCK */ - pthread_rwlock_unlock(&server_opts.config_lock); - - return ret; -} - -API int -nc_server_tls_ch_client_set_crl_paths(const char *client_name, const char *endpt_name, const char *crl_file, - const char *crl_dir) -{ - int ret; - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_OPENSSL, &client); - if (!endpt) { - return -1; - } - - ret = nc_server_tls_set_crl_paths(crl_file, crl_dir, endpt->opts.tls); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return ret; -} - -static void -nc_server_tls_clear_crls(struct nc_server_tls_opts *opts) -{ - if (!opts->crl_store) { - return; - } - - X509_STORE_free(opts->crl_store); - opts->crl_store = NULL; -} - -API void -nc_server_tls_endpt_clear_crls(const char *endpt_name) -{ - struct nc_endpt *endpt; - - if (!endpt_name) { - ERRARG(NULL, "endpt_name"); - return; - } - - /* LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_OPENSSL, NULL); - if (!endpt) { - return; - } - nc_server_tls_clear_crls(endpt->opts.tls); - /* UNLOCK */ - pthread_rwlock_unlock(&server_opts.config_lock); -} - -API void -nc_server_tls_ch_client_endpt_clear_crls(const char *client_name, const char *endpt_name) -{ - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_OPENSSL, &client); - if (!endpt) { - return; - } - - nc_server_tls_clear_crls(endpt->opts.tls); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); -} - -static int -nc_server_tls_add_ctn(uint32_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name, - struct nc_server_tls_opts *opts) -{ - struct nc_ctn *ctn, *new; - - if (!opts->ctn) { - /* the first item */ - opts->ctn = new = calloc(1, sizeof *new); - if (!new) { - ERRMEM; - return -1; - } - } else if (opts->ctn->id > id) { - /* insert at the beginning */ - new = calloc(1, sizeof *new); - if (!new) { - ERRMEM; - return -1; - } - new->next = opts->ctn; - opts->ctn = new; - } else { - for (ctn = opts->ctn; ctn->next && ctn->next->id <= id; ctn = ctn->next) {} - if (ctn->id == id) { - /* it exists already */ - new = ctn; - } else { - /* insert after ctn */ - new = calloc(1, sizeof *new); - if (!new) { - ERRMEM; - return -1; - } - new->next = ctn->next; - ctn->next = new; - } - } - - new->id = id; - if (fingerprint) { - free(new->fingerprint); - new->fingerprint = strdup(fingerprint); - } - if (map_type) { - new->map_type = map_type; - } - if (name) { - free(new->name); - new->name = strdup(name); - } - - return 0; -} - -API int -nc_server_tls_endpt_add_ctn(const char *endpt_name, uint32_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, - const char *name) -{ - int ret; - struct nc_endpt *endpt; - - NC_CHECK_ARG_RET(NULL, endpt_name, -1); - - /* LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_OPENSSL, NULL); - if (!endpt) { - return -1; - } - ret = nc_server_tls_add_ctn(id, fingerprint, map_type, name, endpt->opts.tls); - /* UNLOCK */ - pthread_rwlock_unlock(&server_opts.config_lock); - - return ret; -} - -API int -nc_server_tls_ch_client_endpt_add_ctn(const char *client_name, const char *endpt_name, uint32_t id, - const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name) -{ - int ret; - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_OPENSSL, &client); - if (!endpt) { - return -1; - } - - ret = nc_server_tls_add_ctn(id, fingerprint, map_type, name, endpt->opts.tls); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return ret; -} - -static int -nc_server_tls_del_ctn(int64_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name, - struct nc_server_tls_opts *opts) -{ - struct nc_ctn *ctn, *next, *prev; - int ret = -1; - - if ((id < 0) && !fingerprint && !map_type && !name) { - ctn = opts->ctn; - while (ctn) { - free(ctn->fingerprint); - free(ctn->name); - - next = ctn->next; - free(ctn); - ctn = next; - - ret = 0; - } - opts->ctn = NULL; - } else { - prev = NULL; - ctn = opts->ctn; - while (ctn) { - if (((id < 0) || (ctn->id == id)) && - (!fingerprint || !strcmp(ctn->fingerprint, fingerprint)) && - (!map_type || (ctn->map_type == map_type)) && - (!name || (ctn->name && !strcmp(ctn->name, name)))) { - free(ctn->fingerprint); - free(ctn->name); - - if (prev) { - prev->next = ctn->next; - next = ctn->next; - } else { - opts->ctn = ctn->next; - next = ctn->next; - } - free(ctn); - ctn = next; - - ret = 0; - } else { - prev = ctn; - ctn = ctn->next; - } - } - } - - return ret; -} - -API int -nc_server_tls_endpt_del_ctn(const char *endpt_name, int64_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, - const char *name) -{ - int ret; - struct nc_endpt *endpt; - - NC_CHECK_ARG_RET(NULL, endpt_name, -1); - - /* LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_OPENSSL, NULL); - if (!endpt) { - return -1; - } - ret = nc_server_tls_del_ctn(id, fingerprint, map_type, name, endpt->opts.tls); - /* UNLOCK */ - pthread_rwlock_unlock(&server_opts.config_lock); - - return ret; -} - -API int -nc_server_tls_ch_client_endpt_del_ctn(const char *client_name, const char *endpt_name, int64_t id, - const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name) -{ - int ret; - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_OPENSSL, &client); - if (!endpt) { - return -1; - } - - ret = nc_server_tls_del_ctn(id, fingerprint, map_type, name, endpt->opts.tls); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return ret; -} - -static int -nc_server_tls_get_ctn(uint32_t *id, char **fingerprint, NC_TLS_CTN_MAPTYPE *map_type, char **name, - struct nc_server_tls_opts *opts) -{ - struct nc_ctn *ctn; - int ret = -1; - - for (ctn = opts->ctn; ctn; ctn = ctn->next) { - if (id && *id && (*id != ctn->id)) { - continue; - } - if (fingerprint && *fingerprint && (!ctn->fingerprint || strcmp(*fingerprint, ctn->fingerprint))) { - continue; - } - if (map_type && *map_type && (!ctn->map_type || (*map_type != ctn->map_type))) { - continue; - } - if (name && *name && (!ctn->name || strcmp(*name, ctn->name))) { - continue; - } - - /* first match, good enough */ - if (id && !(*id)) { - *id = ctn->id; - } - if (fingerprint && !(*fingerprint) && ctn->fingerprint) { - *fingerprint = strdup(ctn->fingerprint); - } - if (map_type && !(*map_type) && ctn->map_type) { - *map_type = ctn->map_type; - } - if (name && !(*name) && ctn->name) { - *name = strdup(ctn->name); - } - - ret = 0; - break; - } - - return ret; -} - -API int -nc_server_tls_endpt_get_ctn(const char *endpt_name, uint32_t *id, char **fingerprint, NC_TLS_CTN_MAPTYPE *map_type, - char **name) -{ - int ret; - struct nc_endpt *endpt; - - NC_CHECK_ARG_RET(NULL, endpt_name, -1); - - /* LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_OPENSSL, NULL); - if (!endpt) { - return -1; - } - ret = nc_server_tls_get_ctn(id, fingerprint, map_type, name, endpt->opts.tls); - /* UNLOCK */ - pthread_rwlock_unlock(&server_opts.config_lock); - - return ret; -} - -API int -nc_server_tls_ch_client_endpt_get_ctn(const char *client_name, const char *endpt_name, uint32_t *id, char **fingerprint, - NC_TLS_CTN_MAPTYPE *map_type, char **name) -{ - int ret; - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_OPENSSL, &client); - if (!endpt) { - return -1; - } - - ret = nc_server_tls_get_ctn(id, fingerprint, map_type, name, endpt->opts.tls); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return ret; -} - -API const X509 * -nc_session_get_client_cert(const struct nc_session *session) -{ - if (!session || (session->side != NC_SERVER)) { - ERRARG(session, "session"); - return NULL; - } - - return session->opts.server.client_cert; -} - -API void -nc_server_tls_set_verify_clb(int (*verify_clb)(const struct nc_session *session)) -{ - server_opts.user_verify_clb = verify_clb; -} - -void -nc_server_tls_clear_opts(struct nc_server_tls_opts *opts) -{ - free(opts->server_cert); - nc_server_tls_del_trusted_cert_list(NULL, opts); - free(opts->trusted_ca_file); - free(opts->trusted_ca_dir); - nc_server_tls_clear_crls(opts); - nc_server_tls_del_ctn(-1, NULL, 0, NULL, opts); -} - -static void -nc_tls_make_verify_key(void) -{ - pthread_key_create(&verify_key, NULL); -} - -static X509 * -tls_load_cert(const char *cert_path, const char *cert_data) -{ - X509 *cert; - - if (cert_path) { - cert = pem_to_cert(cert_path); - } else { - cert = base64der_to_cert(cert_data); - } + cert = base64der_to_cert(cert_data); if (!cert) { - if (cert_path) { - ERR(NULL, "Loading a trusted certificate (path \"%s\") failed (%s).", cert_path, - ERR_reason_error_string(ERR_get_error())); - } else { - ERR(NULL, "Loading a trusted certificate (data \"%s\") failed (%s).", cert_data, - ERR_reason_error_string(ERR_get_error())); - } - } - return cert; -} - -static int -nc_tls_ctx_set_server_cert_chain(SSL_CTX *tls_ctx, const char *cert_name) -{ - char **cert_paths = NULL, **cert_data = NULL; - int cert_path_count = 0, cert_data_count = 0, ret = 0, i = 0; - X509 *cert = NULL; - - if (!server_opts.server_cert_chain_clb) { - /* This is optional, so return OK */ - return 0; - } - - if (server_opts.server_cert_chain_clb(cert_name, server_opts.server_cert_chain_data, &cert_paths, - &cert_path_count, &cert_data, &cert_data_count)) { - ERR(NULL, "Server certificate chain callback failed."); - return -1; - } - - for (i = 0; i < cert_path_count; ++i) { - cert = tls_load_cert(cert_paths[i], NULL); - if (!cert || (SSL_CTX_add_extra_chain_cert(tls_ctx, cert) != 1)) { - ERR(NULL, "Loading the server certificate chain failed (%s).", ERR_reason_error_string(ERR_get_error())); - ret = -1; - goto cleanup; - } - } - - for (i = 0; i < cert_data_count; ++i) { - cert = tls_load_cert(NULL, cert_data[i]); - if (!cert || (SSL_CTX_add_extra_chain_cert(tls_ctx, cert) != 1)) { - ERR(NULL, "Loading the server certificate chain failed (%s).", ERR_reason_error_string(ERR_get_error())); - ret = -1; - goto cleanup; - } - } -cleanup: - for (i = 0; i < cert_path_count; ++i) { - free(cert_paths[i]); - } - free(cert_paths); - for (i = 0; i < cert_data_count; ++i) { - free(cert_data[i]); - } - free(cert_data); - /* cert is owned by the SSL_CTX */ - - return ret; -} - -static int -nc_tls_ctx_set_server_cert_key(SSL_CTX *tls_ctx, const char *cert_name) -{ - char *cert_path = NULL, *cert_data = NULL, *privkey_path = NULL, *privkey_data = NULL; - int ret = 0; - NC_SSH_KEY_TYPE privkey_type; - X509 *cert = NULL; - EVP_PKEY *pkey = NULL; - - if (!cert_name) { - ERR(NULL, "Server certificate not set."); - return -1; - } else if (!server_opts.server_cert_clb) { - ERR(NULL, "Callback for retrieving the server certificate is not set."); - return -1; - } - - if (server_opts.server_cert_clb(cert_name, server_opts.server_cert_data, &cert_path, &cert_data, &privkey_path, - &privkey_data, &privkey_type)) { - ERR(NULL, "Server certificate callback failed."); + ERR(NULL, "Loading a trusted certificate (data \"%s\") failed (%s).", cert_data, + ERR_reason_error_string(ERR_get_error())); return -1; } - /* load the certificate */ - if (cert_path) { - if (SSL_CTX_use_certificate_file(tls_ctx, cert_path, SSL_FILETYPE_PEM) != 1) { - ERR(NULL, "Loading the server certificate failed (%s).", ERR_reason_error_string(ERR_get_error())); - ret = -1; - goto cleanup; - } - } else { - cert = base64der_to_cert(cert_data); - if (!cert || (SSL_CTX_use_certificate(tls_ctx, cert) != 1)) { - ERR(NULL, "Loading the server certificate failed (%s).", ERR_reason_error_string(ERR_get_error())); - ret = -1; - goto cleanup; - } - } - - /* load the private key */ - if (privkey_path) { - if (SSL_CTX_use_PrivateKey_file(tls_ctx, privkey_path, SSL_FILETYPE_PEM) != 1) { - ERR(NULL, "Loading the server private key failed (%s).", ERR_reason_error_string(ERR_get_error())); - ret = -1; - goto cleanup; - } - } else { - pkey = base64der_to_privatekey(privkey_data, nc_keytype2str(privkey_type)); - if (!pkey || (SSL_CTX_use_PrivateKey(tls_ctx, pkey) != 1)) { - ERR(NULL, "Loading the server private key failed (%s).", ERR_reason_error_string(ERR_get_error())); - ret = -1; - goto cleanup; - } - } - - ret = nc_tls_ctx_set_server_cert_chain(tls_ctx, cert_name); - -cleanup: - X509_free(cert); - EVP_PKEY_free(pkey); - free(cert_path); - free(cert_data); - free(privkey_path); - free(privkey_data); - return ret; -} - -static void -tls_store_add_trusted_cert(X509_STORE *cert_store, const char *cert_path, const char *cert_data) -{ - X509 *cert = tls_load_cert(cert_path, cert_data); - - if (!cert) { - return; - } - /* add the trusted certificate */ if (X509_STORE_add_cert(cert_store, cert) != 1) { ERR(NULL, "Adding a trusted certificate failed (%s).", ERR_reason_error_string(ERR_get_error())); X509_free(cert); - return; + return -1; } X509_free(cert); + + return 0; } static int -nc_tls_store_set_trusted_certs(X509_STORE *cert_store, char **trusted_cert_lists, uint16_t trusted_cert_list_count) +nc_tls_store_set_trusted_certs(X509_STORE *cert_store, struct nc_cert_grouping *certs) { uint16_t i; - int j; - char **cert_paths, **cert_data; - int cert_path_count, cert_data_count; - - if (!server_opts.trusted_cert_list_clb) { - ERR(NULL, "Callback for retrieving trusted certificate lists is not set."); - return -1; - } - - for (i = 0; i < trusted_cert_list_count; ++i) { - cert_paths = cert_data = NULL; - cert_path_count = cert_data_count = 0; - if (server_opts.trusted_cert_list_clb(trusted_cert_lists[i], server_opts.trusted_cert_list_data, - &cert_paths, &cert_path_count, &cert_data, &cert_data_count)) { - ERR(NULL, "Trusted certificate list callback for \"%s\" failed.", trusted_cert_lists[i]); - return -1; - } - for (j = 0; j < cert_path_count; ++j) { - tls_store_add_trusted_cert(cert_store, cert_paths[j], NULL); - free(cert_paths[j]); + if (certs->store == NC_STORE_LOCAL) { + /* local definition */ + for (i = 0; i < certs->cert_count; i++) { + if (tls_store_add_trusted_cert(cert_store, certs->certs[i].data)) { + return -1; + } } - free(cert_paths); - - for (j = 0; j < cert_data_count; ++j) { - tls_store_add_trusted_cert(cert_store, NULL, cert_data[j]); - free(cert_data[j]); + } else { + /* truststore */ + for (i = 0; i < certs->ts_ref->cert_count; i++) { + if (tls_store_add_trusted_cert(cert_store, certs->ts_ref->certs[i].data)) { + return -1; + } } - free(cert_data); } return 0; @@ -1894,64 +968,46 @@ nc_server_tls_accept_check(int accept_ret, struct nc_session *session) } int -nc_accept_tls_session(struct nc_session *session, int sock, int timeout) +nc_accept_tls_session(struct nc_session *session, struct nc_server_tls_opts *opts, int sock, int timeout) { X509_STORE *cert_store; SSL_CTX *tls_ctx; - X509_LOOKUP *lookup; - struct nc_server_tls_opts *opts; int ret; struct timespec ts_timeout; - opts = session->data; - /* SSL_CTX */ -#if OPENSSL_VERSION_NUMBER >= 0x10100000L // >= 1.1.0 tls_ctx = SSL_CTX_new(TLS_server_method()); -#else - tls_ctx = SSL_CTX_new(TLSv1_2_server_method()); -#endif + if (!tls_ctx) { ERR(session, "Failed to create TLS context."); goto error; } + SSL_CTX_set_verify(tls_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nc_tlsclb_verify); - if (nc_tls_ctx_set_server_cert_key(tls_ctx, opts->server_cert)) { + if (nc_tls_ctx_set_server_cert_key(tls_ctx, opts)) { goto error; } /* X509_STORE, managed (freed) with the context */ cert_store = X509_STORE_new(); - SSL_CTX_set_cert_store(tls_ctx, cert_store); - - if (nc_tls_store_set_trusted_certs(cert_store, opts->trusted_cert_lists, opts->trusted_cert_list_count)) { + if (!cert_store) { + ERR(session, "Creating certificate store failed (%s).", ERR_reason_error_string(ERR_get_error())); goto error; } - if (opts->trusted_ca_file) { - lookup = X509_STORE_add_lookup(cert_store, X509_LOOKUP_file()); - if (!lookup) { - ERR(session, "Failed to add a lookup method."); - goto error; - } - - if (X509_LOOKUP_load_file(lookup, opts->trusted_ca_file, X509_FILETYPE_PEM) != 1) { - ERR(session, "Failed to add a trusted cert file (%s).", ERR_reason_error_string(ERR_get_error())); - goto error; - } + /* set end-entity certs as cert store data, retrieve them if verification fails later */ + ret = X509_STORE_set_ex_data(cert_store, 1, &opts->ee_certs); + if (!ret) { + ERR(session, "Setting certificate store data failed (%s).", ERR_reason_error_string(ERR_get_error())); + goto error; } - if (opts->trusted_ca_dir) { - lookup = X509_STORE_add_lookup(cert_store, X509_LOOKUP_hash_dir()); - if (!lookup) { - ERR(session, "Failed to add a lookup method."); - goto error; - } + /* set store to the context */ + SSL_CTX_set_cert_store(tls_ctx, cert_store); - if (X509_LOOKUP_add_dir(lookup, opts->trusted_ca_dir, X509_FILETYPE_PEM) != 1) { - ERR(session, "Failed to add a trusted cert directory (%s).", ERR_reason_error_string(ERR_get_error())); - goto error; - } + /* set certificate authority certs */ + if (nc_tls_store_set_trusted_certs(cert_store, &opts->ca_certs)) { + goto error; } session->ti_type = NC_TI_OPENSSL; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 68aee07b..24d769c2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,7 +9,7 @@ endif() # list of all the tests in each directory #set(tests test_io test_fd_comm test_init_destroy_client test_init_destroy_server test_client_thread test_thread_messages) -set(tests test_two_channels test_keystore test_unix_socket test_config_new test_truststore test_ec test_ed25519 test_replace test_endpt_share_clients) +set(tests test_unix_socket) # only enable PAM tests if the version of PAM is greater than 1.4 if(LIBPAM_HAVE_CONFDIR) @@ -37,15 +37,12 @@ foreach(mock_func IN LISTS ${test}_mock_funcs) endforeach() #append tests depending on SSH/TLS -if(ENABLE_SSH OR ENABLE_TLS) - #list(APPEND tests test_server_thread) - if(ENABLE_SSH) - list(APPEND client_tests test_client_ssh) - endif() - - if(ENABLE_TLS) - list(APPEND client_tests test_client_tls) - endif() +if(ENABLE_SSH) + list(APPEND tests test_two_channels test_keystore test_config_new test_truststore test_ec test_ed25519 test_replace test_endpt_share_clients) +endif() + +if(ENABLE_TLS) + list(APPEND tests test_tls) endif() foreach(src IN LISTS libsrc) diff --git a/tests/test_config_new.c b/tests/test_config_new.c index b82c6f20..0289ddc8 100644 --- a/tests/test_config_new.c +++ b/tests/test_config_new.c @@ -152,11 +152,11 @@ setup_f(void **state) assert_int_equal(ret, 0); /* create new hostkey data */ - ret = nc_server_config_new_ssh_hostkey(TESTS_DIR "/data/server.key", NULL, ctx, "endpt", "hostkey", &tree); + ret = nc_server_config_new_ssh_hostkey(ctx, "endpt", "hostkey", TESTS_DIR "/data/server.key", NULL, &tree); assert_int_equal(ret, 0); /* create new address and port data */ - ret = nc_server_config_new_ssh_address_port("127.0.0.1", "10005", ctx, "endpt", &tree); + ret = nc_server_config_new_address_port(ctx, "endpt", NC_TI_LIBSSH, "127.0.0.1", "10005", &tree); assert_int_equal(ret, 0); /* create the host-key algorithms data */ @@ -164,7 +164,7 @@ setup_f(void **state) assert_int_equal(ret, 0); /* create the client authentication data, password only */ - ret = nc_server_config_new_ssh_client_auth_password("testpassword123", ctx, "endpt", "client", &tree); + ret = nc_server_config_new_ssh_client_auth_password(ctx, "endpt", "client", "testpassword123", &tree); assert_int_equal(ret, 0); /* configure the server based on the data */ diff --git a/tests/test_ec.c b/tests/test_ec.c index ad95a4e8..95f82ace 100644 --- a/tests/test_ec.c +++ b/tests/test_ec.c @@ -215,19 +215,19 @@ setup_f(void **state) ret = nc_server_config_load_modules(&ctx); assert_int_equal(ret, 0); - ret = nc_server_config_new_ssh_hostkey(TESTS_DIR "/data/key_ecdsa", NULL, ctx, "endpt", "hostkey", &tree); + ret = nc_server_config_new_ssh_hostkey(ctx, "endpt", "hostkey", TESTS_DIR "/data/key_ecdsa", NULL, &tree); assert_int_equal(ret, 0); - ret = nc_server_config_new_ssh_address_port("127.0.0.1", "10009", ctx, "endpt", &tree); + ret = nc_server_config_new_address_port(ctx, "endpt", NC_TI_LIBSSH, "127.0.0.1", "10009", &tree); assert_int_equal(ret, 0); - ret = nc_server_config_new_ssh_client_auth_pubkey(TESTS_DIR "/data/id_ecdsa256.pub", ctx, "endpt", "test_ec256", "pubkey", &tree); + ret = nc_server_config_new_ssh_client_auth_pubkey(ctx, "endpt", "test_ec256", "pubkey", TESTS_DIR "/data/id_ecdsa256.pub", &tree); assert_int_equal(ret, 0); - ret = nc_server_config_new_ssh_client_auth_pubkey(TESTS_DIR "/data/id_ecdsa384.pub", ctx, "endpt", "test_ec384", "pubkey", &tree); + ret = nc_server_config_new_ssh_client_auth_pubkey(ctx, "endpt", "test_ec384", "pubkey", TESTS_DIR "/data/id_ecdsa384.pub", &tree); assert_int_equal(ret, 0); - ret = nc_server_config_new_ssh_client_auth_pubkey(TESTS_DIR "/data/id_ecdsa521.pub", ctx, "endpt", "test_ec521", "pubkey", &tree); + ret = nc_server_config_new_ssh_client_auth_pubkey(ctx, "endpt", "test_ec521", "pubkey", TESTS_DIR "/data/id_ecdsa521.pub", &tree); assert_int_equal(ret, 0); /* configure the server based on the data */ diff --git a/tests/test_ed25519.c b/tests/test_ed25519.c index 3a84f22b..7b27d7f9 100644 --- a/tests/test_ed25519.c +++ b/tests/test_ed25519.c @@ -152,13 +152,13 @@ setup_f(void **state) ret = nc_server_config_load_modules(&ctx); assert_int_equal(ret, 0); - ret = nc_server_config_new_ssh_hostkey(TESTS_DIR "/data/server.key", NULL, ctx, "endpt", "hostkey", &tree); + ret = nc_server_config_new_ssh_hostkey(ctx, "endpt", "hostkey", TESTS_DIR "/data/server.key", NULL, &tree); assert_int_equal(ret, 0); - ret = nc_server_config_new_ssh_address_port("127.0.0.1", "10009", ctx, "endpt", &tree); + ret = nc_server_config_new_address_port(ctx, "endpt", NC_TI_LIBSSH, "127.0.0.1", "10009", &tree); assert_int_equal(ret, 0); - ret = nc_server_config_new_ssh_client_auth_pubkey(TESTS_DIR "/data/id_ed25519.pub", ctx, "endpt", "test_ed25519", "pubkey", &tree); + ret = nc_server_config_new_ssh_client_auth_pubkey(ctx, "endpt", "test_ed25519", "pubkey", TESTS_DIR "/data/id_ed25519.pub", &tree); assert_int_equal(ret, 0); /* configure the server based on the data */ diff --git a/tests/test_tls.c b/tests/test_tls.c new file mode 100644 index 00000000..66930162 --- /dev/null +++ b/tests/test_tls.c @@ -0,0 +1,207 @@ +/** + * @file test_tls.c + * @author Roman Janota + * @brief libnetconf2 TLS authentication test + * + * @copyright + * Copyright (c) 2023 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 + +#include +#include +#include +#include +#include + +#include + +#include "tests/config.h" + +#define NC_ACCEPT_TIMEOUT 2000 +#define NC_PS_POLL_TIMEOUT 2000 + +struct ly_ctx *ctx; + +struct test_state { + pthread_barrier_t barrier; +}; + +static void * +server_thread(void *arg) +{ + int ret; + NC_MSG_TYPE msgtype; + struct nc_session *session; + struct nc_pollsession *ps; + struct test_state *state = arg; + + (void) arg; + + ps = nc_ps_new(); + assert_non_null(ps); + + /* accept a session and add it to the poll session structure */ + pthread_barrier_wait(&state->barrier); + msgtype = nc_accept(NC_ACCEPT_TIMEOUT, ctx, &session); + assert_int_equal(msgtype, NC_MSG_HELLO); + + ret = nc_ps_add_session(ps, session); + assert_int_equal(ret, 0); + + do { + ret = nc_ps_poll(ps, NC_PS_POLL_TIMEOUT, NULL); + assert_int_equal(ret & NC_PSPOLL_RPC, NC_PSPOLL_RPC); + } while (!(ret & NC_PSPOLL_SESSION_TERM)); + + nc_ps_clear(ps, 1, NULL); + nc_ps_free(ps); + nc_thread_destroy(); + return NULL; +} + +static void * +client_thread(void *arg) +{ + int ret; + struct nc_session *session = NULL; + struct test_state *state = arg; + + ret = nc_client_set_schema_searchpath(MODULES_DIR); + assert_int_equal(ret, 0); + + /* set client cert */ + ret = nc_client_tls_set_cert_key_paths(TESTS_DIR "/data/client.crt", TESTS_DIR "/data/client.key"); + assert_int_equal(ret, 0); + + /* set client ca */ + ret = nc_client_tls_set_trusted_ca_paths(NULL, TESTS_DIR "/data"); + assert_int_equal(ret, 0); + + pthread_barrier_wait(&state->barrier); + session = nc_connect_tls("127.0.0.1", 10005, NULL); + assert_non_null(session); + + nc_session_free(session, NULL); + nc_thread_destroy(); + return NULL; +} + +static void +test_nc_tls(void **state) +{ + int ret, i; + pthread_t tids[2]; + + assert_non_null(state); + + ret = pthread_create(&tids[0], NULL, client_thread, *state); + assert_int_equal(ret, 0); + ret = pthread_create(&tids[1], NULL, server_thread, *state); + assert_int_equal(ret, 0); + + for (i = 0; i < 2; i++) { + pthread_join(tids[i], NULL); + } +} + +static int +setup_f(void **state) +{ + int ret; + struct lyd_node *tree = NULL; + struct test_state *test_state; + + nc_verbosity(NC_VERB_VERBOSE); + + /* init barrier */ + test_state = malloc(sizeof *test_state); + assert_non_null(test_state); + + ret = pthread_barrier_init(&test_state->barrier, NULL, 2); + assert_int_equal(ret, 0); + + *state = test_state; + + ret = ly_ctx_new(MODULES_DIR, 0, &ctx); + assert_int_equal(ret, 0); + + ret = nc_server_init_ctx(&ctx); + assert_int_equal(ret, 0); + + ret = nc_server_config_load_modules(&ctx); + assert_int_equal(ret, 0); + + /* create new address and port data */ + ret = nc_server_config_new_address_port(ctx, "endpt", NC_TI_OPENSSL, "127.0.0.1", "10005", &tree); + assert_int_equal(ret, 0); + + /* create new server certificate data */ + ret = nc_server_config_new_tls_server_certificate(ctx, "endpt", NULL, TESTS_DIR "/data/server.key", TESTS_DIR "/data/server.crt", &tree); + assert_int_equal(ret, 0); + + /* create new end entity client cert data */ + ret = nc_server_config_new_tls_client_certificate(ctx, "endpt", "client_cert", TESTS_DIR "/data/client.crt", &tree); + assert_int_equal(ret, 0); + + /* create new client ca data */ + ret = nc_server_config_new_tls_client_ca(ctx, "endpt", "client_ca", TESTS_DIR "/data/serverca.pem", &tree); + assert_int_equal(ret, 0); + + ret = nc_server_config_new_tls_ctn(ctx, "endpt", 1, + "04:85:6B:75:D1:1A:86:E0:D8:FE:5B:BD:72:F5:73:1D:07:EA:32:BF:09:11:21:6A:6E:23:78:8E:B6:D5:73:C3:2D", + NC_TLS_CTN_SPECIFIED, "client", &tree); + assert_int_equal(ret, 0); + + /* configure the server based on the data */ + ret = nc_server_config_setup_diff(tree); + assert_int_equal(ret, 0); + + /* initialize client */ + nc_client_init(); + + ret = nc_server_init(); + assert_int_equal(ret, 0); + + lyd_free_all(tree); + + return 0; +} + +static int +teardown_f(void **state) +{ + int ret = 0; + struct test_state *test_state; + + assert_non_null(state); + test_state = *state; + + ret = pthread_barrier_destroy(&test_state->barrier); + assert_int_equal(ret, 0); + + free(*state); + nc_client_destroy(); + nc_server_destroy(); + ly_ctx_destroy(ctx); + + return 0; +} + +int +main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_nc_tls, setup_f, teardown_f), + }; + + setenv("CMOCKA_TEST_ABORT", "1", 1); + return cmocka_run_group_tests(tests, NULL, NULL); +}