123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681 |
- /* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
- /*
- * Copyright (c) 2015 Red Hat, Inc.
- * Author: Nathaniel McCallum <npmccallum@redhat.com>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- #include <udisks/udisks.h>
- #include <glib-unix.h>
- #include <luksmeta.h>
- #include <jansson.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <sys/wait.h>
- #include <string.h>
- #include <stdbool.h>
- #include <stdint.h>
- #include <stdio.h>
- #include <libaudit.h>
- #include <getopt.h>
- #include <pwd.h>
- #include <grp.h>
- #define MAX_UDP 65507
- #define UERR ((uid_t) -1)
- #define GERR ((gid_t) -1)
- #define UUID_TMPL \
- "%02hhx%02hhx%02hhx%02hhx-" \
- "%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx-" \
- "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
- #define UUID_ARGS(u) \
- u[0x0], u[0x1], u[0x2], u[0x3], u[0x4], u[0x5], u[0x6], u[0x7], \
- u[0x8], u[0x9], u[0xa], u[0xb], u[0xc], u[0xd], u[0xe], u[0xf]
- typedef struct {
- ssize_t used;
- char data[MAX_UDP];
- } pkt_t;
- enum {
- PIPE_RD = 0,
- PIPE_WR = 1
- };
- struct context {
- UDisksClient *clt;
- GMainLoop *loop;
- GList *lst;
- int sock;
- };
- static const luksmeta_uuid_t CLEVIS_LUKS_UUID = {
- 0xcb, 0x6e, 0x89, 0x04, 0x81, 0xff, 0x40, 0xda,
- 0xa8, 0x4a, 0x07, 0xab, 0x9a, 0xb5, 0x71, 0x5e
- };
- static void
- remove_path(GList **lst, const char *path)
- {
- GList *i = NULL;
- while ((i = g_list_find_custom(*lst, path, (GCompareFunc) g_strcmp0))) {
- *lst = g_list_remove(*lst, i->data);
- g_free(i->data);
- }
- }
- static gboolean
- idle(gpointer misc)
- {
- struct context *ctx = misc;
- GVariant *options = NULL;
- options = g_variant_new_parsed("@a{sv} { %s: <true> }",
- "auth.no_user_interaction");
- if (!options)
- goto error;
- g_variant_ref_sink(options);
- for (GList *i = ctx->lst; i; i = i->next) {
- UDisksEncrypted *enc = NULL;
- const char *path = i->data;
- UDisksObject *uobj = NULL;
- UDisksBlock *block = NULL;
- const char *dev = NULL;
- pkt_t pkt = {};
- uobj = udisks_client_peek_object(ctx->clt, path);
- if (!uobj)
- continue;
- enc = udisks_object_peek_encrypted(uobj);
- if (!enc)
- continue;
- block = udisks_object_peek_block(uobj);
- if (!block)
- continue;
- dev = udisks_block_get_device(block);
- if (!dev)
- continue;
- pkt.used = strlen(dev) + 1;
- if ((size_t) pkt.used > sizeof(pkt.data))
- continue;
- strcpy(pkt.data, dev);
- if (send(ctx->sock, pkt.data, pkt.used, 0) != pkt.used) {
- g_main_loop_quit(ctx->loop);
- break;
- }
- memset(&pkt, 0, sizeof(pkt));
- pkt.used = recv(ctx->sock, pkt.data, sizeof(pkt.data), 0);
- if (pkt.used == 0)
- continue;
- else if (pkt.used < 0 || (size_t) pkt.used >= sizeof(pkt.data)) {
- g_main_loop_quit(ctx->loop);
- break;
- }
- /* NOTE: pkt.data is now implicitly NULL terminated regardless of
- * whether or not the plaintext inside the JWE was terminated. */
- udisks_encrypted_call_unlock_sync(enc, pkt.data, options,
- NULL, NULL, NULL);
- memset(&pkt, 0, sizeof(pkt));
- }
- error:
- g_list_free_full(ctx->lst, g_free);
- g_variant_unref(options);
- ctx->lst = NULL;
- return FALSE;
- }
- static void
- oadd(GDBusObjectManager *mgr, GDBusObject *obj, gpointer misc)
- {
- struct context *ctx = misc;
- UDisksObject *uobj = NULL;
- const char *path = NULL;
- const char *back = NULL;
- UDisksBlock *ct = NULL;
- UDisksBlock *pt = NULL;
- GList *tmp = NULL;
- char *ptmp = NULL;
- path = g_dbus_object_get_object_path(obj);
- if (!path)
- return;
- uobj = udisks_client_peek_object(ctx->clt, path);
- if (!uobj)
- return;
- ct = udisks_object_peek_block(uobj);
- if (!ct)
- return;
- back = udisks_block_get_crypto_backing_device(ct);
- if (back)
- remove_path(&ctx->lst, back);
- if (!udisks_block_get_hint_auto(ct))
- return;
- if (!udisks_object_peek_encrypted(uobj))
- return;
- pt = udisks_client_get_cleartext_block(ctx->clt, ct);
- if (pt) {
- g_object_unref(pt);
- return;
- }
- ptmp = g_strdup(path);
- if (!ptmp)
- return;
- tmp = g_list_prepend(ctx->lst, ptmp);
- if (!tmp) {
- g_free(ptmp);
- return;
- }
- ctx->lst = tmp;
- g_idle_add(idle, ctx);
- }
- static void
- orem(GDBusObjectManager *mgr, GDBusObject *obj, gpointer misc)
- {
- struct context *ctx = misc;
- remove_path(&ctx->lst, g_dbus_object_get_object_path(obj));
- }
- static gboolean
- sockerr(gint fd, GIOCondition cond, gpointer misc)
- {
- struct context *ctx = misc;
- close(fd);
- g_main_loop_quit(ctx->loop);
- return FALSE;
- }
- static int
- child_main(int sock)
- {
- struct context ctx = { .sock = sock };
- int exit_status = EXIT_FAILURE;
- GDBusObjectManager *mgr = NULL;
- gulong id = 0;
- ctx.loop = g_main_loop_new(NULL, FALSE);
- if (!ctx.loop)
- goto error;
- ctx.clt = udisks_client_new_sync(NULL, NULL);
- if (!ctx.clt)
- goto error;
- mgr = udisks_client_get_object_manager(ctx.clt);
- if (!mgr)
- goto error;
- id = g_signal_connect(mgr, "object-added", G_CALLBACK(oadd), &ctx);
- if (id == 0)
- goto error;
- id = g_signal_connect(mgr, "object-removed", G_CALLBACK(orem), &ctx);
- if (id == 0)
- goto error;
- id = g_unix_fd_add(sock, G_IO_ERR, sockerr, &ctx);
- if (id == 0)
- goto error;
- g_main_loop_run(ctx.loop);
- exit_status = EXIT_SUCCESS;
- error:
- g_list_free_full(ctx.lst, g_free);
- g_main_loop_unref(ctx.loop);
- g_object_unref(ctx.clt);
- close(sock);
- return exit_status;
- }
- /*
- * ==========================================================================
- * Caution, code below this point runs with euid = 0!
- * ==========================================================================
- */
- static int pair[2] = { -1, -1 };
- pid_t pid = 0;
- static void
- safeclose(int *fd)
- {
- if (*fd >= 0)
- close(*fd);
- *fd = -1;
- }
- static void
- on_signal(int sig)
- {
- if (sig == SIGCHLD) {
- if (wait(NULL) != pid)
- return;
- pid = -1;
- }
- safeclose(&pair[0]);
- }
- static ssize_t
- recover_key(const pkt_t *jwe, char *out, size_t max, uid_t uid, gid_t gid)
- {
- int push[2] = { -1, -1 };
- int pull[2] = { -1, -1 };
- ssize_t bytes = 0;
- pid_t chld = 0;
- if (pipe(push) != 0)
- goto error;
- if (pipe(pull) != 0)
- goto error;
- chld = fork();
- if (chld < 0) {
- perror("fork");
- goto error;
- }
- if (chld == 0) {
- char *const env[] = { "PATH=" BINDIR, NULL };
- int r = 0;
- if (geteuid() != 0) {
- if (setgroups(1, &gid) != 0) {
- perror("setgroups");
- /* Can fail if missing permissions */
- }
- }
- if (initgroups(CLEVIS_USER, gid) != 0) {
- perror("initgroups");
- exit(EXIT_FAILURE);
- }
- if (setgid(gid) != 0) {
- perror("setgid");
- exit(EXIT_FAILURE);
- }
- if (setuid(uid) != 0) {
- perror("setuid");
- exit(EXIT_FAILURE);
- }
- r = dup2(push[PIPE_RD], STDIN_FILENO);
- if (r != STDIN_FILENO) {
- perror("dup2");
- exit(EXIT_FAILURE);
- }
- r = dup2(pull[PIPE_WR], STDOUT_FILENO);
- if (r != STDOUT_FILENO) {
- perror("dup2");
- exit(EXIT_FAILURE);
- }
- safeclose(&push[PIPE_RD]);
- safeclose(&push[PIPE_WR]);
- safeclose(&pull[PIPE_RD]);
- safeclose(&pull[PIPE_WR]);
- execle(BINDIR "/clevis", "clevis", "decrypt", NULL, env);
- perror("execle");
- exit(EXIT_FAILURE);
- }
- safeclose(&push[PIPE_RD]);
- safeclose(&pull[PIPE_WR]);
- bytes = write(push[PIPE_WR], jwe->data, jwe->used);
- safeclose(&push[PIPE_WR]);
- if (bytes < 0 || bytes != jwe->used) {
- errno = errno == 0 ? EIO : errno;
- kill(chld, SIGTERM);
- goto error;
- }
- bytes = 0;
- for (ssize_t block = 1; block > 0; bytes += block) {
- block = read(pull[PIPE_RD], &out[bytes], max - bytes);
- if (block < 0) {
- kill(chld, SIGTERM);
- goto error;
- }
- }
- safeclose(&pull[PIPE_RD]);
- return bytes;
- error:
- safeclose(&push[PIPE_RD]);
- safeclose(&push[PIPE_WR]);
- safeclose(&pull[PIPE_RD]);
- safeclose(&pull[PIPE_WR]);
- return -errno;
- }
- static bool
- log_attempt(int log, struct crypt_device *cd, bool success)
- {
- const char *uuid = NULL;
- char msg[4096] = {};
- char *dev = NULL;
- int r = 0;
- uuid = crypt_get_uuid(cd);
- if (!uuid)
- return false;
- dev = audit_encode_nv_string("device", crypt_get_device_name(cd), 0);
- if (!dev)
- return false;
- r = snprintf(msg, sizeof(msg),
- "op=recovered-key-for uuid=%s %s",
- uuid, dev);
- free(dev);
- if (r < 0 || r == sizeof(msg))
- return false;
- return audit_log_user_message(log, AUDIT_USER_DEVICE, msg,
- NULL, NULL, NULL, success) > 0;
- }
- static const char *sopts = "hu:g:";
- static const struct option lopts[] = {
- { "help", no_argument, .val = 'h' },
- { "user", required_argument, .val = 'u' },
- { "group", required_argument, .val = 'g' },
- {}
- };
- static uid_t
- usr2uid(const char *usr)
- {
- const struct passwd *tmp = getpwnam(usr);
- return tmp ? tmp->pw_uid : UERR;
- }
- static gid_t
- grp2gid(const char *grp)
- {
- const struct group *tmp = getgrnam(grp);
- return tmp ? tmp->gr_gid : GERR;
- }
- static bool
- token_to_jwe(const char *json, pkt_t *pkt)
- {
- json_auto_t *tokn = NULL;
- const json_t *jwe = NULL;
- const char *prt = NULL;
- const char *key = NULL;
- const char *tag = NULL;
- const char *iv = NULL;
- const char *ct = NULL;
- tokn = json_loads(json, 0, NULL);
- if (!tokn)
- return false;
- jwe = json_object_get(tokn, "jwe");
- if (!jwe)
- return false;
- if (json_unpack((json_t *) jwe, "{s:s,s:s,s:s,s:s,s:s}",
- "protected", &prt, "encrypted_key", &key, "iv", &iv,
- "ciphertext", &ct, "tag", &tag) < 0)
- return false;
- pkt->used = snprintf(pkt->data, sizeof(pkt->data),
- "%s.%s.%s.%s.%s", prt, key, iv, ct, tag);
- if (pkt->used < 0 || (size_t) pkt->used > sizeof(pkt->data))
- return false;
- pkt->used--; /* Remove null terminator. */
- return true;
- }
- int
- main(int argc, char *const argv[])
- {
- gid_t recg = grp2gid(CLEVIS_GROUP); /* Recovery group */
- uid_t recu = usr2uid(CLEVIS_USER); /* Recovery user */
- gid_t unlg = getgid(); /* Unlock group */
- uid_t unlu = getuid(); /* Unlock user */
- int log = -1;
- if (recu == UERR) {
- fprintf(stderr, "Invalid user name '%s'!\n", CLEVIS_USER);
- return EXIT_FAILURE;
- }
- if (recg == GERR) {
- fprintf(stderr, "Invalid group name '%s'!\n", CLEVIS_GROUP);
- return EXIT_FAILURE;
- }
- if (geteuid() != 0) {
- fprintf(stderr, "Root privileges required!\n");
- return EXIT_FAILURE;
- }
- for (int c; (c = getopt_long(argc, argv, sopts, lopts, NULL)) >= 0; ) {
- switch (c) {
- case 'u':
- if (getuid() != 0) {
- fprintf(stderr, "You can only specify the user as root!\n");
- return EXIT_FAILURE;
- }
- unlu = usr2uid(optarg);
- if (unlu == 0 || unlu == UERR) {
- fprintf(stderr, "Invalid user name '%s'!\n", optarg);
- return EXIT_FAILURE;
- }
- break;
- case 'g':
- if (getuid() != 0) {
- fprintf(stderr, "You can only specify the group as root!\n");
- return EXIT_FAILURE;
- }
- unlg = grp2gid(optarg);
- if (unlg == 0 || unlg == GERR) {
- fprintf(stderr, "Invalid group name '%s'!\n", optarg);
- return EXIT_FAILURE;
- }
- break;
- default:
- fprintf(stderr, "Usage: clevis-luks-udisks2 [-u USER -g GROUP]\n");
- return EXIT_FAILURE;
- }
- }
- if (unlu == 0 || unlg == 0) {
- fprintf(stderr, "Either run as SETUID=root or use -u/-g!\n");
- return EXIT_FAILURE;
- }
- if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) == -1)
- return EXIT_FAILURE;
- pid = fork();
- if (pid < 0) {
- safeclose(&pair[0]);
- safeclose(&pair[1]);
- return EXIT_FAILURE;
- }
- if (pid == 0) {
- int status = EXIT_FAILURE;
- safeclose(&pair[0]);
- if (setgid(unlg) == 0 && setegid(unlg) == 0 &&
- setuid(unlu) == 0 && seteuid(unlu) == 0)
- status = child_main(pair[1]);
- safeclose(&pair[1]);
- return status;
- }
- safeclose(&pair[1]);
- signal(SIGHUP, on_signal);
- signal(SIGINT, on_signal);
- signal(SIGPIPE, on_signal);
- signal(SIGTERM, on_signal);
- signal(SIGUSR1, on_signal);
- signal(SIGUSR2, on_signal);
- signal(SIGCHLD, on_signal);
- if (setgid(0) == -1 || setegid(0) == -1 ||
- setuid(0) == -1 || seteuid(0) == -1)
- goto error;
- log = audit_open();
- if (log < 0)
- goto error;
- for (pkt_t req = {}, jwe = {}, key = {}; ; key = (pkt_t) {}) {
- struct crypt_device *cd = NULL;
- /* Receive a request. Ensure that it is null terminated. */
- req.used = recv(pair[0], req.data, sizeof(req.data), 0);
- if (req.used < 1 || req.data[req.used - 1])
- break;
- if (crypt_init(&cd, req.data) < 0)
- goto next;
- if (crypt_load(cd, CRYPT_LUKS1, NULL) >= 0) {
- const int slotlen = crypt_keyslot_max(CRYPT_LUKS1);
- luksmeta_uuid_t uuid = {};
- for (uint8_t s = 0; s < slotlen && key.used <= 0; s++) {
- fprintf(stderr, "%s\tSLOT\t%hhu\n", req.data, s);
- switch (crypt_keyslot_status(cd, s)) {
- case CRYPT_SLOT_ACTIVE:
- case CRYPT_SLOT_ACTIVE_LAST:
- break;
- default:
- continue;
- }
- jwe.used = luksmeta_load(cd, s, uuid, jwe.data, sizeof(jwe.data));
- fprintf(stderr, "%s\tMETA\t%s\n",
- req.data, strerror(jwe.used < 0 ? -jwe.used : 0));
- if (jwe.used <= 0)
- continue;
- fprintf(stderr, "%s\tUUID\t" UUID_TMPL "\n",
- req.data, UUID_ARGS(uuid));
- if (memcmp(uuid, CLEVIS_LUKS_UUID, sizeof(uuid)) != 0)
- continue;
- /* Recover the key from the JWE. */
- key.used = recover_key(&jwe, key.data, sizeof(key.data), recu, recg);
- fprintf(stderr, "%s\tRCVR\t%s (%zd)\n", req.data,
- strerror(key.used < 0 ? -key.used : 0), key.used);
- }
- } else if (crypt_load(cd, CRYPT_LUKS2, NULL) >= 0) {
- for (int t = 0; key.used <= 0; t++) {
- const char *json = NULL;
- const char *type = NULL;
- int r = 0;
- r = crypt_token_status(cd, t, &type);
- if (r == CRYPT_TOKEN_INVALID)
- break;
- else if (r != CRYPT_TOKEN_EXTERNAL_UNKNOWN)
- continue;
- fprintf(stderr, "%s\tTOKN\t%d\t%s\n", req.data, t, type);
- if (strcmp(type, "clevis") != 0)
- continue;
- r = crypt_token_json_get(cd, t, &json);
- fprintf(stderr, "%s\tMETA\t%s\n",
- req.data, strerror(r < 0 ? -r : 0));
- if (!token_to_jwe(json, &jwe))
- continue;
- /* Recover the key from the JWE. */
- key.used = recover_key(&jwe, key.data, sizeof(key.data), recu, recg);
- fprintf(stderr, "%s\tRCVR\t%s (%zd)\n", req.data,
- strerror(key.used < 0 ? -key.used : 0), key.used);
- }
- }
- if (key.used < 0)
- key.used = 0;
- /* Don't return the key unless auditing succeeds. */
- if (!log_attempt(log, cd, key.used > 0))
- memset(&key, 0, sizeof(key));
- next:
- crypt_free(cd);
- /* Send the key as a reply. */
- if (send(pair[0], key.data, key.used, 0) != key.used)
- break;
- }
- error:
- safeclose(&log);
- safeclose(&pair[0]);
- if (pid != -1) {
- kill(pid, SIGTERM);
- waitpid(pid, NULL, 0);
- }
- return EXIT_FAILURE;
- }
|