blob: d33778f07857e3e3cdc2d9b12576dab5905a75b4 [file] [log] [blame]
Radek Krejci9f03b482015-10-22 16:02:10 +02001/**
Michal Vasko95ea9ff2021-11-09 12:29:14 +01002 * @file session_client_tls.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
4 * @author Michal Vasko <mvasko@cesnet.cz>
5 * @brief libnetconf2 - TLS specific session client transport functions
Radek Krejci9f03b482015-10-22 16:02:10 +02006 *
Michal Vasko11d4cdb2015-10-29 11:42:52 +01007 * This source is compiled only with libssl.
Radek Krejci9f03b482015-10-22 16:02:10 +02008 *
Michal Vasko95ea9ff2021-11-09 12:29:14 +01009 * @copyright
Radek Krejci9f03b482015-10-22 16:02:10 +020010 * Copyright (c) 2015 CESNET, z.s.p.o.
11 *
Radek Krejci9b81f5b2016-02-24 13:14:49 +010012 * This source code is licensed under BSD 3-Clause License (the "License").
13 * You may not use this file except in compliance with the License.
14 * You may obtain a copy of the License at
Michal Vaskoafd416b2016-02-25 14:51:46 +010015 *
Radek Krejci9b81f5b2016-02-24 13:14:49 +010016 * https://opensource.org/licenses/BSD-3-Clause
Radek Krejci9f03b482015-10-22 16:02:10 +020017 */
18
Michal Vaskoba9f3582023-02-22 10:26:32 +010019#define _GNU_SOURCE /* pthread_rwlock_t, strdup */
20
Radek Krejci9f03b482015-10-22 16:02:10 +020021#include <assert.h>
22#include <errno.h>
Radek Krejci9f03b482015-10-22 16:02:10 +020023#include <string.h>
24#include <unistd.h>
25
26#include <libyang/libyang.h>
Michal Vasko086311b2016-01-08 09:53:11 +010027#include <openssl/err.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020028#include <openssl/ossl_typ.h>
Michal Vaskodae48322016-02-25 14:49:48 +010029#include <openssl/x509.h>
Radek Krejci9f03b482015-10-22 16:02:10 +020030
roman3f9b65c2023-06-05 14:26:58 +020031#include "config.h"
32#include "log_p.h"
Michal Vaskoe22c6732016-01-29 11:03:02 +010033#include "session_client.h"
34#include "session_client_ch.h"
roman3f9b65c2023-06-05 14:26:58 +020035#include "session_p.h"
Michal Vasko11d4cdb2015-10-29 11:42:52 +010036
Radek Krejci62aa0642017-05-25 16:33:49 +020037struct nc_client_context *nc_client_context_location(void);
38
39#define client_opts nc_client_context_location()->opts
40#define tls_opts nc_client_context_location()->tls_opts
41#define tls_ch_opts nc_client_context_location()->tls_ch_opts
Michal Vasko3031aae2016-01-27 16:07:18 +010042
43static int tlsauth_ch;
Radek Krejci9f03b482015-10-22 16:02:10 +020044
Michal Vasko18aeb5d2017-02-17 09:23:56 +010045static int
46tlsauth_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
47{
48 X509_STORE_CTX *store_ctx;
49 X509_OBJECT *obj;
50 X509_NAME *subject, *issuer;
51 X509 *cert;
52 X509_CRL *crl;
53 X509_REVOKED *revoked;
54 EVP_PKEY *pubkey;
55 int i, n, rc;
56 const ASN1_TIME *next_update = NULL;
57 struct nc_client_tls_opts *opts;
58
59 if (!preverify_ok) {
60 return 0;
61 }
62
63 opts = (tlsauth_ch ? &tls_ch_opts : &tls_opts);
64
65 if (!opts->crl_store) {
66 /* nothing to check */
67 return 1;
68 }
69
70 cert = X509_STORE_CTX_get_current_cert(x509_ctx);
71 subject = X509_get_subject_name(cert);
72 issuer = X509_get_issuer_name(cert);
73
74 /* try to retrieve a CRL corresponding to the _subject_ of
75 * the current certificate in order to verify it's integrity */
76 store_ctx = X509_STORE_CTX_new();
77 obj = X509_OBJECT_new();
78 X509_STORE_CTX_init(store_ctx, opts->crl_store, NULL, NULL);
Rosen Penev4f552d62019-06-26 16:10:43 -070079 rc = X509_STORE_CTX_get_by_subject(store_ctx, X509_LU_CRL, subject, obj);
Michal Vasko18aeb5d2017-02-17 09:23:56 +010080 X509_STORE_CTX_free(store_ctx);
81 crl = X509_OBJECT_get0_X509_CRL(obj);
Michal Vaskob83a3fa2021-05-26 09:53:42 +020082 if ((rc > 0) && crl) {
Michal Vasko18aeb5d2017-02-17 09:23:56 +010083 next_update = X509_CRL_get0_nextUpdate(crl);
84
85 /* verify the signature on this CRL */
86 pubkey = X509_get_pubkey(cert);
87 if (X509_CRL_verify(crl, pubkey) <= 0) {
88 X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE);
89 X509_OBJECT_free(obj);
90 if (pubkey) {
91 EVP_PKEY_free(pubkey);
92 }
93 return 0; /* fail */
94 }
95 if (pubkey) {
96 EVP_PKEY_free(pubkey);
97 }
98
99 /* check date of CRL to make sure it's not expired */
100 if (!next_update) {
101 X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD);
102 X509_OBJECT_free(obj);
103 return 0; /* fail */
104 }
105 if (X509_cmp_current_time(next_update) < 0) {
106 X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CRL_HAS_EXPIRED);
107 X509_OBJECT_free(obj);
108 return 0; /* fail */
109 }
110 X509_OBJECT_free(obj);
111 }
112
113 /* try to retrieve a CRL corresponding to the _issuer_ of
114 * the current certificate in order to check for revocation */
115 store_ctx = X509_STORE_CTX_new();
116 obj = X509_OBJECT_new();
117 X509_STORE_CTX_init(store_ctx, opts->crl_store, NULL, NULL);
Rosen Penev4f552d62019-06-26 16:10:43 -0700118 rc = X509_STORE_CTX_get_by_subject(store_ctx, X509_LU_CRL, issuer, obj);
Michal Vasko18aeb5d2017-02-17 09:23:56 +0100119 X509_STORE_CTX_free(store_ctx);
120 crl = X509_OBJECT_get0_X509_CRL(obj);
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200121 if ((rc > 0) && crl) {
Michal Vasko18aeb5d2017-02-17 09:23:56 +0100122 /* check if the current certificate is revoked by this CRL */
123 n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl));
124 for (i = 0; i < n; i++) {
125 revoked = sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i);
126 if (ASN1_INTEGER_cmp(X509_REVOKED_get0_serialNumber(revoked), X509_get_serialNumber(cert)) == 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200127 ERR(NULL, "Certificate revoked!");
Michal Vasko18aeb5d2017-02-17 09:23:56 +0100128 X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CERT_REVOKED);
129 X509_OBJECT_free(obj);
130 return 0; /* fail */
131 }
132 }
133 X509_OBJECT_free(obj);
134 }
135
136 return 1; /* success */
137}
138
Kevin Barrettd37d2fb2019-08-28 14:25:34 -0400139void
Michal Vaskoe22c6732016-01-29 11:03:02 +0100140_nc_client_tls_destroy_opts(struct nc_client_tls_opts *opts)
141{
142 free(opts->cert_path);
143 free(opts->key_path);
144 free(opts->ca_file);
145 free(opts->ca_dir);
146 SSL_CTX_free(opts->tls_ctx);
147
148 free(opts->crl_file);
149 free(opts->crl_dir);
150 X509_STORE_free(opts->crl_store);
Radek Krejci5cebc6b2017-05-26 13:24:38 +0200151
152 memset(opts, 0, sizeof *opts);
Michal Vaskoe22c6732016-01-29 11:03:02 +0100153}
154
Michal Vaskob7558c52016-02-26 15:04:19 +0100155void
Michal Vaskoe22c6732016-01-29 11:03:02 +0100156nc_client_tls_destroy_opts(void)
157{
158 _nc_client_tls_destroy_opts(&tls_opts);
159 _nc_client_tls_destroy_opts(&tls_ch_opts);
160}
161
Michal Vasko3031aae2016-01-27 16:07:18 +0100162static int
Michal Vaskoe22c6732016-01-29 11:03:02 +0100163_nc_client_tls_set_cert_key_paths(const char *client_cert, const char *client_key, struct nc_client_tls_opts *opts)
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100164{
roman40672412023-05-04 11:10:22 +0200165 NC_CHECK_ARG_RET(NULL, client_cert, -1);
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100166
Michal Vasko3031aae2016-01-27 16:07:18 +0100167 free(opts->cert_path);
168 free(opts->key_path);
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100169
Michal Vasko9db2a6f2016-02-01 13:26:03 +0100170 opts->cert_path = strdup(client_cert);
roman3a95bb22023-10-26 11:07:17 +0200171 NC_CHECK_ERRMEM_RET(!opts->cert_path, -1);
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100172
Michal Vasko3031aae2016-01-27 16:07:18 +0100173 if (client_key) {
174 opts->key_path = strdup(client_key);
roman3a95bb22023-10-26 11:07:17 +0200175 NC_CHECK_ERRMEM_RET(!opts->key_path, -1);
Michal Vasko3031aae2016-01-27 16:07:18 +0100176 } else {
177 opts->key_path = NULL;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100178 }
179
Michal Vasko3031aae2016-01-27 16:07:18 +0100180 opts->tls_ctx_change = 1;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100181
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100182 return 0;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100183}
184
Michal Vasko3031aae2016-01-27 16:07:18 +0100185API int
Michal Vaskoe22c6732016-01-29 11:03:02 +0100186nc_client_tls_set_cert_key_paths(const char *client_cert, const char *client_key)
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100187{
Michal Vaskoe22c6732016-01-29 11:03:02 +0100188 return _nc_client_tls_set_cert_key_paths(client_cert, client_key, &tls_opts);
Michal Vasko3031aae2016-01-27 16:07:18 +0100189}
190
191API int
Michal Vaskoe22c6732016-01-29 11:03:02 +0100192nc_client_tls_ch_set_cert_key_paths(const char *client_cert, const char *client_key)
Michal Vasko3031aae2016-01-27 16:07:18 +0100193{
Michal Vaskoe22c6732016-01-29 11:03:02 +0100194 return _nc_client_tls_set_cert_key_paths(client_cert, client_key, &tls_ch_opts);
195}
196
197static void
198_nc_client_tls_get_cert_key_paths(const char **client_cert, const char **client_key, struct nc_client_tls_opts *opts)
199{
200 if (!client_cert && !client_key) {
roman40672412023-05-04 11:10:22 +0200201 ERRARG(NULL, "client_cert and client_key");
Michal Vaskoe22c6732016-01-29 11:03:02 +0100202 return;
203 }
204
205 if (client_cert) {
206 *client_cert = opts->cert_path;
207 }
208 if (client_key) {
209 *client_key = opts->key_path;
210 }
211}
212
213API void
214nc_client_tls_get_cert_key_paths(const char **client_cert, const char **client_key)
215{
216 _nc_client_tls_get_cert_key_paths(client_cert, client_key, &tls_opts);
217}
218
219API void
220nc_client_tls_ch_get_cert_key_paths(const char **client_cert, const char **client_key)
221{
222 _nc_client_tls_get_cert_key_paths(client_cert, client_key, &tls_ch_opts);
Michal Vasko3031aae2016-01-27 16:07:18 +0100223}
224
225static int
Michal Vaskoe22c6732016-01-29 11:03:02 +0100226_nc_client_tls_set_trusted_ca_paths(const char *ca_file, const char *ca_dir, struct nc_client_tls_opts *opts)
Michal Vasko3031aae2016-01-27 16:07:18 +0100227{
Michal Vasko3031aae2016-01-27 16:07:18 +0100228 if (!ca_file && !ca_dir) {
roman40672412023-05-04 11:10:22 +0200229 ERRARG(NULL, "ca_file and ca_dir");
Michal Vasko3031aae2016-01-27 16:07:18 +0100230 return -1;
231 }
232
Michal Vasko3031aae2016-01-27 16:07:18 +0100233 free(opts->ca_file);
234 free(opts->ca_dir);
235
236 if (ca_file) {
237 opts->ca_file = strdup(ca_file);
roman3a95bb22023-10-26 11:07:17 +0200238 NC_CHECK_ERRMEM_RET(!opts->ca_file, -1);
Michal Vasko3031aae2016-01-27 16:07:18 +0100239 } else {
240 opts->ca_file = NULL;
241 }
242
243 if (ca_dir) {
244 opts->ca_dir = strdup(ca_dir);
roman3a95bb22023-10-26 11:07:17 +0200245 NC_CHECK_ERRMEM_RET(!opts->ca_dir, -1);
Michal Vasko3031aae2016-01-27 16:07:18 +0100246 } else {
247 opts->ca_dir = NULL;
248 }
249
250 opts->tls_ctx_change = 1;
251
252 return 0;
253}
254
255API int
Michal Vaskoe22c6732016-01-29 11:03:02 +0100256nc_client_tls_set_trusted_ca_paths(const char *ca_file, const char *ca_dir)
Michal Vasko3031aae2016-01-27 16:07:18 +0100257{
Michal Vaskoe22c6732016-01-29 11:03:02 +0100258 return _nc_client_tls_set_trusted_ca_paths(ca_file, ca_dir, &tls_opts);
Michal Vasko3031aae2016-01-27 16:07:18 +0100259}
260
261API int
Michal Vaskoe22c6732016-01-29 11:03:02 +0100262nc_client_tls_ch_set_trusted_ca_paths(const char *ca_file, const char *ca_dir)
Michal Vasko3031aae2016-01-27 16:07:18 +0100263{
Michal Vaskoe22c6732016-01-29 11:03:02 +0100264 return _nc_client_tls_set_trusted_ca_paths(ca_file, ca_dir, &tls_ch_opts);
265}
266
267static void
268_nc_client_tls_get_trusted_ca_paths(const char **ca_file, const char **ca_dir, struct nc_client_tls_opts *opts)
269{
270 if (!ca_file && !ca_dir) {
roman40672412023-05-04 11:10:22 +0200271 ERRARG(NULL, "ca_file and ca_dir");
Michal Vaskoe22c6732016-01-29 11:03:02 +0100272 return;
273 }
274
275 if (ca_file) {
276 *ca_file = opts->ca_file;
277 }
278 if (ca_dir) {
279 *ca_dir = opts->ca_dir;
280 }
281}
282
283API void
284nc_client_tls_get_trusted_ca_paths(const char **ca_file, const char **ca_dir)
285{
286 _nc_client_tls_get_trusted_ca_paths(ca_file, ca_dir, &tls_opts);
287}
288
289API void
290nc_client_tls_ch_get_trusted_ca_paths(const char **ca_file, const char **ca_dir)
291{
292 _nc_client_tls_get_trusted_ca_paths(ca_file, ca_dir, &tls_ch_opts);
Michal Vasko3031aae2016-01-27 16:07:18 +0100293}
294
295static int
Michal Vaskoe22c6732016-01-29 11:03:02 +0100296_nc_client_tls_set_crl_paths(const char *crl_file, const char *crl_dir, struct nc_client_tls_opts *opts)
Michal Vasko3031aae2016-01-27 16:07:18 +0100297{
Michal Vasko3031aae2016-01-27 16:07:18 +0100298 if (!crl_file && !crl_dir) {
roman40672412023-05-04 11:10:22 +0200299 ERRARG(NULL, "crl_file and crl_dir");
Michal Vasko3031aae2016-01-27 16:07:18 +0100300 return -1;
301 }
302
Michal Vasko3031aae2016-01-27 16:07:18 +0100303 free(opts->crl_file);
304 free(opts->crl_dir);
305
306 if (crl_file) {
307 opts->crl_file = strdup(crl_file);
roman3a95bb22023-10-26 11:07:17 +0200308 NC_CHECK_ERRMEM_RET(!opts->crl_file, -1);
Michal Vasko3031aae2016-01-27 16:07:18 +0100309 } else {
310 opts->crl_file = NULL;
311 }
312
313 if (crl_dir) {
314 opts->crl_dir = strdup(crl_dir);
roman3a95bb22023-10-26 11:07:17 +0200315 NC_CHECK_ERRMEM_RET(!opts->crl_dir, -1);
Michal Vasko3031aae2016-01-27 16:07:18 +0100316 } else {
317 opts->crl_dir = NULL;
318 }
319
320 opts->crl_store_change = 1;
321
322 return 0;
323}
324
325API int
Michal Vaskoe22c6732016-01-29 11:03:02 +0100326nc_client_tls_set_crl_paths(const char *crl_file, const char *crl_dir)
Michal Vasko3031aae2016-01-27 16:07:18 +0100327{
Michal Vaskoe22c6732016-01-29 11:03:02 +0100328 return _nc_client_tls_set_crl_paths(crl_file, crl_dir, &tls_opts);
Michal Vasko3031aae2016-01-27 16:07:18 +0100329}
330
331API int
Michal Vaskoe22c6732016-01-29 11:03:02 +0100332nc_client_tls_ch_set_crl_paths(const char *crl_file, const char *crl_dir)
Michal Vasko3031aae2016-01-27 16:07:18 +0100333{
Michal Vaskoe22c6732016-01-29 11:03:02 +0100334 return _nc_client_tls_set_crl_paths(crl_file, crl_dir, &tls_ch_opts);
335}
336
337static void
338_nc_client_tls_get_crl_paths(const char **crl_file, const char **crl_dir, struct nc_client_tls_opts *opts)
339{
340 if (!crl_file && !crl_dir) {
roman40672412023-05-04 11:10:22 +0200341 ERRARG(NULL, "crl_file and crl_dir");
Michal Vaskoe22c6732016-01-29 11:03:02 +0100342 return;
343 }
344
345 if (crl_file) {
346 *crl_file = opts->crl_file;
347 }
348 if (crl_dir) {
349 *crl_dir = opts->crl_dir;
350 }
351}
352
353API void
354nc_client_tls_get_crl_paths(const char **crl_file, const char **crl_dir)
355{
356 _nc_client_tls_get_crl_paths(crl_file, crl_dir, &tls_opts);
357}
358
359API void
360nc_client_tls_ch_get_crl_paths(const char **crl_file, const char **crl_dir)
361{
362 _nc_client_tls_get_crl_paths(crl_file, crl_dir, &tls_ch_opts);
Michal Vasko3031aae2016-01-27 16:07:18 +0100363}
364
365API int
366nc_client_tls_ch_add_bind_listen(const char *address, uint16_t port)
367{
Michal Vasko9d4cca52022-09-07 11:19:57 +0200368 return nc_client_ch_add_bind_listen(address, port, NULL, NC_TI_OPENSSL);
369}
370
371API int
372nc_client_tls_ch_add_bind_hostname_listen(const char *address, uint16_t port, const char *hostname)
373{
374 return nc_client_ch_add_bind_listen(address, port, hostname, NC_TI_OPENSSL);
Michal Vasko3031aae2016-01-27 16:07:18 +0100375}
376
377API int
378nc_client_tls_ch_del_bind(const char *address, uint16_t port)
379{
380 return nc_client_ch_del_bind(address, port, NC_TI_OPENSSL);
381}
382
Michal Vasko3031aae2016-01-27 16:07:18 +0100383static int
Michal Vasko55ce24a2022-09-07 09:24:43 +0200384nc_client_tls_update_opts(struct nc_client_tls_opts *opts, const char *peername)
Michal Vasko3031aae2016-01-27 16:07:18 +0100385{
Michal Vasko9af52322022-07-25 14:39:30 +0200386 int rc = 0;
Michal Vasko3031aae2016-01-27 16:07:18 +0100387 char *key;
388 X509_LOOKUP *lookup;
Michal Vasko9af52322022-07-25 14:39:30 +0200389 X509_VERIFY_PARAM *vpm = NULL;
Michal Vasko3031aae2016-01-27 16:07:18 +0100390
391 if (!opts->tls_ctx || opts->tls_ctx_change) {
Michal Vaskoe22c6732016-01-29 11:03:02 +0100392 SSL_CTX_free(opts->tls_ctx);
Michal Vasko18aeb5d2017-02-17 09:23:56 +0100393 /* prepare global SSL context, highest available method is negotiated autmatically */
roman7fdc84d2023-06-06 13:14:53 +0200394 if (!(opts->tls_ctx = SSL_CTX_new(TLS_client_method()))) {
Michal Vasko05532772021-06-03 12:12:38 +0200395 ERR(NULL, "Unable to create OpenSSL context (%s).", ERR_reason_error_string(ERR_get_error()));
Michal Vasko9af52322022-07-25 14:39:30 +0200396 rc = -1;
397 goto cleanup;
Michal Vasko3031aae2016-01-27 16:07:18 +0100398 }
399 SSL_CTX_set_verify(opts->tls_ctx, SSL_VERIFY_PEER, tlsauth_verify_callback);
400
401 /* get peer certificate */
402 if (SSL_CTX_use_certificate_file(opts->tls_ctx, opts->cert_path, SSL_FILETYPE_PEM) != 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200403 ERR(NULL, "Loading the client certificate from \'%s\' failed (%s).", opts->cert_path,
404 ERR_reason_error_string(ERR_get_error()));
Michal Vasko9af52322022-07-25 14:39:30 +0200405 rc = -1;
406 goto cleanup;
Michal Vasko3031aae2016-01-27 16:07:18 +0100407 }
408
409 /* if the file with private key not specified, expect that the private key is stored with the certificate */
410 if (!opts->key_path) {
411 key = opts->cert_path;
412 } else {
413 key = opts->key_path;
414 }
415 if (SSL_CTX_use_PrivateKey_file(opts->tls_ctx, key, SSL_FILETYPE_PEM) != 1) {
Michal Vasko9af52322022-07-25 14:39:30 +0200416 ERR(NULL, "Loading the client private key from \'%s\' failed (%s).", key,
Michal Vasko05532772021-06-03 12:12:38 +0200417 ERR_reason_error_string(ERR_get_error()));
Michal Vasko9af52322022-07-25 14:39:30 +0200418 rc = -1;
419 goto cleanup;
Michal Vasko3031aae2016-01-27 16:07:18 +0100420 }
421
422 if (!SSL_CTX_load_verify_locations(opts->tls_ctx, opts->ca_file, opts->ca_dir)) {
Michal Vasko05532772021-06-03 12:12:38 +0200423 ERR(NULL, "Failed to load the locations of trusted CA certificates (%s).",
424 ERR_reason_error_string(ERR_get_error()));
Michal Vasko9af52322022-07-25 14:39:30 +0200425 rc = -1;
426 goto cleanup;
Michal Vasko3031aae2016-01-27 16:07:18 +0100427 }
Michal Vasko9af52322022-07-25 14:39:30 +0200428
Michal Vasko55ce24a2022-09-07 09:24:43 +0200429 if (peername) {
430 /* server identity (hostname) verification */
431 vpm = X509_VERIFY_PARAM_new();
432 if (!X509_VERIFY_PARAM_set1_host(vpm, peername, 0)) {
433 ERR(NULL, "Failed to set expected server hostname (%s).", ERR_reason_error_string(ERR_get_error()));
434 rc = -1;
435 goto cleanup;
436 }
437 if (!SSL_CTX_set1_param(opts->tls_ctx, vpm)) {
438 ERR(NULL, "Failed to set verify params (%s).", ERR_reason_error_string(ERR_get_error()));
439 rc = -1;
440 goto cleanup;
441 }
Michal Vasko9af52322022-07-25 14:39:30 +0200442 }
Michal Vasko3031aae2016-01-27 16:07:18 +0100443 }
444
445 if (opts->crl_store_change || (!opts->crl_store && (opts->crl_file || opts->crl_dir))) {
446 /* set the revocation store with the correct paths for the callback */
447 X509_STORE_free(opts->crl_store);
448
449 opts->crl_store = X509_STORE_new();
450 if (!opts->crl_store) {
Michal Vasko05532772021-06-03 12:12:38 +0200451 ERR(NULL, "Unable to create a certificate store (%s).", ERR_reason_error_string(ERR_get_error()));
Michal Vasko9af52322022-07-25 14:39:30 +0200452 rc = -1;
453 goto cleanup;
Michal Vasko3031aae2016-01-27 16:07:18 +0100454 }
Michal Vasko18aeb5d2017-02-17 09:23:56 +0100455
Michal Vasko3031aae2016-01-27 16:07:18 +0100456 if (opts->crl_file) {
457 if (!(lookup = X509_STORE_add_lookup(opts->crl_store, X509_LOOKUP_file()))) {
Michal Vasko05532772021-06-03 12:12:38 +0200458 ERR(NULL, "Failed to add lookup method to CRL checking.");
Michal Vasko9af52322022-07-25 14:39:30 +0200459 rc = -1;
460 goto cleanup;
Michal Vasko3031aae2016-01-27 16:07:18 +0100461 }
462 if (X509_LOOKUP_add_dir(lookup, opts->crl_file, X509_FILETYPE_PEM) != 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200463 ERR(NULL, "Failed to add the revocation lookup file \"%s\".", opts->crl_file);
Michal Vasko9af52322022-07-25 14:39:30 +0200464 rc = -1;
465 goto cleanup;
Michal Vasko3031aae2016-01-27 16:07:18 +0100466 }
467 }
468
469 if (opts->crl_dir) {
470 if (!(lookup = X509_STORE_add_lookup(opts->crl_store, X509_LOOKUP_hash_dir()))) {
Michal Vasko05532772021-06-03 12:12:38 +0200471 ERR(NULL, "Failed to add lookup method to CRL checking.");
Michal Vasko9af52322022-07-25 14:39:30 +0200472 rc = -1;
473 goto cleanup;
Michal Vasko3031aae2016-01-27 16:07:18 +0100474 }
475 if (X509_LOOKUP_add_dir(lookup, opts->crl_dir, X509_FILETYPE_PEM) != 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200476 ERR(NULL, "Failed to add the revocation lookup directory \"%s\".", opts->crl_dir);
Michal Vasko9af52322022-07-25 14:39:30 +0200477 rc = -1;
478 goto cleanup;
Michal Vasko3031aae2016-01-27 16:07:18 +0100479 }
480 }
481 }
482
Michal Vasko9af52322022-07-25 14:39:30 +0200483cleanup:
484 X509_VERIFY_PARAM_free(vpm);
485 return rc;
486}
487
488static int
Michal Vaskoa82745b2022-09-07 11:48:12 +0200489nc_client_tls_connect_check(int connect_ret, SSL *tls, const char *peername)
Michal Vasko9af52322022-07-25 14:39:30 +0200490{
491 int verify;
492
493 /* check certificate verification result */
494 verify = SSL_get_verify_result(tls);
495 switch (verify) {
496 case X509_V_OK:
497 if (connect_ret == 1) {
Michal Vasko7ffcd142022-09-07 09:24:22 +0200498 VRB(NULL, "Server certificate verified (domain \"%s\").", peername);
Michal Vasko9af52322022-07-25 14:39:30 +0200499 }
500 break;
501 default:
502 ERR(NULL, "Server certificate error (%s).", X509_verify_cert_error_string(verify));
503 }
504
505 /* check TLS connection result */
506 if (connect_ret != 1) {
507 switch (SSL_get_error(tls, connect_ret)) {
508 case SSL_ERROR_SYSCALL:
Michal Vasko7ffcd142022-09-07 09:24:22 +0200509 ERR(NULL, "SSL connect to \"%s\" failed (%s).", peername, errno ? strerror(errno) : "unexpected EOF");
Michal Vasko9af52322022-07-25 14:39:30 +0200510 break;
511 case SSL_ERROR_SSL:
Michal Vasko7ffcd142022-09-07 09:24:22 +0200512 ERR(NULL, "SSL connect to \"%s\" failed (%s).", peername, ERR_reason_error_string(ERR_get_error()));
Michal Vasko9af52322022-07-25 14:39:30 +0200513 break;
514 default:
Michal Vasko7ffcd142022-09-07 09:24:22 +0200515 ERR(NULL, "SSL connect to \"%s\" failed.", peername);
Michal Vasko9af52322022-07-25 14:39:30 +0200516 break;
517 }
518 }
519
520 return connect_ret;
Radek Krejci9f03b482015-10-22 16:02:10 +0200521}
522
523API struct nc_session *
Michal Vasko9bee18d2015-12-08 14:41:42 +0100524nc_connect_tls(const char *host, unsigned short port, struct ly_ctx *ctx)
Radek Krejci9f03b482015-10-22 16:02:10 +0200525{
Radek Krejci9f03b482015-10-22 16:02:10 +0200526 struct nc_session *session = NULL;
Michal Vasko9af52322022-07-25 14:39:30 +0200527 int sock, ret;
roman6ece9c52022-06-22 09:29:17 +0200528 struct timespec ts_timeout;
Michal Vasko66032bc2019-01-22 15:03:12 +0100529 char *ip_host = NULL;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100530
romand82caf12024-03-05 14:21:39 +0100531 if (!tls_opts.cert_path) {
532 ERR(NULL, "Client certificate not set.");
533 return NULL;
534 } else if (!tls_opts.ca_file && !tls_opts.ca_dir) {
535 ERR(NULL, "Certificate authority certificates not set.");
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100536 return NULL;
537 }
Radek Krejci9f03b482015-10-22 16:02:10 +0200538
539 /* process parameters */
roman3f9b65c2023-06-05 14:26:58 +0200540 if (!host || (host[0] == '\0')) {
Radek Krejci9f03b482015-10-22 16:02:10 +0200541 host = "localhost";
542 }
543
544 if (!port) {
545 port = NC_PORT_TLS;
546 }
547
Michal Vasko3031aae2016-01-27 16:07:18 +0100548 /* create/update TLS structures */
Michal Vasko9af52322022-07-25 14:39:30 +0200549 if (nc_client_tls_update_opts(&tls_opts, host)) {
Michal Vasko3031aae2016-01-27 16:07:18 +0100550 return NULL;
551 }
552
Radek Krejci9f03b482015-10-22 16:02:10 +0200553 /* prepare session structure */
Michal Vasko131120a2018-05-29 15:44:02 +0200554 session = nc_new_session(NC_CLIENT, 0);
roman3a95bb22023-10-26 11:07:17 +0200555 NC_CHECK_ERRMEM_RET(!session, NULL);
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100556 session->status = NC_STATUS_STARTING;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100557
558 /* fill the session */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100559 session->ti_type = NC_TI_OPENSSL;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100560 if (!(session->ti.tls = SSL_new(tls_opts.tls_ctx))) {
Michal Vasko05532772021-06-03 12:12:38 +0200561 ERR(NULL, "Failed to create a new TLS session structure (%s).", ERR_reason_error_string(ERR_get_error()));
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100562 goto fail;
563 }
Radek Krejci9f03b482015-10-22 16:02:10 +0200564
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100565 /* create and assign socket */
Michal Vaskoe49a15f2019-05-27 14:18:36 +0200566 sock = nc_sock_connect(host, port, -1, &client_opts.ka, NULL, &ip_host);
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100567 if (sock == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200568 ERR(NULL, "Unable to connect to %s:%u (%s).", host, port, strerror(errno));
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100569 goto fail;
570 }
571 SSL_set_fd(session->ti.tls, sock);
572
573 /* set the SSL_MODE_AUTO_RETRY flag to allow OpenSSL perform re-handshake automatically */
574 SSL_set_mode(session->ti.tls, SSL_MODE_AUTO_RETRY);
575
576 /* connect and perform the handshake */
Michal Vaskod8a74192023-02-06 15:51:50 +0100577 nc_timeouttime_get(&ts_timeout, NC_TRANSPORT_TIMEOUT);
Michal Vasko3031aae2016-01-27 16:07:18 +0100578 tlsauth_ch = 0;
Michal Vaskod51a2642021-09-03 13:03:24 +0200579 while (((ret = SSL_connect(session->ti.tls)) != 1) && (SSL_get_error(session->ti.tls, ret) == SSL_ERROR_WANT_READ)) {
Michal Vasko0190bc32016-03-02 15:47:49 +0100580 usleep(NC_TIMEOUT_STEP);
Michal Vaskod8a74192023-02-06 15:51:50 +0100581 if (nc_timeouttime_cur_diff(&ts_timeout) < 1) {
Michal Vasko7ffcd142022-09-07 09:24:22 +0200582 ERR(NULL, "SSL connect timeout.");
Michal Vasko0190bc32016-03-02 15:47:49 +0100583 goto fail;
584 }
585 }
Michal Vasko3cd55a72022-09-06 15:52:39 +0200586
587 /* check for errors */
Michal Vaskoa82745b2022-09-07 11:48:12 +0200588 if (nc_client_tls_connect_check(ret, session->ti.tls, host) != 1) {
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100589 goto fail;
590 }
591
Michal Vasko78939072022-12-12 07:43:18 +0100592 if (nc_client_session_new_ctx(session, ctx) != EXIT_SUCCESS) {
Radek Krejcifd5b6682017-06-13 15:52:53 +0200593 goto fail;
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100594 }
Radek Krejcifd5b6682017-06-13 15:52:53 +0200595 ctx = session->ctx;
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100596
Radek Krejci9f03b482015-10-22 16:02:10 +0200597 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +0200598 if (nc_handshake_io(session) != NC_MSG_HELLO) {
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100599 goto fail;
Radek Krejci9f03b482015-10-22 16:02:10 +0200600 }
Michal Vaskoad611702015-12-03 13:41:51 +0100601 session->status = NC_STATUS_RUNNING;
Radek Krejci9f03b482015-10-22 16:02:10 +0200602
Michal Vaskoef578332016-01-25 13:20:09 +0100603 if (nc_ctx_check_and_fill(session) == -1) {
Michal Vasko57eb9402015-12-08 14:38:12 +0100604 goto fail;
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100605 }
606
607 /* store information into session and the dictionary */
Michal Vasko93224072021-11-09 12:14:28 +0100608 session->host = ip_host;
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100609 session->port = port;
Michal Vasko93224072021-11-09 12:14:28 +0100610 session->username = strdup("certificate-based");
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100611
Radek Krejci9f03b482015-10-22 16:02:10 +0200612 return session;
613
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100614fail:
Michal Vasko66032bc2019-01-22 15:03:12 +0100615 free(ip_host);
Michal Vaskoe1a64ec2016-03-01 12:21:58 +0100616 nc_session_free(session, NULL);
Radek Krejci9f03b482015-10-22 16:02:10 +0200617 return NULL;
618}
619
620API struct nc_session *
621nc_connect_libssl(SSL *tls, struct ly_ctx *ctx)
622{
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100623 struct nc_session *session;
Radek Krejci9f03b482015-10-22 16:02:10 +0200624
roman40672412023-05-04 11:10:22 +0200625 NC_CHECK_ARG_RET(NULL, tls, NULL);
626
627 if (!SSL_is_init_finished(tls)) {
Michal Vasko05532772021-06-03 12:12:38 +0200628 ERR(NULL, "Supplied TLS session is not fully connected!");
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100629 return NULL;
630 }
631
632 /* prepare session structure */
Michal Vasko131120a2018-05-29 15:44:02 +0200633 session = nc_new_session(NC_CLIENT, 0);
roman3a95bb22023-10-26 11:07:17 +0200634 NC_CHECK_ERRMEM_RET(!session, NULL);
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100635 session->status = NC_STATUS_STARTING;
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100636 session->ti_type = NC_TI_OPENSSL;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100637 session->ti.tls = tls;
638
Michal Vasko78939072022-12-12 07:43:18 +0100639 if (nc_client_session_new_ctx(session, ctx) != EXIT_SUCCESS) {
Radek Krejcifd5b6682017-06-13 15:52:53 +0200640 goto fail;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100641 }
Radek Krejcifd5b6682017-06-13 15:52:53 +0200642 ctx = session->ctx;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100643
644 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +0200645 if (nc_handshake_io(session) != NC_MSG_HELLO) {
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100646 goto fail;
647 }
Michal Vaskoad611702015-12-03 13:41:51 +0100648 session->status = NC_STATUS_RUNNING;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100649
Michal Vaskoef578332016-01-25 13:20:09 +0100650 if (nc_ctx_check_and_fill(session) == -1) {
Michal Vasko57eb9402015-12-08 14:38:12 +0100651 goto fail;
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100652 }
653
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100654 return session;
655
656fail:
Michal Vaskob604ed52022-01-24 09:56:14 +0100657 session->ti_type = NC_TI_NONE;
Michal Vaskoa42a7f02021-07-02 08:43:12 +0200658 session->ti.tls = NULL;
Michal Vaskoe1a64ec2016-03-01 12:21:58 +0100659 nc_session_free(session, NULL);
Radek Krejci9f03b482015-10-22 16:02:10 +0200660 return NULL;
661}
662
Michal Vasko3031aae2016-01-27 16:07:18 +0100663struct nc_session *
Michal Vasko9d4cca52022-09-07 11:19:57 +0200664nc_accept_callhome_tls_sock(int sock, const char *host, uint16_t port, struct ly_ctx *ctx, int timeout, const char *peername)
Michal Vasko80cad7f2015-12-08 14:42:27 +0100665{
Michal Vasko3cd55a72022-09-06 15:52:39 +0200666 int ret;
Michal Vaskoc64c38c2021-07-02 08:43:53 +0200667 SSL *tls = NULL;
668 struct nc_session *session = NULL;
roman6ece9c52022-06-22 09:29:17 +0200669 struct timespec ts_timeout;
Michal Vasko80cad7f2015-12-08 14:42:27 +0100670
Michal Vasko9d4cca52022-09-07 11:19:57 +0200671 /* create/update TLS structures with explicit expected peername, if any set, the host is just the IP */
672 if (nc_client_tls_update_opts(&tls_ch_opts, peername)) {
Michal Vaskoc64c38c2021-07-02 08:43:53 +0200673 goto cleanup;
Michal Vaskoc61c4492016-01-25 11:13:34 +0100674 }
675
Michal Vasko3031aae2016-01-27 16:07:18 +0100676 if (!(tls = SSL_new(tls_ch_opts.tls_ctx))) {
Michal Vasko05532772021-06-03 12:12:38 +0200677 ERR(NULL, "Failed to create new TLS session structure (%s).", ERR_reason_error_string(ERR_get_error()));
Michal Vaskoc64c38c2021-07-02 08:43:53 +0200678 goto cleanup;
Michal Vasko80cad7f2015-12-08 14:42:27 +0100679 }
680
681 SSL_set_fd(tls, sock);
682
683 /* set the SSL_MODE_AUTO_RETRY flag to allow OpenSSL perform re-handshake automatically */
684 SSL_set_mode(tls, SSL_MODE_AUTO_RETRY);
685
686 /* connect and perform the handshake */
Michal Vasko36c7be82017-02-22 13:37:59 +0100687 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +0100688 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +0100689 }
Michal Vasko3031aae2016-01-27 16:07:18 +0100690 tlsauth_ch = 1;
Michal Vasko0190bc32016-03-02 15:47:49 +0100691 while (((ret = SSL_connect(tls)) == -1) && (SSL_get_error(tls, ret) == SSL_ERROR_WANT_READ)) {
692 usleep(NC_TIMEOUT_STEP);
Michal Vaskod8a74192023-02-06 15:51:50 +0100693 if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
Michal Vasko7ffcd142022-09-07 09:24:22 +0200694 ERR(NULL, "SSL connect timeout.");
roman6ece9c52022-06-22 09:29:17 +0200695 goto cleanup;
Michal Vasko0190bc32016-03-02 15:47:49 +0100696 }
697 }
Michal Vasko80cad7f2015-12-08 14:42:27 +0100698
Michal Vasko3cd55a72022-09-06 15:52:39 +0200699 /* check for errors */
Michal Vaskoa82745b2022-09-07 11:48:12 +0200700 if (nc_client_tls_connect_check(ret, tls, peername ? peername : host) != 1) {
Michal Vasko3cd55a72022-09-06 15:52:39 +0200701 goto cleanup;
Michal Vasko80cad7f2015-12-08 14:42:27 +0100702 }
703
Michal Vaskoc64c38c2021-07-02 08:43:53 +0200704 /* connect */
Michal Vasko80cad7f2015-12-08 14:42:27 +0100705 session = nc_connect_libssl(tls, ctx);
Michal Vaskoc64c38c2021-07-02 08:43:53 +0200706 if (!session) {
707 goto cleanup;
Michal Vasko80cad7f2015-12-08 14:42:27 +0100708 }
709
Michal Vaskoc64c38c2021-07-02 08:43:53 +0200710 session->flags |= NC_SESSION_CALLHOME;
711
712 /* store information into session and the dictionary */
Michal Vasko93224072021-11-09 12:14:28 +0100713 session->host = strdup(host);
Michal Vaskoc64c38c2021-07-02 08:43:53 +0200714 session->port = port;
Michal Vasko93224072021-11-09 12:14:28 +0100715 session->username = strdup("certificate-based");
Michal Vaskoc64c38c2021-07-02 08:43:53 +0200716
717cleanup:
718 if (!session) {
719 SSL_free(tls);
720 close(sock);
721 }
Michal Vasko80cad7f2015-12-08 14:42:27 +0100722 return session;
723}