/* * Copyright (C) 2019 Red Hat, Inc. * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include "credentials.h" #include #include #include #include #include #include #include #include #include #include #include "common/cockpitmemory.h" #include "common/cockpitwebcertificate.h" #include "utils.h" struct _Credentials { gnutls_certificate_credentials_t creds; int ref_count; }; static Credentials * credentials_new (gnutls_certificate_credentials_t creds) { Credentials *self = mallocx (sizeof (Credentials)); self->creds = creds; self->ref_count = 1; return self; } Credentials * credentials_ref (Credentials *self) { self->ref_count++; return self; } void credentials_unref (Credentials *self) { if (--self->ref_count == 0) { gnutls_certificate_free_credentials (self->creds); free (self); } } gnutls_certificate_credentials_t credentials_get (Credentials *self) { return self->creds; } /* Load a file into a gnutls_datum_t. * * Returns true on success, false if the file doesn't exist. * Exits on any other failure. * On success, data->data must be freed with free(). */ static bool load_file (int dirfd, const char *filename, gnutls_datum_t *data) { int fd = openat (dirfd, filename, O_RDONLY | O_CLOEXEC | O_NOCTTY); if (fd < 0) { if (errno == ENOENT) return false; err (EXIT_FAILURE, "Failed to open '%s'", filename); } struct stat st; if (fstat (fd, &st) < 0) err (EXIT_FAILURE, "Failed to stat '%s'", filename); if (!S_ISREG (st.st_mode)) errx (EXIT_FAILURE, "'%s' is not a regular file", filename); if (st.st_size <= 0) errx (EXIT_FAILURE, "'%s' is empty", filename); if (st.st_size > 640 * 1024) /* ought to be enough for anybody! */ errx (EXIT_FAILURE, "'%s' is too large", filename); size_t file_size = (size_t) st.st_size; unsigned char *buffer = mallocx (file_size + 1); if (buffer == NULL) errx (EXIT_FAILURE, "Failed to allocate memory for '%s'", filename); ssize_t n; do n = read (fd, buffer, file_size); while (n < 0 && errno == EINTR); if (n < 0) err (EXIT_FAILURE, "Failed to read '%s'", filename); if (n != (ssize_t) file_size) errx (EXIT_FAILURE, "Failed to read '%s': expected %zu bytes, got %zd", filename, file_size, n); close (fd); buffer[file_size] = '\0'; data->data = buffer; data->size = (unsigned int) file_size; /* <= 640k */ return true; } /* Load credentials from `{n}.crt` and `{n}.key` files in dirfd. Files * are deleted after loading to avoid leaving unnecessary copies of * secrets lying around. */ Credentials * credentials_load (int dirfd) { gnutls_certificate_credentials_t creds; int ret; ret = gnutls_certificate_allocate_credentials (&creds); assert (ret == GNUTLS_E_SUCCESS); Credentials *self = credentials_new(creds); /* Load files sequentially 0.{crt,key}, 1.{crt,key}, 2.{crt,key}, etc... */ int i; for (i = 0; ; i++) { char crt_name[32]; snprintf (crt_name, sizeof crt_name, "%d.crt", i); gnutls_datum_t crt_data; if (!load_file (dirfd, crt_name, &crt_data)) break; debug (SERVER, "Adding certificate %s", crt_name); char key_name[32]; snprintf (key_name, sizeof key_name, "%d.key", i); gnutls_datum_t key_data; if (!load_file (dirfd, key_name, &key_data)) errx (EXIT_FAILURE, "Certificate '%s' exists but key '%s' is missing", crt_name, key_name); int ret = gnutls_certificate_set_x509_key_mem2 (self->creds, &crt_data, &key_data, GNUTLS_X509_FMT_PEM, NULL, 0); if (ret < 0) errx (EXIT_FAILURE, "Failed to load keypair %s/%s: %s", crt_name, key_name, gnutls_strerror (ret)); gnutls_memset (key_data.data, 0, key_data.size); free (key_data.data); free (crt_data.data); /* Remove files after loading - secrets shouldn't sit on disk */ if (unlinkat (dirfd, key_name, 0) != 0) err (EXIT_FAILURE, "Failed to remove '%s'", key_name); if (unlinkat (dirfd, crt_name, 0) != 0) err (EXIT_FAILURE, "Failed to remove '%s'", crt_name); } if (i == 0) errx (EXIT_FAILURE, "No certificates found in directory"); debug (SERVER, "Loaded %d certificate(s)", i); return self; }