pacemaker 3.0.1-16e74fc4da
Scalable High-Availability cluster resource manager
Loading...
Searching...
No Matches
tls.c
Go to the documentation of this file.
1/*
2 * Copyright 2024-2025 the Pacemaker project contributors
3 *
4 * The version control history for this file may have further details.
5 *
6 * This source code is licensed under the GNU Lesser General Public License
7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 */
9
10#include <crm_internal.h>
11
12#include <errno.h>
13#include <gnutls/gnutls.h>
14#include <gnutls/x509.h>
15#include <stdlib.h>
16
17#include <glib.h> // gpointer, GPOINTER_TO_INT(), GINT_TO_POINTER()
18
20
21static char *
22get_gnutls_priorities(gnutls_credentials_type_t cred_type)
23{
24 const char *prio_base = pcmk__env_option(PCMK__ENV_TLS_PRIORITIES);
25
26 if (prio_base == NULL) {
27 prio_base = PCMK__GNUTLS_PRIORITIES;
28 }
29
30 if (cred_type == GNUTLS_CRD_ANON) {
31 return crm_strdup_printf("%s:+ANON-DH", prio_base);
32 } else if (cred_type == GNUTLS_CRD_PSK) {
33 return crm_strdup_printf("%s:+DHE-PSK:+PSK", prio_base);
34 } else {
35 return strdup(prio_base);
36 }
37}
38
39static const char *
40tls_cred_str(gnutls_credentials_type_t cred_type)
41{
42 if (cred_type == GNUTLS_CRD_ANON) {
43 return "unauthenticated";
44 } else if (cred_type == GNUTLS_CRD_PSK) {
45 return "shared-key-authenticated";
46 } else if (cred_type == GNUTLS_CRD_CERTIFICATE) {
47 return "certificate-authenticated";
48 } else {
49 return "unknown";
50 }
51}
52
53static int
54tls_load_x509_data(pcmk__tls_t *tls)
55{
56 int rc;
57
58 CRM_CHECK(tls->cred_type == GNUTLS_CRD_CERTIFICATE, return EINVAL);
59
60 /* Load a trusted CA to be used to verify client certificates. Use
61 * of this function instead of gnutls_certificate_set_x509_system_trust
62 * means we do not look at the system-wide authorities installed in
63 * /etc/pki somewhere. This requires the cluster admin to set up their
64 * own CA.
65 */
66 rc = gnutls_certificate_set_x509_trust_file(tls->credentials.cert,
67 tls->ca_file,
68 GNUTLS_X509_FMT_PEM);
69 if (rc <= 0) {
70 crm_err("Failed to set X509 CA file: %s", gnutls_strerror(rc));
71 return ENODATA;
72 }
73
74 /* If a Certificate Revocation List (CRL) file was given in the environment,
75 * load that now so we know which clients have been banned.
76 */
77 if (tls->crl_file != NULL) {
78 rc = gnutls_certificate_set_x509_crl_file(tls->credentials.cert,
79 tls->crl_file,
80 GNUTLS_X509_FMT_PEM);
81 if (rc < 0) {
82 crm_err("Failed to set X509 CRL file: %s",
83 gnutls_strerror(rc));
84 return ENODATA;
85 }
86 }
87
88 /* NULL = no password for the key, GNUTLS_PKCS_PLAIN = unencrypted key
89 * file
90 */
91 rc = gnutls_certificate_set_x509_key_file2(tls->credentials.cert,
92 tls->cert_file, tls->key_file,
93 GNUTLS_X509_FMT_PEM, NULL,
94 GNUTLS_PKCS_PLAIN);
95 if (rc < 0) {
96 crm_err("Failed to set X509 cert/key pair: %s",
97 gnutls_strerror(rc));
98 return ENODATA;
99 }
100
101 return pcmk_rc_ok;
102}
103
111static int
112verify_peer_cert(gnutls_session_t session)
113{
114 int rc;
115 int type;
116 unsigned int status;
117 gnutls_datum_t out;
118
119 /* NULL = no hostname comparison will be performed */
120 rc = gnutls_certificate_verify_peers3(session, NULL, &status);
121
122 /* Success means it was able to perform the verification. We still have
123 * to check status to see whether the cert is valid or not.
124 */
125 if (rc != GNUTLS_E_SUCCESS) {
126 crm_err("Failed to verify peer certificate: %s", gnutls_strerror(rc));
127 return -1;
128 }
129
130 if (status == 0) {
131 /* The certificate is trusted. */
132 return 0;
133 }
134
135 type = gnutls_certificate_type_get(session);
136 gnutls_certificate_verification_status_print(status, type, &out, 0);
137 crm_err("Peer certificate invalid: %s", out.data);
138 gnutls_free(out.data);
139 return GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR;
140}
141
142static void
143_gnutls_log_func(int level, const char *msg)
144{
145 crm_trace("%s", msg);
146}
147
148void
150{
151 if (tls == NULL) {
152 return;
153 }
154
155 /* This is only set on the server side. */
156 if (tls->server) {
157 gnutls_dh_params_deinit(tls->dh_params);
158 }
159
160 if (tls->cred_type == GNUTLS_CRD_ANON) {
161 if (tls->server) {
162 gnutls_anon_free_server_credentials(tls->credentials.anon_s);
163 } else {
164 gnutls_anon_free_client_credentials(tls->credentials.anon_c);
165 }
166 } else if (tls->cred_type == GNUTLS_CRD_CERTIFICATE) {
167 gnutls_certificate_free_credentials(tls->credentials.cert);
168 } else if (tls->cred_type == GNUTLS_CRD_PSK) {
169 if (tls->server) {
170 gnutls_psk_free_server_credentials(tls->credentials.psk_s);
171 } else {
172 gnutls_psk_free_client_credentials(tls->credentials.psk_c);
173 }
174 }
175
176 free(tls);
177 tls = NULL;
178
179 gnutls_global_deinit();
180}
181
182int
183pcmk__init_tls(pcmk__tls_t **tls, bool server, gnutls_credentials_type_t cred_type)
184{
185 int rc = pcmk_rc_ok;
186
187 if (*tls != NULL) {
188 return rc;
189 }
190
191 *tls = pcmk__assert_alloc(1, sizeof(pcmk__tls_t));
192
193 signal(SIGPIPE, SIG_IGN);
194
195 /* gnutls_global_init is safe to call multiple times, but we have to call
196 * gnutls_global_deinit the same number of times for that function to do
197 * anything.
198 *
199 * FIXME: When we can use gnutls >= 3.3.0, we don't have to call
200 * gnutls_global_init anymore.
201 */
202 gnutls_global_init();
203 gnutls_global_set_log_level(8);
204 gnutls_global_set_log_function(_gnutls_log_func);
205
206 if (server) {
207 rc = pcmk__init_tls_dh(&(*tls)->dh_params);
208 if (rc != pcmk_rc_ok) {
209 pcmk__free_tls(*tls);
210 *tls = NULL;
211 return rc;
212 }
213 }
214
215 (*tls)->cred_type = cred_type;
216 (*tls)->server = server;
217
218 if (cred_type == GNUTLS_CRD_ANON) {
219 if (server) {
220 gnutls_anon_allocate_server_credentials(&(*tls)->credentials.anon_s);
221 gnutls_anon_set_server_dh_params((*tls)->credentials.anon_s,
222 (*tls)->dh_params);
223 } else {
224 gnutls_anon_allocate_client_credentials(&(*tls)->credentials.anon_c);
225 }
226 } else if (cred_type == GNUTLS_CRD_CERTIFICATE) {
227 /* Try the PCMK_ version of each environment variable first, and if
228 * it's not set then try the CIB_ version.
229 */
230 (*tls)->ca_file = pcmk__env_option(PCMK__ENV_CA_FILE);
231 if (pcmk__str_empty((*tls)->ca_file)) {
232 (*tls)->ca_file = getenv("CIB_ca_file");
233 }
234
235 (*tls)->cert_file = pcmk__env_option(PCMK__ENV_CERT_FILE);
236 if (pcmk__str_empty((*tls)->cert_file)) {
237 (*tls)->cert_file = getenv("CIB_cert_file");
238 }
239
240 (*tls)->crl_file = pcmk__env_option(PCMK__ENV_CRL_FILE);
241 if (pcmk__str_empty((*tls)->crl_file)) {
242 (*tls)->crl_file = getenv("CIB_crl_file");
243 }
244
245 (*tls)->key_file = pcmk__env_option(PCMK__ENV_KEY_FILE);
246 if (pcmk__str_empty((*tls)->key_file)) {
247 (*tls)->key_file = getenv("CIB_key_file");
248 }
249
250 gnutls_certificate_allocate_credentials(&(*tls)->credentials.cert);
251
252 if (server) {
253 gnutls_certificate_set_dh_params((*tls)->credentials.cert,
254 (*tls)->dh_params);
255
256 }
257
258 rc = tls_load_x509_data(*tls);
259 if (rc != pcmk_rc_ok) {
260 pcmk__free_tls(*tls);
261 *tls = NULL;
262 return rc;
263 }
264 } else if (cred_type == GNUTLS_CRD_PSK) {
265 if (server) {
266 gnutls_psk_allocate_server_credentials(&(*tls)->credentials.psk_s);
267 gnutls_psk_set_server_dh_params((*tls)->credentials.psk_s,
268 (*tls)->dh_params);
269 } else {
270 gnutls_psk_allocate_client_credentials(&(*tls)->credentials.psk_c);
271 }
272 }
273
274 return rc;
275}
276
277int
278pcmk__init_tls_dh(gnutls_dh_params_t *dh_params)
279{
280 int rc = GNUTLS_E_SUCCESS;
281 unsigned int dh_bits = 0;
282 int dh_max_bits = 0;
283
284 rc = gnutls_dh_params_init(dh_params);
285 if (rc != GNUTLS_E_SUCCESS) {
286 goto error;
287 }
288
289 dh_bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH,
290 GNUTLS_SEC_PARAM_NORMAL);
291 if (dh_bits == 0) {
292 rc = GNUTLS_E_DH_PRIME_UNACCEPTABLE;
293 goto error;
294 }
295
297 if ((dh_max_bits > 0) && (dh_bits > dh_max_bits)) {
298 dh_bits = dh_max_bits;
299 }
300
301 crm_info("Generating Diffie-Hellman parameters with %u-bit prime for TLS",
302 dh_bits);
303 rc = gnutls_dh_params_generate2(*dh_params, dh_bits);
304 if (rc != GNUTLS_E_SUCCESS) {
305 goto error;
306 }
307
308 return pcmk_rc_ok;
309
310error:
311 crm_err("Could not initialize Diffie-Hellman parameters for TLS: %s "
312 QB_XS " rc=%d", gnutls_strerror(rc), rc);
313 return EPROTO;
314}
315
316gnutls_session_t
318{
319 unsigned int conn_type = GNUTLS_CLIENT;
320 int rc = GNUTLS_E_SUCCESS;
321 char *prio = NULL;
322 gnutls_session_t session = NULL;
323
324 CRM_CHECK((tls != NULL) && (csock >= 0), return NULL);
325
326 if (tls->server) {
327 conn_type = GNUTLS_SERVER;
328 }
329
330 rc = gnutls_init(&session, conn_type);
331 if (rc != GNUTLS_E_SUCCESS) {
332 goto error;
333 }
334
335 /* Determine list of acceptable ciphers, etc. Pacemaker always adds the
336 * values required for its functionality.
337 *
338 * For an example of anonymous authentication, see:
339 * http://www.manpagez.com/info/gnutls/gnutls-2.10.4/gnutls_81.php#Echo-Server-with-anonymous-authentication
340 */
341 prio = get_gnutls_priorities(tls->cred_type);
342
343 /* @TODO On the server side, it would be more efficient to cache the
344 * priority with gnutls_priority_init2() and set it with
345 * gnutls_priority_set() for all sessions.
346 */
347 rc = gnutls_priority_set_direct(session, prio, NULL);
348 if (rc != GNUTLS_E_SUCCESS) {
349 goto error;
350 }
351
352 gnutls_transport_set_ptr(session,
353 (gnutls_transport_ptr_t) GINT_TO_POINTER(csock));
354
355 /* gnutls does not make this easy */
356 if (tls->cred_type == GNUTLS_CRD_ANON && tls->server) {
357 rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.anon_s);
358 } else if (tls->cred_type == GNUTLS_CRD_ANON) {
359 rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.anon_c);
360 } else if (tls->cred_type == GNUTLS_CRD_CERTIFICATE) {
361 rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.cert);
362 } else if (tls->cred_type == GNUTLS_CRD_PSK && tls->server) {
363 rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.psk_s);
364 } else if (tls->cred_type == GNUTLS_CRD_PSK) {
365 rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.psk_c);
366 } else {
367 crm_err("Unknown credential type: %d", tls->cred_type);
368 rc = EINVAL;
369 goto error;
370 }
371
372 if (rc != GNUTLS_E_SUCCESS) {
373 goto error;
374 }
375
376 free(prio);
377
378 if (tls->cred_type == GNUTLS_CRD_CERTIFICATE) {
379 if (conn_type == GNUTLS_SERVER) {
380 /* Require the client to send a certificate for the server to verify. */
381 gnutls_certificate_server_set_request(session, GNUTLS_CERT_REQUIRE);
382 }
383
384 /* Register a function to verify the peer's certificate.
385 *
386 * FIXME: When we can require gnutls >= 3.4.6, remove verify_peer_cert
387 * and use gnutls_session_set_verify_cert instead.
388 */
389 gnutls_certificate_set_verify_function(tls->credentials.cert, verify_peer_cert);
390 }
391
392 return session;
393
394error:
395 crm_err("Could not initialize %s TLS %s session: %s " QB_XS " rc=%d priority='%s'",
396 tls_cred_str(tls->cred_type),
397 (conn_type == GNUTLS_SERVER)? "server" : "client",
398 gnutls_strerror(rc), rc, prio);
399 free(prio);
400 if (session != NULL) {
401 gnutls_deinit(session);
402 }
403 return NULL;
404}
405
417int
419{
420 gpointer sock_ptr = NULL;
421
422 pcmk__assert((remote != NULL) && (remote->tls_session != NULL));
423
424 sock_ptr = (gpointer) gnutls_transport_get_ptr(remote->tls_session);
425 return GPOINTER_TO_INT(sock_ptr);
426}
427
428int
430{
431 int rc = 0;
432
433 pcmk__assert((client != NULL) && (client->remote != NULL)
434 && (client->remote->tls_session != NULL));
435
436 do {
437 rc = gnutls_handshake(client->remote->tls_session);
438 } while (rc == GNUTLS_E_INTERRUPTED);
439
440 if (rc == GNUTLS_E_AGAIN) {
441 /* No more data is available at the moment. This function should be
442 * invoked again once the client sends more.
443 */
444 return EAGAIN;
445 } else if (rc != GNUTLS_E_SUCCESS) {
446 crm_err("TLS handshake with remote client failed: %s "
447 QB_XS " rc=%d", gnutls_strerror(rc), rc);
448 return EPROTO;
449 }
450 return pcmk_rc_ok;
451}
452
453void
454pcmk__tls_add_psk_key(pcmk__tls_t *tls, gnutls_datum_t *key)
455{
456 gnutls_psk_set_client_credentials(tls->credentials.psk_c,
458 GNUTLS_PSK_KEY_RAW);
459}
460
461void
463 gnutls_psk_server_credentials_function *cb)
464{
465 gnutls_psk_set_server_credentials_function(tls->credentials.psk_s, cb);
466}
467
468void
469pcmk__tls_check_cert_expiration(gnutls_session_t session)
470{
471 gnutls_x509_crt_t cert;
472 const gnutls_datum_t *datum = NULL;
473 time_t expiry;
474
475 if (session == NULL) {
476 return;
477 }
478
479 if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) {
480 return;
481 }
482
483 datum = gnutls_certificate_get_ours(session);
484 if (datum == NULL) {
485 return;
486 }
487
488 gnutls_x509_crt_init(&cert);
489 gnutls_x509_crt_import(cert, datum, GNUTLS_X509_FMT_DER);
490
491 expiry = gnutls_x509_crt_get_expiration_time(cert);
492
493 if (expiry != -1) {
494 time_t now = time(NULL);
495
496 /* If the cert is going to expire within ~ one month (30 days), log it */
497 if (expiry - now <= 60 * 60 * 24 * 30) {
498 crm_time_t *expiry_t = pcmk__copy_timet(expiry);
499
500 crm_time_log(LOG_WARNING, "TLS certificate will expire on",
502 crm_time_free(expiry_t);
503 }
504 }
505
506 gnutls_x509_crt_deinit(cert);
507}
508
509int
511{
512 int rc = pcmk_rc_ok;
513
514 if (gnutls_rc != NULL) {
515 *gnutls_rc = GNUTLS_E_SUCCESS;
516 }
517
518 rc = gnutls_handshake(remote->tls_session);
519
520 switch (rc) {
521 case GNUTLS_E_SUCCESS:
522 rc = pcmk_rc_ok;
523 break;
524
525 case GNUTLS_E_INTERRUPTED:
526 case GNUTLS_E_AGAIN:
527 rc = EAGAIN;
528 break;
529
530 default:
531 if (gnutls_rc != NULL) {
532 *gnutls_rc = rc;
533 }
534
535 rc = EPROTO;
536 break;
537 }
538
539 return rc;
540}
541
542int
544 int *gnutls_rc)
545{
546 const time_t time_limit = time(NULL) + timeout_sec;
547
548 do {
549 int rc = pcmk__tls_client_try_handshake(remote, gnutls_rc);
550
551 if (rc != EAGAIN) {
552 return rc;
553 }
554 } while (time(NULL) < time_limit);
555
556 return ETIME;
557}
558
559bool
561{
562 /* Environment variables for servers come through the sysconfig file, and
563 * have names like PCMK_<whatever>. Environment variables for clients come
564 * from the environment and have names like CIB_<whatever>. This function
565 * is used for both, so we need to check both.
566 */
567 return (!pcmk__str_empty(pcmk__env_option(PCMK__ENV_CERT_FILE)) ||
568 !pcmk__str_empty(getenv("CIB_cert_file"))) &&
569 (!pcmk__str_empty(pcmk__env_option(PCMK__ENV_CA_FILE)) ||
570 !pcmk__str_empty(getenv("CIB_ca_file"))) &&
571 (!pcmk__str_empty(pcmk__env_option(PCMK__ENV_KEY_FILE)) ||
572 !pcmk__str_empty(getenv("CIB_key_file")));
573}
#define pcmk__assert_alloc(nmemb, size)
Definition internal.h:246
#define PCMK__GNUTLS_PRIORITIES
Definition config.h:490
enum pcmk_ipc_server type
Definition cpg.c:3
#define crm_time_log_timeofday
Definition iso8601.h:68
void crm_time_free(crm_time_t *dt)
Definition iso8601.c:150
#define crm_time_log_date
Definition iso8601.h:67
struct crm_time_s crm_time_t
Definition iso8601.h:32
#define crm_time_log(level, prefix, dt, flags)
Definition iso8601.h:60
crm_time_t * pcmk__copy_timet(time_t source)
Definition iso8601.c:1488
#define crm_info(fmt, args...)
Definition logging.h:365
#define CRM_CHECK(expr, failure_action)
Definition logging.h:213
#define crm_err(fmt, args...)
Definition logging.h:357
#define crm_trace(fmt, args...)
Definition logging.h:370
#define DEFAULT_REMOTE_USERNAME
Definition lrmd.h:57
#define PCMK__ENV_KEY_FILE
#define PCMK__ENV_DH_MAX_BITS
#define PCMK__ENV_CERT_FILE
#define PCMK__ENV_TLS_PRIORITIES
const char * pcmk__env_option(const char *option)
Definition options.c:1085
#define PCMK__ENV_CRL_FILE
#define PCMK__ENV_CA_FILE
#define ENODATA
Definition portability.h:61
#define ETIME
Definition portability.h:66
@ pcmk_rc_ok
Definition results.h:159
#define pcmk__assert(expr)
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
int pcmk__scan_min_int(const char *text, int *result, int minimum)
Definition strings.c:116
struct pcmk__remote_s * remote
gnutls_session_t tls_session
gnutls_psk_server_credentials_t psk_s
gnutls_psk_client_credentials_t psk_c
const char * cert_file
gnutls_credentials_type_t cred_type
const char * key_file
const char * ca_file
gnutls_anon_server_credentials_t anon_s
gnutls_dh_params_t dh_params
gnutls_anon_client_credentials_t anon_c
gnutls_certificate_credentials_t cert
const char * crl_file
union pcmk__tls_t::@10 credentials
void pcmk__tls_add_psk_callback(pcmk__tls_t *tls, gnutls_psk_server_credentials_function *cb)
Definition tls.c:462
void pcmk__tls_add_psk_key(pcmk__tls_t *tls, gnutls_datum_t *key)
Definition tls.c:454
int pcmk__tls_get_client_sock(const pcmk__remote_t *remote)
Definition tls.c:418
int pcmk__tls_client_try_handshake(pcmk__remote_t *remote, int *gnutls_rc)
Definition tls.c:510
void pcmk__free_tls(pcmk__tls_t *tls)
Definition tls.c:149
bool pcmk__x509_enabled(void)
Definition tls.c:560
int pcmk__init_tls(pcmk__tls_t **tls, bool server, gnutls_credentials_type_t cred_type)
Definition tls.c:183
int pcmk__tls_client_handshake(pcmk__remote_t *remote, int timeout_sec, int *gnutls_rc)
Definition tls.c:543
void pcmk__tls_check_cert_expiration(gnutls_session_t session)
Definition tls.c:469
int pcmk__read_handshake_data(const pcmk__client_t *client)
Definition tls.c:429
int pcmk__init_tls_dh(gnutls_dh_params_t *dh_params)
Definition tls.c:278
gnutls_session_t pcmk__new_tls_session(pcmk__tls_t *tls, int csock)
Definition tls.c:317