root/lib/common/tls.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. get_gnutls_priorities
  2. tls_cred_str
  3. tls_load_x509_data
  4. verify_peer_cert
  5. _gnutls_log_func
  6. pcmk__free_tls
  7. pcmk__init_tls
  8. pcmk__init_tls_dh
  9. pcmk__new_tls_session
  10. pcmk__tls_get_client_sock
  11. pcmk__read_handshake_data
  12. pcmk__tls_add_psk_key
  13. pcmk__tls_add_psk_callback
  14. pcmk__tls_check_cert_expiration
  15. pcmk__tls_client_try_handshake
  16. pcmk__tls_client_handshake
  17. pcmk__x509_enabled

   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 
  19 #include <crm/common/tls_internal.h>
  20 
  21 static char *
  22 get_gnutls_priorities(gnutls_credentials_type_t cred_type)
     /* [previous][next][first][last][top][bottom][index][help] */
  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 
  39 static const char *
  40 tls_cred_str(gnutls_credentials_type_t cred_type)
     /* [previous][next][first][last][top][bottom][index][help] */
  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 
  53 static int
  54 tls_load_x509_data(pcmk__tls_t *tls)
     /* [previous][next][first][last][top][bottom][index][help] */
  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 
 104 /*!
 105  * \internal
 106  * \brief Verify a peer's certificate
 107  *
 108  * \return 0 if the certificate is trusted and the gnutls handshake should
 109  *         continue, -1 otherwise
 110  */
 111 static int
 112 verify_peer_cert(gnutls_session_t session)
     /* [previous][next][first][last][top][bottom][index][help] */
 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 
 142 static void
 143 _gnutls_log_func(int level, const char *msg)
     /* [previous][next][first][last][top][bottom][index][help] */
 144 {
 145     crm_trace("%s", msg);
 146 }
 147 
 148 void
 149 pcmk__free_tls(pcmk__tls_t *tls)
     /* [previous][next][first][last][top][bottom][index][help] */
 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 
 182 int
 183 pcmk__init_tls(pcmk__tls_t **tls, bool server, gnutls_credentials_type_t cred_type)
     /* [previous][next][first][last][top][bottom][index][help] */
 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 
 277 int
 278 pcmk__init_tls_dh(gnutls_dh_params_t *dh_params)
     /* [previous][next][first][last][top][bottom][index][help] */
 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 
 296     pcmk__scan_min_int(pcmk__env_option(PCMK__ENV_DH_MAX_BITS), &dh_max_bits, 0);
 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 
 310 error:
 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 
 316 gnutls_session_t
 317 pcmk__new_tls_session(pcmk__tls_t *tls, int csock)
     /* [previous][next][first][last][top][bottom][index][help] */
 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 
 394 error:
 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 
 406 /*!
 407  * \internal
 408  * \brief Get the socket file descriptor for a remote connection's TLS session
 409  *
 410  * \param[in] remote  Remote connection
 411  *
 412  * \return Socket file descriptor for \p remote
 413  *
 414  * \note The remote connection's \c tls_session must have already been
 415  *       initialized using \c pcmk__new_tls_session().
 416  */
 417 int
 418 pcmk__tls_get_client_sock(const pcmk__remote_t *remote)
     /* [previous][next][first][last][top][bottom][index][help] */
 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 
 428 int
 429 pcmk__read_handshake_data(const pcmk__client_t *client)
     /* [previous][next][first][last][top][bottom][index][help] */
 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 
 453 void
 454 pcmk__tls_add_psk_key(pcmk__tls_t *tls, gnutls_datum_t *key)
     /* [previous][next][first][last][top][bottom][index][help] */
 455 {
 456     gnutls_psk_set_client_credentials(tls->credentials.psk_c,
 457                                       DEFAULT_REMOTE_USERNAME, key,
 458                                       GNUTLS_PSK_KEY_RAW);
 459 }
 460 
 461 void
 462 pcmk__tls_add_psk_callback(pcmk__tls_t *tls,
     /* [previous][next][first][last][top][bottom][index][help] */
 463                            gnutls_psk_server_credentials_function *cb)
 464 {
 465     gnutls_psk_set_server_credentials_function(tls->credentials.psk_s, cb);
 466 }
 467 
 468 void
 469 pcmk__tls_check_cert_expiration(gnutls_session_t session)
     /* [previous][next][first][last][top][bottom][index][help] */
 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",
 501                          expiry_t, crm_time_log_date | crm_time_log_timeofday);
 502             crm_time_free(expiry_t);
 503         }
 504     }
 505 
 506     gnutls_x509_crt_deinit(cert);
 507 }
 508 
 509 int
 510 pcmk__tls_client_try_handshake(pcmk__remote_t *remote, int *gnutls_rc)
     /* [previous][next][first][last][top][bottom][index][help] */
 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 
 542 int
 543 pcmk__tls_client_handshake(pcmk__remote_t *remote, int timeout_sec,
     /* [previous][next][first][last][top][bottom][index][help] */
 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 
 559 bool
 560 pcmk__x509_enabled(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 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 }

/* [previous][next][first][last][top][bottom][index][help] */