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