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__read_handshake_data
  11. pcmk__tls_add_psk_key
  12. pcmk__tls_add_psk_callback
  13. pcmk__tls_check_cert_expiration
  14. pcmk__tls_client_try_handshake
  15. pcmk__tls_client_handshake
  16. pcmk__x509_enabled

   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 
  17 #include <crm/common/tls_internal.h>
  18 
  19 static char *
  20 get_gnutls_priorities(gnutls_credentials_type_t cred_type)
     /* [previous][next][first][last][top][bottom][index][help] */
  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)
     /* [previous][next][first][last][top][bottom][index][help] */
  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)
     /* [previous][next][first][last][top][bottom][index][help] */
  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 
 102 /*!
 103  * \internal
 104  * \brief Verify a peer's certificate
 105  *
 106  * \return 0 if the certificate is trusted and the gnutls handshake should
 107  *         continue, -1 otherwise
 108  */
 109 static int
 110 verify_peer_cert(gnutls_session_t session)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 142 {
 143     crm_trace("%s", msg);
 144 }
 145 
 146 void
 147 pcmk__free_tls(pcmk__tls_t *tls)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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 
 294     pcmk__scan_min_int(pcmk__env_option(PCMK__ENV_DH_MAX_BITS), &dh_max_bits, 0);
 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
 315 pcmk__new_tls_session(pcmk__tls_t *tls, int csock)
     /* [previous][next][first][last][top][bottom][index][help] */
 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
 399 pcmk__read_handshake_data(const pcmk__client_t *client)
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 425 {
 426     gnutls_psk_set_client_credentials(tls->credentials.psk_c,
 427                                       DEFAULT_REMOTE_USERNAME, key,
 428                                       GNUTLS_PSK_KEY_RAW);
 429 }
 430 
 431 void
 432 pcmk__tls_add_psk_callback(pcmk__tls_t *tls,
     /* [previous][next][first][last][top][bottom][index][help] */
 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)
     /* [previous][next][first][last][top][bottom][index][help] */
 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",
 471                          expiry_t, crm_time_log_date | crm_time_log_timeofday);
 472             crm_time_free(expiry_t);
 473         }
 474     }
 475 
 476     gnutls_x509_crt_deinit(cert);
 477 }
 478 
 479 int
 480 pcmk__tls_client_try_handshake(pcmk__remote_t *remote, int *gnutls_rc)
     /* [previous][next][first][last][top][bottom][index][help] */
 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
 513 pcmk__tls_client_handshake(pcmk__remote_t *remote, int timeout_sec,
     /* [previous][next][first][last][top][bottom][index][help] */
 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
 530 pcmk__x509_enabled(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 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 }

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