summaryrefslogtreecommitdiffstats
path: root/lib/luks2/luks2_token.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/luks2/luks2_token.c')
-rw-r--r--lib/luks2/luks2_token.c650
1 files changed, 650 insertions, 0 deletions
diff --git a/lib/luks2/luks2_token.c b/lib/luks2/luks2_token.c
new file mode 100644
index 0000000..9d0a889
--- /dev/null
+++ b/lib/luks2/luks2_token.c
@@ -0,0 +1,650 @@
+/*
+ * LUKS - Linux Unified Key Setup v2, token handling
+ *
+ * Copyright (C) 2016-2021 Red Hat, Inc. All rights reserved.
+ * Copyright (C) 2016-2021 Milan Broz
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <assert.h>
+
+#include "luks2_internal.h"
+
+/* Builtin tokens */
+extern const crypt_token_handler keyring_handler;
+
+static token_handler token_handlers[LUKS2_TOKENS_MAX] = {
+ /* keyring builtin token */
+ {
+ .get = token_keyring_get,
+ .set = token_keyring_set,
+ .h = &keyring_handler
+ },
+};
+
+static int is_builtin_candidate(const char *type)
+{
+ return !strncmp(type, LUKS2_BUILTIN_TOKEN_PREFIX, LUKS2_BUILTIN_TOKEN_PREFIX_LEN);
+}
+
+int crypt_token_register(const crypt_token_handler *handler)
+{
+ int i;
+
+ if (is_builtin_candidate(handler->name)) {
+ log_dbg(NULL, "'" LUKS2_BUILTIN_TOKEN_PREFIX "' is reserved prefix for builtin tokens.");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < LUKS2_TOKENS_MAX && token_handlers[i].h; i++) {
+ if (!strcmp(token_handlers[i].h->name, handler->name)) {
+ log_dbg(NULL, "Keyslot handler %s is already registered.", handler->name);
+ return -EINVAL;
+ }
+ }
+
+ if (i == LUKS2_TOKENS_MAX)
+ return -EINVAL;
+
+ token_handlers[i].h = handler;
+ return 0;
+}
+
+static const token_handler
+*LUKS2_token_handler_type_internal(struct crypt_device *cd, const char *type)
+{
+ int i;
+
+ for (i = 0; i < LUKS2_TOKENS_MAX && token_handlers[i].h; i++)
+ if (!strcmp(token_handlers[i].h->name, type))
+ return token_handlers + i;
+
+ return NULL;
+}
+
+static const crypt_token_handler
+*LUKS2_token_handler_type(struct crypt_device *cd, const char *type)
+{
+ const token_handler *th = LUKS2_token_handler_type_internal(cd, type);
+
+ return th ? th->h : NULL;
+}
+
+static const token_handler
+*LUKS2_token_handler_internal(struct crypt_device *cd, int token)
+{
+ struct luks2_hdr *hdr;
+ json_object *jobj1, *jobj2;
+
+ if (token < 0)
+ return NULL;
+
+ if (!(hdr = crypt_get_hdr(cd, CRYPT_LUKS2)))
+ return NULL;
+
+ if (!(jobj1 = LUKS2_get_token_jobj(hdr, token)))
+ return NULL;
+
+ if (!json_object_object_get_ex(jobj1, "type", &jobj2))
+ return NULL;
+
+ return LUKS2_token_handler_type_internal(cd, json_object_get_string(jobj2));
+}
+
+static const crypt_token_handler
+*LUKS2_token_handler(struct crypt_device *cd, int token)
+{
+ const token_handler *th = LUKS2_token_handler_internal(cd, token);
+
+ return th ? th->h : NULL;
+}
+
+static int LUKS2_token_find_free(struct luks2_hdr *hdr)
+{
+ int i;
+
+ for (i = 0; i < LUKS2_TOKENS_MAX; i++)
+ if (!LUKS2_get_token_jobj(hdr, i))
+ return i;
+
+ return -EINVAL;
+}
+
+int LUKS2_token_create(struct crypt_device *cd,
+ struct luks2_hdr *hdr,
+ int token,
+ const char *json,
+ int commit)
+{
+ const crypt_token_handler *h;
+ const token_handler *th;
+ json_object *jobj_tokens, *jobj_type, *jobj;
+ enum json_tokener_error jerr;
+ char num[16];
+
+ if (token == CRYPT_ANY_TOKEN) {
+ if (!json)
+ return -EINVAL;
+ token = LUKS2_token_find_free(hdr);
+ }
+
+ if (token < 0 || token >= LUKS2_TOKENS_MAX)
+ return -EINVAL;
+
+ if (!json_object_object_get_ex(hdr->jobj, "tokens", &jobj_tokens))
+ return -EINVAL;
+
+ if (snprintf(num, sizeof(num), "%d", token) < 0)
+ return -EINVAL;
+
+ /* Remove token */
+ if (!json)
+ json_object_object_del(jobj_tokens, num);
+ else {
+
+ jobj = json_tokener_parse_verbose(json, &jerr);
+ if (!jobj) {
+ log_dbg(cd, "Token JSON parse failed.");
+ return -EINVAL;
+ }
+
+ if (LUKS2_token_validate(cd, hdr->jobj, jobj, num)) {
+ json_object_put(jobj);
+ return -EINVAL;
+ }
+
+ json_object_object_get_ex(jobj, "type", &jobj_type);
+ if (is_builtin_candidate(json_object_get_string(jobj_type))) {
+ th = LUKS2_token_handler_type_internal(cd, json_object_get_string(jobj_type));
+ if (!th || !th->set) {
+ log_dbg(cd, "%s is builtin token candidate with missing handler", json_object_get_string(jobj_type));
+ json_object_put(jobj);
+ return -EINVAL;
+ }
+ h = th->h;
+ } else
+ h = LUKS2_token_handler_type(cd, json_object_get_string(jobj_type));
+
+ if (h && h->validate && h->validate(cd, json)) {
+ json_object_put(jobj);
+ log_dbg(cd, "Token type %s validation failed.", h->name);
+ return -EINVAL;
+ }
+
+ json_object_object_add(jobj_tokens, num, jobj);
+ if (LUKS2_check_json_size(cd, hdr)) {
+ log_dbg(cd, "Not enough space in header json area for new token.");
+ json_object_object_del(jobj_tokens, num);
+ return -ENOSPC;
+ }
+ }
+
+ if (commit)
+ return LUKS2_hdr_write(cd, hdr) ?: token;
+
+ return token;
+}
+
+crypt_token_info LUKS2_token_status(struct crypt_device *cd,
+ struct luks2_hdr *hdr,
+ int token,
+ const char **type)
+{
+ const char *tmp;
+ const token_handler *th;
+ json_object *jobj_type, *jobj_token;
+
+ if (token < 0 || token >= LUKS2_TOKENS_MAX)
+ return CRYPT_TOKEN_INVALID;
+
+ if (!(jobj_token = LUKS2_get_token_jobj(hdr, token)))
+ return CRYPT_TOKEN_INACTIVE;
+
+ json_object_object_get_ex(jobj_token, "type", &jobj_type);
+ tmp = json_object_get_string(jobj_type);
+
+ if ((th = LUKS2_token_handler_type_internal(cd, tmp))) {
+ if (type)
+ *type = th->h->name;
+ return th->set ? CRYPT_TOKEN_INTERNAL : CRYPT_TOKEN_EXTERNAL;
+ }
+
+ if (type)
+ *type = tmp;
+
+ return is_builtin_candidate(tmp) ? CRYPT_TOKEN_INTERNAL_UNKNOWN : CRYPT_TOKEN_EXTERNAL_UNKNOWN;
+}
+
+int LUKS2_builtin_token_get(struct crypt_device *cd,
+ struct luks2_hdr *hdr,
+ int token,
+ const char *type,
+ void *params)
+{
+ const token_handler *th = LUKS2_token_handler_type_internal(cd, type);
+
+ // internal error
+ assert(th && th->get);
+
+ return th->get(LUKS2_get_token_jobj(hdr, token), params) ?: token;
+}
+
+int LUKS2_builtin_token_create(struct crypt_device *cd,
+ struct luks2_hdr *hdr,
+ int token,
+ const char *type,
+ const void *params,
+ int commit)
+{
+ const token_handler *th;
+ int r;
+ json_object *jobj_token, *jobj_tokens;
+
+ th = LUKS2_token_handler_type_internal(cd, type);
+
+ // at this point all builtin handlers must exist and have validate fn defined
+ assert(th && th->set && th->h->validate);
+
+ if (token == CRYPT_ANY_TOKEN) {
+ if ((token = LUKS2_token_find_free(hdr)) < 0)
+ log_err(cd, _("No free token slot."));
+ }
+ if (token < 0 || token >= LUKS2_TOKENS_MAX)
+ return -EINVAL;
+
+ r = th->set(&jobj_token, params);
+ if (r) {
+ log_err(cd, _("Failed to create builtin token %s."), type);
+ return r;
+ }
+
+ // builtin tokens must produce valid json
+ r = LUKS2_token_validate(cd, hdr->jobj, jobj_token, "new");
+ assert(!r);
+ r = th->h->validate(cd, json_object_to_json_string_ext(jobj_token,
+ JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE));
+ assert(!r);
+
+ json_object_object_get_ex(hdr->jobj, "tokens", &jobj_tokens);
+ json_object_object_add_by_uint(jobj_tokens, token, jobj_token);
+ if (LUKS2_check_json_size(cd, hdr)) {
+ log_dbg(cd, "Not enough space in header json area for new %s token.", type);
+ json_object_object_del_by_uint(jobj_tokens, token);
+ return -ENOSPC;
+ }
+
+ if (commit)
+ return LUKS2_hdr_write(cd, hdr) ?: token;
+
+ return token;
+}
+
+static int LUKS2_token_open(struct crypt_device *cd,
+ struct luks2_hdr *hdr,
+ int token,
+ char **buffer,
+ size_t *buffer_len,
+ void *usrptr)
+{
+ const char *json;
+ const crypt_token_handler *h;
+ int r;
+
+ if (!(h = LUKS2_token_handler(cd, token)))
+ return -ENOENT;
+
+ if (h->validate) {
+ if (LUKS2_token_json_get(cd, hdr, token, &json))
+ return -EINVAL;
+
+ if (h->validate(cd, json)) {
+ log_dbg(cd, "Token %d (%s) validation failed.", token, h->name);
+ return -EINVAL;
+ }
+ }
+
+ r = h->open(cd, token, buffer, buffer_len, usrptr);
+ if (r < 0)
+ log_dbg(cd, "Token %d (%s) open failed with %d.", token, h->name, r);
+
+ return r;
+}
+
+static void LUKS2_token_buffer_free(struct crypt_device *cd,
+ int token,
+ void *buffer,
+ size_t buffer_len)
+{
+ const crypt_token_handler *h = LUKS2_token_handler(cd, token);
+
+ if (h && h->buffer_free)
+ h->buffer_free(buffer, buffer_len);
+ else {
+ crypt_safe_memzero(buffer, buffer_len);
+ free(buffer);
+ }
+}
+
+static int LUKS2_keyslot_open_by_token(struct crypt_device *cd,
+ struct luks2_hdr *hdr,
+ int token,
+ int segment,
+ const char *buffer,
+ size_t buffer_len,
+ struct volume_key **vk)
+{
+ const crypt_token_handler *h;
+ json_object *jobj_token, *jobj_token_keyslots, *jobj;
+ unsigned int num = 0;
+ int i, r;
+
+ if (!(h = LUKS2_token_handler(cd, token)))
+ return -ENOENT;
+
+ jobj_token = LUKS2_get_token_jobj(hdr, token);
+ if (!jobj_token)
+ return -EINVAL;
+
+ json_object_object_get_ex(jobj_token, "keyslots", &jobj_token_keyslots);
+ if (!jobj_token_keyslots)
+ return -EINVAL;
+
+ /* Try to open keyslot referenced in token */
+ r = -EINVAL;
+ for (i = 0; i < (int) json_object_array_length(jobj_token_keyslots) && r < 0; i++) {
+ jobj = json_object_array_get_idx(jobj_token_keyslots, i);
+ num = atoi(json_object_get_string(jobj));
+ log_dbg(cd, "Trying to open keyslot %u with token %d (type %s).", num, token, h->name);
+ r = LUKS2_keyslot_open(cd, num, segment, buffer, buffer_len, vk);
+ }
+
+ if (r < 0)
+ return r;
+
+ return num;
+}
+
+int LUKS2_token_open_and_activate(struct crypt_device *cd,
+ struct luks2_hdr *hdr,
+ int token,
+ const char *name,
+ uint32_t flags,
+ void *usrptr)
+{
+ bool use_keyring;
+ int keyslot, r;
+ char *buffer;
+ size_t buffer_len;
+ struct volume_key *vk = NULL;
+
+ r = LUKS2_token_open(cd, hdr, token, &buffer, &buffer_len, usrptr);
+ if (r < 0)
+ return r;
+
+ r = LUKS2_keyslot_open_by_token(cd, hdr, token,
+ (flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY) ?
+ CRYPT_ANY_SEGMENT : CRYPT_DEFAULT_SEGMENT,
+ buffer, buffer_len, &vk);
+
+ LUKS2_token_buffer_free(cd, token, buffer, buffer_len);
+
+ if (r < 0)
+ return r;
+
+ keyslot = r;
+
+ if (!crypt_use_keyring_for_vk(cd))
+ use_keyring = false;
+ else
+ use_keyring = ((name && !crypt_is_cipher_null(crypt_get_cipher(cd))) ||
+ (flags & CRYPT_ACTIVATE_KEYRING_KEY));
+
+ if (use_keyring) {
+ if (!(r = LUKS2_volume_key_load_in_keyring_by_keyslot(cd, hdr, vk, keyslot)))
+ flags |= CRYPT_ACTIVATE_KEYRING_KEY;
+ }
+
+ if (r >= 0 && name)
+ r = LUKS2_activate(cd, name, vk, flags);
+
+ if (r < 0)
+ crypt_drop_keyring_key(cd, vk);
+ crypt_free_volume_key(vk);
+
+ return r < 0 ? r : keyslot;
+}
+
+int LUKS2_token_open_and_activate_any(struct crypt_device *cd,
+ struct luks2_hdr *hdr,
+ const char *name,
+ uint32_t flags)
+{
+ char *buffer;
+ json_object *tokens_jobj;
+ size_t buffer_len;
+ int keyslot, token, r = -EINVAL;
+ struct volume_key *vk = NULL;
+
+ json_object_object_get_ex(hdr->jobj, "tokens", &tokens_jobj);
+
+ json_object_object_foreach(tokens_jobj, slot, val) {
+ UNUSED(val);
+ token = atoi(slot);
+
+ r = LUKS2_token_open(cd, hdr, token, &buffer, &buffer_len, NULL);
+ if (r < 0)
+ continue;
+
+ r = LUKS2_keyslot_open_by_token(cd, hdr, token,
+ (flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY) ?
+ CRYPT_ANY_SEGMENT : CRYPT_DEFAULT_SEGMENT,
+ buffer, buffer_len, &vk);
+ LUKS2_token_buffer_free(cd, token, buffer, buffer_len);
+ if (r >= 0)
+ break;
+ }
+
+ keyslot = r;
+
+ if (r >= 0 && (name || (flags & CRYPT_ACTIVATE_KEYRING_KEY)) && crypt_use_keyring_for_vk(cd)) {
+ if (!(r = LUKS2_volume_key_load_in_keyring_by_keyslot(cd, hdr, vk, keyslot)))
+ flags |= CRYPT_ACTIVATE_KEYRING_KEY;
+ }
+
+ if (r >= 0 && name)
+ r = LUKS2_activate(cd, name, vk, flags);
+
+ if (r < 0)
+ crypt_drop_keyring_key(cd, vk);
+ crypt_free_volume_key(vk);
+
+ return r < 0 ? r : keyslot;
+}
+
+void LUKS2_token_dump(struct crypt_device *cd, int token)
+{
+ const crypt_token_handler *h;
+ json_object *jobj_token;
+
+ h = LUKS2_token_handler(cd, token);
+ if (h && h->dump) {
+ jobj_token = LUKS2_get_token_jobj(crypt_get_hdr(cd, CRYPT_LUKS2), token);
+ if (jobj_token)
+ h->dump(cd, json_object_to_json_string_ext(jobj_token,
+ JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE));
+ }
+}
+
+int LUKS2_token_json_get(struct crypt_device *cd, struct luks2_hdr *hdr,
+ int token, const char **json)
+{
+ json_object *jobj_token;
+
+ jobj_token = LUKS2_get_token_jobj(hdr, token);
+ if (!jobj_token)
+ return -EINVAL;
+
+ *json = json_object_to_json_string_ext(jobj_token,
+ JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE);
+ return 0;
+}
+
+static int assign_one_keyslot(struct crypt_device *cd, struct luks2_hdr *hdr,
+ int token, int keyslot, int assign)
+{
+ json_object *jobj1, *jobj_token, *jobj_token_keyslots;
+ char num[16];
+
+ log_dbg(cd, "Keyslot %i %s token %i.", keyslot, assign ? "assigned to" : "unassigned from", token);
+
+ jobj_token = LUKS2_get_token_jobj(hdr, token);
+ if (!jobj_token)
+ return -EINVAL;
+
+ json_object_object_get_ex(jobj_token, "keyslots", &jobj_token_keyslots);
+ if (!jobj_token_keyslots)
+ return -EINVAL;
+
+ if (snprintf(num, sizeof(num), "%d", keyslot) < 0)
+ return -EINVAL;
+
+ if (assign) {
+ jobj1 = LUKS2_array_jobj(jobj_token_keyslots, num);
+ if (!jobj1)
+ json_object_array_add(jobj_token_keyslots, json_object_new_string(num));
+ } else {
+ jobj1 = LUKS2_array_remove(jobj_token_keyslots, num);
+ if (jobj1)
+ json_object_object_add(jobj_token, "keyslots", jobj1);
+ }
+
+ return 0;
+}
+
+static int assign_one_token(struct crypt_device *cd, struct luks2_hdr *hdr,
+ int keyslot, int token, int assign)
+{
+ json_object *jobj_keyslots;
+ int r = 0;
+
+ if (!LUKS2_get_token_jobj(hdr, token))
+ return -EINVAL;
+
+ if (keyslot == CRYPT_ANY_SLOT) {
+ json_object_object_get_ex(hdr->jobj, "keyslots", &jobj_keyslots);
+
+ json_object_object_foreach(jobj_keyslots, key, val) {
+ UNUSED(val);
+ r = assign_one_keyslot(cd, hdr, token, atoi(key), assign);
+ if (r < 0)
+ break;
+ }
+ } else
+ r = assign_one_keyslot(cd, hdr, token, keyslot, assign);
+
+ return r;
+}
+
+int LUKS2_token_assign(struct crypt_device *cd, struct luks2_hdr *hdr,
+ int keyslot, int token, int assign, int commit)
+{
+ json_object *jobj_tokens;
+ int r = 0;
+
+ if (token == CRYPT_ANY_TOKEN) {
+ json_object_object_get_ex(hdr->jobj, "tokens", &jobj_tokens);
+
+ json_object_object_foreach(jobj_tokens, key, val) {
+ UNUSED(val);
+ r = assign_one_token(cd, hdr, keyslot, atoi(key), assign);
+ if (r < 0)
+ break;
+ }
+ } else
+ r = assign_one_token(cd, hdr, keyslot, token, assign);
+
+ if (r < 0)
+ return r;
+
+ // FIXME: do not write header in nothing changed
+ if (commit)
+ return LUKS2_hdr_write(cd, hdr) ?: token;
+
+ return token;
+}
+
+static int token_is_assigned(struct luks2_hdr *hdr, int keyslot, int token)
+{
+ int i;
+ json_object *jobj, *jobj_token_keyslots,
+ *jobj_token = LUKS2_get_token_jobj(hdr, token);
+
+ if (!jobj_token)
+ return -ENOENT;
+
+ json_object_object_get_ex(jobj_token, "keyslots", &jobj_token_keyslots);
+
+ for (i = 0; i < (int) json_object_array_length(jobj_token_keyslots); i++) {
+ jobj = json_object_array_get_idx(jobj_token_keyslots, i);
+ if (keyslot == atoi(json_object_get_string(jobj)))
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+int LUKS2_token_is_assigned(struct crypt_device *cd, struct luks2_hdr *hdr,
+ int keyslot, int token)
+{
+ if (keyslot < 0 || keyslot >= LUKS2_KEYSLOTS_MAX || token < 0 || token >= LUKS2_TOKENS_MAX)
+ return -EINVAL;
+
+ return token_is_assigned(hdr, keyslot, token);
+}
+
+int LUKS2_tokens_count(struct luks2_hdr *hdr)
+{
+ json_object *jobj_tokens = LUKS2_get_tokens_jobj(hdr);
+ if (!jobj_tokens)
+ return -EINVAL;
+
+ return json_object_object_length(jobj_tokens);
+}
+
+int LUKS2_token_assignment_copy(struct crypt_device *cd,
+ struct luks2_hdr *hdr,
+ int keyslot_from,
+ int keyslot_to,
+ int commit)
+{
+ int i, r;
+
+ if (keyslot_from < 0 || keyslot_from >= LUKS2_KEYSLOTS_MAX || keyslot_to < 0 || keyslot_to >= LUKS2_KEYSLOTS_MAX)
+ return -EINVAL;
+
+ r = LUKS2_tokens_count(hdr);
+ if (r <= 0)
+ return r;
+
+ for (i = 0; i < LUKS2_TOKENS_MAX; i++) {
+ if (!token_is_assigned(hdr, keyslot_from, i)) {
+ if ((r = assign_one_token(cd, hdr, keyslot_to, i, 1)))
+ return r;
+ }
+ }
+
+ return commit ? LUKS2_hdr_write(cd, hdr) : 0;
+}