Coverage Report

Created: 2026-10-30 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pacemaker/lib/common/tls.c
Line
Count
Source
1
/*
2
 * Copyright 2024-2026 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 <ctype.h>                  // isxdigit
13
#include <errno.h>                  // EAGAIN, ENODATA, EPROTO, EINVAL, ETIME
14
#include <pwd.h>                    // getpwuid
15
#include <signal.h>                 // signal, SIGPIPE, SIG_IGN
16
#include <stdbool.h>                // bool
17
#include <stdlib.h>                 // NULL, getenv, free
18
#include <string.h>                 // memcpy, strdup, strlen
19
#include <sys/stat.h>               // S_ISREG, stat
20
#include <syslog.h>                 // LOG_WARNING
21
#include <time.h>                   // time, time_t
22
#include <unistd.h>                 // geteuid
23
24
#include <glib.h>                   // g_clear_pointer, g_file_get_contents
25
#include <gnutls/gnutls.h>          // gnutls_*, GNUTLS_E_SUCCESS
26
#include <gnutls/x509.h>            // gnutls_x509_*
27
#include <qb/qblog.h>               // QB_XS
28
29
#include <crm/common/internal.h>
30
#include <crm/common/iso8601.h>     // crm_time_*
31
#include <crm/common/logging.h>     // CRM_CHECK
32
#include <crm/common/results.h>     // pcmk_rc_*
33
34
/*!
35
 * \internal
36
 * \brief Initialize Diffie-Hellman parameters for a TLS server
37
 *
38
 * \param[out] dh_params  Parameter object to initialize
39
 *
40
 * \return Standard Pacemaker return code
41
 * \todo The current best practice is to allow the client and server to
42
 *       negotiate the Diffie-Hellman parameters via a TLS extension (RFC 7919).
43
 *       However, we have to support both older versions of GnuTLS (<3.6) that
44
 *       don't support the extension on our side, and older Pacemaker versions
45
 *       that don't support the extension on the other side. The next best
46
 *       practice would be to use a known good prime (see RFC 5114 section 2.2),
47
 *       possibly stored in a file distributed with Pacemaker.
48
 */
49
static int
50
init_tls_dh(gnutls_dh_params_t *dh_params)
51
0
{
52
0
    int rc = GNUTLS_E_SUCCESS;
53
0
    unsigned int dh_bits = 0;
54
0
    int dh_max_bits = 0;
55
56
0
    rc = gnutls_dh_params_init(dh_params);
57
0
    if (rc != GNUTLS_E_SUCCESS) {
58
0
        goto error;
59
0
    }
60
61
0
    dh_bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH,
62
0
                                          GNUTLS_SEC_PARAM_NORMAL);
63
0
    if (dh_bits == 0) {
64
0
        rc = GNUTLS_E_DH_PRIME_UNACCEPTABLE;
65
0
        goto error;
66
0
    }
67
68
0
    pcmk__scan_min_int(pcmk__env_option(PCMK__ENV_DH_MAX_BITS), &dh_max_bits, 0);
69
0
    if ((dh_max_bits > 0) && (dh_bits > dh_max_bits)) {
70
0
        pcmk__warn("Support for the " PCMK__ENV_DH_MAX_BITS " "
71
0
                   "environment variable is deprecated and will be removed "
72
0
                   "in a future release");
73
0
        dh_bits = dh_max_bits;
74
0
    }
75
76
0
    pcmk__info("Generating Diffie-Hellman parameters with %u-bit prime for TLS",
77
0
               dh_bits);
78
0
    rc = gnutls_dh_params_generate2(*dh_params, dh_bits);
79
0
    if (rc != GNUTLS_E_SUCCESS) {
80
0
        goto error;
81
0
    }
82
83
0
    return pcmk_rc_ok;
84
85
0
error:
86
0
    pcmk__err("Could not initialize Diffie-Hellman parameters for TLS: %s "
87
0
              QB_XS " rc=%d",
88
0
              gnutls_strerror(rc), rc);
89
0
    return EPROTO;
90
0
}
91
92
static char *
93
get_gnutls_priorities(gnutls_credentials_type_t cred_type)
94
0
{
95
0
    const char *prio_base = pcmk__env_option(PCMK__ENV_TLS_PRIORITIES);
96
97
0
    if (prio_base == NULL) {
98
0
        prio_base = PCMK__GNUTLS_PRIORITIES;
99
0
    }
100
101
0
    if (cred_type == GNUTLS_CRD_ANON) {
102
0
        return pcmk__assert_asprintf("%s:+ANON-DH", prio_base);
103
0
    } else if (cred_type == GNUTLS_CRD_PSK) {
104
0
        return pcmk__assert_asprintf("%s:+DHE-PSK:+PSK", prio_base);
105
0
    } else {
106
0
        return strdup(prio_base);
107
0
    }
108
0
}
109
110
static const char *
111
tls_cred_str(gnutls_credentials_type_t cred_type)
112
0
{
113
0
    if (cred_type == GNUTLS_CRD_ANON) {
114
0
        return "unauthenticated";
115
0
    } else if (cred_type == GNUTLS_CRD_PSK) {
116
0
        return "shared-key-authenticated";
117
0
    } else if (cred_type == GNUTLS_CRD_CERTIFICATE) {
118
0
        return "certificate-authenticated";
119
0
    } else {
120
0
        return "unknown";
121
0
    }
122
0
}
123
124
static int
125
tls_load_x509_data(pcmk__tls_t *tls)
126
0
{
127
0
    int rc;
128
129
0
    CRM_CHECK(tls->cred_type == GNUTLS_CRD_CERTIFICATE, return EINVAL);
130
131
    /* Load a trusted CA to be used to verify client certificates.  Use
132
     * of this function instead of gnutls_certificate_set_x509_system_trust
133
     * means we do not look at the system-wide authorities installed in
134
     * /etc/pki somewhere.  This requires the cluster admin to set up their
135
     * own CA.
136
     */
137
0
    rc = gnutls_certificate_set_x509_trust_file(tls->credentials.cert,
138
0
                                                tls->ca_file,
139
0
                                                GNUTLS_X509_FMT_PEM);
140
0
    if (rc <= 0) {
141
0
        pcmk__err("Failed to set X509 CA file: %s", gnutls_strerror(rc));
142
0
        return ENODATA;
143
0
    }
144
145
    /* If a Certificate Revocation List (CRL) file was given in the environment,
146
     * load that now so we know which clients have been banned.
147
     */
148
0
    if (tls->crl_file != NULL) {
149
0
        rc = gnutls_certificate_set_x509_crl_file(tls->credentials.cert,
150
0
                                                  tls->crl_file,
151
0
                                                  GNUTLS_X509_FMT_PEM);
152
0
        if (rc < 0) {
153
0
            pcmk__err("Failed to set X509 CRL file: %s", gnutls_strerror(rc));
154
0
            return ENODATA;
155
0
        }
156
0
    }
157
158
    /* NULL = no password for the key, GNUTLS_PKCS_PLAIN = unencrypted key
159
     * file
160
     */
161
0
    rc = gnutls_certificate_set_x509_key_file2(tls->credentials.cert,
162
0
                                               tls->cert_file, tls->key_file,
163
0
                                               GNUTLS_X509_FMT_PEM, NULL,
164
0
                                               GNUTLS_PKCS_PLAIN);
165
0
    if (rc < 0) {
166
0
        pcmk__err("Failed to set X509 cert/key pair: %s", gnutls_strerror(rc));
167
0
        return ENODATA;
168
0
    }
169
170
0
    return pcmk_rc_ok;
171
0
}
172
173
static void
174
_gnutls_log_func(int level, const char *msg)
175
0
{
176
0
    pcmk__trace("%s", msg);
177
0
}
178
179
void
180
pcmk__free_tls(pcmk__tls_t *tls)
181
0
{
182
0
    if (tls == NULL) {
183
0
        return;
184
0
    }
185
186
    /* This is only set on the server side. */
187
0
    if (tls->server) {
188
0
        gnutls_dh_params_deinit(tls->dh_params);
189
0
    }
190
191
0
    if (tls->cred_type == GNUTLS_CRD_ANON) {
192
0
        if (tls->server) {
193
0
            gnutls_anon_free_server_credentials(tls->credentials.anon_s);
194
0
        } else {
195
0
            gnutls_anon_free_client_credentials(tls->credentials.anon_c);
196
0
        }
197
0
    } else if (tls->cred_type == GNUTLS_CRD_CERTIFICATE) {
198
0
        gnutls_certificate_free_credentials(tls->credentials.cert);
199
0
    } else if (tls->cred_type == GNUTLS_CRD_PSK) {
200
0
        if (tls->server) {
201
0
            gnutls_psk_free_server_credentials(tls->credentials.psk_s);
202
0
        } else {
203
0
            gnutls_psk_free_client_credentials(tls->credentials.psk_c);
204
0
        }
205
0
    }
206
207
0
    free(tls);
208
0
}
209
210
int
211
pcmk__init_tls(pcmk__tls_t **tls, bool server, bool have_psk)
212
0
{
213
0
    int rc = pcmk_rc_ok;
214
215
0
    if (*tls != NULL) {
216
0
        return rc;
217
0
    }
218
219
0
    *tls = pcmk__assert_alloc(1, sizeof(pcmk__tls_t));
220
221
0
    signal(SIGPIPE, SIG_IGN);
222
223
0
    gnutls_global_set_log_level(8);
224
0
    gnutls_global_set_log_function(_gnutls_log_func);
225
226
0
    if (server) {
227
0
        rc = init_tls_dh(&(*tls)->dh_params);
228
0
        if (rc != pcmk_rc_ok) {
229
0
            g_clear_pointer(tls, pcmk__free_tls);
230
0
            return rc;
231
0
        }
232
0
    }
233
234
0
    if (pcmk__x509_enabled()) {
235
0
        (*tls)->cred_type = GNUTLS_CRD_CERTIFICATE;
236
237
0
    } else if (have_psk) {
238
0
        (*tls)->cred_type = GNUTLS_CRD_PSK;
239
240
0
    } else {
241
0
        (*tls)->cred_type = GNUTLS_CRD_ANON;
242
0
    }
243
244
0
    (*tls)->server = server;
245
246
0
    if ((*tls)->cred_type == GNUTLS_CRD_ANON) {
247
0
        pcmk__warn("Using anonymous authentication.  This is insecure and will "
248
0
                   "be removed in a future release.  Use X509 certificates or PSK "
249
0
                   "instead.");
250
251
0
        if (server) {
252
0
            gnutls_anon_allocate_server_credentials(&(*tls)->credentials.anon_s);
253
0
            gnutls_anon_set_server_dh_params((*tls)->credentials.anon_s,
254
0
                                             (*tls)->dh_params);
255
0
        } else {
256
0
            gnutls_anon_allocate_client_credentials(&(*tls)->credentials.anon_c);
257
0
        }
258
259
0
    } else if ((*tls)->cred_type == GNUTLS_CRD_CERTIFICATE) {
260
        /* Try the PCMK_ version of each environment variable first, and if
261
         * it's not set then try the CIB_ version.
262
         */
263
0
        (*tls)->ca_file = pcmk__env_option(PCMK__ENV_CA_FILE);
264
0
        if (pcmk__str_empty((*tls)->ca_file)) {
265
0
            (*tls)->ca_file = getenv("CIB_ca_file");
266
0
        }
267
268
0
        (*tls)->cert_file = pcmk__env_option(PCMK__ENV_CERT_FILE);
269
0
        if (pcmk__str_empty((*tls)->cert_file)) {
270
0
            (*tls)->cert_file = getenv("CIB_cert_file");
271
0
        }
272
273
0
        (*tls)->crl_file = pcmk__env_option(PCMK__ENV_CRL_FILE);
274
0
        if (pcmk__str_empty((*tls)->crl_file)) {
275
0
            (*tls)->crl_file = getenv("CIB_crl_file");
276
0
        }
277
278
0
        (*tls)->key_file = pcmk__env_option(PCMK__ENV_KEY_FILE);
279
0
        if (pcmk__str_empty((*tls)->key_file)) {
280
0
            (*tls)->key_file = getenv("CIB_key_file");
281
0
        }
282
283
0
        gnutls_certificate_allocate_credentials(&(*tls)->credentials.cert);
284
285
0
        if (server) {
286
0
            gnutls_certificate_set_dh_params((*tls)->credentials.cert,
287
0
                                             (*tls)->dh_params);
288
289
0
        }
290
291
0
        rc = tls_load_x509_data(*tls);
292
0
        if (rc != pcmk_rc_ok) {
293
0
            g_clear_pointer(tls, pcmk__free_tls);
294
0
            return rc;
295
0
        }
296
0
    } else {    // GNUTLS_CRD_PSK
297
0
        if (server) {
298
0
            gnutls_psk_allocate_server_credentials(&(*tls)->credentials.psk_s);
299
0
            gnutls_psk_set_server_dh_params((*tls)->credentials.psk_s,
300
0
                                            (*tls)->dh_params);
301
0
        } else {
302
0
            gnutls_psk_allocate_client_credentials(&(*tls)->credentials.psk_c);
303
0
        }
304
0
    }
305
306
0
    return rc;
307
0
}
308
309
gnutls_session_t
310
pcmk__new_tls_session(pcmk__tls_t *tls, int csock)
311
0
{
312
0
    unsigned int conn_type = GNUTLS_CLIENT;
313
0
    int rc = GNUTLS_E_SUCCESS;
314
0
    char *prio = NULL;
315
0
    gnutls_session_t session = NULL;
316
317
0
    CRM_CHECK((tls != NULL) && (csock >= 0), return NULL);
318
319
0
    if (tls->server) {
320
0
        conn_type = GNUTLS_SERVER;
321
0
    }
322
323
0
    rc = gnutls_init(&session, conn_type);
324
0
    if (rc != GNUTLS_E_SUCCESS) {
325
0
        goto error;
326
0
    }
327
328
    /* Determine list of acceptable ciphers, etc. Pacemaker always adds the
329
     * values required for its functionality.
330
     *
331
     * For an example of anonymous authentication, see:
332
     * http://www.manpagez.com/info/gnutls/gnutls-2.10.4/gnutls_81.php#Echo-Server-with-anonymous-authentication
333
     */
334
0
    prio = get_gnutls_priorities(tls->cred_type);
335
336
    /* @TODO On the server side, it would be more efficient to cache the
337
     * priority with gnutls_priority_init2() and set it with
338
     * gnutls_priority_set() for all sessions.
339
     */
340
0
    rc = gnutls_priority_set_direct(session, prio, NULL);
341
0
    if (rc != GNUTLS_E_SUCCESS) {
342
0
        goto error;
343
0
    }
344
345
0
    gnutls_transport_set_int(session, csock);
346
347
    /* gnutls does not make this easy */
348
0
    if (tls->cred_type == GNUTLS_CRD_ANON && tls->server) {
349
0
        rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.anon_s);
350
0
    } else if (tls->cred_type == GNUTLS_CRD_ANON) {
351
0
        rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.anon_c);
352
0
    } else if (tls->cred_type == GNUTLS_CRD_CERTIFICATE) {
353
0
        rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.cert);
354
0
    } else if (tls->cred_type == GNUTLS_CRD_PSK && tls->server) {
355
0
        rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.psk_s);
356
0
    } else if (tls->cred_type == GNUTLS_CRD_PSK) {
357
0
        rc = gnutls_credentials_set(session, tls->cred_type, tls->credentials.psk_c);
358
0
    } else {
359
0
        pcmk__err("Unknown credential type: %d", tls->cred_type);
360
0
        rc = EINVAL;
361
0
        goto error;
362
0
    }
363
364
0
    if (rc != GNUTLS_E_SUCCESS) {
365
0
        goto error;
366
0
    }
367
368
0
    free(prio);
369
370
0
    if (tls->cred_type == GNUTLS_CRD_CERTIFICATE) {
371
0
        if (conn_type == GNUTLS_SERVER) {
372
            /* Require the client to send a certificate for the server to verify. */
373
0
            gnutls_certificate_server_set_request(session, GNUTLS_CERT_REQUIRE);
374
0
        }
375
376
        // Register a function to verify the peer's certificate
377
0
        gnutls_session_set_verify_cert(session, NULL, 0);
378
0
    }
379
380
0
    return session;
381
382
0
error:
383
0
    pcmk__err("Could not initialize %s TLS %s session: %s "
384
0
              QB_XS " rc=%d priority='%s'",
385
0
              tls_cred_str(tls->cred_type),
386
0
              ((conn_type == GNUTLS_SERVER)? "server" : "client"),
387
0
              gnutls_strerror(rc), rc, prio);
388
0
    free(prio);
389
0
    if (session != NULL) {
390
0
        gnutls_deinit(session);
391
0
    }
392
0
    return NULL;
393
0
}
394
395
int
396
pcmk__tls_get_client_sock(const pcmk__remote_t *remote)
397
0
{
398
0
    pcmk__assert((remote != NULL) && (remote->tls_session != NULL));
399
400
0
    return gnutls_transport_get_int(remote->tls_session);
401
0
}
402
403
int
404
pcmk__read_handshake_data(const pcmk__client_t *client)
405
0
{
406
0
    int rc = 0;
407
408
0
    pcmk__assert((client != NULL) && (client->remote != NULL)
409
0
                 && (client->remote->tls_session != NULL));
410
411
0
    do {
412
0
        rc = gnutls_handshake(client->remote->tls_session);
413
0
    } while (rc == GNUTLS_E_INTERRUPTED);
414
415
0
    if (rc == GNUTLS_E_AGAIN) {
416
        /* No more data is available at the moment. This function should be
417
         * invoked again once the client sends more.
418
         */
419
0
        return EAGAIN;
420
0
    } else if (rc == GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR) {
421
0
        int type = gnutls_certificate_type_get(client->remote->tls_session);
422
0
        unsigned int status = gnutls_session_get_verify_cert_status(client->remote->tls_session);
423
0
        gnutls_datum_t out;
424
425
0
        gnutls_certificate_verification_status_print(status, type, &out, 0);
426
0
        pcmk__err("Certificate verification failed: %s", out.data);
427
0
        gnutls_free(out.data);
428
0
    } else if (rc != GNUTLS_E_SUCCESS) {
429
0
        pcmk__err("TLS handshake with remote client failed: %s " QB_XS " rc=%d",
430
0
                  gnutls_strerror(rc), rc);
431
0
        return EPROTO;
432
0
    }
433
0
    return pcmk_rc_ok;
434
0
}
435
436
void
437
pcmk__tls_client_add_psk_key(pcmk__tls_t *tls, const char *username,
438
                             gnutls_datum_t *key, bool raw)
439
0
{
440
0
    gnutls_psk_set_client_credentials(tls->credentials.psk_c, username, key,
441
0
                                      raw ? GNUTLS_PSK_KEY_RAW : GNUTLS_PSK_KEY_HEX);
442
0
}
443
444
void
445
pcmk__tls_check_cert_expiration(gnutls_session_t session)
446
0
{
447
0
    gnutls_x509_crt_t cert;
448
0
    const gnutls_datum_t *datum = NULL;
449
0
    time_t expiry;
450
451
0
    if (session == NULL) {
452
0
        return;
453
0
    }
454
455
0
    if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) {
456
0
        return;
457
0
    }
458
459
0
    datum = gnutls_certificate_get_ours(session);
460
0
    if (datum == NULL) {
461
0
        return;
462
0
    }
463
464
0
    gnutls_x509_crt_init(&cert);
465
0
    gnutls_x509_crt_import(cert, datum, GNUTLS_X509_FMT_DER);
466
467
0
    expiry = gnutls_x509_crt_get_expiration_time(cert);
468
469
0
    if (expiry != (time_t) -1) {
470
0
        time_t now = time(NULL);
471
472
        /* If the cert is going to expire within ~ one month (30 days), log it */
473
0
        if (expiry - now <= 60 * 60 * 24 * 30) {
474
0
            crm_time_t *expiry_t = pcmk__copy_timet(expiry);
475
476
0
            pcmk__time_log(LOG_WARNING, "TLS certificate will expire on",
477
0
                           expiry_t, pcmk__time_fmt_date|pcmk__time_fmt_time);
478
0
            free(expiry_t);
479
0
        }
480
0
    }
481
482
0
    gnutls_x509_crt_deinit(cert);
483
0
}
484
485
int
486
pcmk__tls_client_try_handshake(pcmk__remote_t *remote, int *gnutls_rc)
487
0
{
488
0
    int rc = pcmk_rc_ok;
489
490
0
    pcmk__assert(gnutls_rc != NULL);
491
0
    *gnutls_rc = GNUTLS_E_SUCCESS;
492
493
0
    rc = gnutls_handshake(remote->tls_session);
494
495
0
    switch (rc) {
496
0
        case GNUTLS_E_SUCCESS:
497
0
            rc = pcmk_rc_ok;
498
0
            break;
499
500
0
        case GNUTLS_E_INTERRUPTED:
501
0
        case GNUTLS_E_AGAIN:
502
0
            rc = EAGAIN;
503
0
            break;
504
505
0
        case GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR: {
506
0
            int type = gnutls_certificate_type_get(remote->tls_session);
507
0
            unsigned int status = gnutls_session_get_verify_cert_status(remote->tls_session);
508
0
            gnutls_datum_t out;
509
510
0
            gnutls_certificate_verification_status_print(status, type, &out, 0);
511
0
            pcmk__err("Certificate verification failed: %s", out.data);
512
0
            gnutls_free(out.data);
513
514
0
            *gnutls_rc = rc;
515
0
            rc = EPROTO;
516
0
            break;
517
0
        }
518
519
0
        default:
520
0
            *gnutls_rc = rc;
521
0
            rc = EPROTO;
522
0
            break;
523
0
    }
524
525
0
    return rc;
526
0
}
527
528
int
529
pcmk__tls_client_handshake(pcmk__remote_t *remote, int timeout_sec,
530
                           int *gnutls_rc)
531
0
{
532
0
    const time_t time_limit = time(NULL) + timeout_sec;
533
534
0
    do {
535
0
        int rc = pcmk__tls_client_try_handshake(remote, gnutls_rc);
536
537
0
        if (rc != EAGAIN) {
538
0
            return rc;
539
0
        }
540
0
    } while (time(NULL) < time_limit);
541
542
0
    return ETIME;
543
0
}
544
545
bool
546
pcmk__x509_enabled(void)
547
0
{
548
    /* Environment variables for servers come through the sysconfig file, and
549
     * have names like PCMK_<whatever>.  Environment variables for clients come
550
     * from the environment and have names like CIB_<whatever>.  This function
551
     * is used for both, so we need to check both.
552
     */
553
0
    return (!pcmk__str_empty(pcmk__env_option(PCMK__ENV_CERT_FILE)) ||
554
0
            !pcmk__str_empty(getenv("CIB_cert_file"))) &&
555
0
           (!pcmk__str_empty(pcmk__env_option(PCMK__ENV_CA_FILE)) ||
556
0
            !pcmk__str_empty(getenv("CIB_ca_file"))) &&
557
0
           (!pcmk__str_empty(pcmk__env_option(PCMK__ENV_KEY_FILE)) ||
558
0
            !pcmk__str_empty(getenv("CIB_key_file")));
559
0
}
560
561
void
562
pcmk__copy_key(gnutls_datum_t *dest, const gnutls_datum_t *source)
563
0
{
564
0
    pcmk__assert((dest != NULL) && (source != NULL) && (source->data != NULL));
565
566
0
    dest->data = gnutls_malloc(source->size);
567
0
    pcmk__mem_assert(dest->data);
568
569
0
    memcpy(dest->data, source->data, source->size);
570
0
    dest->size = source->size;
571
0
}
572
573
int
574
pcmk__load_key(const char *location, gnutls_datum_t *key, bool raw)
575
0
{
576
0
    gchar *contents = NULL;
577
0
    gsize len = 0;
578
0
    int rc = pcmk_rc_ok;
579
580
0
    pcmk__assert((location != NULL) && (key != NULL));
581
582
0
    if (!g_file_get_contents(location, &contents, &len, NULL)) {
583
0
        return ENOKEY;
584
0
    }
585
586
0
    if (!raw) {
587
        /* This is a plain text key.  gnutls expects only hex characters, so
588
         * remove any whitespace and check that the characters are valid.
589
         */
590
0
        contents = g_strstrip(contents);
591
592
0
        for (const char *c = contents; *c != '\0'; c++) {
593
0
            if (!isxdigit(*c)) {
594
0
                pcmk__err("Key file %s contains characters besides hex digits",
595
0
                          location);
596
0
                rc = ENOKEY;
597
0
                goto done;
598
0
            }
599
0
        }
600
601
0
        len = strlen(contents);
602
0
    }
603
604
0
    key->size = len;
605
0
    key->data = gnutls_malloc(key->size);
606
0
    pcmk__mem_assert(key->data);
607
0
    memcpy(key->data, contents, key->size);
608
609
0
done:
610
0
    g_free(contents);
611
0
    return rc;
612
0
}
613
614
bool
615
pcmk__cred_file_useable(const char *location, bool *file_exists)
616
0
{
617
0
    int rc = pcmk_rc_ok;
618
0
    struct stat sb;
619
0
    uid_t euid;
620
621
0
    pcmk__assert((location != NULL) && (file_exists != NULL));
622
623
0
    rc = stat(location, &sb);
624
0
    if ((rc != 0) || !S_ISREG(sb.st_mode)) {
625
0
        *file_exists = false;
626
0
        return false;
627
0
    }
628
629
0
    *file_exists = true;
630
631
0
    euid = geteuid();
632
0
    if (sb.st_uid != euid) {
633
0
        struct passwd *pwent = getpwuid(euid);
634
635
0
        if (pwent == NULL) {
636
0
            pcmk__err("Refusing to use PSK credentials file %s because it is "
637
0
                      "not owned by UID %lld", (long long) euid);
638
0
        } else {
639
0
            pcmk__err("Refusing to use PSK credentials file %s because it is "
640
0
                      "not owned by %s", location, pwent->pw_name);
641
0
        }
642
643
0
        return false;
644
0
    }
645
646
0
    if ((sb.st_mode & (S_IRWXG | S_IRWXO)) != 0) {
647
0
        pcmk__err("Refusing to use PSK credentials file %s because it has "
648
0
                  "group and/or other permissions set", location);
649
0
        return false;
650
0
    }
651
652
0
    return true;
653
0
}