blob: 4ac373611b346d60b187b46b6ba7ed2dec90b273 [file] [log] [blame]
/**
* @file session_server_tls.c
* @author Michal Vasko <mvasko@cesnet.cz>
* @author Roman Janota <janota@cesnet.cz>
* @brief libnetconf2 TLS server session manipulation functions
*
* @copyright
* 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.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/
#define _GNU_SOURCE
#include <poll.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <curl/curl.h>
#include "compat.h"
#include "config.h"
#include "log_p.h"
#include "session.h"
#include "session_p.h"
#include "session_wrapper.h"
struct nc_server_tls_opts tls_ch_opts;
extern struct nc_server_opts server_opts;
static int
nc_server_tls_ks_ref_get_cert_key(const char *referenced_key_name, const char *referenced_cert_name,
char **privkey_data, NC_PRIVKEY_FORMAT *privkey_type, char **cert_data)
{
uint16_t i, j;
struct nc_keystore *ks = &server_opts.keystore;
*privkey_data = NULL;
*cert_data = NULL;
/* lookup name */
for (i = 0; i < ks->asym_key_count; i++) {
if (!strcmp(referenced_key_name, ks->asym_keys[i].name)) {
break;
}
}
if (i == ks->asym_key_count) {
ERR(NULL, "Keystore entry \"%s\" not found.", referenced_key_name);
return -1;
}
for (j = 0; j < ks->asym_keys[i].cert_count; j++) {
if (!strcmp(referenced_cert_name, ks->asym_keys[i].certs[j].name)) {
break;
}
}
if (j == ks->asym_keys[i].cert_count) {
ERR(NULL, "Keystore certificate entry \"%s\" associated with the key \"%s\" not found.",
referenced_cert_name, referenced_key_name);
return -1;
}
*privkey_data = ks->asym_keys[i].privkey_data;
*privkey_type = ks->asym_keys[i].privkey_type;
*cert_data = ks->asym_keys[i].certs[j].data;
return 0;
}
static int
nc_server_tls_ts_ref_get_certs(const char *referenced_name, struct nc_certificate **certs, uint16_t *cert_count)
{
uint16_t i;
struct nc_truststore *ts = &server_opts.truststore;
*certs = NULL;
*cert_count = 0;
/* lookup name */
for (i = 0; i < ts->cert_bag_count; i++) {
if (!strcmp(referenced_name, ts->cert_bags[i].name)) {
break;
}
}
if (i == ts->cert_bag_count) {
ERR(NULL, "Truststore entry \"%s\" not found.", referenced_name);
return -1;
}
*certs = ts->cert_bags[i].certs;
*cert_count = ts->cert_bags[i].cert_count;
return 0;
}
static void *
nc_base64der_to_cert(const char *in)
{
char *buf = NULL;
void *cert;
NC_CHECK_ARG_RET(NULL, in, NULL);
if (asprintf(&buf, "%s%s%s", "-----BEGIN CERTIFICATE-----\n", in, "\n-----END CERTIFICATE-----") == -1) {
ERRMEM;
return NULL;
}
cert = nc_tls_pem_to_cert_wrap(buf);
free(buf);
return cert;
}
static void *
nc_base64der_to_privkey(const char *in, const char *key_str)
{
char *buf = NULL;
void *pkey;
NC_CHECK_ARG_RET(NULL, in, NULL);
if (asprintf(&buf, "%s%s%s%s%s%s%s", "-----BEGIN", key_str, "PRIVATE KEY-----\n", in, "\n-----END",
key_str, "PRIVATE KEY-----") == -1) {
ERRMEM;
return NULL;
}
pkey = nc_tls_pem_to_privkey_wrap(buf);
free(buf);
return pkey;
}
static char *
nc_server_tls_digest_to_hex(const unsigned char *digest, unsigned int digest_len)
{
unsigned int i;
char *hex;
hex = malloc(digest_len * 3);
NC_CHECK_ERRMEM_RET(!hex, NULL);
for (i = 0; i < digest_len - 1; ++i) {
sprintf(hex + (i * 3), "%02x:", digest[i]);
}
sprintf(hex + (i * 3), "%02x", digest[i]);
return hex;
}
static char *
nc_server_tls_md5(void *cert)
{
int rc;
unsigned int buf_len = 16;
unsigned char buf[buf_len];
/* compute MD-5 hash of cert and store it in buf */
rc = nc_server_tls_md5_wrap(cert, buf);
if (rc) {
return NULL;
}
/* convert the hash to hex */
return nc_server_tls_digest_to_hex(buf, buf_len);
}
static char *
nc_server_tls_sha1(void *cert)
{
int rc;
unsigned int buf_len = 20;
unsigned char buf[buf_len];
/* compute SHA-1 hash of cert and store it in buf */
rc = nc_server_tls_sha1_wrap(cert, buf);
if (rc) {
return NULL;
}
/* convert the hash to hex */
return nc_server_tls_digest_to_hex(buf, buf_len);
}
static char *
nc_server_tls_sha224(void *cert)
{
int rc;
unsigned int buf_len = 28;
unsigned char buf[buf_len];
/* compute SHA-224 hash of cert and store it in buf */
rc = nc_server_tls_sha224_wrap(cert, buf);
if (rc) {
return NULL;
}
/* convert the hash to hex */
return nc_server_tls_digest_to_hex(buf, buf_len);
}
static char *
nc_server_tls_sha256(void *cert)
{
int rc;
unsigned int buf_len = 32;
unsigned char buf[buf_len];
/* compute SHA-256 hash of cert and store it in buf */
rc = nc_server_tls_sha256_wrap(cert, buf);
if (rc) {
return NULL;
}
/* convert the hash to hex */
return nc_server_tls_digest_to_hex(buf, buf_len);
}
static char *
nc_server_tls_sha384(void *cert)
{
int rc;
unsigned int buf_len = 48;
unsigned char buf[buf_len];
/* compute SHA-384 hash of cert and store it in buf */
rc = nc_server_tls_sha384_wrap(cert, buf);
if (rc) {
return NULL;
}
/* convert the hash to hex */
return nc_server_tls_digest_to_hex(buf, buf_len);
}
static char *
nc_server_tls_sha512(void *cert)
{
int rc;
unsigned int buf_len = 64;
unsigned char buf[buf_len];
/* compute SHA-512 hash of cert and store it in buf */
rc = nc_server_tls_sha512_wrap(cert, buf);
if (rc) {
return NULL;
}
/* convert the hash to hex */
return nc_server_tls_digest_to_hex(buf, buf_len);
}
static int
nc_server_tls_cert_to_name(struct nc_ctn *ctn_first, void *cert, struct nc_ctn_data *data)
{
int ret = 0;
char *digest_md5 = NULL, *digest_sha1 = NULL, *digest_sha224 = NULL;
char *digest_sha256 = NULL, *digest_sha384 = NULL, *digest_sha512 = NULL;
struct nc_ctn *ctn;
NC_TLS_CTN_MAPTYPE map_type;
for (ctn = ctn_first; ctn; ctn = ctn->next) {
/* reset map_type */
map_type = NC_TLS_CTN_UNKNOWN;
/* first make sure the entry is valid */
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;
/* MD5 */
} else if (!strncmp(ctn->fingerprint, "01", 2)) {
if (!digest_md5) {
digest_md5 = nc_server_tls_md5(cert);
if (!digest_md5) {
ret = -1;
goto cleanup;
}
}
if (!strcasecmp(ctn->fingerprint + 3, digest_md5)) {
/* we got ourselves a potential winner! */
VRB(NULL, "Cert verify CTN: entry with a matching fingerprint found.");
map_type = ctn->map_type;
}
/* SHA-1 */
} else if (!strncmp(ctn->fingerprint, "02", 2)) {
if (!digest_sha1) {
digest_sha1 = nc_server_tls_sha1(cert);
if (!digest_sha1) {
ret = -1;
goto cleanup;
}
}
if (!strcasecmp(ctn->fingerprint + 3, digest_sha1)) {
/* we got ourselves a potential winner! */
VRB(NULL, "Cert verify CTN: entry with a matching fingerprint found.");
map_type = ctn->map_type;
}
/* SHA-224 */
} else if (!strncmp(ctn->fingerprint, "03", 2)) {
if (!digest_sha224) {
digest_sha224 = nc_server_tls_sha224(cert);
if (!digest_sha224) {
ret = -1;
goto cleanup;
}
}
if (!strcasecmp(ctn->fingerprint + 3, digest_sha224)) {
/* we got ourselves a potential winner! */
VRB(NULL, "Cert verify CTN: entry with a matching fingerprint found.");
map_type = ctn->map_type;
}
/* SHA-256 */
} else if (!strncmp(ctn->fingerprint, "04", 2)) {
if (!digest_sha256) {
digest_sha256 = nc_server_tls_sha256(cert);
if (!digest_sha256) {
ret = -1;
goto cleanup;
}
}
if (!strcasecmp(ctn->fingerprint + 3, digest_sha256)) {
/* we got ourselves a potential winner! */
VRB(NULL, "Cert verify CTN: entry with a matching fingerprint found.");
map_type = ctn->map_type;
}
/* SHA-384 */
} else if (!strncmp(ctn->fingerprint, "05", 2)) {
if (!digest_sha384) {
digest_sha384 = nc_server_tls_sha384(cert);
if (!digest_sha384) {
ret = -1;
goto cleanup;
}
}
if (!strcasecmp(ctn->fingerprint + 3, digest_sha384)) {
/* we got ourselves a potential winner! */
VRB(NULL, "Cert verify CTN: entry with a matching fingerprint found.");
map_type = ctn->map_type;
}
/* SHA-512 */
} else if (!strncmp(ctn->fingerprint, "06", 2)) {
if (!digest_sha512) {
digest_sha512 = nc_server_tls_sha512(cert);
if (!digest_sha512) {
ret = -1;
goto cleanup;
}
}
if (!strcasecmp(ctn->fingerprint + 3, digest_sha512)) {
/* we got ourselves a potential winner! */
VRB(NULL, "Cert verify CTN: entry with a matching fingerprint found.");
map_type = ctn->map_type;
}
/* unknown */
} else {
WRN(NULL, "Unknown fingerprint algorithm used (%s), skipping.", ctn->fingerprint);
continue;
}
if (map_type != NC_TLS_CTN_UNKNOWN) {
/* found a fingerprint match */
if (!(map_type & data->matched_ctns)) {
data->matched_ctns |= map_type;
data->matched_ctn_type[data->matched_ctn_count++] = map_type;
if (!data->username && (map_type == NC_TLS_CTN_SPECIFIED)) {
data->username = ctn->name;
}
}
}
}
cleanup:
free(digest_md5);
free(digest_sha1);
free(digest_sha224);
free(digest_sha256);
free(digest_sha384);
free(digest_sha512);
return ret;
}
int
nc_server_tls_get_username_from_cert(void *cert, NC_TLS_CTN_MAPTYPE map_type, char **username)
{
char *subject, *cn, *san_value = NULL, rdn_separator;
void *sans;
int i, nsans = 0, rc;
NC_TLS_CTN_MAPTYPE san_type = 0;
#ifdef HAVE_LIBMEDTLS
rdn_separator = ',';
#else
rdn_separator = '/';
#endif
if (map_type == NC_TLS_CTN_COMMON_NAME) {
subject = nc_server_tls_get_subject_wrap(cert);
if (!subject) {
return -1;
}
cn = strstr(subject, "CN=");
if (!cn) {
WRN(NULL, "Certificate does not include the commonName field.");
free(subject);
return 1;
}
/* skip "CN=" */
cn += 3;
if (strchr(cn, rdn_separator)) {
*strchr(cn, rdn_separator) = '\0';
}
*username = strdup(cn);
free(subject);
NC_CHECK_ERRMEM_RET(!*username, -1);
} else {
sans = nc_tls_get_sans_wrap(cert);
if (!sans) {
WRN(NULL, "Certificate has no SANs or failed to retrieve them.");
return 1;
}
nsans = nc_tls_get_num_sans_wrap(sans);
for (i = 0; i < nsans; i++) {
if ((rc = nc_tls_get_san_value_type_wrap(sans, i, &san_value, &san_type))) {
if (rc == -1) {
/* fatal error */
nc_tls_sans_destroy_wrap(sans);
return -1;
}
/* got a type that we dont care about */
continue;
}
if ((map_type == NC_TLS_CTN_SAN_ANY) || (map_type == san_type)) {
/* found a match */
*username = san_value;
break;
}
free(san_value);
}
nc_tls_sans_destroy_wrap(sans);
if (i == nsans) {
switch (map_type) {
case NC_TLS_CTN_SAN_RFC822_NAME:
WRN(NULL, "Certificate does not include the SAN rfc822Name field.");
break;
case NC_TLS_CTN_SAN_DNS_NAME:
WRN(NULL, "Certificate does not include the SAN dNSName field.");
break;
case NC_TLS_CTN_SAN_IP_ADDRESS:
WRN(NULL, "Certificate does not include the SAN iPAddress field.");
break;
case NC_TLS_CTN_SAN_ANY:
WRN(NULL, "Certificate does not include any relevant SAN fields.");
break;
default:
break;
}
return 1;
}
}
return 0;
}
static int
_nc_server_tls_verify_peer_cert(void *peer_cert, struct nc_cert_grouping *ee_certs)
{
int i, ret;
void *cert;
struct nc_certificate *certs;
uint16_t cert_count;
if (ee_certs->store == NC_STORE_LOCAL) {
/* local definition */
certs = ee_certs->certs;
cert_count = ee_certs->cert_count;
} else {
/* truststore reference */
if (nc_server_tls_ts_ref_get_certs(ee_certs->ts_ref, &certs, &cert_count)) {
ERR(NULL, "Error getting end-entity certificates from the truststore reference \"%s\".", ee_certs->ts_ref);
return -1;
}
}
for (i = 0; i < cert_count; i++) {
/* import stored cert */
cert = nc_base64der_to_cert(certs[i].data);
/* compare stored with received */
ret = nc_server_tls_certs_match_wrap(peer_cert, cert);
nc_tls_cert_destroy_wrap(cert);
if (ret) {
/* found a match */
VRB(NULL, "Cert verify: fail, but the end-entity certificate is trusted, continuing.");
return 0;
}
}
return 1;
}
int
nc_server_tls_verify_peer_cert(void *peer_cert, struct nc_server_tls_opts *opts)
{
int rc;
struct nc_endpt *referenced_endpt;
rc = _nc_server_tls_verify_peer_cert(peer_cert, &opts->ee_certs);
if (!rc) {
return 0;
}
if (opts->referenced_endpt_name) {
if (nc_server_get_referenced_endpt(opts->referenced_endpt_name, &referenced_endpt)) {
ERRINT;
return -1;
}
rc = _nc_server_tls_verify_peer_cert(peer_cert, &referenced_endpt->opts.tls->ee_certs);
if (!rc) {
return 0;
}
}
return 1;
}
int
nc_server_tls_verify_cert(void *cert, int depth, int trusted, struct nc_tls_verify_cb_data *cb_data)
{
int ret = 0, i;
char *subject = NULL, *issuer = NULL;
struct nc_server_tls_opts *opts = cb_data->opts;
struct nc_session *session = cb_data->session;
struct nc_endpt *referenced_endpt;
if (session->username) {
/* already verified */
return 0;
}
subject = nc_server_tls_get_subject_wrap(cert);
issuer = nc_server_tls_get_issuer_wrap(cert);
if (!subject || !issuer) {
ERR(session, "Failed to get certificate's subject or issuer.");
ret = -1;
goto cleanup;
}
VRB(session, "Cert verify: depth %d.", depth);
VRB(session, "Cert verify: subject: %s.", subject);
VRB(session, "Cert verify: issuer: %s.", issuer);
if (depth == 0) {
if (!trusted) {
/* peer cert is not trusted, so it must match any configured end-entity cert
* on the given endpoint in order for the client to be authenticated */
ret = nc_server_tls_verify_peer_cert(cert, opts);
if (ret) {
ERR(session, "Cert verify: fail (Client certificate not trusted and does not match any configured end-entity certificate).");
goto cleanup;
}
}
}
/* get matching ctn entries */
ret = nc_server_tls_cert_to_name(opts->ctn, cert, &cb_data->ctn_data);
if (ret == -1) {
/* fatal error */
goto cleanup;
}
/* check the referenced endpoint's ctn entries */
if (opts->referenced_endpt_name) {
if (nc_server_get_referenced_endpt(opts->referenced_endpt_name, &referenced_endpt)) {
ERRINT;
ret = -1;
goto cleanup;
}
ret = nc_server_tls_cert_to_name(referenced_endpt->opts.tls->ctn, cert, &cb_data->ctn_data);
if (ret == -1) {
/* fatal error */
goto cleanup;
}
}
/* obtain username from matched ctn entries */
if (depth == 0) {
for (i = 0; i < cb_data->ctn_data.matched_ctn_count; i++) {
if (cb_data->ctn_data.matched_ctn_type[i] == NC_TLS_CTN_SPECIFIED) {
session->username = strdup(cb_data->ctn_data.username);
NC_CHECK_ERRMEM_GOTO(!session->username, ret = -1, cleanup);
} else {
ret = nc_server_tls_get_username_from_cert(cert, cb_data->ctn_data.matched_ctn_type[i], &session->username);
if (ret == -1) {
/* fatal error */
goto cleanup;
} else if (!ret) {
/* username obtained */
break;
}
}
}
if (session->username) {
VRB(NULL, "Cert verify CTN: new client username recognized as \"%s\".", session->username);
} else {
VRB(NULL, "Cert verify CTN: unsuccessful, dropping the new client.");
ret = 1;
goto cleanup;
}
}
if (session->username && server_opts.user_verify_clb && !server_opts.user_verify_clb(session)) {
VRB(session, "Cert verify: user verify callback revoked authorization.");
ret = 1;
goto cleanup;
}
cleanup:
free(subject);
free(issuer);
return ret;
}
API const void *
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;
}
int
nc_server_tls_load_server_cert_key(struct nc_server_tls_opts *opts, void **srv_cert, void **srv_pkey)
{
char *privkey_data = NULL, *cert_data = NULL;
NC_PRIVKEY_FORMAT privkey_type;
void *cert = NULL;
void *pkey = NULL;
*srv_cert = *srv_pkey = NULL;
/* get data needed for setting the server cert */
if (opts->store == NC_STORE_LOCAL) {
/* local definition */
cert_data = opts->cert_data;
privkey_data = opts->privkey_data;
privkey_type = opts->privkey_type;
} else {
/* keystore */
if (nc_server_tls_ks_ref_get_cert_key(opts->key_ref, opts->cert_ref, &privkey_data, &privkey_type, &cert_data)) {
ERR(NULL, "Getting server certificate from the keystore reference \"%s\" failed.", opts->key_ref);
return 1;
}
}
if (!cert_data || !privkey_data) {
ERR(NULL, "Server certificate not configured.");
return 1;
}
cert = nc_base64der_to_cert(cert_data);
if (!cert) {
return 1;
}
pkey = nc_base64der_to_privkey(privkey_data, nc_privkey_format_to_str(privkey_type));
if (!pkey) {
nc_tls_cert_destroy_wrap(cert);
return 1;
}
*srv_cert = cert;
*srv_pkey = pkey;
return 0;
}
static size_t
nc_server_tls_curl_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
{
struct nc_curl_data *data;
size = nmemb;
data = (struct nc_curl_data *)userdata;
data->data = nc_realloc(data->data, data->size + size);
NC_CHECK_ERRMEM_RET(!data->data, 0);
memcpy(&data->data[data->size], ptr, size);
data->size += size;
return size;
}
static int
nc_server_tls_curl_fetch(CURL *handle, const char *url)
{
char err_buf[CURL_ERROR_SIZE];
/* set uri */
if (curl_easy_setopt(handle, CURLOPT_URL, url)) {
ERR(NULL, "Setting URI \"%s\" to download CRL from failed.", url);
return 1;
}
/* set err buf */
if (curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, err_buf)) {
ERR(NULL, "Setting CURL error buffer option failed.");
return 1;
}
/* download */
if (curl_easy_perform(handle)) {
ERR(NULL, "Downloading CRL from \"%s\" failed (%s).", url, err_buf);
return 1;
}
return 0;
}
static int
nc_server_tls_curl_init(CURL **handle, struct nc_curl_data *data)
{
NC_CHECK_ARG_RET(NULL, handle, data, -1);
*handle = NULL;
*handle = curl_easy_init();
if (!*handle) {
ERR(NULL, "Initializing CURL failed.");
return 1;
}
if (curl_easy_setopt(*handle, CURLOPT_WRITEFUNCTION, nc_server_tls_curl_cb)) {
ERR(NULL, "Setting curl callback failed.");
return 1;
}
if (curl_easy_setopt(*handle, CURLOPT_WRITEDATA, data)) {
ERR(NULL, "Setting curl callback data failed.");
return 1;
}
return 0;
}
static int
nc_server_tls_crl_path(const char *path, void *crl_store)
{
return nc_tls_import_crl_path_wrap(path, crl_store);
}
static int
nc_server_tls_crl_url(const char *url, void *crl_store)
{
int ret = 0;
CURL *handle = NULL;
struct nc_curl_data downloaded = {0};
/* init curl */
ret = nc_server_tls_curl_init(&handle, &downloaded);
if (ret) {
goto cleanup;
}
VRB(NULL, "Downloading CRL from \"%s\".", url);
/* download the CRL */
ret = nc_server_tls_curl_fetch(handle, url);
if (ret) {
goto cleanup;
}
/* convert the downloaded data to CRL and add it to the store */
ret = nc_server_tls_add_crl_to_store_wrap(downloaded.data, downloaded.size, crl_store);
if (ret) {
goto cleanup;
}
cleanup:
curl_easy_cleanup(handle);
return ret;
}
static int
nc_server_tls_crl_cert_ext(void *cert_store, void *crl_store)
{
int ret = 0;
CURL *handle = NULL;
struct nc_curl_data downloaded = {0};
char **uris = NULL;
int uri_count = 0, i;
/* init curl */
ret = nc_server_tls_curl_init(&handle, &downloaded);
if (ret) {
goto cleanup;
}
/* get all the uris we can, even though some may point to the same CRL */
ret = nc_server_tls_get_crl_distpoint_uris_wrap(cert_store, &uris, &uri_count);
if (ret) {
goto cleanup;
}
for (i = 0; i < uri_count; i++) {
VRB(NULL, "Downloading CRL from \"%s\".", uris[i]);
ret = nc_server_tls_curl_fetch(handle, uris[i]);
if (ret) {
/* failed to download the CRL from this entry, try the next entry */
WRN(NULL, "Failed to fetch CRL from \"%s\".", uris[i]);
continue;
}
/* convert the downloaded data to CRL and add it to the store */
ret = nc_server_tls_add_crl_to_store_wrap(downloaded.data, downloaded.size, crl_store);
if (ret) {
goto cleanup;
}
}
cleanup:
for (i = 0; i < uri_count; i++) {
free(uris[i]);
}
free(uris);
curl_easy_cleanup(handle);
return ret;
}
int
nc_server_tls_load_crl(struct nc_server_tls_opts *opts, void *cert_store, void *crl_store)
{
if (opts->crl_path) {
if (nc_server_tls_crl_path(opts->crl_path, crl_store)) {
return 1;
}
} else if (opts->crl_url) {
if (nc_server_tls_crl_url(opts->crl_url, crl_store)) {
return 1;
}
} else {
if (nc_server_tls_crl_cert_ext(cert_store, crl_store)) {
return 1;
}
}
return 0;
}
int
nc_server_tls_load_trusted_certs(struct nc_cert_grouping *ca_certs, void *cert_store)
{
struct nc_certificate *certs;
uint16_t i, cert_count;
void *cert;
if (ca_certs->store == NC_STORE_LOCAL) {
/* local definition */
certs = ca_certs->certs;
cert_count = ca_certs->cert_count;
} else {
/* truststore */
if (nc_server_tls_ts_ref_get_certs(ca_certs->ts_ref, &certs, &cert_count)) {
ERR(NULL, "Error getting certificate-authority certificates from the truststore reference \"%s\".", ca_certs->ts_ref);
return 1;
}
}
for (i = 0; i < cert_count; i++) {
/* parse data into cert */
cert = nc_base64der_to_cert(certs[i].data);
if (!cert) {
return 1;
}
/* store cert in cert store */
if (nc_tls_add_cert_to_store_wrap(cert, cert_store)) {
nc_tls_cert_destroy_wrap(cert);
return 1;
}
}
return 0;
}
static int
nc_server_tls_accept_check(int accept_ret, void *tls_session)
{
uint32_t verify;
char *err;
/* check certificate verification result */
verify = nc_tls_get_verify_result_wrap(tls_session);
if (!verify && (accept_ret == 1)) {
VRB(NULL, "Client certificate verified.");
} else if (verify) {
err = nc_tls_verify_error_string_wrap(verify);
ERR(NULL, "Client certificate error (%s).", err);
free(err);
}
if (accept_ret != 1) {
nc_server_tls_print_accept_err_wrap(accept_ret, tls_session);
}
return accept_ret;
}
int
nc_accept_tls_session(struct nc_session *session, struct nc_server_tls_opts *opts, int sock, int timeout)
{
int rc, timeouted = 0;
struct timespec ts_timeout;
struct nc_tls_verify_cb_data cb_data = {0};
struct nc_endpt *referenced_endpt;
void *tls_cfg, *srv_cert, *srv_pkey, *cert_store, *crl_store;
tls_cfg = srv_cert = srv_pkey = cert_store = crl_store = NULL;
/* set verify cb data */
cb_data.session = session;
cb_data.opts = opts;
/* prepare TLS context from which a session will be created */
tls_cfg = nc_tls_config_new_wrap(NC_SERVER);
if (!tls_cfg) {
goto fail;
}
/* opaque CA/CRL certificate store */
cert_store = nc_tls_cert_store_new_wrap();
if (!cert_store) {
goto fail;
}
/* load server's key and certificate */
if (nc_server_tls_load_server_cert_key(opts, &srv_cert, &srv_pkey)) {
ERR(session, "Loading server certificate and/or private key failed.");
goto fail;
}
/* load trusted CA certificates */
if (nc_server_tls_load_trusted_certs(&opts->ca_certs, cert_store)) {
ERR(session, "Loading server CA certs failed.");
goto fail;
}
/* load referenced endpoint's trusted CA certs if set */
if (opts->referenced_endpt_name) {
if (nc_server_get_referenced_endpt(opts->referenced_endpt_name, &referenced_endpt)) {
ERR(session, "Referenced endpoint \"%s\" not found.", opts->referenced_endpt_name);
goto fail;
}
if (nc_server_tls_load_trusted_certs(&referenced_endpt->opts.tls->ca_certs, cert_store)) {
ERR(session, "Loading server CA certs from referenced endpoint failed.");
goto fail;
}
}
if (opts->crl_path || opts->crl_url || opts->crl_cert_ext) {
/* opaque CRL store */
crl_store = nc_tls_crl_store_new_wrap();
if (!crl_store) {
goto fail;
}
/* load CRLs into one of the stores */
if (nc_server_tls_load_crl(opts, cert_store, crl_store)) {
ERR(session, "Loading server CRL failed.");
goto fail;
}
}
/* set supported TLS versions */
if (opts->tls_versions) {
if (nc_server_tls_set_tls_versions_wrap(tls_cfg, opts->tls_versions)) {
ERR(session, "Setting supported server TLS versions failed.");
goto fail;
}
}
/* set supported cipher suites */
if (opts->ciphers) {
nc_server_tls_set_cipher_suites_wrap(tls_cfg, opts->ciphers);
}
/* set verify flags, callback and its data */
nc_server_tls_set_verify_wrap(tls_cfg, &cb_data);
/* init TLS context and store data which may be needed later in it */
if (nc_tls_init_ctx_wrap(sock, srv_cert, srv_pkey, cert_store, crl_store, &session->ti.tls.ctx)) {
goto fail;
}
/* memory is managed by context now */
srv_cert = srv_pkey = cert_store = crl_store = NULL;
/* setup config from ctx */
if (nc_tls_setup_config_from_ctx_wrap(&session->ti.tls.ctx, NC_SERVER, tls_cfg)) {
goto fail;
}
session->ti.tls.config = tls_cfg;
tls_cfg = NULL;
/* fill session data and create TLS session from config */
session->ti_type = NC_TI_TLS;
if (!(session->ti.tls.session = nc_tls_session_new_wrap(session->ti.tls.config))) {
goto fail;
}
/* set session fd */
nc_server_tls_set_fd_wrap(session->ti.tls.session, sock, &session->ti.tls.ctx);
sock = -1;
/* do the handshake */
if (timeout > -1) {
nc_timeouttime_get(&ts_timeout, timeout);
}
while ((rc = nc_server_tls_handshake_step_wrap(session->ti.tls.session)) == 0) {
usleep(NC_TIMEOUT_STEP);
if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
ERR(session, "TLS accept timeout.");
timeouted = 1;
goto fail;
}
}
/* check if handshake was ok */
if (nc_server_tls_accept_check(rc, session->ti.tls.session) != 1) {
goto fail;
}
return 1;
fail:
if (sock > -1) {
close(sock);
}
nc_tls_config_destroy_wrap(tls_cfg);
nc_tls_cert_destroy_wrap(srv_cert);
nc_tls_privkey_destroy_wrap(srv_pkey);
nc_tls_cert_store_destroy_wrap(cert_store);
nc_tls_crl_store_destroy_wrap(crl_store);
if (timeouted) {
return 0;
} else {
return -1;
}
}