diff --git a/src/tls/cockpit-certificate-ensure.c b/src/tls/cockpit-certificate-ensure.c index dabba072ebc5..8be4763ab08d 100644 --- a/src/tls/cockpit-certificate-ensure.c +++ b/src/tls/cockpit-certificate-ensure.c @@ -92,6 +92,36 @@ read_file (const char *filename, close (fd); } +static void +write_file (int dirfd, + const char *dirfd_filename, + const char *filename, + const gnutls_datum_t *data, + uid_t uid, + gid_t gid) +{ + /* Just open the file directly: it doesn't exist yet and nobody will + * look at it until after we're done here. + */ + int fd = openat (dirfd, filename, O_CREAT | O_EXCL | O_WRONLY, 0400); + + if (fd == -1) + err (EXIT_FAILURE, "%s/%s: creat", dirfd_filename, filename); + + size_t s = write (fd, data->data, data->size); + if (s == -1) + err (EXIT_FAILURE, "%s/%s: write", dirfd_filename, filename); + if (s != data->size) + errx (EXIT_FAILURE, "%s/%s: write: wrote %zu bytes, expecting %zu", + dirfd_filename, filename, s, (size_t) data->size); + + /* This is actually making the file more accessible, to do it last */ + if (fchown (fd, uid, gid) != 0) + err (EXIT_FAILURE, "%s/%s: fchown", dirfd_filename, filename); + + close (fd); +} + static bool is_selfsigned (const char *certificate_filename) { @@ -162,6 +192,45 @@ certificate_and_key_clear (CertificateKeyPair *self) self->filename_for_errors = NULL; } +static void +certificate_and_key_write (const CertificateKeyPair *self, + const char *directory) +{ + int dirfd = open (directory, O_PATH | O_DIRECTORY | O_NOFOLLOW); + if (dirfd == -1) + err (EXIT_FAILURE, "open: %s", directory); + + struct stat buf; + if (fstat (dirfd, &buf) != 0) + err (EXIT_FAILURE, "fstat: %s", directory); + + int r = mkdirat (dirfd, "server", 0700); + if (r != 0) + err (EXIT_FAILURE, "mkdir: %s/%s", directory, "server"); + + /* fchown() won't accept file descriptors opened O_PATH */ + int fd = openat (dirfd, "server", O_RDONLY | O_DIRECTORY | O_NOFOLLOW); + if (fd == -1) + err (EXIT_FAILURE, "open: %s", directory); + + /* Copy the owner/group from the parent directory */ + if (fchown (fd, buf.st_uid, buf.st_gid) != 0) + err (EXIT_FAILURE, "%s: fchown", directory); + + if (symlinkat (self->certificate_filename, fd, "cert.source") != 0) + err (EXIT_FAILURE, "%s/%s: symlinkat", directory, "certificate.source"); + + if (symlinkat (self->key_filename, fd, "key.source") != 0) + err (EXIT_FAILURE, "%s/%s: symlinkat", directory, "key.source"); + + write_file (fd, directory, "cert", &self->certificate, buf.st_uid, buf.st_gid); + + write_file (fd, directory, "key", &self->key, buf.st_uid, buf.st_gid); + + close (dirfd); + close (fd); +} + static bool certificate_and_key_split (CertificateKeyPair *self) { @@ -321,11 +390,14 @@ main (int argc, char **argv) { CertificateKeyPair result = { }; bool check = false; + bool for_cockpit_tls = false; if (argc == 1) ; else if (argc == 2 && strcmp (argv[1], "--check") == 0) check = true; + else if (argc == 2 && strcmp (argv[1], "--for-cockpit-tls") == 0) + for_cockpit_tls = true; else errx (EXIT_FAILURE, "usage: %s [--check]", argv[0]); @@ -343,6 +415,15 @@ main (int argc, char **argv) if (check) printf ("Would use certificate %s", result.certificate_filename); + if (for_cockpit_tls) + { + const char *runtime_directory = getenv ("RUNTIME_DIRECTORY"); + if (runtime_directory == NULL) + errx (EXIT_FAILURE, "--for-cockpit-tls cannot be used unless RUNTIME_DIRECTORY is set"); + + certificate_and_key_write (&result, runtime_directory); + } + certificate_and_key_clear (&result); return 0;