diff --git a/Makefile.am b/Makefile.am index de0d2a22..f5638316 100644 --- a/Makefile.am +++ b/Makefile.am @@ -35,6 +35,7 @@ include lib/crypto_backend/Makemodule.am include lib/Makemodule.am include src/Makemodule.am +include tokens/Makemodule.am ACLOCAL_AMFLAGS = -I m4 diff --git a/configure.ac b/configure.ac index 874627b2..b97e0bd1 100644 --- a/configure.ac +++ b/configure.ac @@ -372,6 +372,9 @@ PKG_CHECK_MODULES([JSON_C], [json-c]) AC_CHECK_DECLS([json_object_object_add_ex], [], [], [#include ]) AC_CHECK_DECLS([json_object_deep_copy], [], [], [#include ]) +PKG_CHECK_MODULES([TSS2_ESYS], [tss2-esys]) +PKG_CHECK_MODULES([TSS2_RC], [tss2-rc]) + dnl Crypto backend configuration. AC_ARG_WITH([crypto_backend], AS_HELP_STRING([--with-crypto_backend=BACKEND], [crypto backend (gcrypt/openssl/nss/kernel/nettle) [openssl]]), @@ -527,6 +530,8 @@ AC_SUBST([CRYPTO_STATIC_LIBS]) AC_SUBST([JSON_C_LIBS]) AC_SUBST([LIBARGON2_LIBS]) AC_SUBST([BLKID_LIBS]) +AC_SUBST([TSS2_ESYS_LIBS]) +AC_SUBST([TSS2_RC_LIBS]) AC_SUBST([LIBCRYPTSETUP_VERSION]) AC_SUBST([LIBCRYPTSETUP_VERSION_INFO]) diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h index 3942df03..5c099b10 100644 --- a/lib/libcryptsetup.h +++ b/lib/libcryptsetup.h @@ -2100,6 +2100,27 @@ typedef int (*crypt_token_open_func) ( size_t *buffer_len, void *usrptr); +/** + * Token handler open with passphrase/PIN function prototype. + * This function retrieves password from a token and return allocated buffer + * containing this password. This buffer has to be deallocated by calling + * free() function and content should be wiped before deallocation. + * + * @param cd crypt device handle + * @param token token id + * @param pin passphrase (or PIN) to unlock token + * @param buffer returned allocated buffer with password + * @param buffer_len length of the buffer + * @param usrptr user data in @link crypt_activate_by_token @endlink + */ +typedef int (*crypt_token_open_pin_func) ( + struct crypt_device *cd, + int token, + const char *pin, + char **buffer, + size_t *buffer_len, + void *usrptr); + /** * Token handler buffer free function prototype. * This function is used by library to free the buffer with keyslot @@ -2146,6 +2167,7 @@ typedef struct { crypt_token_buffer_free_func buffer_free; /**< token handler buffer_free function (optional) */ crypt_token_validate_func validate; /**< token handler validate function (optional) */ crypt_token_dump_func dump; /**< token handler dump function (optional) */ + crypt_token_open_pin_func open_pin; /**< open with passphrase function (optional) */ } crypt_token_handler; /** @@ -2157,6 +2179,20 @@ typedef struct { */ int crypt_token_register(const crypt_token_handler *handler); +/** ABI version for external token in libcryptsetup-token-.so */ +#define CRYPT_TOKEN_ABI_VERSION1 "CRYPTSETUP_TOKEN_1.0" +/** ABI exported symbol for external token */ +#define CRYPT_TOKEN_ABI_HANDLER "cryptsetup_token_handler" + +/** + * Find external library, load and register token handler + * + * @param name token name to register + * + * @return @e 0 on success or negative errno value otherwise. + */ +int crypt_token_load(const char *name); + /** * Activate device or check key using a token. * @@ -2167,12 +2203,34 @@ int crypt_token_register(const crypt_token_handler *handler); * @param flags activation flags * * @return unlocked key slot number or negative errno otherwise. + * + * @note EAGAIN errno means that token is PIN protected and you should call + * @link crypt_activate_by_pin_token @endlink with PIN */ int crypt_activate_by_token(struct crypt_device *cd, const char *name, int token, void *usrptr, uint32_t flags); + +/** + * Activate device or check key using a token with PIN. + * + * @param cd crypt device handle + * @param name name of device to create, if @e NULL only check token + * @param token requested token to check or CRYPT_ANY_TOKEN to check all + * @param pin passphrase (or PIN) to unlock token + * @param usrptr provided identification in callback + * @param flags activation flags + * + * @return unlocked key slot number or negative errno otherwise. + */ +int crypt_activate_by_pin_token(struct crypt_device *cd, + const char *name, + int token, + const char *pin, + void *usrptr, + uint32_t flags); /** @} */ /** diff --git a/lib/libcryptsetup.sym b/lib/libcryptsetup.sym index 59a998c6..ef46fe4e 100644 --- a/lib/libcryptsetup.sym +++ b/lib/libcryptsetup.sym @@ -50,8 +50,10 @@ CRYPTSETUP_2.0 { crypt_token_unassign_keyslot; crypt_token_is_assigned; crypt_token_register; + crypt_token_load; crypt_activate_by_token; + crypt_activate_by_pin_token; crypt_keyslot_destroy; crypt_activate_by_passphrase; diff --git a/lib/luks2/luks2.h b/lib/luks2/luks2.h index 6ab753a4..629ce8c9 100644 --- a/lib/luks2/luks2.h +++ b/lib/luks2/luks2.h @@ -341,33 +341,30 @@ crypt_token_info LUKS2_token_status(struct crypt_device *cd, int token, const char **type); -int LUKS2_builtin_token_get(struct crypt_device *cd, - struct luks2_hdr *hdr, - int token, - const char *type, - void *params); - -int LUKS2_builtin_token_create(struct crypt_device *cd, +int LUKS2_token_open_and_activate(struct crypt_device *cd, struct luks2_hdr *hdr, int token, - const char *type, - const void *params, - int commit); - -int LUKS2_token_open_and_activate(struct crypt_device *cd, - struct luks2_hdr *hdr, - int token, - const char *name, - uint32_t flags, - void *usrptr); + const char *name, + const char *pin, + uint32_t flags, + void *usrptr); int LUKS2_token_open_and_activate_any(struct crypt_device *cd, struct luks2_hdr *hdr, const char *name, + const char *pin, uint32_t flags); int LUKS2_tokens_count(struct luks2_hdr *hdr); +int LUKS2_token_keyring_get(struct crypt_device *cd, + struct luks2_hdr *hdr, + int token, + struct crypt_token_params_luks2_keyring *keyring_params); + +int LUKS2_token_keyring_json(char *buffer, size_t buffer_size, + const struct crypt_token_params_luks2_keyring *keyring_params); + /* * Generic LUKS2 segment */ diff --git a/lib/luks2/luks2_internal.h b/lib/luks2/luks2_internal.h index b9fec6b5..6406d7fa 100644 --- a/lib/luks2/luks2_internal.h +++ b/lib/luks2/luks2_internal.h @@ -162,23 +162,6 @@ typedef struct { digest_dump_func dump; } digest_handler; -/** - * LUKS2 token handlers (internal use only) - */ -typedef int (*builtin_token_get_func) (json_object *jobj_token, void *params); -typedef int (*builtin_token_set_func) (json_object **jobj_token, const void *params); - -typedef struct { - /* internal only section used by builtin tokens */ - builtin_token_get_func get; - builtin_token_set_func set; - /* public token handler */ - const crypt_token_handler *h; -} token_handler; - -int token_keyring_set(json_object **, const void *); -int token_keyring_get(json_object *, void *); - int LUKS2_find_area_gap(struct crypt_device *cd, struct luks2_hdr *hdr, size_t keylength, uint64_t *area_offset, uint64_t *area_length); int LUKS2_find_area_max_gap(struct crypt_device *cd, struct luks2_hdr *hdr, diff --git a/lib/luks2/luks2_token.c b/lib/luks2/luks2_token.c index ad6722a3..cc1bf13c 100644 --- a/lib/luks2/luks2_token.c +++ b/lib/luks2/luks2_token.c @@ -19,6 +19,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include +#include #include #include "luks2_internal.h" @@ -26,32 +28,74 @@ /* Builtin tokens */ extern const crypt_token_handler keyring_handler; -static token_handler token_handlers[LUKS2_TOKENS_MAX] = { +static const crypt_token_handler *token_handlers[LUKS2_TOKENS_MAX] = { /* keyring builtin token */ - { - .get = token_keyring_get, - .set = token_keyring_set, - .h = &keyring_handler - }, + &keyring_handler, + NULL }; +static const crypt_token_handler +*crypt_token_load_external(const char *name) +{ + const crypt_token_handler *token = NULL; + void *handle; + char *error; + char buf[512]; + int i, r; + + if (!name || strlen(name) > 64) + return NULL; + + for (i = 0; name[i]; i++) + if (!isalnum(name[i])) + return NULL; + + r = snprintf(buf, sizeof(buf), "libcryptsetup-token-%s.so", name); + if (r < 0 || (size_t)r >= sizeof(buf)) + return NULL; + + log_dbg(NULL, "Trying to load %s.", buf); + + handle = dlopen(buf, RTLD_LAZY); + if (!handle) { + log_dbg(NULL, "%s", dlerror()); + return NULL; + } + dlerror(); + + token = dlvsym(handle, CRYPT_TOKEN_ABI_HANDLER, CRYPT_TOKEN_ABI_VERSION1); + error = dlerror(); + if (error) { + log_dbg(NULL, "%s", dlerror()); + dlclose(handle); + return NULL; + } + + if (crypt_token_register(token) < 0) { + dlclose(handle); + return NULL; + } + + return token; +} + 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) +static int crypt_token_find_free(const char *name, int *index) { int i; - if (is_builtin_candidate(handler->name)) { + if (is_builtin_candidate(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); + for (i = 0; i < LUKS2_TOKENS_MAX && token_handlers[i]; i++) { + if (!strcmp(token_handlers[i]->name, name)) { + log_dbg(NULL, "Keyslot handler %s is already registered.", name); return -EINVAL; } } @@ -59,32 +103,57 @@ int crypt_token_register(const crypt_token_handler *handler) if (i == LUKS2_TOKENS_MAX) return -EINVAL; - token_handlers[i].h = handler; + if (index) + *index = i; + return 0; } -static const token_handler -*LUKS2_token_handler_type_internal(struct crypt_device *cd, const char *type) +int crypt_token_register(const crypt_token_handler *handler) { - int i; + int i, r; - for (i = 0; i < LUKS2_TOKENS_MAX && token_handlers[i].h; i++) - if (!strcmp(token_handlers[i].h->name, type)) - return token_handlers + i; + if (!handler->name || !handler->open) + return -EINVAL; + + r = crypt_token_find_free(handler->name, &i); + if (r < 0) + return r; - return NULL; + token_handlers[i] = handler; + return 0; +} + +int crypt_token_load(const char *name) +{ + int i, r; + + r = crypt_token_find_free(name, &i); + if (r < 0) + return r; + + token_handlers[i] = crypt_token_load_external(name); + + return token_handlers[i] ? 0 : -ENOENT; } 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); + int i; - return th ? th->h : NULL; + for (i = 0; i < LUKS2_TOKENS_MAX && token_handlers[i]; i++) + if (!strcmp(token_handlers[i]->name, type)) + return token_handlers[i]; + + if (is_builtin_candidate(type)) + return NULL; + + return crypt_token_load_external(type); } -static const token_handler -*LUKS2_token_handler_internal(struct crypt_device *cd, int token) +static const crypt_token_handler +*LUKS2_token_handler(struct crypt_device *cd, int token) { struct luks2_hdr *hdr; json_object *jobj1, *jobj2; @@ -101,15 +170,7 @@ static const token_handler 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; + return LUKS2_token_handler_type(cd, json_object_get_string(jobj2)); } static int LUKS2_token_find_free(struct luks2_hdr *hdr) @@ -130,7 +191,6 @@ int LUKS2_token_create(struct crypt_device *cd, 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]; @@ -166,16 +226,14 @@ int LUKS2_token_create(struct crypt_device *cd, } 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)); + h = LUKS2_token_handler_type(cd, json_object_get_string(jobj_type)); + + if (is_builtin_candidate(json_object_get_string(jobj_type)) && !h) { + log_dbg(cd, "%s is builtin token candidate with missing handler", + json_object_get_string(jobj_type)); + json_object_put(jobj); + return -EINVAL; + } if (h && h->validate && h->validate(cd, json)) { json_object_put(jobj); @@ -203,7 +261,7 @@ crypt_token_info LUKS2_token_status(struct crypt_device *cd, const char **type) { const char *tmp; - const token_handler *th; + const crypt_token_handler *th; json_object *jobj_type, *jobj_token; if (token < 0 || token >= LUKS2_TOKENS_MAX) @@ -215,10 +273,10 @@ crypt_token_info LUKS2_token_status(struct crypt_device *cd, 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 ((th = LUKS2_token_handler_type(cd, tmp))) { if (type) - *type = th->h->name; - return th->set ? CRYPT_TOKEN_INTERNAL : CRYPT_TOKEN_EXTERNAL; + *type = th->name; + return is_builtin_candidate(tmp) ? CRYPT_TOKEN_INTERNAL : CRYPT_TOKEN_EXTERNAL; } if (type) @@ -227,73 +285,10 @@ crypt_token_info LUKS2_token_status(struct crypt_device *cd, 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, + const char *pin, char **buffer, size_t *buffer_len, void *usrptr) @@ -315,7 +310,12 @@ static int LUKS2_token_open(struct crypt_device *cd, } } - r = h->open(cd, token, buffer, buffer_len, usrptr); + if (pin && !h->open_pin) + r = -ENOENT; + else if (pin) + r = h->open_pin(cd, token, pin, buffer, buffer_len, usrptr); + else + 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); @@ -377,18 +377,19 @@ static int LUKS2_keyslot_open_by_token(struct crypt_device *cd, } int LUKS2_token_open_and_activate(struct crypt_device *cd, - struct luks2_hdr *hdr, - int token, - const char *name, - uint32_t flags, - void *usrptr) + struct luks2_hdr *hdr, + int token, + const char *name, + const char *pin, + uint32_t flags, + void *usrptr) { 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); + r = LUKS2_token_open(cd, hdr, token, pin, &buffer, &buffer_len, usrptr); if (r < 0) return r; @@ -422,6 +423,7 @@ int LUKS2_token_open_and_activate(struct crypt_device *cd, int LUKS2_token_open_and_activate_any(struct crypt_device *cd, struct luks2_hdr *hdr, const char *name, + const char *pin, uint32_t flags) { char *buffer; @@ -436,7 +438,7 @@ int LUKS2_token_open_and_activate_any(struct crypt_device *cd, UNUSED(val); token = atoi(slot); - r = LUKS2_token_open(cd, hdr, token, &buffer, &buffer_len, NULL); + r = LUKS2_token_open(cd, hdr, token, pin, &buffer, &buffer_len, NULL); if (r < 0) continue; diff --git a/lib/luks2/luks2_token_keyring.c b/lib/luks2/luks2_token_keyring.c index 448ad45c..5d9e44de 100644 --- a/lib/luks2/luks2_token_keyring.c +++ b/lib/luks2/luks2_token_keyring.c @@ -111,47 +111,21 @@ static void keyring_dump(struct crypt_device *cd, const char *json) json_object_put(jobj_token); } -int token_keyring_set(json_object **jobj_builtin_token, - const void *params) +int LUKS2_token_keyring_json(char *buffer, size_t buffer_size, + const struct crypt_token_params_luks2_keyring *keyring_params) { - json_object *jobj_token, *jobj; - const struct crypt_token_params_luks2_keyring *keyring_params = (const struct crypt_token_params_luks2_keyring *) params; - - jobj_token = json_object_new_object(); - if (!jobj_token) - return -ENOMEM; - - jobj = json_object_new_string(LUKS2_TOKEN_KEYRING); - if (!jobj) { - json_object_put(jobj_token); - return -ENOMEM; - } - json_object_object_add(jobj_token, "type", jobj); + snprintf(buffer, buffer_size, "{ \"type\": \"%s\", \"keyslots\":[],\"key_description\":\"%s\"}", + LUKS2_TOKEN_KEYRING, keyring_params->key_description); - jobj = json_object_new_array(); - if (!jobj) { - json_object_put(jobj_token); - return -ENOMEM; - } - json_object_object_add(jobj_token, "keyslots", jobj); - - jobj = json_object_new_string(keyring_params->key_description); - if (!jobj) { - json_object_put(jobj_token); - return -ENOMEM; - } - json_object_object_add(jobj_token, "key_description", jobj); - - *jobj_builtin_token = jobj_token; return 0; } -int token_keyring_get(json_object *jobj_token, - void *params) +int LUKS2_token_keyring_get(struct crypt_device *cd, struct luks2_hdr *hdr, int token, + struct crypt_token_params_luks2_keyring *keyring_params) { - json_object *jobj; - struct crypt_token_params_luks2_keyring *keyring_params = (struct crypt_token_params_luks2_keyring *) params; + json_object *jobj_token, *jobj; + jobj_token = LUKS2_get_token_jobj(hdr, token); json_object_object_get_ex(jobj_token, "type", &jobj); assert(!strcmp(json_object_get_string(jobj), LUKS2_TOKEN_KEYRING)); @@ -159,7 +133,7 @@ int token_keyring_get(json_object *jobj_token, keyring_params->key_description = json_object_get_string(jobj); - return 0; + return token; } const crypt_token_handler keyring_handler = { diff --git a/lib/setup.c b/lib/setup.c index b144731f..e31ca668 100644 --- a/lib/setup.c +++ b/lib/setup.c @@ -5524,8 +5524,8 @@ void crypt_set_reenc_context(struct crypt_device *cd, struct luks2_reenc_context /* * Token handling */ -int crypt_activate_by_token(struct crypt_device *cd, - const char *name, int token, void *usrptr, uint32_t flags) +int crypt_activate_by_pin_token(struct crypt_device *cd, const char *name, int token, + const char *pin, void *usrptr, uint32_t flags) { int r; @@ -5542,9 +5542,15 @@ int crypt_activate_by_token(struct crypt_device *cd, return -EINVAL; if (token == CRYPT_ANY_TOKEN) - return LUKS2_token_open_and_activate_any(cd, &cd->u.luks2.hdr, name, flags); + return LUKS2_token_open_and_activate_any(cd, &cd->u.luks2.hdr, name, pin, flags); - return LUKS2_token_open_and_activate(cd, &cd->u.luks2.hdr, token, name, flags, usrptr); + return LUKS2_token_open_and_activate(cd, &cd->u.luks2.hdr, token, name, pin, flags, usrptr); +} + +int crypt_activate_by_token(struct crypt_device *cd, + const char *name, int token, void *usrptr, uint32_t flags) +{ + return crypt_activate_by_pin_token(cd, name, token, NULL, usrptr, flags); } int crypt_token_json_get(struct crypt_device *cd, int token, const char **json) @@ -5617,7 +5623,7 @@ int crypt_token_luks2_keyring_get(struct crypt_device *cd, return -EINVAL; } - return LUKS2_builtin_token_get(cd, &cd->u.luks2.hdr, token, LUKS2_TOKEN_KEYRING, params); + return LUKS2_token_keyring_get(cd, &cd->u.luks2.hdr, token, params); } int crypt_token_luks2_keyring_set(struct crypt_device *cd, @@ -5625,6 +5631,7 @@ int crypt_token_luks2_keyring_set(struct crypt_device *cd, const struct crypt_token_params_luks2_keyring *params) { int r; + char json[4096]; if (!params) return -EINVAL; @@ -5634,7 +5641,9 @@ int crypt_token_luks2_keyring_set(struct crypt_device *cd, if ((r = onlyLUKS2(cd))) return r; - return LUKS2_builtin_token_create(cd, &cd->u.luks2.hdr, token, LUKS2_TOKEN_KEYRING, params, 1); + LUKS2_token_keyring_json(json, sizeof(json), params); + + return LUKS2_token_create(cd, &cd->u.luks2.hdr, token, json, 1); } int crypt_token_assign_keyslot(struct crypt_device *cd, int token, int keyslot) diff --git a/man/Makemodule.am b/man/Makemodule.am index 3f68441f..9b03bd97 100644 --- a/man/Makemodule.am +++ b/man/Makemodule.am @@ -1,7 +1,9 @@ -EXTRA_DIST += man/cryptsetup.8 man/integritysetup.8 man/veritysetup.8 man/cryptsetup-reencrypt.8 +EXTRA_DIST += man/cryptsetup.8 man/cryptsetup-tpm2.8 man/integritysetup.8 man/veritysetup.8 man/cryptsetup-reencrypt.8 man8_MANS += man/cryptsetup.8 +man8_MANS += man/cryptsetup-tpm2.8 + if VERITYSETUP man8_MANS += man/veritysetup.8 endif diff --git a/man/cryptsetup-tpm2.8 b/man/cryptsetup-tpm2.8 new file mode 100644 index 00000000..0fa6ea15 --- /dev/null +++ b/man/cryptsetup-tpm2.8 @@ -0,0 +1,187 @@ +.TH CRYPTSETUP-TPM2 "8" "December 2019" "cryptsetup-tpm2" "Maintenance Commands" +.SH NAME +cryptsetup-tpm2 - tool for activating LUKS2 encrypted volumes using TPM2 +.SH SYNOPSIS +.B cryptsetup-tpm2 +.SH DESCRIPTION +.PP +cryptsetup-tpm2 is used to conveniently lock and activate LUKS2 volumes using a +passphrase stored in a \fBTPM 2.0\fR. \fBTPM\fR is a secure cryptoprocessor +(similar to a smartcard) present in most of the recent laptops (and some desktops). +cryptsetup-tpm2 stores metadata in a \fItpm2\fR token inside the \fItokens\fR +section of the LUKS2 header (see \fILUKS2 On-Disk Format Specification\fR). + +This utility supports sealing the TPM key to a current HW/SW state. To fully +utilize this feature, it is required that the state is measured to the PCR banks +by BIOS/UEFI, bootloader (e.g. \fITrustedGrub\fR) and kernel (IMA subsystem). +If anything in the boot chain deviates from the pre-measured state, the passphrase +won't be released from the TPM. + +You can seal the passphrase to a specified PCR. They have the following meaning: +.IP +\fIPCR#0-7\fR Measured by BIOS/EFI. includes measurement of items like boot +options and order, microcode or secure boot status. +.IP +\fIPCR#16\fR Debug register + +.PP +In TrustedGrub2 PCRs contain the following measurements: +.IP +\fIPCR#8\fR +First sector of TrustedGRUB2 kernel (diskboot.img) +.IP +\fIPCR#9\fR +TrustedGRUB2 kernel (core.img) +.IP +\fIPCR#10\fR +Loader measurements - currently linux-kernel, initrd, ntldr, chainloader, multiboot, module +.IP +\fIPCR#11\fR +Contains all commandline arguments from scripts (e.g. grub.cfg) and those entered in the shell +.IP +\fIPCR#12\fR +LUKS-header +.IP +\fIPCR#13\fR +Parts of GRUB2 that are loaded from disk like GRUB2-modules + +.PP +To protect the TPM-stored passphrase \fIdictionary attack (DA) protection\fR +can be enabled inside the TPM. \fBBe careful not to enter wrong TPM password +too many times with this option enabled.\fR + +\fBIt is strongly recommended to have a least one regular (non-TPM) passphrase +added.\fR This will prevent a lockout in case of changed PCR configuration +(e.g. after kernel update or kernel command line change) or after exceeded +number of attempts on a DA-protected passphrase. + +.SH BASIC COMMANDS + +\fIadd\fR +.IP +This will generate a random LUKS2 passphrase (using random data supplied from +the TPM) and store the passphrase inside NV memory of the TPM. + +\fB\fR can be [--tpm2-nv, --tpm2-pcr, --tpm2-bank, --tpm2-daprotect, +--tpm2-no-pin, --tpm2-key-size, --timeout=secs] + + +.PP +\fIopen\fR +.IP +Retrieves a LUKS2 passphrase from the TPM's NV index stored in the header +metadata. If the NV index is PIN protected, the user will be asked for the PIN. +Then \fB\fR will be activated using the passphrase and opened as +\fB\fR. + +\fB\fR can be [--token-id] + +.PP +\fIkill\fR --token-id or --tpm2-nv= +.IP +This will remove the randomly generated passphrase from the TPM and the +associated LUKS2 keyslot. Specifying either token ID or NV index is mandatory. + +.PP +\fIdump\fR +.IP +The LUKS2 header will be dumped in a human-readable way. This includes the +fields specific to TPM2 token. + +.PP +\fIlist\fR +.IP +This action does not operate on any data device. It only lists supported PCRs +for every bank. + +.SH OPTIONS +.TP +.B --tpm2-nv=<0x01800000..0x01BFFFFF> +Specify an index on which to store the randomly generated passphrase if none is +supplied a free one is found. +.TP +.B --tpm2-pcr=[,[,[...]]] +When supplied, the randomly generated passphrase will will be sealed to current +values of the specified PCRs. +.TP +.B --tpm2-bank=[,[,[...]]] +Selection of banks for PCR sealing +.TP +.B --tpm2-daprotect +Enable dictionary attack protection on the TPM stored passphrase. +.TP +.B --tpm2-no-pin +Don't PIN-protect TPM NV index. This way you don't have to enter any +passphrase/PIN when activating the disk. \fBWhen enabled, it is strongly +recommended to have TPM enabled bootloader and kernel which make measurements +of the current state and to seal the passphrase to the appropriate PCRs. Even +then, the system is susceptible to cold-boot attacks.\fR +.TP +.B --tpm2-key-size= +Size of a randomly generated passphrase which will be stored in the TPM. It +makes no sense to set it to a greater number than the master key length. +.TP +.B --token-id=INT +Specify the token number (as listed by the \fIlist\fR command) +.TP +.B --timeout=secs +Timeout for TPM PIN interactive passphrase prompt (in seconds) +.TP +.B --debug +Run in debug mode with full diagnostic logs. Debug output lines are always +prefixed by '#'. + +.SH RETURN CODES +Cryptsetup returns 0 on success and a non-zero value on error. + +Error codes are: 1 wrong parameters, 2 no permission (bad passphrase), +3 out of memory, 4 wrong device specified, 5 device already exists +or device is busy. + +.SH AUTHORS +cryptsetup-tpm2 is based on code originally written by Andreas Fuchs from +Fraunhofer SIT sponsored by Infineon Technologies AG and further extended by +Daniel Zatovic +.br +The cryptsetup-tpm2 man page was written by Daniel Zatovic + +.br + +.SH COPYRIGHT +Copyright \(co 2018-2019 Fraunhofer SIT sponsored by Infineon Technologies AG +.br +Copyright \(co 2019-2020 Red Hat, Inc. All rights reserved. +.br +Copyright \(co 2019-2020 Daniel Zatovic +.br +Copyright \(co 2009-2020 Milan Broz +.br + +.B libcryptsetup + +.IP +Copyright \(co 2004 Jana Saout +.br +Copyright \(co 2004-2006 Clemens Fruhwirth +.br +Copyright \(co 2012-2014 Arno Wagner +.br +Copyright \(co 2009-2020 Red Hat, Inc. +.br +Copyright \(co 2009-2020 Milan Broz +.br + +.PP +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +The LUKS website at \fBhttps://gitlab.com/cryptsetup/cryptsetup/\fR + +The cryptsetup FAQ, contained in the distribution package and +online at +\fBhttps://gitlab.com/cryptsetup/cryptsetup/wikis/FrequentlyAskedQuestions\fR + +The cryptsetup mailing list and list archive, see FAQ entry 1.6. + +The LUKS on-disk format specification available at +\fBhttps://gitlab.com/cryptsetup/cryptsetup/wikis/Specification\fR diff --git a/src/Makemodule.am b/src/Makemodule.am index ccba17a5..ece70fb1 100644 --- a/src/Makemodule.am +++ b/src/Makemodule.am @@ -78,6 +78,7 @@ integritysetup_SOURCES = \ lib/utils_io.c \ lib/utils_blkid.c \ src/utils_tools.c \ + src/utils_blockdev.c \ src/integritysetup.c \ src/cryptsetup.h diff --git a/src/cryptsetup.c b/src/cryptsetup.c index 601a967c..3687a959 100644 --- a/src/cryptsetup.c +++ b/src/cryptsetup.c @@ -1472,6 +1472,18 @@ static int action_open_luks(void) } else { r = crypt_activate_by_token(cd, activated_name, opt_token, NULL, activate_flags); tools_keyslot_msg(r, UNLOCKED); + + /* Token requires PIN, but ask only there will be no password query later */ + if (opt_token_only && r == -EAGAIN) { + r = tools_get_key(_("Enter token PIN:"), &password, &passwordLen, 0, 0, NULL, + opt_timeout, _verify_passphrase(0), 0, cd); + if (r < 0) + goto out; + r = crypt_activate_by_pin_token(cd, activated_name, opt_token, + password, NULL, activate_flags); + tools_keyslot_msg(r, UNLOCKED); + } + if (r >= 0 || opt_token_only) goto out; diff --git a/src/utils_blockdev.c b/src/utils_blockdev.c index 960b7a79..9b524a56 100644 --- a/src/utils_blockdev.c +++ b/src/utils_blockdev.c @@ -187,3 +187,133 @@ int tools_lookup_crypt_device(struct crypt_device *cd, const char *type, st.st_rdev, name, name_length); return r; } + + +static void report_partition(const char *value, const char *device) +{ + if (opt_batch_mode) + log_dbg("Device %s already contains a '%s' partition signature.", device, value); + else + log_std(_("WARNING: Device %s already contains a '%s' partition signature.\n"), device, value); +} + +static void report_superblock(const char *value, const char *device) +{ + if (opt_batch_mode) + log_dbg("Device %s already contains a '%s' superblock signature.", device, value); + else + log_std(_("WARNING: Device %s already contains a '%s' superblock signature.\n"), device, value); +} + +int tools_detect_signatures(const char *device, int ignore_luks, size_t *count) +{ + int r; + size_t tmp_count; + struct blkid_handle *h; + blk_probe_status pr; + + if (!count) + count = &tmp_count; + + *count = 0; + + if (!blk_supported()) { + log_dbg("Blkid support disabled."); + return 0; + } + + if ((r = blk_init_by_path(&h, device))) { + log_err(_("Failed to initialize device signature probes.")); + return -EINVAL; + } + + blk_set_chains_for_full_print(h); + + if (ignore_luks && blk_superblocks_filter_luks(h)) { + r = -EINVAL; + goto out; + } + + while ((pr = blk_probe(h)) < PRB_EMPTY) { + if (blk_is_partition(h)) + report_partition(blk_get_partition_type(h), device); + else if (blk_is_superblock(h)) + report_superblock(blk_get_superblock_type(h), device); + else { + log_dbg("Internal tools_detect_signatures() error."); + r = -EINVAL; + goto out; + } + (*count)++; + } + + if (pr == PRB_FAIL) + r = -EINVAL; +out: + blk_free(h); + return r; +} + +int tools_wipe_all_signatures(const char *path) +{ + int fd, flags, r; + blk_probe_status pr; + struct stat st; + struct blkid_handle *h = NULL; + + if (!blk_supported()) { + log_dbg("Blkid support disabled."); + return 0; + } + + if (stat(path, &st)) { + log_err(_("Failed to stat device %s."), path); + return -EINVAL; + } + + flags = O_RDWR; + if (S_ISBLK(st.st_mode)) + flags |= O_EXCL; + + /* better than opening regular file with O_EXCL (undefined) */ + /* coverity[toctou] */ + fd = open(path, flags); + if (fd < 0) { + if (errno == EBUSY) + log_err(_("Device %s is in use. Can not proceed with format operation."), path); + else + log_err(_("Failed to open file %s in read/write mode."), path); + return -EINVAL; + } + + if ((r = blk_init_by_fd(&h, fd))) { + log_err(_("Failed to initialize device signature probes.")); + r = -EINVAL; + goto out; + } + + blk_set_chains_for_wipes(h); + + while ((pr = blk_probe(h)) < PRB_EMPTY) { + if (blk_is_partition(h)) + log_verbose("Existing '%s' partition signature on device %s will be wiped.", + blk_get_partition_type(h), path); + if (blk_is_superblock(h)) + log_verbose("Existing '%s' superblock signature on device %s will be wiped.", + blk_get_superblock_type(h), path); + if (blk_do_wipe(h)) { + log_err(_("Failed to wipe device signature.")); + r = -EINVAL; + goto out; + } + } + + if (pr != PRB_EMPTY) { + log_err(_("Failed to probe device %s for a signature."), path); + r = -EINVAL; + } +out: + close(fd); + blk_free(h); + return r; +} diff --git a/src/utils_tools.c b/src/utils_tools.c index 47bcfe12..b13b7fca 100644 --- a/src/utils_tools.c +++ b/src/utils_tools.c @@ -467,135 +467,6 @@ int tools_wipe_progress(uint64_t size, uint64_t offset, void *usrptr) return r; } -static void report_partition(const char *value, const char *device) -{ - if (opt_batch_mode) - log_dbg("Device %s already contains a '%s' partition signature.", device, value); - else - log_std(_("WARNING: Device %s already contains a '%s' partition signature.\n"), device, value); -} - -static void report_superblock(const char *value, const char *device) -{ - if (opt_batch_mode) - log_dbg("Device %s already contains a '%s' superblock signature.", device, value); - else - log_std(_("WARNING: Device %s already contains a '%s' superblock signature.\n"), device, value); -} - -int tools_detect_signatures(const char *device, int ignore_luks, size_t *count) -{ - int r; - size_t tmp_count; - struct blkid_handle *h; - blk_probe_status pr; - - if (!count) - count = &tmp_count; - - *count = 0; - - if (!blk_supported()) { - log_dbg("Blkid support disabled."); - return 0; - } - - if ((r = blk_init_by_path(&h, device))) { - log_err(_("Failed to initialize device signature probes.")); - return -EINVAL; - } - - blk_set_chains_for_full_print(h); - - if (ignore_luks && blk_superblocks_filter_luks(h)) { - r = -EINVAL; - goto out; - } - - while ((pr = blk_probe(h)) < PRB_EMPTY) { - if (blk_is_partition(h)) - report_partition(blk_get_partition_type(h), device); - else if (blk_is_superblock(h)) - report_superblock(blk_get_superblock_type(h), device); - else { - log_dbg("Internal tools_detect_signatures() error."); - r = -EINVAL; - goto out; - } - (*count)++; - } - - if (pr == PRB_FAIL) - r = -EINVAL; -out: - blk_free(h); - return r; -} - -int tools_wipe_all_signatures(const char *path) -{ - int fd, flags, r; - blk_probe_status pr; - struct stat st; - struct blkid_handle *h = NULL; - - if (!blk_supported()) { - log_dbg("Blkid support disabled."); - return 0; - } - - if (stat(path, &st)) { - log_err(_("Failed to stat device %s."), path); - return -EINVAL; - } - - flags = O_RDWR; - if (S_ISBLK(st.st_mode)) - flags |= O_EXCL; - - /* better than opening regular file with O_EXCL (undefined) */ - /* coverity[toctou] */ - fd = open(path, flags); - if (fd < 0) { - if (errno == EBUSY) - log_err(_("Device %s is in use. Can not proceed with format operation."), path); - else - log_err(_("Failed to open file %s in read/write mode."), path); - return -EINVAL; - } - - if ((r = blk_init_by_fd(&h, fd))) { - log_err(_("Failed to initialize device signature probes.")); - r = -EINVAL; - goto out; - } - - blk_set_chains_for_wipes(h); - - while ((pr = blk_probe(h)) < PRB_EMPTY) { - if (blk_is_partition(h)) - log_verbose(_("Existing '%s' partition signature (offset: %" PRIi64 " bytes) on device %s will be wiped."), - blk_get_partition_type(h), blk_get_offset(h), path); - if (blk_is_superblock(h)) - log_verbose(_("Existing '%s' superblock signature (offset: %" PRIi64 " bytes) on device %s will be wiped."), - blk_get_superblock_type(h), blk_get_offset(h), path); - if (blk_do_wipe(h)) { - log_err(_("Failed to wipe device signature.")); - r = -EINVAL; - goto out; - } - } - - if (pr != PRB_EMPTY) { - log_err(_("Failed to probe device %s for a signature."), path); - r = -EINVAL; - } -out: - close(fd); - blk_free(h); - return r; -} - int tools_is_cipher_null(const char *cipher) { if (!cipher) diff --git a/tests/tpm2-test b/tests/tpm2-test new file mode 100755 index 00000000..82a5a14d --- /dev/null +++ b/tests/tpm2-test @@ -0,0 +1,119 @@ +#!/bin/bash + +[ -z "$CRYPTSETUP_PATH" ] && CRYPTSETUP_PATH=".." +CRYPTSETUP=$CRYPTSETUP_PATH/cryptsetup +CRYPTSETUP_TPM2=$CRYPTSETUP_PATH/cryptsetup-tpm2 +MNT_DIR="./mnt_luks_tpm" +DEV_NAME="dummy" +PWD="93R4P4pIqAH8" +TPM_PWD="mymJeD8ivEhE" +FAST_PBKDF_OPT="--pbkdf pbkdf2 --pbkdf-force-iterations 1000" +TPM_PCR=8 + +cleanup() { + [ -b /dev/mapper/$DEV_NAME ] && dmsetup remove --retry $DEV_NAME + udevadm settle >/dev/null 2>&1 + if [ -d "$MNT_DIR" ] ; then + umount -f $MNT_DIR 2>/dev/null + rmdir $MNT_DIR 2>/dev/null + fi + sleep 2 +} + +fail() +{ + [ -n "$1" ] && echo "FAIL $1" + echo "FAILED backtrace:" + while caller $frame; do ((frame++)); done + cleanup + exit 100 +} + + +format() # format +{ + dd if=/dev/zero of=$DEV bs=1M count=32 >/dev/null 2>&1 + + echo $PWD | $CRYPTSETUP luksFormat --type luks2 $DEV -q $FAST_PBKDF_OPT -c aes-cbc-essiv:sha256 + [ $? -ne 0 ] && fail "Format failed." +} + +skip() +{ + echo "TEST SKIPPED: $1" + cleanup + exit 77 +} + +test_sealing() +{ + if [ "`tpm2_getcap pcrs | grep $1 | cut -d "[" -f2 | cut -d "]" -f1 | grep " $TPM_PCR,"`" = "" ]; then + echo "PCR $TPM_PCR doesn't support $1 hash algorithm" + + echo -e "$PWD\n$TPM_PWD\n$TPM_PWD" | $CRYPTSETUP_TPM2 add $DEV --tpm2-pcr=$TPM_PCR --tpm2-bank=$1 + [ $? -eq 0 ] && fail "Successfully sealed to PCR $TPM_PCR using $1 hash algorithm even though it is not supported by TPM." + + return 77 + fi + + echo "TPM supports $1" + + echo -e "$PWD\n$TPM_PWD\n$TPM_PWD" | $CRYPTSETUP_TPM2 add $DEV --tpm2-pcr=$TPM_PCR --tpm2-bank=$1 + [ $? -ne 0 ] && fail "Failed to add TPM2 token sealed to PCR." + + echo $TPM_PWD | $CRYPTSETUP --token-only luksOpen $DEV $DEV_NAME + [ $? -ne 0 ] && fail "Failed open device by TPM2 token sealed to PCR." + + if [ $1 = "sha1" ]; then + tpm2_pcrextend 8:sha1=`printf '0%.0s' {1..40}` + fi + + if [ $1 = "sha256" ]; then + tpm2_pcrextend 8:sha256=`printf '0%.0s' {1..64}` + fi + + if [ $1 = "sha512" ]; then + tpm2_pcrextend 8:sha512=`printf '0%.0s' {1..128}` + fi + + echo $TPM_PWD | $CRYPTSETUP --token-only luksOpen $DEV $DEV_NAME + [ $? -eq 0 ] && fail "Opened device by TPM2 token sealed to PCR even though registers have been changed." + + $CRYPTSETUP close $DEV_NAME || fail + + echo -e "$PWD\n$TPM_PWD\n$TPM_PWD" | $CRYPTSETUP_TPM2 kill $DEV --token-id=0 + [ $? -ne 0 ] && fail "Failed to kill TPM2 token sealed to PCR." +} + +if [ $(id -u) != 0 ]; then + skip "You must be root to run this test, test skipped." +fi + +if [ "`ls /dev/tpm*`" == "" ]; then + fail "No TPM device found." +fi + +[ ! -d $MNT_DIR ] && mkdir $MNT_DIR + +echo "[1] Using tmpfs for image" +DEV="$MNT_DIR/test.img" +mount -t tmpfs none $MNT_DIR || skip "Mounting tmpfs not available." +format + +# basic test +echo -e "$PWD\n$TPM_PWD\n$TPM_PWD" | $CRYPTSETUP_TPM2 add $DEV +[ $? -ne 0 ] && fail "Failed to add TPM2 token." + +echo $TPM_PWD | $CRYPTSETUP --token-only luksOpen $DEV $DEV_NAME +[ $? -ne 0 ] && fail "Failed open device by TPM2 token." + +$CRYPTSETUP close $DEV_NAME || fail + +echo -e "$PWD\n$TPM_PWD\n$TPM_PWD" | $CRYPTSETUP_TPM2 kill $DEV --token-id=0 +[ $? -ne 0 ] && fail "Failed to kill TPM2 token." + +test_sealing sha1 +test_sealing sha256 +test_sealing sha512 + +cleanup diff --git a/tokens/Makemodule.am b/tokens/Makemodule.am new file mode 100644 index 00000000..3e5927f5 --- /dev/null +++ b/tokens/Makemodule.am @@ -0,0 +1,48 @@ +EXTRA_DIST += tokens/libcryptsetup-token.sym + +TOKENS_LDFLAGS = $(AM_LDFLAGS) -no-undefined \ + -Wl,--version-script=$(top_srcdir)/tokens/libcryptsetup-token.sym \ + -version-info 0:0:0 + +# SSHTEST token an app +libcryptsetup_token_sshtest_la_LDFLAGS = $(TOKENS_LDFLAGS) +libcryptsetup_token_sshtest_la_SOURCES = tokens/libcryptsetup-token-sshtest.c +libcryptsetup_token_sshtest_la_LIBADD = -lssh libcryptsetup.la @JSON_C_LIBS@ +lib_LTLIBRARIES += libcryptsetup-token-sshtest.la + +cryptsetup_sshtest_SOURCES = tokens/cryptsetup-sshtest.c +cryptsetup_sshtest_LDADD = -lm libcryptsetup.la @JSON_C_LIBS@ +sbin_PROGRAMS += cryptsetup-sshtest + +# TPM2 token and app +libcryptsetup_token_tpm2_la_LDFLAGS = $(TOKENS_LDFLAGS) +libcryptsetup_token_tpm2_la_SOURCES = \ + tokens/tpm2/libcryptsetup-token-tpm2.c \ + tokens/tpm2/utils_tpm2.h \ + tokens/tpm2/utils_tpm2.c \ + tokens/tpm2/utils_tpm2_json.c + +libcryptsetup_token_tpm2_la_LIBADD = -lm libcryptsetup.la @JSON_C_LIBS@ @TSS2_ESYS_LIBS@ @TSS2_RC_LIBS@ +lib_LTLIBRARIES += libcryptsetup-token-tpm2.la + +cryptsetup_tpm2_SOURCES = \ + tokens/tpm2/cryptsetup-tpm2.c \ + tokens/tpm2/utils_tpm2.h \ + tokens/tpm2/utils_tpm2.c \ + tokens/tpm2/utils_tpm2_json.c \ + src/cryptsetup.h \ + src/utils_tools.c \ + src/utils_password.c \ + lib/utils_io.c \ + lib/utils_crypt.c \ + lib/utils_loop.c + +cryptsetup_tpm2_LDADD = -lm libcryptsetup.la \ + @JSON_C_LIBS@ \ + @TSS2_ESYS_LIBS@ \ + @TSS2_RC_LIBS@ \ + @POPT_LIBS@ \ + @PWQUALITY_LIBS@ \ + @PASSWDQC_LIBS@ +cryptsetup_tpm2_CFLAGS = $(AM_CFLAGS) +sbin_PROGRAMS += cryptsetup-tpm2 diff --git a/tokens/cryptsetup-sshtest.c b/tokens/cryptsetup-sshtest.c new file mode 100644 index 00000000..9698142f --- /dev/null +++ b/tokens/cryptsetup-sshtest.c @@ -0,0 +1,151 @@ +/* + * Example of LUKS2 keyslot handler, config app + * + * Use: + * cryptsetup luksFormat --type luks2 + * cryptsetup-sshtest add host user /home/user/keyfile /home/myuser/.ssh/id_rsa + * cryptsetup/cryptsetup-sshtest open test + * + * Copyright (C) 2016-2020 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 +#include +#include +#include + +#define CRYPT_TOKEN_VERSION "CRYPTSETUP_TOKEN_1.0" +#define TOKEN_NUM 0 +#define TOKEN_NAME "sshtest" + +static int token_add(const char *device, const char *server, + const char *user, const char *path, const char *keypath) +{ + struct crypt_device *cd = NULL; + json_object *jobj = NULL, *jobj_keyslots; + int r; + + r = crypt_init(&cd, device); + if (r < 0) + return EXIT_FAILURE; + + r = crypt_load(cd, CRYPT_LUKS2, NULL); + if (r < 0) { + crypt_free(cd); + return EXIT_FAILURE; + } + + jobj = json_object_new_object(); + json_object_object_add(jobj, "type", json_object_new_string(TOKEN_NAME)); /* mandatory */ + + jobj_keyslots = json_object_new_array(); + json_object_array_add(jobj_keyslots, json_object_new_string("0")); /* assign to first keyslot only */ + json_object_object_add(jobj, "keyslots", jobj_keyslots); /* mandatory array field (may be empty and assigned later */ + + /* custom metadata */ + json_object_object_add(jobj, "ssh_server", json_object_new_string(server)); + json_object_object_add(jobj, "ssh_user", json_object_new_string(user)); + json_object_object_add(jobj, "ssh_path", json_object_new_string(path)); + json_object_object_add(jobj, "ssh_keypath", json_object_new_string(keypath)); + + /* libcryptsetup API call */ + r = crypt_token_json_set(cd, TOKEN_NUM, json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PLAIN)); + + crypt_free(cd); + json_object_put(jobj); + + return r ? EXIT_FAILURE : EXIT_SUCCESS; +} + +static int open_by_token(const char *device, const char *name) +{ + struct crypt_device *cd = NULL; + int r; + + r = crypt_init(&cd, device); + if (r < 0) + return EXIT_FAILURE; + + r = crypt_load(cd, CRYPT_LUKS2, NULL); + if (r < 0) { + crypt_free(cd); + return EXIT_FAILURE; + } + + r = crypt_activate_by_token(cd, name, TOKEN_NUM, NULL, 0); + + crypt_free(cd); + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + +static int dump_by_token(const char *device) +{ + struct crypt_device *cd = NULL; + int r; + + r = crypt_init(&cd, device); + if (r < 0) + return EXIT_FAILURE; + + r = crypt_load(cd, CRYPT_LUKS2, NULL); + if (r < 0) { + crypt_free(cd); + return EXIT_FAILURE; + } + + r = crypt_dump(cd); + crypt_free(cd); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + + +static void keyslot_help(void) +{ + printf("Use parameters:\n add device server user path sshkeypath\n" + " open device name\n dump device\n"); + exit(1); +} + +int main(int argc, char *argv[]) +{ + if (crypt_token_load(TOKEN_NAME)) { + printf("Cannot find token lib %s.\n", TOKEN_NAME); + return EXIT_FAILURE; + } + + crypt_set_debug_level(CRYPT_LOG_DEBUG); + + /* Adding slot to device */ + if (argc == 7 && !strcmp("add", argv[1])) + return token_add(argv[2], argv[3], argv[4], argv[5], argv[6]); + + /* Key check without activation */ + if (argc == 3 && !strcmp("open", argv[1])) + return open_by_token(argv[2], NULL); + + /* Key check with activation (requires root) */ + if (argc == 4 && !strcmp("open", argv[1])) + return open_by_token(argv[2], argv[3]); + + /* Dump with keyslot info */ + if (argc == 3 && !strcmp("dump", argv[1])) + return dump_by_token(argv[2]); + + keyslot_help(); + return 1; +} diff --git a/tokens/libcryptsetup-token-sshtest.c b/tokens/libcryptsetup-token-sshtest.c new file mode 100644 index 00000000..d86c36fb --- /dev/null +++ b/tokens/libcryptsetup-token-sshtest.c @@ -0,0 +1,275 @@ +/* + * Example of LUKS2 keyslot handler, token lib + * + * Copyright (C) 2016-2020 Milan Broz + * + * Use: + * - generate LUKS device + * - store passphrase used in previous step remotely (single line w/o \r\n) + * - add new token using this example + * - activate device by token + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include "libcryptsetup.h" + +#define PASSWORD_LENGTH 8192 + +typedef int (*password_cb_func) (char **password); + +static json_object *get_token_jobj(struct crypt_device *cd, int token) +{ + const char *json_slot; + + /* libcryptsetup API call */ + if (crypt_token_json_get(cd, token, &json_slot)) + return NULL; + + return json_tokener_parse(json_slot); +} + +static int sshtest_download_password(struct crypt_device *cd, ssh_session ssh, + const char *path, char **password, size_t *password_len) +{ + char *pass = NULL; + size_t pass_len; + int r; + sftp_attributes sftp_attr = NULL; + sftp_session sftp = NULL; + sftp_file file = NULL; + + + sftp = sftp_new(ssh); + if (!sftp) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot create sftp session: "); + r = SSH_FX_FAILURE; + goto out; + } + + r = sftp_init(sftp); + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot init sftp session: "); + goto out; + } + + file = sftp_open(sftp, path, O_RDONLY, 0); + if (!file) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot create sftp session: "); + r = SSH_FX_FAILURE; + goto out; + } + + sftp_attr = sftp_fstat(file); + if (!sftp_attr) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot stat sftp file: "); + r = SSH_FX_FAILURE; + goto out; + } + + pass_len = sftp_attr->size > PASSWORD_LENGTH ? PASSWORD_LENGTH : sftp_attr->size; + pass = malloc(pass_len); + if (!pass) { + crypt_log(cd, CRYPT_LOG_ERROR, "Not enough memory.\n"); + r = SSH_FX_FAILURE; + goto out; + } + + r = sftp_read(file, pass, pass_len); + if (r < 0 || (size_t)r != pass_len) { + crypt_log(cd, CRYPT_LOG_ERROR, "Cannot read remote key: "); + r = SSH_FX_FAILURE; + goto out; + } + + *password = pass; + *password_len = pass_len; + + r = SSH_OK; +out: + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh)); + crypt_log(cd, CRYPT_LOG_ERROR, "\n"); + free(pass); + } + + if (sftp_attr) + sftp_attributes_free(sftp_attr); + + if (file) + sftp_close(file); + if (sftp) + sftp_free(sftp); + return r == SSH_OK ? 0 : -EINVAL; +} + +static ssh_session sshtest_session_init(struct crypt_device *cd, + const char *host, const char *user) +{ + int r, port = 22; + ssh_session ssh = ssh_new(); + if (!ssh) + return NULL; + + ssh_options_set(ssh, SSH_OPTIONS_HOST, host); + ssh_options_set(ssh, SSH_OPTIONS_USER, user); + ssh_options_set(ssh, SSH_OPTIONS_PORT, &port); + + crypt_log(cd, CRYPT_LOG_NORMAL, "SSHTEST token initiating ssh session.\n"); + + r = ssh_connect(ssh); + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Connection failed: "); + goto out; + } + + r = ssh_is_server_known(ssh); + if (r != SSH_SERVER_KNOWN_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, "Server not known: "); + r = SSH_AUTH_ERROR; + goto out; + } + + r = SSH_OK; + + /* initialise list of authentication methods. yes, according to official libssh docs... */ + ssh_userauth_none(ssh, NULL); +out: + if (r != SSH_OK) { + crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh)); + crypt_log(cd, CRYPT_LOG_ERROR, "\n"); + ssh_disconnect(ssh); + ssh_free(ssh); + ssh = NULL; + } + + return ssh; +} + +static int sshtest_public_key_auth(struct crypt_device *cd, ssh_session ssh, const ssh_key pkey) +{ + int r; + + crypt_log(cd, CRYPT_LOG_DEBUG, "Trying public key authentication method.\n"); + + if (!(ssh_userauth_list(ssh, NULL) & SSH_AUTH_METHOD_PUBLICKEY)) { + crypt_log(cd, CRYPT_LOG_ERROR, "Public key auth method not allowed on host.\n"); + return SSH_AUTH_ERROR; + } + + r = ssh_userauth_try_publickey(ssh, NULL, pkey); + if (r == SSH_AUTH_SUCCESS) { + crypt_log(cd, CRYPT_LOG_DEBUG, "Public key method accepted.\n"); + r = ssh_userauth_publickey(ssh, NULL, pkey); + } + + if (r != SSH_AUTH_SUCCESS) { + crypt_log(cd, CRYPT_LOG_ERROR, "Public key authentication error: "); + crypt_log(cd, CRYPT_LOG_ERROR, ssh_get_error(ssh)); + crypt_log(cd, CRYPT_LOG_ERROR, "\n"); + } + + return r; +} + +static int SSHTEST_open_pin(struct crypt_device *cd, int token, const char *pin, + char **password, size_t *password_len, void *usrptr) +{ + int r; + json_object *jobj_server, *jobj_user, *jobj_path, *jobj_token, *jobj_keypath; + ssh_key pkey; + ssh_session ssh; + + jobj_token = get_token_jobj(cd, token); + json_object_object_get_ex(jobj_token, "ssh_server", &jobj_server); + json_object_object_get_ex(jobj_token, "ssh_user", &jobj_user); + json_object_object_get_ex(jobj_token, "ssh_path", &jobj_path); + json_object_object_get_ex(jobj_token, "ssh_keypath",&jobj_keypath); + + r = ssh_pki_import_privkey_file(json_object_get_string(jobj_keypath), pin, NULL, NULL, &pkey); + if (r != SSH_OK) { + if (r == SSH_EOF) { + crypt_log(cd, CRYPT_LOG_ERROR, "Failed to open and import private key.\n"); + return -EINVAL; + } + crypt_log(cd, CRYPT_LOG_ERROR, "Failed to import private key (password protected?).\n"); + return -EAGAIN; + } + + ssh = sshtest_session_init(cd, json_object_get_string(jobj_server), + json_object_get_string(jobj_user)); + if (!ssh) { + ssh_key_free(pkey); + return -EINVAL; + } + + r = sshtest_public_key_auth(cd, ssh, pkey); + ssh_key_free(pkey); + + if (r == SSH_AUTH_SUCCESS) + r = sshtest_download_password(cd, ssh, json_object_get_string(jobj_path), + password, password_len); + + ssh_disconnect(ssh); + ssh_free(ssh); + + return r ? -EINVAL : r; +} + +static int SSHTEST_open(struct crypt_device *cd, int token, + char **password, size_t *password_len, void *usrptr) +{ + return SSHTEST_open_pin(cd, token, NULL, password, password_len, usrptr); +} + +static void SSHTEST_dump(struct crypt_device *cd, const char *json) +{ + json_object *jobj_token, *jobj_server, *jobj_user, *jobj_path, *jobj_keypath; + char buf[4096]; + + jobj_token = json_tokener_parse(json); + if (!jobj_token) + return; + + json_object_object_get_ex(jobj_token, "ssh_server", &jobj_server); + json_object_object_get_ex(jobj_token, "ssh_user", &jobj_user); + json_object_object_get_ex(jobj_token, "ssh_path", &jobj_path); + json_object_object_get_ex(jobj_token, "ssh_keypath",&jobj_keypath); + + snprintf(buf, sizeof(buf) - 1, "\tssh_server: %s\n\tssh_user: %s\n" + "\tssh_path: %s\n\tssh_key_path: %s\n", + json_object_get_string(jobj_server), + json_object_get_string(jobj_user), + json_object_get_string(jobj_path), + json_object_get_string(jobj_keypath)); + + crypt_log(cd, CRYPT_LOG_NORMAL, buf); + json_object_put(jobj_token); +} + +const crypt_token_handler cryptsetup_token_handler = { + .name = "sshtest", + .open = SSHTEST_open, + .open_pin = SSHTEST_open_pin, + .dump = SSHTEST_dump, +}; diff --git a/tokens/libcryptsetup-token.sym b/tokens/libcryptsetup-token.sym new file mode 100644 index 00000000..c4456ddd --- /dev/null +++ b/tokens/libcryptsetup-token.sym @@ -0,0 +1,4 @@ +CRYPTSETUP_TOKEN_1.0 { + global: cryptsetup_token_handler; + local: *; +}; diff --git a/tokens/tpm2/cryptsetup-tpm2.c b/tokens/tpm2/cryptsetup-tpm2.c new file mode 100644 index 00000000..795dfbde --- /dev/null +++ b/tokens/tpm2/cryptsetup-tpm2.c @@ -0,0 +1,422 @@ +/* + * LUKS - Linux Unified Key Setup v2, TPM type keyslot handler + * + * Copyright (C) 2018-2020 Fraunhofer SIT sponsorred by Infineon Technologies AG + * Copyright (C) 2019-2020 Red Hat, Inc. All rights reserved. + * Copyright (C) 2019-2020 Daniel Zatovic + * Copyright (C) 2019-2020 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 "../src/cryptsetup.h" +#include "utils_tpm2.h" + +#define PACKAGE_CRYPTSETUP_TPM2 "cryptsetup-tpm2" + +#define DEFAULT_PCR_BANK "sha256" +#define DEFAULT_TPM2_SIZE 64 +#define DEFAULT_TPM2_SIZE_MAX 512 + +static uint32_t opt_tpmnv = 0; +static uint32_t opt_tpmpcr = 0; +static uint32_t opt_tpmbanks = 0; +static long int opt_pass_size = DEFAULT_TPM2_SIZE; +static int opt_tpmdaprotect = 0; +static int opt_no_tpm_pin = 0; +static int opt_token = CRYPT_ANY_TOKEN; +static int opt_timeout = 0; + +static const char **action_argv; +static int action_argc; + +static int action_tpm2_open(struct crypt_device *cd) +{ + int r; + + r = crypt_activate_by_token(cd, action_argv[1], opt_token, NULL, 0); + + return r < 0 ? r : 0; +} + +static int action_tpm2_dump(struct crypt_device *cd) +{ + return crypt_dump(cd); +} + +static int action_tpm2_kill(struct crypt_device *cd) +{ + const char *type; + int i, r; + + if (!opt_tpmnv && opt_token == CRYPT_ANY_TOKEN) { + l_err(cd, "Token ID or TPM2 nvindex option must be specified."); + return -EINVAL; + } + + if (opt_token == CRYPT_ANY_TOKEN) + opt_token = tpm2_token_by_nvindex(cd, opt_tpmnv); + + if (opt_token < 0 || + crypt_token_status(cd, opt_token, &type) != CRYPT_TOKEN_EXTERNAL || + strcmp(type, "tpm2")) { + l_err(cd, "No TPM2 token to destroy."); + return -EINVAL; + } + + /* Destroy all keyslots assigned to TPM 2 token */ + for (i = 0; i < crypt_keyslot_max(CRYPT_LUKS2) ; i++) { + if (!crypt_token_is_assigned(cd, opt_token, i)) { + r = crypt_keyslot_destroy(cd, i); + if (r < 0) { + l_err(cd, "Cannot destroy keyslot %d.", i); + return r; + } + } + } + + /* Destroy TPM2 NV index and token object itself */ + return tpm2_token_kill(cd, opt_token); +} + +static int action_tpm2_add(struct crypt_device *cd) +{ + char *existing_pass = NULL, *tpm_pin = NULL, *random_pass = NULL; + size_t existing_pass_len, tpm_pin_len = 0; + int token, r, keyslot = CRYPT_ANY_SLOT; + bool supports_algs_for_pcrs; + TSS2_RC tpm_rc; + + if (!opt_tpmbanks) { + l_err(cd, "PCR banks must be selected."); + return -EINVAL; + } + + tpm_rc = tpm2_supports_algs_for_pcrs(NULL, opt_tpmbanks, opt_tpmpcr, &supports_algs_for_pcrs); + if (tpm_rc != TSS2_RC_SUCCESS) { + l_err(NULL, "Failed to get PCRS capability from TPM."); + LOG_TPM_ERR(NULL, tpm_rc); + return -ECOMM; + } + + if(!supports_algs_for_pcrs) { + l_err(NULL, "Your TPM doesn't support selected PCR and banks combination."); + return -ENOTSUP; + } + + random_pass = crypt_safe_alloc(opt_pass_size); + if (!random_pass) + return -ENOMEM; + + r = tpm_get_random(cd, random_pass, opt_pass_size); + if (r < 0) + goto out; + + r = tools_get_key("Enter existing LUKS2 pasphrase:", + &existing_pass, &existing_pass_len, + 0, 0, NULL, opt_timeout, 0, 0, cd); + if (r < 0) + goto out; + + if (!opt_no_tpm_pin) { + r = tools_get_key("Enter new TPM password:", + &tpm_pin, &tpm_pin_len, + 0, 0, NULL, opt_timeout, 1, 0, cd); + if (r < 0) + goto out; + } + + if (opt_tpmnv == 0) { + tpm_rc = tpm_nv_find(cd, &opt_tpmnv); + if (tpm_rc != TSS2_RC_SUCCESS) { + l_err(cd, "Error while trying to find free NV index."); + LOG_TPM_ERR(cd, tpm_rc); + r = -EINVAL; + goto out; + } + + if (!opt_tpmnv) { + l_err(cd, "Error no free TPM NV-Index found."); + r = -EACCES; + goto out; + } + } + + tpm_rc = tpm_nv_define(cd, opt_tpmnv, tpm_pin, tpm_pin_len, opt_tpmpcr, + opt_tpmbanks, opt_tpmdaprotect, NULL, 0, opt_pass_size); + if (tpm_rc != TSS2_RC_SUCCESS) { + l_err(cd, "TPM NV-Index definition failed"); + LOG_TPM_ERR(cd, tpm_rc); + r = -EINVAL; + goto out; + } + + tpm_rc = tpm_nv_write(cd, opt_tpmnv, tpm_pin, tpm_pin_len, + random_pass, opt_pass_size); + if (tpm_rc != TSS2_RC_SUCCESS) { + l_err(cd, "TPM NV-Index write error."); + LOG_TPM_ERR(cd, tpm_rc); + tpm_nv_undefine(cd, opt_tpmnv); + r = -EINVAL; + goto out; + } + + r = crypt_keyslot_add_by_passphrase(cd, keyslot, existing_pass, existing_pass_len, random_pass, opt_pass_size); + if (r < 0) { + if (r == -EPERM) + l_err(cd, "Wrong LUKS2 passphrase supplied."); + tpm_nv_undefine(cd, opt_tpmnv); + goto out; + } + keyslot = r; + l_std(cd, "Using keyslot %d.\n", keyslot); + + r = tpm2_token_add(cd, opt_tpmnv, opt_tpmpcr, opt_tpmbanks, opt_tpmdaprotect, !opt_no_tpm_pin, opt_pass_size); + if (r < 0) { + tpm_nv_undefine(cd, opt_tpmnv); + crypt_keyslot_destroy(cd, keyslot); + goto out; + } + token = r; + l_std(cd, "Token: %d\n", token); + + r = crypt_token_assign_keyslot(cd, token, keyslot); + if (r < 0) { + l_err(cd, "Failed to assign keyslot %d to token %d.", keyslot, token); + tpm_nv_undefine(cd, opt_tpmnv); + crypt_keyslot_destroy(cd, keyslot); + crypt_token_json_set(cd, token, NULL); + } + + if (r > 0) + r = 0; +out: + crypt_safe_free(random_pass); + crypt_safe_free(existing_pass); + crypt_safe_free(tpm_pin); + + return r; +} + +static int action_tpm2_list(struct crypt_device *cd) +{ + TSS2_RC r; + TPMS_CAPABILITY_DATA *savedPCRs; + TPMS_PCR_SELECTION *selection; + unsigned i, j, k; + + r = getPCRsCapability(cd, &savedPCRs); + if (r != TSS2_RC_SUCCESS) { + l_err(cd, "Failed to get PCRS capability from TPM."); + LOG_TPM_ERR(cd, r); + return -EINVAL; + } + + l_std(cd, "Supported PCRs for banks:\n"); + for (i = 0; i < CRYPT_HASH_ALGS_COUNT; i++) { + if ((selection = tpm2_get_pcrs_by_alg(savedPCRs, hash_algs[i].crypt_id))) { + k = 0; + l_std(cd, "%s:\t", hash_algs[i].name); + for (j = 0; j < selection->sizeofSelect * 8; j++) { + if (selection->pcrSelect[i/8] & (1 << i % 8)) { + l_std(cd, "%s%d", k++ ? ", " : "", j); + } + } + l_std(cd, "\n"); + } + } + + free(savedPCRs); + return 0; +} + +static struct action_type { + const char *type; + int (*handler)(struct crypt_device*); + int required_action_argc; + const char *arg_desc; + const char *desc; +} action_types[] = { + { "add", action_tpm2_add, 1, N_(""),N_("add TPM2 token") }, + { "open", action_tpm2_open, 2, N_(" "),N_("open device as using TPM token") }, + { "kill", action_tpm2_kill, 1, N_(" --token-id "),N_("remove specified TPM token and related keyslots") }, + { "dump", action_tpm2_dump, 1, N_(""),N_("show TPM2 token information") }, + { "list", action_tpm2_list, 0, N_("none"),N_("just list supported PCRs for banks") }, + { NULL, NULL, 0, NULL, NULL } +}; + +static void help(poptContext popt_context, + enum poptCallbackReason reason __attribute__((unused)), + struct poptOption *key, + const char *arg __attribute__((unused)), + void *data __attribute__((unused))) +{ + struct action_type *action; + unsigned i, n = 0; + + if (key->shortName == '?') { + log_std("%s %s\n", PACKAGE_CRYPTSETUP_TPM2, PACKAGE_VERSION); + poptPrintHelp(popt_context, stdout, 0); + log_std(_("\n" + " is one of:\n")); + for(action = action_types; action->type; action++) + log_std("\t%s %s - %s\n", action->type, _(action->arg_desc), _(action->desc)); + l_std(NULL, "\nDefault pass-size: %d [bytes]\n", DEFAULT_TPM2_SIZE); + l_std(NULL, "Default PCRs: (none)\n"); + l_std(NULL, "Default PCR banks: %s\n", DEFAULT_PCR_BANK); + + l_std(NULL, "Possible PCR banks:"); + for (i = 0; i < CRYPT_HASH_ALGS_COUNT; i++) { + printf("%s %s", n++ ? "," : "", hash_algs[i].name); + } + putchar('\n'); + + exit(EXIT_SUCCESS); + } else + usage(popt_context, EXIT_SUCCESS, NULL, NULL); +} + +static int run_action(struct action_type *action, struct crypt_device *cd) +{ + int r; + + log_dbg("Running command %s.", action->type); + + r = action->handler(cd); + + show_status(r); + return translate_errno(r); +} + +int main(int argc, const char **argv) +{ + int r = 0; + struct crypt_device *cd = NULL; + static const char *null_action_argv[] = {NULL}; + struct action_type *action; + const char *aname; + static long int tpmnv = 0; + static const char *tpmbanks = DEFAULT_PCR_BANK; + static const char *tpmpcr = NULL; + + static struct poptOption popt_help_options[] = { + { NULL, '\0', POPT_ARG_CALLBACK, help, 0, NULL, NULL }, + { "help", '?', POPT_ARG_NONE, NULL, 0, N_("Show this help message"), NULL }, + { "usage", '\0', POPT_ARG_NONE, NULL, 0, N_("Display brief usage"), NULL }, + POPT_TABLEEND + }; + + static struct poptOption token_popt_options[] = { + { NULL, '\0', POPT_ARG_INCLUDE_TABLE, popt_help_options, 0, "Help options:", NULL }, + { "tpm2-nv", '\0', POPT_ARG_LONG, &tpmnv, 0, "Select TPM's NV index", "<0x01800000..0x01BFFFFF>" }, + { "tpm2-pcr", '\0', POPT_ARG_STRING, &tpmpcr, 0, "Selection of TPM PCRs", "[,[,[...]]]" }, + { "tpm2-bank", '\0', POPT_ARG_STRING, &tpmbanks, 0, "Selection of TPM PCR banks", "[,[,[...]]]" }, + { "tpm2-daprotect", '\0', POPT_ARG_NONE, &opt_tpmdaprotect, 0, "Enable TPM dictionary attack protection", NULL}, + { "tpm2-no-pin", '\0', POPT_ARG_NONE, &opt_no_tpm_pin, 0, "Don't PIN protect TPM NV index", NULL}, + { "tpm2-key-size", '\0', POPT_ARG_LONG, &opt_pass_size, 0, "Size of randomly generated key for unlocking keyslot", ""}, + { "token-id", '\0', POPT_ARG_INT, &opt_token, 0, "Token number", NULL }, + { "timeout", '\0', POPT_ARG_INT, &opt_timeout, 0, "Timeout for interactive passphrase prompt (in seconds)", "secs" }, + { "debug", '\0', POPT_ARG_NONE, &opt_debug, 0, "Show debug messages", NULL }, + POPT_TABLEEND + }; + poptContext popt_context; + + crypt_set_log_callback(NULL, tool_log, NULL); + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + popt_context = poptGetContext("cryptsetup_tpm2", argc, argv, token_popt_options, 0); + poptSetOtherOptionHelp(popt_context, "[OPTION...] "); + + while ((r = poptGetNextOpt(popt_context)) > 0) ; + if (r < -1) + usage(popt_context, EXIT_FAILURE, poptStrerror(r), + poptBadOption(popt_context, POPT_BADOPTION_NOALIAS)); + + if (!(aname = poptGetArg(popt_context))) + usage(popt_context, EXIT_FAILURE, "Argument missing.", + poptGetInvocationName(popt_context)); + + action_argc = 0; + action_argv = poptGetArgs(popt_context); + /* Make return values of poptGetArgs more consistent in case of remaining argc = 0 */ + if (!action_argv) + action_argv = null_action_argv; + + while(action_argv[action_argc] != NULL) + action_argc++; + + for (action = action_types; action->type; action++) + if (strcmp(action->type, aname) == 0) + break; + + if (!action->type) + usage(popt_context, EXIT_FAILURE, _("Unknown action."), + poptGetInvocationName(popt_context)); + + if (action_argc < action->required_action_argc) { + char buf[128]; + snprintf(buf, 128,_("%s: requires %s as arguments"), action->type, action->arg_desc); + usage(popt_context, EXIT_FAILURE, buf, + poptGetInvocationName(popt_context)); + } + + opt_tpmnv = (uint32_t)tpmnv; + + if (tpmpcr && tpm2_token_get_pcrs(tpmpcr, &opt_tpmpcr) < 0) + usage(popt_context, EXIT_FAILURE, "Wrong PCR value.", + poptGetInvocationName(popt_context)); + + if (tpmbanks && tpm2_token_get_pcrbanks(tpmbanks, &opt_tpmbanks) < 0) + usage(popt_context, EXIT_FAILURE, "Wrong PCR bank value.", + poptGetInvocationName(popt_context)); + + if (opt_debug) { + crypt_set_debug_level(CRYPT_DEBUG_ALL); + dbg_version_and_cmd(argc, argv); + } + + r = crypt_token_load("tpm2"); + if (r < 0) { + l_err(NULL, "Failed to load tpm2 token handler."); + return EXIT_FAILURE; + } + + r = crypt_init(&cd, action_argv[0]); + if (r < 0) { + l_err(NULL, "Failed to init device %s.", argv[1]); + return EXIT_FAILURE; + } + + if (strcmp(action->type, "list") != 0) + r = crypt_load(cd, CRYPT_LUKS2, NULL); + if (r < 0) { + l_err(cd, "Failed to load luks2 device %s.", action_argv[0]); + r = EXIT_FAILURE; + } else + r = run_action(action, cd); + + if (r) { + l_err(cd, "Action %s FAILED.", action->type); + } else { + l_std(cd, "Action %s successful.\n", action->type); + } + + crypt_free(cd); + poptFreeContext(popt_context); + return r; +} diff --git a/tokens/tpm2/libcryptsetup-token-tpm2.c b/tokens/tpm2/libcryptsetup-token-tpm2.c new file mode 100644 index 00000000..89371cfd --- /dev/null +++ b/tokens/tpm2/libcryptsetup-token-tpm2.c @@ -0,0 +1,157 @@ +/* + * LUKS - Linux Unified Key Setup v2, TPM type token handler + * + * Copyright (C) 2018-2020 Fraunhofer SIT sponsorred by Infineon Technologies AG + * Copyright (C) 2019-2020 Red Hat, Inc. All rights reserved. + * Copyright (C) 2019-2020 Daniel Zatovic + * Copyright (C) 2019-2020 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 +#include +#include +#include +#include +#include "utils_tpm2.h" +#include "libcryptsetup.h" + +static void tpm2_token_dump(struct crypt_device *cd, const char *json) +{ + uint32_t nvindex, pcrs, pcrbanks; + size_t nvkey_size; + bool daprotect, pin; + char buf[1024], num[32]; + unsigned i, n; + + if (tpm2_token_read(cd, json, &nvindex, &pcrs, &pcrbanks, + &daprotect, &pin, &nvkey_size)) { + l_err(cd, "Cannot read JSON token metadata."); + return; + } + + l_std(cd, "\tNVindex: 0x%08" PRIx32 "\n", nvindex); + l_std(cd, "\tNVKey: %zu [bytes]\n", nvkey_size); + + for (*buf = '\0', n = 0, i = 0; i < 32; i++) { + if (!(pcrs & (1 << i))) + continue; + snprintf(num, sizeof(num), "%s%u", n ? "," : "", i); + strcat(buf, num); + n++; + } + l_std(cd, "\tPCRs: %s\n", buf); + + *buf = '\0'; + n = 0; + + for (i = 0; i < CRYPT_HASH_ALGS_COUNT; i++) { + if (pcrbanks & hash_algs[i].crypt_id) { + if (n) + strcat(buf, ","); + strcat(buf, hash_algs[i].name); + n++; + } + } + + l_std(cd, "\tPCRBanks: %s\n", buf); + + *buf = '\0'; + n = 0; + if (daprotect) { + strcat(buf, "DA_PROTECT"); + n++; + } + if (pin) + strcat(buf, n++ ? ",PIN" : "PIN"); + l_std(cd, "\tflags: %s\n", buf); +} + +static int tpm2_token_open_pin(struct crypt_device *cd, + int token, + const char *tpm_pass, + char **buffer, + size_t *buffer_len, + void *usrptr) +{ + int r; + TSS2_RC tpm_rc; + uint32_t nvindex, pcrselection, pcrbanks; + size_t nvkey_size; + bool daprotect, pin; + const char *json; + + r = crypt_token_json_get(cd, token, &json); + if (r < 0) { + l_err(cd, "Cannot read JSON token metadata."); + return r; + } + + r = tpm2_token_read(cd, json, &nvindex, &pcrselection, &pcrbanks, + &daprotect, &pin, &nvkey_size); + if (r < 0) { + l_err(cd, "Cannot read JSON token metadata."); + return r; + } + + if (pin && !tpm_pass) { + if (daprotect) + l_std(cd, "TPM stored password has dictionary attack protection turned on. " + "Don't enter password too many times.\n"); + return -EAGAIN; + } + + *buffer = malloc(nvkey_size); + if (!*buffer) + return -ENOMEM; + *buffer_len = nvkey_size; + + tpm_rc = tpm_nv_read(cd, nvindex, tpm_pass, tpm_pass ? strlen(tpm_pass) : 0, + pcrselection, pcrbanks, *buffer, nvkey_size); + + if (tpm_rc == TSS2_RC_SUCCESS) + return 0; + + if (tpm_rc == (TPM2_RC_S | TPM2_RC_1 | TPM2_RC_BAD_AUTH) || + tpm_rc == (TPM2_RC_S | TPM2_RC_1 | TPM2_RC_AUTH_FAIL)) { + LOG_TPM_ERR(cd, tpm_rc); + return -EPERM; + } + + return -EINVAL; +} + +static int tpm2_token_open(struct crypt_device *cd, + int token, + char **buffer, + size_t *buffer_len, + void *usrptr) +{ + return tpm2_token_open_pin(cd, token, NULL, buffer, buffer_len, usrptr); +} + +static int _tpm2_token_validate(struct crypt_device *cd, const char *json) +{ + return tpm2_token_validate(json); +} + +const crypt_token_handler cryptsetup_token_handler = { + .name = "tpm2", + .open = tpm2_token_open, + .open_pin = tpm2_token_open_pin, + .validate = _tpm2_token_validate, + .dump = tpm2_token_dump +}; diff --git a/tokens/tpm2/utils_tpm2.c b/tokens/tpm2/utils_tpm2.c new file mode 100644 index 00000000..836fa29c --- /dev/null +++ b/tokens/tpm2/utils_tpm2.c @@ -0,0 +1,727 @@ +/* + * TPM2 utilities for LUKS2 TPM2 type keyslot + * + * Copyright (C) 2018-2020 Fraunhofer SIT sponsorred by Infineon Technologies AG + * Copyright (C) 2019-2020 Red Hat, Inc. All rights reserved. + * Copyright (C) 2019-2020 Daniel Zatovic + * Copyright (C) 2019-2020 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 +#include +#include +#include +#include +#include +#include "utils_tpm2.h" +#include "libcryptsetup.h" + +#define LOG_MAX_LEN 4096 + +__attribute__((format(printf, 3, 4))) +void my_logger(struct crypt_device *cd, int level, const char *format, ...) +{ + va_list argp; + char target[LOG_MAX_LEN + 2]; + int len; + + va_start(argp, format); + + len = vsnprintf(&target[0], LOG_MAX_LEN, format, argp); + if (len > 0 && len < LOG_MAX_LEN) { + /* All verbose and error messages in tools end with EOL. */ + if (level == CRYPT_LOG_VERBOSE || level == CRYPT_LOG_ERROR || + level == CRYPT_LOG_DEBUG || level == CRYPT_LOG_DEBUG_JSON) + strncat(target, "\n", LOG_MAX_LEN); + + crypt_log(cd, level, target); + } + + va_end(argp); +} + +const alg_info *get_alg_info_by_name(const char *name) { + unsigned i; + + for(i = 0; i < CRYPT_HASH_ALGS_COUNT; i++) { + if (!strcasecmp(hash_algs[i].name, name)) + return &hash_algs[i]; + } + + return NULL; +} + +const alg_info *get_alg_info_by_id(TPM2_ALG_ID id) { + unsigned i; + + for(i = 0; i < CRYPT_HASH_ALGS_COUNT; i++) { + if (hash_algs[i].id == id) + return &hash_algs[i]; + } + + return NULL; +} + +const alg_info *get_alg_info_by_crypt_id(uint32_t crypt_id) { + unsigned i; + + for(i = 0; i < CRYPT_HASH_ALGS_COUNT; i++) { + if (hash_algs[i].crypt_id == crypt_id) + return &hash_algs[i]; + } + + return NULL; +} + +/* + * Initialize the TPM including a potentially necessary TPM2_Startup command, + * which is needed for simulators and RPi TPM hats. + */ +static TSS2_RC tpm_init(struct crypt_device *cd, ESYS_CONTEXT **ctx) +{ + TSS2_RC r; + l_dbg(cd, "Initializing ESYS connection"); + + r = Esys_Initialize(ctx, NULL, NULL); + if (r != TSS2_RC_SUCCESS) { + l_err(cd, "Error initializing ESYS"); + return r; + } + + r = Esys_Startup(*ctx, TPM2_SU_CLEAR); + if (r == TPM2_RC_INITIALIZE) { + l_dbg(cd, "TPM already started up. Not an error!"); + r = TSS2_RC_SUCCESS; + } + + if (r != TSS2_RC_SUCCESS) { + l_err(cd, "TPM StartUp command failed"); + Esys_Finalize(ctx); + } + + return r; +} + +static TSS2_RC tpm_getPcrDigest(struct crypt_device *cd, + ESYS_CONTEXT *ctx, + const TPML_PCR_SELECTION *pcrs, + TPM2_ALG_ID hashAlg, + TPM2B_DIGEST *pcrDigest) +{ + TSS2_RC r; + TPM2B_AUTH auth = {0}; + ESYS_TR hash; + TPML_DIGEST *value; + TPML_PCR_SELECTION readPCRs = { + .count = 1, + .pcrSelections = {} + }; + TPM2B_DIGEST *returnPcrDigest; + TPM2B_MAX_BUFFER digest; + unsigned int i, j; + + r = Esys_HashSequenceStart(ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + &auth, hashAlg, &hash); + if (r) + return r; + + for (i = 0; i < pcrs->count; i++) { + + readPCRs.pcrSelections[0].hash = pcrs->pcrSelections[i].hash; + readPCRs.pcrSelections[0].sizeofSelect = 3; + for (j = 0; j < 24; j++) { + if (!(pcrs->pcrSelections[i].pcrSelect[j / 8] & (1 << (j % 8)))) + continue; + + readPCRs.pcrSelections[0].pcrSelect[0] = 0; + readPCRs.pcrSelections[0].pcrSelect[1] = 0; + readPCRs.pcrSelections[0].pcrSelect[2] = 0; + + readPCRs.pcrSelections[0].pcrSelect[j / 8] = (1 << (j % 8)); + + r = Esys_PCR_Read(ctx, + ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + &readPCRs, NULL, NULL, &value); + //TODO: if (r == 0x984) continue; + if (r) { + l_err(cd, "PCR Read failed"); + return r; + } + if (!value->count) { + free(value); + continue; + } + + digest.size = value->digests[0].size; + memcpy(&digest.buffer[0], &value->digests[0].buffer, digest.size); + + r = Esys_SequenceUpdate(ctx, hash, ESYS_TR_PASSWORD, ESYS_TR_NONE, + ESYS_TR_NONE, &digest); + free(value); + if (r) + return r; + } + } + + r = Esys_SequenceComplete(ctx, hash, ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + NULL, TPM2_RH_NULL, &returnPcrDigest, NULL); + if (r) + return r; + + *pcrDigest = *returnPcrDigest; + free(returnPcrDigest); + + return 0; +} + +static TSS2_RC tpm_policy_Read(struct crypt_device *cd, + ESYS_CONTEXT *ctx, + uint32_t tpm_pcr, + uint32_t pcrbanks, + ESYS_TR *authSession, + TPM2B_DIGEST *authPolicy) +{ + TSS2_RC r; + ESYS_TR session; + TPM2B_DIGEST *policyDigest; + TPM2B_DIGEST pcrDigest = { + .size = 0, + .buffer = {} + }; + TPML_PCR_SELECTION pcrs = { + .count = 0 + }; + TPMT_SYM_DEF sym = { + .algorithm = TPM2_ALG_AES, + .keyBits = { + .aes = 128 + }, + .mode = { + .aes = TPM2_ALG_CFB + } + }; + unsigned int i; + + if (pcrbanks == 0) { + l_err(cd, "No banks selected."); + return TSS2_BASE_RC_BAD_SIZE; + } + + for (i = 0; i < CRYPT_HASH_ALGS_COUNT; i++) { + if (pcrbanks & hash_algs[i].crypt_id) { + pcrs.pcrSelections[pcrs.count].hash = hash_algs[i].id; + pcrs.count++; + } + } + + for (i = 0; i < pcrs.count; i++) { + pcrs.pcrSelections[i].sizeofSelect = 3; + pcrs.pcrSelections[i].pcrSelect[0] = tpm_pcr & 0xff; + pcrs.pcrSelections[i].pcrSelect[1] = tpm_pcr >> 8 & 0xff; + pcrs.pcrSelections[i].pcrSelect[2] = tpm_pcr >>16 & 0xff; + } + + r = tpm_getPcrDigest(cd, ctx, &pcrs, TPM2_ALG_SHA256, &pcrDigest); + if (r != TSS2_RC_SUCCESS) + return r; + + r = Esys_StartAuthSession(ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + ESYS_TR_NONE, ESYS_TR_NONE, NULL, TPM2_SE_POLICY, + &sym, TPM2_ALG_SHA256, &session); + if (r != TSS2_RC_SUCCESS) { + return r; + } + + r = Esys_PolicyPCR(ctx, session, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + &pcrDigest, &pcrs); + if (r != TSS2_RC_SUCCESS) { + Esys_FlushContext(ctx, session); + return r; + } + + r = Esys_PolicyPassword(ctx, session, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE); + if (r != TSS2_RC_SUCCESS) { + Esys_FlushContext(ctx, session); + return r; + } + + r = Esys_PolicyCommandCode(ctx, session, ESYS_TR_NONE, ESYS_TR_NONE, + ESYS_TR_NONE, TPM2_CC_NV_Read); + if (r != TSS2_RC_SUCCESS) { + Esys_FlushContext(ctx, session); + return r; + } + + r = Esys_PolicyGetDigest(ctx, session, ESYS_TR_NONE, ESYS_TR_NONE, + ESYS_TR_NONE, &policyDigest); + if (r != TSS2_RC_SUCCESS) { + Esys_FlushContext(ctx, session); + return r; + } + + if (authSession) + *authSession = session; + else + Esys_FlushContext(ctx, session); + + if (authPolicy) + *authPolicy = *policyDigest; + free(policyDigest); + + return TSS2_RC_SUCCESS; +} + +static TSS2_RC tpm_nv_prep(struct crypt_device *cd, + uint32_t tpm_nv, + const char *pin, + size_t pin_size, + ESYS_CONTEXT **ctx, + ESYS_TR *nvIndex) +{ + TSS2_RC r; + TPM2B_AUTH tpm_pin = { + .size = pin_size, + .buffer = {} + }; + + if (pin_size > sizeof(tpm_pin.buffer)) + return TSS2_BASE_RC_BAD_SIZE; + + if (pin_size > 0) + memcpy(&tpm_pin.buffer[0], pin, tpm_pin.size); + + if (tpm_nv < 0x01800000 || tpm_nv > 0x01BFFFFF) { + l_err(cd, "NV index handle %08x out of range", tpm_nv); + return TSS2_BASE_RC_BAD_SIZE; + } + + r = tpm_init(cd, ctx); + if (r != TSS2_RC_SUCCESS) + return r; + + r = Esys_TR_FromTPMPublic(*ctx, tpm_nv, ESYS_TR_NONE, ESYS_TR_NONE, + ESYS_TR_NONE, nvIndex); + if (r != TSS2_RC_SUCCESS) { + Esys_Finalize(ctx); + return r; + } + + r = Esys_TR_SetAuth(*ctx, *nvIndex, &tpm_pin); + if (r != TSS2_RC_SUCCESS) { + Esys_Finalize(ctx); + return r; + } + + return r; +} + +TSS2_RC tpm_nv_read(struct crypt_device *cd, + uint32_t tpm_nv, + const char *pin, + size_t pin_size, + uint32_t tpm_pcr, + uint32_t pcrbanks, + char *nvkey, + size_t nvkey_size) +{ + TSS2_RC r; + ESYS_CONTEXT *ctx; + ESYS_TR nvIndex, session; + TPM2B_MAX_NV_BUFFER *nv_pass = NULL; + + r = tpm_nv_prep(cd, tpm_nv, pin, pin_size, &ctx, &nvIndex); + if (r != TSS2_RC_SUCCESS) + return r; + + r = tpm_policy_Read(cd, ctx, tpm_pcr, pcrbanks, &session, NULL); + if (r != TSS2_RC_SUCCESS) { + goto out; + } + Esys_TRSess_SetAttributes(ctx, session, 0, TPMA_SESSION_CONTINUESESSION); + + r = Esys_NV_Read(ctx, nvIndex, nvIndex, session, ESYS_TR_NONE, ESYS_TR_NONE, + nvkey_size, 0, &nv_pass); + if (r != TSS2_RC_SUCCESS) { + r = Esys_FlushContext(ctx, session); + goto out; + } + + if (nvkey_size != nv_pass->size) { + l_err(cd, "VK lengths differ"); + r = TSS2_BASE_RC_BAD_VALUE; + goto out; + } + + memcpy(nvkey, &nv_pass->buffer[0], nvkey_size); +out: + free(nv_pass); + Esys_Finalize(&ctx); + + return r; +} + +TSS2_RC tpm_nv_write(struct crypt_device *cd, + uint32_t tpm_nv, + const char *pin, + size_t pin_size, + const char *buffer, + size_t buffer_size) +{ + TSS2_RC r; + ESYS_CONTEXT *ctx; + ESYS_TR nvIndex; + TPM2B_MAX_NV_BUFFER nv_pass = { + .size = buffer_size, + .buffer = {} + }; + + if (buffer_size > sizeof(nv_pass.buffer)) + return TPM2_RC_FAILURE; + + memcpy(&nv_pass.buffer[0], buffer, buffer_size); + + r = tpm_nv_prep(cd, tpm_nv, pin, pin_size, &ctx, &nvIndex); + if (r != TSS2_RC_SUCCESS) + return r; + + r = Esys_NV_Write(ctx, nvIndex, nvIndex, ESYS_TR_PASSWORD, ESYS_TR_NONE, + ESYS_TR_NONE, &nv_pass, 0); + Esys_Finalize(&ctx); + + return r; +} + +TSS2_RC tpm_nv_define(struct crypt_device *cd, + uint32_t tpm_nv, + const char *pin, + size_t pin_size, + uint32_t tpm_pcr, + uint32_t pcrbanks, + bool daprotect, + const char *ownerpw, + size_t ownerpw_size, + size_t nvkey_size) +{ + TSS2_RC r; + ESYS_CONTEXT *ctx; + ESYS_TR nvIndex; + TPM2B_AUTH tpm_pin = { + .size = pin_size, + .buffer = {} + }; + TPM2B_NV_PUBLIC nvInfo = { + .size = 0, + .nvPublic = { + .nvIndex = tpm_nv, + .nameAlg = TPM2_ALG_SHA256, + .attributes = TPMA_NV_AUTHWRITE | TPMA_NV_POLICYREAD | TPMA_NV_WRITEALL, + .authPolicy = { + .size = 0, + .buffer = {}, + }, + .dataSize = (uint16_t)nvkey_size + } + }; + TPM2B_AUTH ownerauth = { + .size = ownerpw_size, + .buffer={} + }; + + if (nvkey_size > UINT16_MAX) + return TPM2_RC_FAILURE; + + if (pin_size > sizeof(tpm_pin.buffer)) + return TPM2_RC_FAILURE; + + if (pin_size > 0) + memcpy(&tpm_pin.buffer[0], pin, tpm_pin.size); + + if (!daprotect) + nvInfo.nvPublic.attributes |= TPMA_NV_NO_DA; + + if (tpm_nv < 0x01800000 || tpm_nv > 0x01BFFFFF) { + l_err(cd, "NV index handle %08x out of range", tpm_nv); + return TPM2_RC_FAILURE; + } + + r = tpm_init(cd, &ctx); + if (r != TSS2_RC_SUCCESS) + return r; + + if (ownerpw != NULL) { + if (ownerpw_size > sizeof(ownerauth.buffer)) + return TPM2_RC_FAILURE; + memcpy(&ownerauth.buffer[0], ownerpw, ownerauth.size); + + r = Esys_TR_SetAuth(ctx, ESYS_TR_RH_OWNER, &ownerauth); + if (r != TSS2_RC_SUCCESS) + goto out; + } + + r = tpm_policy_Read(cd, ctx, tpm_pcr, pcrbanks, NULL, &nvInfo.nvPublic.authPolicy); + if (r != TSS2_RC_SUCCESS) + goto out; + + l_dbg(cd, "Defining TPM handle 0x%08x.", tpm_nv); + + r = Esys_NV_DefineSpace(ctx, ESYS_TR_RH_OWNER, ESYS_TR_PASSWORD, ESYS_TR_NONE, + ESYS_TR_NONE, &tpm_pin, &nvInfo, &nvIndex); +out: + Esys_Finalize(&ctx); + + return r; +} + +TSS2_RC tpm_nv_undefine(struct crypt_device *cd, uint32_t tpm_nv) +{ + TSS2_RC r; + ESYS_CONTEXT *ctx; + ESYS_TR nvIndex; + + r = tpm_nv_prep(cd, tpm_nv, NULL, 0, &ctx, &nvIndex); + if (r != TSS2_RC_SUCCESS) + return r; + + l_dbg(cd, "Deleting TPM handle 0x%08x.", tpm_nv); + + r = Esys_NV_UndefineSpace(ctx, ESYS_TR_RH_OWNER, nvIndex, ESYS_TR_PASSWORD, + ESYS_TR_NONE, ESYS_TR_NONE); + Esys_Finalize(&ctx); + + return r; +} + +TSS2_RC tpm_nv_find(struct crypt_device *cd, uint32_t *tpm_nv) +{ + TSS2_RC r; + ESYS_CONTEXT *ctx; + ESYS_TR nvIndex; + TPMS_CAPABILITY_DATA *capabilityData; + int i; + + *tpm_nv = 0; + + r = tpm_init(cd, &ctx); + if (r != TSS2_RC_SUCCESS) + return r; + + r = Esys_GetCapability(ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + TPM2_CAP_HANDLES, 0x01000000, 0xffff, NULL, &capabilityData); + Esys_Finalize(&ctx); + if (r != TSS2_RC_SUCCESS) + return r; + + for (nvIndex = 0x01BF0000; nvIndex < 0x01BF00FF; nvIndex++) { + for (i = capabilityData->data.handles.count-1; i >= 0; i--) { + if (nvIndex == capabilityData->data.handles.handle[i]) + break; + } + + if (i < 0) { + *tpm_nv = nvIndex; + l_dbg(cd, "Found NV-Index 0x%08x.", nvIndex); + free(capabilityData); + return 0; + } else + l_dbg(cd, "NV-Index 0x%08x already in use.", nvIndex); + } + + free(capabilityData); + return r; +} + +TSS2_RC tpm_nv_exists(struct crypt_device *cd, uint32_t tpm_nv, bool *exists) +{ + TSS2_RC r; + ESYS_CONTEXT *ctx; + TPMS_CAPABILITY_DATA *capabilityData; + int i; + + r = tpm_init(cd, &ctx); + if (r != TSS2_RC_SUCCESS) + return r; + + r = Esys_GetCapability(ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + TPM2_CAP_HANDLES, 0x01000000, 0xffff, NULL, &capabilityData); + Esys_Finalize(&ctx); + if (r != TSS2_RC_SUCCESS) { + l_err(cd, "Error retrieving TPM capabilities."); + return r; + } + + *exists = false; + for (i = capabilityData->data.handles.count-1; i >= 0; i--) { + if (tpm_nv == capabilityData->data.handles.handle[i]) { + l_dbg(cd, "TPM-NV-Handle 0x%08x does exist.", tpm_nv); + *exists = true; + break; + } + } + + free(capabilityData); + return r; +} + +int tpm_get_random(struct crypt_device *cd, char *random_bytes, size_t len) +{ + TSS2_RC r; + ESYS_CONTEXT *ctx; + TPM2B_DIGEST *random_bytes_in; + unsigned int i = 0; + unsigned int j; + unsigned int retries = 5; + + if (len > UINT16_MAX) + return -EINVAL; + + r = tpm_init(cd, &ctx); + if (r != TSS2_RC_SUCCESS) + return -EINVAL; + + while (retries && i < len) { + /* SHA256 should be supported on all TPM2 chips, so 256 bits + should be minimal supported random length */ + r = Esys_GetRandom(ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + TPM2_SHA256_DIGEST_SIZE, &random_bytes_in); + + if (r != TPM2_RC_SUCCESS) { + retries--; + continue; + } + + for (j = 0; j < random_bytes_in->size && i < len; j++) { + random_bytes[i] = random_bytes_in->buffer[j]; + random_bytes_in->buffer[j] = 0; + i++; + } + + free(random_bytes_in); + } + + Esys_Finalize(&ctx); + + if (i < len) { + return -EINVAL; + } + + return 0; +} + +bool pcrs_in_selection(uint32_t required_pcrs, TPMS_PCR_SELECTION *selection) { + uint32_t selected_pcrs = 0; + int i = 0; + + if (!selection) + return false; + + for (i = 0; i < selection->sizeofSelect; i++) + selected_pcrs |= selection->pcrSelect[i] << (i * 8); + + return (selected_pcrs & required_pcrs) == required_pcrs; +} + +TPMS_PCR_SELECTION *tpm2_get_pcrs_by_alg(TPMS_CAPABILITY_DATA *savedPCRs, uint32_t pcrbank) +{ + unsigned i; + const alg_info *info = get_alg_info_by_crypt_id(pcrbank); + + if (!savedPCRs) + return NULL; + if (!info) + return NULL; + + for (i = 0; i < savedPCRs->data.assignedPCR.count; i++) { + if (savedPCRs->data.assignedPCR.pcrSelections[i].hash == info->id) + return &savedPCRs->data.assignedPCR.pcrSelections[i]; + } + + return NULL; +} + +TSS2_RC getPCRsCapability(struct crypt_device *cd, TPMS_CAPABILITY_DATA **savedPCRs) +{ + ESYS_CONTEXT *ctx; + TSS2_RC r; + TPMS_CAPABILITY_DATA *currentPCRs; + TPMI_YES_NO more_data; + size_t current_count = 0; + + r = tpm_init(cd, &ctx); + if (r != TSS2_RC_SUCCESS) + return r; + *savedPCRs = NULL; + + do { + r = Esys_GetCapability(ctx, ESYS_TR_NONE, ESYS_TR_NONE, + ESYS_TR_NONE, TPM2_CAP_PCRS, 0, TPM2_NUM_PCR_BANKS, + &more_data, ¤tPCRs); + if (r != TSS2_RC_SUCCESS) { + if (*savedPCRs) + free(*savedPCRs); + goto out; + } + + if (currentPCRs->data.assignedPCR.count + current_count > TPM2_NUM_PCR_BANKS) { + currentPCRs->data.assignedPCR.count = TPM2_NUM_PCR_BANKS - current_count; + } + + if (*savedPCRs) { + memmove(&(*savedPCRs)->data.assignedPCR.pcrSelections[current_count], + currentPCRs->data.assignedPCR.pcrSelections, + currentPCRs->data.assignedPCR.count * sizeof(currentPCRs->data.assignedPCR.pcrSelections[0])); + free(currentPCRs); + current_count += currentPCRs->data.assignedPCR.count; + } else { + *savedPCRs = currentPCRs; + current_count = currentPCRs->data.assignedPCR.count; + } + } while (more_data && current_count < TPM2_NUM_PCR_BANKS); + +out: + Esys_Finalize(&ctx); + return r; +} + +TSS2_RC tpm2_supports_algs_for_pcrs(struct crypt_device *cd, uint32_t pcrbanks, uint32_t pcrs, bool *supports) +{ + TSS2_RC r = TSS2_RC_SUCCESS; + TPMS_CAPABILITY_DATA *savedPCRs; + TPMS_PCR_SELECTION *supported_pcrs_selection; + unsigned int i = 0; + + r = getPCRsCapability(cd, &savedPCRs); + if (r != TSS2_RC_SUCCESS) + return r; + + for (i = 0; i < sizeof(pcrbanks) * 8; i++) { + if (pcrbanks & (1 << i)) { + supported_pcrs_selection = tpm2_get_pcrs_by_alg(savedPCRs, pcrbanks & (1 << i)); + if (!supported_pcrs_selection) { + *supports = false; + goto out; + } + if (!pcrs_in_selection(pcrs, supported_pcrs_selection)) { + *supports = false; + goto out; + } + } + } + *supports = true; +out: + free(savedPCRs); + return r; +} diff --git a/tokens/tpm2/utils_tpm2.h b/tokens/tpm2/utils_tpm2.h new file mode 100644 index 00000000..15b019a7 --- /dev/null +++ b/tokens/tpm2/utils_tpm2.h @@ -0,0 +1,145 @@ +/* + * TPM2 utilities for LUKS2 TPM2 type keyslot + * + * Copyright (C) 2018-2020 Fraunhofer SIT sponsorred by Infineon Technologies AG + * Copyright (C) 2019-2020 Red Hat, Inc. All rights reserved. + * Copyright (C) 2019-2020 Daniel Zatovic + * Copyright (C) 2019-2020 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. + */ + +#ifndef _UTILS_TPM2_H +#define _UTILS_TPM2_H + +#include +#include +#include +#include +#include + +struct crypt_device; + +__attribute__((format(printf, 3, 4))) +void my_logger(struct crypt_device *cd, int level, const char *format, ...); + +#define l_std(cd, x...) my_logger(cd, CRYPT_LOG_NORMAL, x) +#define l_err(cd, x...) my_logger(cd, CRYPT_LOG_ERROR, x) +#define l_dbg(cd, x...) my_logger(cd, CRYPT_LOG_DEBUG, x) + +#define LOG_TPM_ERR(cd, r) l_err(cd, "TPM error: %s (code 0x%08x)", Tss2_RC_Decode(r), r) + +/* Flags for activating the PCR banks */ +#define CRYPT_TPM_PCRBANK_SHA1 ((uint32_t) (1 << 0)) +#define CRYPT_TPM_PCRBANK_SHA256 ((uint32_t) (1 << 1)) +#define CRYPT_TPM_PCRBANK_SHA384 ((uint32_t) (1 << 2)) +#define CRYPT_TPM_PCRBANK_SHA512 ((uint32_t) (1 << 3)) +#define CRYPT_TPM_PCRBANK_SM3_256 ((uint32_t) (1 << 4)) +#define CRYPT_TPM_PCRBANK_SHA3_256 ((uint32_t) (1 << 5)) +#define CRYPT_TPM_PCRBANK_SHA3_384 ((uint32_t) (1 << 6)) +#define CRYPT_TPM_PCRBANK_SHA3_512 ((uint32_t) (1 << 7)) + +typedef struct alg_info alg_info; +struct alg_info { + const char *name; + TPM2_ALG_ID id; + uint32_t crypt_id; +}; + +static const alg_info hash_algs[] = { + { .name = "sha1", .id = TPM2_ALG_SHA1, .crypt_id = CRYPT_TPM_PCRBANK_SHA1 }, + { .name = "sha256", .id = TPM2_ALG_SHA256, .crypt_id = CRYPT_TPM_PCRBANK_SHA256 }, + { .name = "sha384", .id = TPM2_ALG_SHA384, .crypt_id = CRYPT_TPM_PCRBANK_SHA384 }, + { .name = "sha512", .id = TPM2_ALG_SHA512, .crypt_id = CRYPT_TPM_PCRBANK_SHA512 }, + { .name = "sm3_256", .id = TPM2_ALG_SM3_256, .crypt_id = CRYPT_TPM_PCRBANK_SM3_256 }, + { .name = "sha3_256", .id = TPM2_ALG_SHA3_256, .crypt_id = CRYPT_TPM_PCRBANK_SHA3_256 }, + { .name = "sha3_384", .id = TPM2_ALG_SHA3_384, .crypt_id = CRYPT_TPM_PCRBANK_SHA3_384 }, + { .name = "sha3_512", .id = TPM2_ALG_SHA3_512, .crypt_id = CRYPT_TPM_PCRBANK_SHA3_512 } +}; + +#define CRYPT_HASH_ALGS_COUNT (sizeof(hash_algs)/sizeof(hash_algs[0])) + +const alg_info *get_alg_info_by_name(const char *name); +const alg_info *get_alg_info_by_id(TPM2_ALG_ID id); +const alg_info *get_alg_info_by_crypt_id(uint32_t crypt_id); + +TSS2_RC tpm_nv_read(struct crypt_device *cd, + uint32_t tpm_nv, + const char *pin, + size_t pin_size, + uint32_t tpm_pcr, + uint32_t pcrbanks, + char *nvkey, + size_t nvkey_size); + +TSS2_RC tpm_nv_write(struct crypt_device *cd, + uint32_t tpm_nv, + const char *pin, + size_t pin_size, + const char *buffer, + size_t buffer_size); + +TSS2_RC tpm_nv_define(struct crypt_device *cd, + uint32_t tpm_nv, + const char *pin, + size_t pin_size, + uint32_t tpm_pcr, + uint32_t pcrbanks, + bool daprotect, + const char *ownerpw, + size_t ownerpw_size, + size_t nvkey_size); + +TSS2_RC tpm_nv_undefine(struct crypt_device *cd, uint32_t tpm_nv); + +TSS2_RC tpm_nv_find(struct crypt_device *cd, uint32_t *tpm_nv); + +TSS2_RC tpm_nv_exists(struct crypt_device *cd, uint32_t tpm_nv, bool *exists); + +int tpm_get_random(struct crypt_device *cd, char *random_bytes, size_t len); + +/* + * TPM2 token helpers + */ + +int tpm2_token_add(struct crypt_device *cd, + uint32_t tpm_nv, + uint32_t tpm_pcr, + uint32_t pcrbanks, + bool daprotect, + bool pin, + size_t nvkey_size); + +int tpm2_token_read(struct crypt_device *cd, + const char *json, + uint32_t *tpm_nv, + uint32_t *tpm_pcr, + uint32_t *pcrbanks, + bool *daprotect, + bool *pin, + size_t *nvkey_size); + +int tpm2_token_by_nvindex(struct crypt_device *cd, uint32_t tpm_nv); + +int tpm2_token_kill(struct crypt_device *cd, int token); +int tpm2_token_validate(const char *json); + +int tpm2_token_get_pcrbanks(const char *pcrbanks_str, uint32_t *pcrbanks); +int tpm2_token_get_pcrs(const char *pcrs_str, uint32_t *pcrs); +TPMS_PCR_SELECTION *tpm2_get_pcrs_by_alg(TPMS_CAPABILITY_DATA *savedPCRs, uint32_t pcrbank); +TSS2_RC getPCRsCapability(struct crypt_device *cd, TPMS_CAPABILITY_DATA **savedPCRs); +TSS2_RC tpm2_supports_algs_for_pcrs(struct crypt_device *cd, uint32_t pcrbanks, uint32_t pcrs, bool *supports); + +#endif /* _UTILS_TPM2_H */ diff --git a/tokens/tpm2/utils_tpm2_json.c b/tokens/tpm2/utils_tpm2_json.c new file mode 100644 index 00000000..c9f76ab3 --- /dev/null +++ b/tokens/tpm2/utils_tpm2_json.c @@ -0,0 +1,373 @@ +/* + * TPM2 utilities for LUKS2 TPM2 type keyslot + * + * Copyright (C) 2018-2019 Fraunhofer SIT sponsorred by Infineon Technologies AG + * Copyright (C) 2019 Red Hat, Inc. All rights reserved. + * Copyright (C) 2019 Daniel Zatovic + * Copyright (C) 2019 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 +#include +#include +#include +#include "utils_tpm2.h" +#include "libcryptsetup.h" + +int tpm2_token_get_pcrbanks(const char *pcrbanks_str, uint32_t *pcrbanks) +{ + char *p, *s, *orig_s; + int r = 0; + const alg_info *info; + + if (!pcrbanks_str || !pcrbanks) + return -EINVAL; + + s = strdup(pcrbanks_str); + if (!s) + return -ENOMEM; + orig_s = s; + + while ((p = strsep(&s, ","))) { + if (!(info = get_alg_info_by_name(p))) + r = -EINVAL; + else + *pcrbanks |= info->crypt_id; + } + + free(orig_s); + return r; +} + +static void tpm2_token_set_pcrbanks(json_object *jobj, uint32_t pcrbanks) +{ + unsigned i; + + for (i = 0; i < CRYPT_HASH_ALGS_COUNT; i++) { + if (pcrbanks & hash_algs[i].crypt_id) { + json_object_array_add(jobj, json_object_new_string(hash_algs[i].name)); + } + } +} + +int tpm2_token_get_pcrs(const char *pcrs_str, uint32_t *pcrs) +{ + char *p, *s, *orig_s; + int i, r = 0; + + if (!pcrs_str || !pcrs) + return -EINVAL; + + s = strdup(pcrs_str); + if (!s) + return -ENOMEM; + orig_s = s; + + while ((p = strsep(&s, ","))) { + if (sscanf(p, "%i", &i) != 1 || i < 0 || i >= 24) { + r = -EINVAL; + break; + } + *pcrs |= 1 << i; + } + + free(orig_s); + return r; +} + +static void tpm2_token_set_pcrs(json_object *jobj, uint32_t pcrselection) +{ + for (int i = 0; i < 32; i++) { + if (pcrselection & (1 << i)) + json_object_array_add(jobj, json_object_new_int64(i)); + } +} + +static bool array_of_type(json_object *jobj, json_type type) +{ + for (int i = 0; i < json_object_array_length(jobj); i++) + if (!json_object_is_type(json_object_array_get_idx(jobj, i), type)) + return false; + return true; +} + +int tpm2_token_validate(const char *json) +{ + enum json_tokener_error jerr; + json_object *jobj_token, *jobj; + int r = -EINVAL; + + jobj_token = json_tokener_parse_verbose(json, &jerr); + if (!jobj_token) + return -EINVAL; + + if (!json_object_object_get_ex(jobj_token, "nvindex", &jobj) || + !json_object_is_type(jobj, json_type_int)) + goto out; + + if (!json_object_object_get_ex(jobj_token, "nvkey-size", &jobj) || + !json_object_is_type(jobj, json_type_int)) + goto out; + + if (!json_object_object_get_ex(jobj_token, "pcrbanks", &jobj) || + !json_object_is_type(jobj, json_type_array) || + !array_of_type(jobj, json_type_string)) + goto out; + + if (!json_object_object_get_ex(jobj_token, "pcrselection", &jobj) || + !json_object_is_type(jobj, json_type_array) || + !array_of_type(jobj, json_type_int)) + goto out; + + if (!json_object_object_get_ex(jobj_token, "flags", &jobj) || + !json_object_is_type(jobj, json_type_array) || + !array_of_type(jobj, json_type_string)) + goto out; + + r = 0; +out: + json_object_put(jobj_token); + return r; +} + +int tpm2_token_read(struct crypt_device *cd, + const char *json, + uint32_t *tpm_nv, + uint32_t *tpm_pcr, + uint32_t *pcrbanks, + bool *daprotect, + bool *pin, + size_t *nvkey_size) +{ + enum json_tokener_error jerr; + json_object *jobj_token, *jobj, *jobj1; + const char *str; + bool tmp_da = false, tmp_pin = false; + uint32_t tmp_pcrbanks = 0, tmp_pcrselection = 0; + int i, r = -EINVAL; + + jobj_token = json_tokener_parse_verbose(json, &jerr); + if (!jobj_token) + return -EINVAL; + + if (!json_object_object_get_ex(jobj_token, "nvindex", &jobj)) + goto out; + if (tpm_nv) + *tpm_nv = (uint32_t)json_object_get_int64(jobj); + + if (!json_object_object_get_ex(jobj_token, "nvkey-size", &jobj)) + goto out; + if (nvkey_size) + *nvkey_size = (size_t)json_object_get_int64(jobj); + + /* PCR banks */ + if (!json_object_object_get_ex(jobj_token, "pcrbanks", &jobj)) + goto out; + for (tmp_pcrbanks = 0, i = 0; i < json_object_array_length(jobj); i++) { + jobj1 = json_object_array_get_idx(jobj, i); + tpm2_token_get_pcrbanks(json_object_get_string(jobj1), &tmp_pcrbanks); + } + if (pcrbanks) + *pcrbanks = tmp_pcrbanks; + + /* PCR selection */ + if (!json_object_object_get_ex(jobj_token, "pcrselection", &jobj)) + goto out; + for (tmp_pcrselection = 0, i = 0; i < json_object_array_length(jobj); i++) { + jobj1 = json_object_array_get_idx(jobj, i); + tpm2_token_get_pcrs(json_object_get_string(jobj1), &tmp_pcrselection); + } + if (tpm_pcr) + *tpm_pcr = tmp_pcrselection; + + /* flags */ + if (!json_object_object_get_ex(jobj_token, "flags", &jobj)) + goto out; + for (i = 0; i < json_object_array_length(jobj); i++) { + jobj1 = json_object_array_get_idx(jobj, i); + if (!jobj1 || !(str = json_object_get_string(jobj1))) + continue; + else if (!strcmp("DA_PROTECT", str)) + tmp_da = true; + else if (!strcmp("PIN", str)) + tmp_pin = true; + } + if (daprotect) + *daprotect = tmp_da; + if (pin) + *pin = tmp_pin; + + + r = 0; +out: + json_object_put(jobj_token); + return r; +} + +int tpm2_token_add(struct crypt_device *cd, + uint32_t tpm_nv, + uint32_t tpm_pcr, + uint32_t pcrbanks, + bool daprotect, + bool pin, + size_t nvkey_size) +{ + int token; + json_object *jobj, *jobj_token; + const char *string_token; + + jobj_token = json_object_new_object(); + if (!jobj_token) + return -EINVAL; + + /* type is mandatory field in all tokens and must match handler name member */ + jobj = json_object_new_string("tpm2"); + if (!jobj) + goto out; + json_object_object_add(jobj_token, "type", jobj); + + jobj = json_object_new_int64(tpm_nv); + if (!jobj) + goto out; + json_object_object_add(jobj_token, "nvindex", jobj); + + jobj = json_object_new_int64(nvkey_size); + if (!jobj) + goto out; + json_object_object_add(jobj_token, "nvkey-size", jobj); + + /* PCR banks */ + jobj = json_object_new_array(); + tpm2_token_set_pcrbanks(jobj, pcrbanks); + json_object_object_add(jobj_token, "pcrbanks", jobj); + + /* PCR selection */ + jobj = json_object_new_array(); + tpm2_token_set_pcrs(jobj, tpm_pcr); + json_object_object_add(jobj_token, "pcrselection", jobj); + + /* flags */ + jobj = json_object_new_array(); + if (daprotect) + json_object_array_add(jobj, json_object_new_string("DA_PROTECT")); + if (pin) + json_object_array_add(jobj, json_object_new_string("PIN")); + json_object_object_add(jobj_token, "flags", jobj); + + /* keyslots */ + jobj = json_object_new_array(); + if (!jobj) + goto out; + json_object_object_add(jobj_token, "keyslots", jobj); + + string_token = json_object_to_json_string_ext(jobj_token, JSON_C_TO_STRING_PLAIN); + if (!string_token) + goto out; + + l_dbg(cd, "Token JSON: %s\n", string_token); + + token = crypt_token_json_set(cd, CRYPT_ANY_TOKEN, string_token); + if (token < 0) + goto out; + + json_object_put(jobj_token); + return token; +out: + l_err(cd, "Error creating token JSON."); + json_object_put(jobj_token); + return -EINVAL; +} + +static uint32_t token_nvindex(struct crypt_device *cd, int token) +{ + const char *json; + uint32_t nvindex; + + if (crypt_token_json_get(cd, token, &json) < 0) + return 0; + + if (tpm2_token_read(cd, json, &nvindex, NULL, NULL, NULL, NULL, NULL)) + return 0; + + return nvindex; +} + +int tpm2_token_by_nvindex(struct crypt_device *cd, uint32_t tpm_nv) +{ + crypt_token_info token_info; + const char *type; + uint32_t nvindex; + int i; + + if (!tpm_nv) + return -EINVAL; + + for (i = 0;; i++) { + token_info = crypt_token_status(cd, i, &type); + + if (token_info == CRYPT_TOKEN_INVALID) + break; + + if (token_info != CRYPT_TOKEN_EXTERNAL || strcmp(type, "tpm2")) + continue; + + nvindex = token_nvindex(cd, i); + if (!nvindex) + continue; // FIXME -EINVAL? + + if (nvindex == tpm_nv) + return i; + } + + return -ENOENT; +} + +int tpm2_token_kill(struct crypt_device *cd, int token) +{ + uint32_t nvindex; + TSS2_RC r; + + nvindex = token_nvindex(cd, token); + if (!nvindex) + return -EINVAL; + + bool exists; + + r = tpm_nv_exists(cd, nvindex, &exists); + if (r != TSS2_RC_SUCCESS) { + l_err(cd, "Failed to check if TPM2 NV-Index 0x%x exists.", nvindex); + LOG_TPM_ERR(cd, r); + return -EINVAL; + } + + if (exists) { + r = tpm_nv_undefine(cd, nvindex); + if (r) { + l_err(cd, "Failed to undefine TPM2 NV-Index 0x%x.", nvindex); + return -EINVAL; + } + } else { + l_err(cd, "TPM2 NV-Index 0x%x is already deleted.", nvindex); + } + + if (crypt_token_json_set(cd, token, NULL) < 0) { + l_err(cd, "Cannot destroy TPM2 token %d.", token); + return -EINVAL; + } + + return 0; +}