diff options
Diffstat (limited to 'src/cryptsetup/cryptsetup-tokens')
-rw-r--r-- | src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c | 218 | ||||
-rw-r--r-- | src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c | 144 | ||||
-rw-r--r-- | src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c | 352 | ||||
-rw-r--r-- | src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.c | 70 | ||||
-rw-r--r-- | src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h | 40 | ||||
-rw-r--r-- | src/cryptsetup/cryptsetup-tokens/cryptsetup-token.h | 19 | ||||
-rw-r--r-- | src/cryptsetup/cryptsetup-tokens/cryptsetup-token.sym | 19 | ||||
-rw-r--r-- | src/cryptsetup/cryptsetup-tokens/luks2-fido2.c | 158 | ||||
-rw-r--r-- | src/cryptsetup/cryptsetup-tokens/luks2-fido2.h | 24 | ||||
-rw-r--r-- | src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c | 272 | ||||
-rw-r--r-- | src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.h | 21 | ||||
-rw-r--r-- | src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c | 109 | ||||
-rw-r--r-- | src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h | 30 | ||||
-rw-r--r-- | src/cryptsetup/cryptsetup-tokens/meson.build | 75 |
14 files changed, 1551 insertions, 0 deletions
diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c new file mode 100644 index 0000000..fdb3b17 --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c @@ -0,0 +1,218 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <libcryptsetup.h> +#include <string.h> + +#include "cryptsetup-token.h" +#include "cryptsetup-token-util.h" +#include "hexdecoct.h" +#include "json.h" +#include "luks2-fido2.h" +#include "memory-util.h" +#include "version.h" + +#define TOKEN_NAME "systemd-fido2" +#define TOKEN_VERSION_MAJOR "1" +#define TOKEN_VERSION_MINOR "0" + +/* for libcryptsetup debug purpose */ +_public_ const char *cryptsetup_token_version(void) { + return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR " systemd-v" STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")"; +} + +_public_ int cryptsetup_token_open_pin( + struct crypt_device *cd, /* is always LUKS2 context */ + int token /* is always >= 0 */, + const char *pin, + size_t pin_size, + char **password, /* freed by cryptsetup_token_buffer_free */ + size_t *password_len, + void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) { + + int r; + const char *json; + _cleanup_(erase_and_freep) char *pin_string = NULL; + + assert(!pin || pin_size); + assert(token >= 0); + + /* This must not fail at this moment (internal error) */ + r = crypt_token_json_get(cd, token, &json); + /* Use assert_se() here to avoid emitting warning with -DNDEBUG */ + assert_se(token == r); + assert(json); + + r = crypt_normalize_pin(pin, pin_size, &pin_string); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Cannot normalize PIN: %m"); + + return acquire_luks2_key(cd, json, (const char *)usrptr, pin_string, password, password_len); +} + +/* + * This function is called from within following libcryptsetup calls + * provided conditions further below are met: + * + * crypt_activate_by_token(), crypt_activate_by_token_type(type == 'systemd-fido2'): + * + * - token is assigned to at least one luks2 keyslot eligible to activate LUKS2 device + * (alternatively: name is set to null, flags contains CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY + * and token is assigned to at least single keyslot). + * + * - if plugin defines validate function (see cryptsetup_token_validate below) it must have + * passed the check (aka return 0) + */ +_public_ int cryptsetup_token_open( + struct crypt_device *cd, /* is always LUKS2 context */ + int token /* is always >= 0 */, + char **password, /* freed by cryptsetup_token_buffer_free */ + size_t *password_len, + void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) { + + return cryptsetup_token_open_pin(cd, token, NULL, 0, password, password_len, usrptr); +} + +/* + * libcryptsetup callback for memory deallocation of 'password' parameter passed in + * any crypt_token_open_* plugin function + */ +_public_ void cryptsetup_token_buffer_free(void *buffer, size_t buffer_len) { + erase_and_free(buffer); +} + +/* + * prints systemd-fido2 token content in crypt_dump(). + * 'type' and 'keyslots' fields are printed by libcryptsetup + */ +_public_ void cryptsetup_token_dump( + struct crypt_device *cd /* is always LUKS2 context */, + const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) { + + int r; + Fido2EnrollFlags required; + size_t cid_size, salt_size; + const char *client_pin_req_str, *up_req_str, *uv_req_str; + _cleanup_free_ void *cid = NULL, *salt = NULL; + _cleanup_free_ char *rp_id = NULL, *cid_str = NULL, *salt_str = NULL; + + assert(json); + + r = parse_luks2_fido2_data(cd, json, &rp_id, &salt, &salt_size, &cid, &cid_size, &required); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m."); + + r = crypt_dump_buffer_to_hex_string(cid, cid_size, &cid_str); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); + + r = crypt_dump_buffer_to_hex_string(salt, salt_size, &salt_str); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); + + if (required & FIDO2ENROLL_PIN) + client_pin_req_str = "true"; + else if (required & FIDO2ENROLL_PIN_IF_NEEDED) + client_pin_req_str = NULL; + else + client_pin_req_str = "false"; + + if (required & FIDO2ENROLL_UP) + up_req_str = "true"; + else if (required & FIDO2ENROLL_UP_IF_NEEDED) + up_req_str = NULL; + else + up_req_str = "false"; + + if (required & FIDO2ENROLL_UV) + uv_req_str = "true"; + else if (required & FIDO2ENROLL_UV_OMIT) + uv_req_str = NULL; + else + uv_req_str = "false"; + + crypt_log(cd, "\tfido2-credential:" CRYPT_DUMP_LINE_SEP "%s\n", cid_str); + crypt_log(cd, "\tfido2-salt: %s\n", salt_str); + + /* optional fields */ + if (rp_id) + crypt_log(cd, "\tfido2-rp: %s\n", rp_id); + if (client_pin_req_str) + crypt_log(cd, "\tfido2-clientPin-required:" CRYPT_DUMP_LINE_SEP "%s\n", + client_pin_req_str); + if (up_req_str) + crypt_log(cd, "\tfido2-up-required:" CRYPT_DUMP_LINE_SEP "%s\n", up_req_str); + if (uv_req_str) + crypt_log(cd, "\tfido2-uv-required:" CRYPT_DUMP_LINE_SEP "%s\n", uv_req_str); +} + +/* + * Note: + * If plugin is available in library path, it's called in before following libcryptsetup calls: + * + * crypt_token_json_set, crypt_dump, any crypt_activate_by_token_* flavour + */ +_public_ int cryptsetup_token_validate( + struct crypt_device *cd, /* is always LUKS2 context */ + const char *json /* contains valid 'type' and 'keyslots' fields. 'type' is 'systemd-tpm2' */) { + + int r; + JsonVariant *w; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + assert(json); + + r = json_parse(json, 0, &v, NULL, NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m."); + + w = json_variant_by_key(v, "fido2-credential"); + if (!w || !json_variant_is_string(w)) { + crypt_log_debug(cd, "FIDO2 token data lacks 'fido2-credential' field."); + return 1; + } + + r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'fido2-credential' field: %m"); + + w = json_variant_by_key(v, "fido2-salt"); + if (!w || !json_variant_is_string(w)) { + crypt_log_debug(cd, "FIDO2 token data lacks 'fido2-salt' field."); + return 1; + } + + r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded salt: %m."); + + /* The "rp" field is optional. */ + w = json_variant_by_key(v, "fido2-rp"); + if (w && !json_variant_is_string(w)) { + crypt_log_debug(cd, "FIDO2 token data's 'fido2-rp' field is not a string."); + return 1; + } + + /* The "fido2-clientPin-required" field is optional. */ + w = json_variant_by_key(v, "fido2-clientPin-required"); + if (w && !json_variant_is_boolean(w)) { + crypt_log_debug(cd, "FIDO2 token data's 'fido2-clientPin-required' field is not a boolean."); + return 1; + } + + /* The "fido2-up-required" field is optional. */ + w = json_variant_by_key(v, "fido2-up-required"); + if (w && !json_variant_is_boolean(w)) { + crypt_log_debug(cd, "FIDO2 token data's 'fido2-up-required' field is not a boolean."); + return 1; + } + + /* The "fido2-uv-required" field is optional. */ + w = json_variant_by_key(v, "fido2-uv-required"); + if (w && !json_variant_is_boolean(w)) { + crypt_log_debug(cd, "FIDO2 token data's 'fido2-uv-required' field is not a boolean."); + return 1; + } + + return 0; +} diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c new file mode 100644 index 0000000..2ac8a27 --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <libcryptsetup.h> + +#include "cryptsetup-token.h" +#include "cryptsetup-token-util.h" +#include "hexdecoct.h" +#include "json.h" +#include "luks2-pkcs11.h" +#include "memory-util.h" +#include "pkcs11-util.h" +#include "version.h" + +#define TOKEN_NAME "systemd-pkcs11" +#define TOKEN_VERSION_MAJOR "1" +#define TOKEN_VERSION_MINOR "0" + +/* for libcryptsetup debug purpose */ +_public_ const char *cryptsetup_token_version(void) { + return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR " systemd-v" STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")"; +} + +_public_ int cryptsetup_token_open_pin( + struct crypt_device *cd, /* is always LUKS2 context */ + int token /* is always >= 0 */, + const char *pin, + size_t pin_size, + char **password, /* freed by cryptsetup_token_buffer_free */ + size_t *password_len, + void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) { + + const char *json; + int r; + + assert(!pin || pin_size); + assert(token >= 0); + + /* This must not fail at this moment (internal error) */ + r = crypt_token_json_get(cd, token, &json); + /* Use assert_se() here to avoid emitting warning with -DNDEBUG */ + assert_se(token == r); + assert(json); + + return acquire_luks2_key(cd, json, usrptr, pin, pin_size, password, password_len); +} + +/* + * This function is called from within following libcryptsetup calls + * provided conditions further below are met: + * + * crypt_activate_by_token(), crypt_activate_by_token_type(type == 'systemd-pkcs11'): + * + * - token is assigned to at least one luks2 keyslot eligible to activate LUKS2 device + * (alternatively: name is set to null, flags contains CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY + * and token is assigned to at least single keyslot). + * + * - if plugin defines validate function (see cryptsetup_token_validate below) it must have + * passed the check (aka return 0) + */ +_public_ int cryptsetup_token_open( + struct crypt_device *cd, /* is always LUKS2 context */ + int token /* is always >= 0 */, + char **password, /* freed by cryptsetup_token_buffer_free */ + size_t *password_len, + void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) { + + return cryptsetup_token_open_pin(cd, token, NULL, 0, password, password_len, usrptr); +} + +/* + * libcryptsetup callback for memory deallocation of 'password' parameter passed in + * any crypt_token_open_* plugin function + */ +_public_ void cryptsetup_token_buffer_free(void *buffer, size_t buffer_len) { + erase_and_free(buffer); +} + +/* + * prints systemd-pkcs11 token content in crypt_dump(). + * 'type' and 'keyslots' fields are printed by libcryptsetup + */ +_public_ void cryptsetup_token_dump( + struct crypt_device *cd /* is always LUKS2 context */, + const char *json /* validated 'systemd-pkcs11' token if cryptsetup_token_validate is defined */) { + + int r; + size_t pkcs11_key_size; + _cleanup_free_ char *pkcs11_uri = NULL, *key_str = NULL; + _cleanup_free_ void *pkcs11_key = NULL; + + r = parse_luks2_pkcs11_data(cd, json, &pkcs11_uri, &pkcs11_key, &pkcs11_key_size); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m."); + + r = crypt_dump_buffer_to_hex_string(pkcs11_key, pkcs11_key_size, &key_str); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); + + crypt_log(cd, "\tpkcs11-uri: %s\n", pkcs11_uri); + crypt_log(cd, "\tpkcs11-key: %s\n", key_str); +} + +/* + * Note: + * If plugin is available in library path, it's called in before following libcryptsetup calls: + * + * crypt_token_json_set, crypt_dump, any crypt_activate_by_token_* flavour + */ +_public_ int cryptsetup_token_validate( + struct crypt_device *cd, /* is always LUKS2 context */ + const char *json /* contains valid 'type' and 'keyslots' fields. 'type' is 'systemd-pkcs11' */) { + + int r; + JsonVariant *w; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + r = json_parse(json, 0, &v, NULL, NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m."); + + w = json_variant_by_key(v, "pkcs11-uri"); + if (!w || !json_variant_is_string(w)) { + crypt_log_debug(cd, "PKCS#11 token data lacks 'pkcs11-uri' field."); + return 1; + } + + if (!pkcs11_uri_valid(json_variant_string(w))) { + crypt_log_debug(cd, "PKCS#11 token data contains invalid PKCS#11 URI."); + return 1; + } + + w = json_variant_by_key(v, "pkcs11-key"); + if (!w || !json_variant_is_string(w)) { + crypt_log_debug(cd, "PKCS#11 token data lacks 'pkcs11-key' field."); + return 1; + } + + r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded key: %m."); + + return 0; +} diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c new file mode 100644 index 0000000..6fee831 --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c @@ -0,0 +1,352 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <libcryptsetup.h> + +#include "cryptsetup-token.h" +#include "cryptsetup-token-util.h" +#include "hexdecoct.h" +#include "json.h" +#include "luks2-tpm2.h" +#include "memory-util.h" +#include "strv.h" +#include "tpm2-util.h" +#include "version.h" + +#define TOKEN_NAME "systemd-tpm2" +#define TOKEN_VERSION_MAJOR "1" +#define TOKEN_VERSION_MINOR "0" + +/* for libcryptsetup debug purpose */ +_public_ const char *cryptsetup_token_version(void) { + + return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR " systemd-v" STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")"; +} + +static int log_debug_open_error(struct crypt_device *cd, int r) { + if (r == -EAGAIN) + return crypt_log_debug_errno(cd, r, "TPM2 device not found."); + if (r == -ENXIO) + return crypt_log_debug_errno(cd, r, "No matching TPM2 token data found."); + + return crypt_log_debug_errno(cd, r, TOKEN_NAME " open failed: %m."); +} + +_public_ int cryptsetup_token_open_pin( + struct crypt_device *cd, /* is always LUKS2 context */ + int token /* is always >= 0 */, + const char *pin, + size_t pin_size, + char **ret_password, /* freed by cryptsetup_token_buffer_free */ + size_t *ret_password_len, + void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) { + + _cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL; + _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL, *srk_buf = NULL; + size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size, salt_size = 0, srk_buf_size = 0; + _cleanup_(erase_and_freep) void *decrypted_key = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + uint32_t hash_pcr_mask, pubkey_pcr_mask; + systemd_tpm2_plugin_params params = { + .search_pcr_mask = UINT32_MAX + }; + uint16_t pcr_bank, primary_alg; + ssize_t base64_encoded_size; + TPM2Flags flags = 0; + const char *json; + int r; + + assert(token >= 0); + assert(!pin || pin_size > 0); + assert(ret_password); + assert(ret_password_len); + + /* This must not fail at this moment (internal error) */ + r = crypt_token_json_get(cd, token, &json); + assert(token == r); + assert(json); + + r = crypt_normalize_pin(pin, pin_size, &pin_string); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Cannot normalize PIN: %m"); + + if (usrptr) + params = *(systemd_tpm2_plugin_params *)usrptr; + + r = json_parse(json, 0, &v, NULL, NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Failed to parse token JSON data: %m"); + + r = tpm2_parse_luks2_json( + v, + NULL, + &hash_pcr_mask, + &pcr_bank, + &pubkey, + &pubkey_size, + &pubkey_pcr_mask, + &primary_alg, + &blob, + &blob_size, + &policy_hash, + &policy_hash_size, + &salt, + &salt_size, + &srk_buf, + &srk_buf_size, + &flags); + if (r < 0) + return log_debug_open_error(cd, r); + + if (params.search_pcr_mask != UINT32_MAX && hash_pcr_mask != params.search_pcr_mask) + return crypt_log_debug_errno(cd, ENXIO, "PCR mask doesn't match expectation (%" PRIu32 " vs. %" PRIu32 ")", hash_pcr_mask, params.search_pcr_mask); + + r = acquire_luks2_key( + params.device, + hash_pcr_mask, + pcr_bank, + pubkey, pubkey_size, + pubkey_pcr_mask, + params.signature_path, + pin_string, + params.pcrlock_path, + primary_alg, + blob, + blob_size, + policy_hash, + policy_hash_size, + salt, + salt_size, + srk_buf, + srk_buf_size, + flags, + &decrypted_key, + &decrypted_key_size); + if (r < 0) + return log_debug_open_error(cd, r); + + /* Before using this key as passphrase we base64 encode it, for compat with homed */ + base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); + if (base64_encoded_size < 0) + return log_debug_open_error(cd, base64_encoded_size); + + /* free'd automatically by libcryptsetup */ + *ret_password = TAKE_PTR(base64_encoded); + *ret_password_len = base64_encoded_size; + + return 0; +} + +/* + * This function is called from within following libcryptsetup calls + * provided conditions further below are met: + * + * crypt_activate_by_token(), crypt_activate_by_token_type(type == 'systemd-tpm2'): + * + * - token is assigned to at least one luks2 keyslot eligible to activate LUKS2 device + * (alternatively: name is set to null, flags contains CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY + * and token is assigned to at least single keyslot). + * + * - if plugin defines validate function (see cryptsetup_token_validate below) it must have + * passed the check (aka return 0) + */ +_public_ int cryptsetup_token_open( + struct crypt_device *cd, /* is always LUKS2 context */ + int token /* is always >= 0 */, + char **ret_password, /* freed by cryptsetup_token_buffer_free */ + size_t *ret_password_len, + void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) { + + return cryptsetup_token_open_pin(cd, token, NULL, 0, ret_password, ret_password_len, usrptr); +} + +/* + * libcryptsetup callback for memory deallocation of 'password' parameter passed in + * any crypt_token_open_* plugin function + */ +_public_ void cryptsetup_token_buffer_free(void *buffer, size_t buffer_len) { + erase_and_free(buffer); +} + +/* + * prints systemd-tpm2 token content in crypt_dump(). + * 'type' and 'keyslots' fields are printed by libcryptsetup + */ +_public_ void cryptsetup_token_dump( + struct crypt_device *cd /* is always LUKS2 context */, + const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) { + + _cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL; + _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL, *srk_buf = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0, srk_buf_size = 0; + uint32_t hash_pcr_mask, pubkey_pcr_mask; + uint16_t pcr_bank, primary_alg; + TPM2Flags flags = 0; + int r; + + assert(json); + + r = json_parse(json, 0, &v, NULL, NULL); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON object: %m"); + + r = tpm2_parse_luks2_json( + v, + NULL, + &hash_pcr_mask, + &pcr_bank, + &pubkey, + &pubkey_size, + &pubkey_pcr_mask, + &primary_alg, + &blob, + &blob_size, + &policy_hash, + &policy_hash_size, + &salt, + &salt_size, + &srk_buf, + &srk_buf_size, + &flags); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m"); + + hash_pcrs_str = tpm2_pcr_mask_to_string(hash_pcr_mask); + if (!hash_pcrs_str) + return (void) crypt_log_debug_errno(cd, ENOMEM, "Cannot format PCR hash mask: %m"); + + pubkey_pcrs_str = tpm2_pcr_mask_to_string(pubkey_pcr_mask); + if (!pubkey_pcrs_str) + return (void) crypt_log_debug_errno(cd, ENOMEM, "Cannot format PCR hash mask: %m"); + + r = crypt_dump_buffer_to_hex_string(blob, blob_size, &blob_str); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); + + r = crypt_dump_buffer_to_hex_string(pubkey, pubkey_size, &pubkey_str); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); + + r = crypt_dump_buffer_to_hex_string(policy_hash, policy_hash_size, &policy_hash_str); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); + + crypt_log(cd, "\ttpm2-hash-pcrs: %s\n", strna(hash_pcrs_str)); + crypt_log(cd, "\ttpm2-pcr-bank: %s\n", strna(tpm2_hash_alg_to_string(pcr_bank))); + crypt_log(cd, "\ttpm2-pubkey:" CRYPT_DUMP_LINE_SEP "%s\n", pubkey_str); + crypt_log(cd, "\ttpm2-pubkey-pcrs: %s\n", strna(pubkey_pcrs_str)); + crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_asym_alg_to_string(primary_alg))); + crypt_log(cd, "\ttpm2-blob: %s\n", blob_str); + crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str); + crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN)); + crypt_log(cd, "\ttpm2-pcrlock: %s\n", true_false(flags & TPM2_FLAGS_USE_PCRLOCK)); + crypt_log(cd, "\ttpm2-salt: %s\n", true_false(salt)); + crypt_log(cd, "\ttpm2-srk: %s\n", true_false(srk_buf)); +} + +/* + * Note: + * If plugin is available in library path, it's called in before following libcryptsetup calls: + * + * crypt_token_json_set, crypt_dump, any crypt_activate_by_token_* flavour + */ +_public_ int cryptsetup_token_validate( + struct crypt_device *cd, /* is always LUKS2 context */ + const char *json /* contains valid 'type' and 'keyslots' fields. 'type' is 'systemd-tpm2' */) { + + int r; + JsonVariant *w, *e; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + assert(json); + + r = json_parse(json, 0, &v, NULL, NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m"); + + w = json_variant_by_key(v, "tpm2-pcrs"); + if (!w || !json_variant_is_array(w)) { + crypt_log_debug(cd, "TPM2 token data lacks 'tpm2-pcrs' field."); + return 1; + } + + JSON_VARIANT_ARRAY_FOREACH(e, w) { + uint64_t u; + + if (!json_variant_is_number(e)) { + crypt_log_debug(cd, "TPM2 PCR is not a number."); + return 1; + } + + u = json_variant_unsigned(e); + if (!TPM2_PCR_INDEX_VALID(u)) { + crypt_log_debug(cd, "TPM2 PCR number out of range."); + return 1; + } + } + + /* The bank field is optional, since it was added in systemd 250 only. Before the bank was hardcoded + * to SHA256. */ + w = json_variant_by_key(v, "tpm2-pcr-bank"); + if (w) { + /* The PCR bank field is optional */ + + if (!json_variant_is_string(w)) { + crypt_log_debug(cd, "TPM2 PCR bank is not a string."); + return 1; + } + + if (tpm2_hash_alg_from_string(json_variant_string(w)) < 0) { + crypt_log_debug(cd, "TPM2 PCR bank invalid or not supported: %s.", json_variant_string(w)); + return 1; + } + } + + /* The primary key algorithm field is optional, since it was also added in systemd 250 only. Before + * the algorithm was hardcoded to ECC. */ + w = json_variant_by_key(v, "tpm2-primary-alg"); + if (w) { + /* The primary key algorithm is optional */ + + if (!json_variant_is_string(w)) { + crypt_log_debug(cd, "TPM2 primary key algorithm is not a string."); + return 1; + } + + if (tpm2_asym_alg_from_string(json_variant_string(w)) < 0) { + crypt_log_debug(cd, "TPM2 primary key algorithm invalid or not supported: %s", json_variant_string(w)); + return 1; + } + } + + w = json_variant_by_key(v, "tpm2-blob"); + if (!w || !json_variant_is_string(w)) { + crypt_log_debug(cd, "TPM2 token data lacks 'tpm2-blob' field."); + return 1; + } + + r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-blob' field: %m"); + + w = json_variant_by_key(v, "tpm2-policy-hash"); + if (!w || !json_variant_is_string(w)) { + crypt_log_debug(cd, "TPM2 token data lacks 'tpm2-policy-hash' field."); + return 1; + } + + r = unhexmem(json_variant_string(w), SIZE_MAX, NULL, NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-policy-hash' field: %m"); + + w = json_variant_by_key(v, "tpm2-pin"); + if (w) { + if (!json_variant_is_boolean(w)) { + crypt_log_debug(cd, "TPM2 PIN policy is not a boolean."); + return 1; + } + } + + return 0; +} diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.c new file mode 100644 index 0000000..4e3090b --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.c @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "cryptsetup-token-util.h" +#include "string-util.h" + +int crypt_dump_buffer_to_hex_string( + const char *buf, + size_t buf_size, + char **ret_dump_str) { + + int r; + _cleanup_free_ char *dump_str = NULL; + + assert(buf || !buf_size); + assert(ret_dump_str); + + for (size_t i = 0; i < buf_size; i++) { + /* crypt_dump() breaks line after every + * 16th couple of chars in dumped hexstring */ + r = strextendf_with_separator( + &dump_str, + (i && !(i % 16)) ? CRYPT_DUMP_LINE_SEP : " ", + "%02hhx", buf[i]); + if (r < 0) + return r; + } + + *ret_dump_str = TAKE_PTR(dump_str); + + return 0; +} + +int crypt_dump_hex_string(const char *hex_str, char **ret_dump_str) { + + int r; + size_t len; + _cleanup_free_ char *dump_str = NULL; + + assert(hex_str); + assert(ret_dump_str); + + len = strlen(hex_str) >> 1; + + for (size_t i = 0; i < len; i++) { + /* crypt_dump() breaks line after every + * 16th couple of chars in dumped hexstring */ + r = strextendf_with_separator( + &dump_str, + (i && !(i % 16)) ? CRYPT_DUMP_LINE_SEP : " ", + "%.2s", hex_str + (i<<1)); + if (r < 0) + return r; + } + + *ret_dump_str = TAKE_PTR(dump_str); + + return 0; +} + +int crypt_normalize_pin(const void *pin, size_t pin_size, char **ret_pin_string) { + assert(pin || pin_size == 0); + assert(ret_pin_string); + + if (pin_size == 0) { + *ret_pin_string = NULL; + return 0; + } + + return make_cstring(pin, pin_size, MAKE_CSTRING_ALLOW_TRAILING_NUL, ret_pin_string); +} diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h new file mode 100644 index 0000000..146beff --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#pragma once + +#include <stdbool.h> +#include <stddef.h> +#include <libcryptsetup.h> + +/* crypt_dump() internal indentation magic */ +#define CRYPT_DUMP_LINE_SEP "\n\t " + +#define crypt_log_debug(cd, ...) crypt_logf(cd, CRYPT_LOG_DEBUG, __VA_ARGS__) +#define crypt_log_error(cd, ...) crypt_logf(cd, CRYPT_LOG_ERROR, __VA_ARGS__) +#define crypt_log_verbose(cd, ...) crypt_logf(cd, CRYPT_LOG_VERBOSE, __VA_ARGS__) +#define crypt_log(cd, ...) crypt_logf(cd, CRYPT_LOG_NORMAL, __VA_ARGS__) + +#define crypt_log_full_errno(cd, e, lvl, ...) ({ \ + int _e = abs(e), _s = errno; \ + errno = _e; \ + crypt_logf(cd, lvl, __VA_ARGS__); \ + errno = _s; \ + -_e; \ +}) + +#define crypt_log_debug_errno(cd, e, ...) \ + crypt_log_full_errno(cd, e, CRYPT_LOG_DEBUG, __VA_ARGS__) + +#define crypt_log_error_errno(cd, e, ...) \ + crypt_log_full_errno(cd, e, CRYPT_LOG_ERROR, __VA_ARGS__) + +#define crypt_log_oom(cd) crypt_log_error_errno(cd, ENOMEM, "Not enough memory.") + +int crypt_dump_buffer_to_hex_string( + const char *buf, + size_t buf_size, + char **ret_dump_str); + +int crypt_dump_hex_string(const char *hex_str, char **ret_dump_str); + +int crypt_normalize_pin(const void *pin, size_t pin_size, char **ret_pin_string); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.h b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.h new file mode 100644 index 0000000..2a9d23f --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/* for more information see libcryptsetup.h crypt-tokens section */ + +const char *cryptsetup_token_version(void); + +int cryptsetup_token_open(struct crypt_device *cd, int token, + char **password, size_t *password_len, void *usrptr); + +int cryptsetup_token_open_pin(struct crypt_device *cd, int token, + const char *pin, size_t pin_size, + char **password, size_t *password_len, void *usrptr); + +void cryptsetup_token_dump(struct crypt_device *cd, const char *json); + +int cryptsetup_token_validate(struct crypt_device *cd, const char *json); + +void cryptsetup_token_buffer_free(void *buffer, size_t buffer_len); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.sym b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.sym new file mode 100644 index 0000000..730e78e --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.sym @@ -0,0 +1,19 @@ +/*** + SPDX-License-Identifier: LGPL-2.1-or-later + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. +***/ + +CRYPTSETUP_TOKEN_1.0 { +global: + cryptsetup_token_open; + cryptsetup_token_open_pin; + cryptsetup_token_buffer_free; + cryptsetup_token_validate; + cryptsetup_token_dump; + cryptsetup_token_version; +local: *; +}; diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c new file mode 100644 index 0000000..a1c85e6 --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <libcryptsetup.h> + +#include "cryptsetup-token-util.h" +#include "hexdecoct.h" +#include "json.h" +#include "luks2-fido2.h" +#include "memory-util.h" +#include "strv.h" + +int acquire_luks2_key( + struct crypt_device *cd, + const char *json, + const char *device, + const char *pin, + char **ret_keyslot_passphrase, + size_t *ret_keyslot_passphrase_size) { + + int r; + Fido2EnrollFlags required; + size_t cid_size, salt_size, decrypted_key_size; + _cleanup_free_ void *cid = NULL, *salt = NULL; + _cleanup_free_ char *rp_id = NULL; + _cleanup_(erase_and_freep) void *decrypted_key = NULL; + _cleanup_(erase_and_freep) char *base64_encoded = NULL; + _cleanup_strv_free_erase_ char **pins = NULL; + ssize_t base64_encoded_size; + + assert(ret_keyslot_passphrase); + assert(ret_keyslot_passphrase_size); + + r = parse_luks2_fido2_data(cd, json, &rp_id, &salt, &salt_size, &cid, &cid_size, &required); + if (r < 0) + return r; + + if (pin) { + pins = strv_new(pin); + if (!pins) + return crypt_log_oom(cd); + } + + /* configured to use pin but none was provided */ + if ((required & FIDO2ENROLL_PIN) && strv_isempty(pins)) + return -ENOANO; + + r = fido2_use_hmac_hash( + device, + rp_id ?: "io.systemd.cryptsetup", + salt, salt_size, + cid, cid_size, + pins, + required, + &decrypted_key, + &decrypted_key_size); + if (r == -ENOLCK) /* libcryptsetup returns -ENOANO also on wrong PIN */ + r = -ENOANO; + if (r < 0) + return r; + + /* Before using this key as passphrase we base64 encode it, for compat with homed */ + base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); + if (base64_encoded_size < 0) + return crypt_log_error_errno(cd, (int) base64_encoded_size, "Failed to base64 encode key: %m"); + + *ret_keyslot_passphrase = TAKE_PTR(base64_encoded); + *ret_keyslot_passphrase_size = base64_encoded_size; + + return 0; +} + +/* this function expects valid "systemd-fido2" in json */ +int parse_luks2_fido2_data( + struct crypt_device *cd, + const char *json, + char **ret_rp_id, + void **ret_salt, + size_t *ret_salt_size, + void **ret_cid, + size_t *ret_cid_size, + Fido2EnrollFlags *ret_required) { + + _cleanup_free_ void *cid = NULL, *salt = NULL; + size_t cid_size = 0, salt_size = 0; + _cleanup_free_ char *rp = NULL; + int r; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + JsonVariant *w; + Fido2EnrollFlags required = 0; + + assert(json); + assert(ret_rp_id); + assert(ret_salt); + assert(ret_salt_size); + assert(ret_cid); + assert(ret_cid_size); + assert(ret_required); + + r = json_parse(json, 0, &v, NULL, NULL); + if (r < 0) + return crypt_log_error_errno(cd, r, "Failed to parse JSON token data: %m"); + + w = json_variant_by_key(v, "fido2-credential"); + if (!w) + return -EINVAL; + + r = unbase64mem(json_variant_string(w), SIZE_MAX, &cid, &cid_size); + if (r < 0) + return crypt_log_error_errno(cd, r, "Failed to parse 'fido2-credentials' field: %m"); + + w = json_variant_by_key(v, "fido2-salt"); + if (!w) + return -EINVAL; + + r = unbase64mem(json_variant_string(w), SIZE_MAX, &salt, &salt_size); + if (r < 0) + return crypt_log_error_errno(cd, r, "Failed to parse 'fido2-salt' field: %m"); + + w = json_variant_by_key(v, "fido2-rp"); + if (w) { + /* The "rp" field is optional. */ + rp = strdup(json_variant_string(w)); + if (!rp) { + crypt_log_error(cd, "Not enough memory."); + return -ENOMEM; + } + } + + w = json_variant_by_key(v, "fido2-clientPin-required"); + if (w) + /* The "fido2-clientPin-required" field is optional. */ + SET_FLAG(required, FIDO2ENROLL_PIN, json_variant_boolean(w)); + else + required |= FIDO2ENROLL_PIN_IF_NEEDED; /* compat with 248, where the field was unset */ + + w = json_variant_by_key(v, "fido2-up-required"); + if (w) + /* The "fido2-up-required" field is optional. */ + SET_FLAG(required, FIDO2ENROLL_UP, json_variant_boolean(w)); + else + required |= FIDO2ENROLL_UP_IF_NEEDED; /* compat with 248 */ + + w = json_variant_by_key(v, "fido2-uv-required"); + if (w) + /* The "fido2-uv-required" field is optional. */ + SET_FLAG(required, FIDO2ENROLL_UV, json_variant_boolean(w)); + else + required |= FIDO2ENROLL_UV_OMIT; /* compat with 248 */ + + *ret_rp_id = TAKE_PTR(rp); + *ret_cid = TAKE_PTR(cid); + *ret_cid_size = cid_size; + *ret_salt = TAKE_PTR(salt); + *ret_salt_size = salt_size; + *ret_required = required; + + return 0; +} diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.h b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.h new file mode 100644 index 0000000..48416ec --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "libfido2-util.h" + +struct crypt_device; + +int acquire_luks2_key( + struct crypt_device *cd, + const char *json, + const char *device, + const char *pin, + char **ret_keyslot_passphrase, + size_t *ret_keyslot_passphrase_size); + +int parse_luks2_fido2_data( + struct crypt_device *cd, + const char *json, + char **ret_rp_id, + void **ret_salt, + size_t *ret_salt_size, + void **ret_cid, + size_t *ret_cid_size, + Fido2EnrollFlags *ret_required); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c new file mode 100644 index 0000000..178fc7a --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c @@ -0,0 +1,272 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <p11-kit/p11-kit.h> +#include <p11-kit/uri.h> + +#include "cryptsetup-token-util.h" +#include "escape.h" +#include "hexdecoct.h" +#include "json.h" +#include "luks2-pkcs11.h" +#include "memory-util.h" +#include "pkcs11-util.h" +#include "time-util.h" + +struct luks2_pkcs11_callback_data { + struct crypt_device *cd; + const char *pin; + size_t pin_size; + void *encrypted_key; + size_t encrypted_key_size; + void *decrypted_key; + size_t decrypted_key_size; +}; + +static int luks2_pkcs11_callback( + CK_FUNCTION_LIST *m, + CK_SESSION_HANDLE session, + CK_SLOT_ID slot_id, + const CK_SLOT_INFO *slot_info, + const CK_TOKEN_INFO *token_info, + P11KitUri *uri, + void *userdata) { + + CK_OBJECT_HANDLE object; + CK_RV rv; + CK_TOKEN_INFO updated_token_info; + int r; + _cleanup_free_ char *token_label = NULL; + struct luks2_pkcs11_callback_data *data = ASSERT_PTR(userdata); + + assert(m); + assert(slot_info); + assert(token_info); + assert(uri); + + token_label = pkcs11_token_label(token_info); + if (!token_label) + return -ENOMEM; + + /* Called for every token matching our URI */ + r = pkcs11_token_login_by_pin(m, session, token_info, token_label, data->pin, data->pin_size); + if (r == -ENOLCK) { + /* Refresh the token info, so that we can prompt knowing the new flags if they changed. */ + rv = m->C_GetTokenInfo(slot_id, &updated_token_info); + if (rv != CKR_OK) { + crypt_log_error(data->cd, + "Failed to acquire updated security token information for slot %lu: %s", + slot_id, sym_p11_kit_strerror(rv)); + return -EIO; + } + token_info = &updated_token_info; + r = -ENOANO; + } + + if (r == -ENOANO) { + if (FLAGS_SET(token_info->flags, CKF_USER_PIN_FINAL_TRY)) + crypt_log_error(data->cd, "Please enter correct PIN for security token " + "'%s' in order to unlock it (final try).", token_label); + else if (FLAGS_SET(token_info->flags, CKF_USER_PIN_COUNT_LOW)) + crypt_log_error(data->cd, "PIN has been entered incorrectly previously, " + "please enter correct PIN for security token '%s' in order to unlock it.", + token_label); + } + + if (r == -EPERM) /* pin is locked, but map it to -ENOANO anyway */ + r = -ENOANO; + + if (r < 0) + return r; + + r = pkcs11_token_find_private_key(m, session, uri, &object); + if (r < 0) + return r; + + r = pkcs11_token_decrypt_data( + m, + session, + object, + data->encrypted_key, + data->encrypted_key_size, + &data->decrypted_key, + &data->decrypted_key_size); + if (r < 0) + return r; + + return 0; +} + +static void luks2_pkcs11_callback_data_release(struct luks2_pkcs11_callback_data *data) { + erase_and_free(data->decrypted_key); +} + +static int acquire_luks2_key_by_pin( + struct crypt_device *cd, + const char *pkcs11_uri, + const void *pin, + size_t pin_size, + void *encrypted_key, + size_t encrypted_key_size, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size) { + + int r; + _cleanup_(luks2_pkcs11_callback_data_release) struct luks2_pkcs11_callback_data data = { + .cd = cd, + .pin = pin, + .pin_size = pin_size, + .encrypted_key = encrypted_key, + .encrypted_key_size = encrypted_key_size, + }; + + assert(pkcs11_uri); + assert(encrypted_key); + assert(ret_decrypted_key); + assert(ret_decrypted_key_size); + + r = pkcs11_find_token(pkcs11_uri, luks2_pkcs11_callback, &data); + if (r < 0) + return r; + + *ret_decrypted_key = TAKE_PTR(data.decrypted_key); + *ret_decrypted_key_size = data.decrypted_key_size; + + return 0; +} + +/* called from within systemd utilities */ +static int acquire_luks2_key_systemd( + const char *pkcs11_uri, + systemd_pkcs11_plugin_params *params, + void *encrypted_key, + size_t encrypted_key_size, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size) { + + int r; + _cleanup_(pkcs11_crypt_device_callback_data_release) pkcs11_crypt_device_callback_data data = { + .encrypted_key = encrypted_key, + .encrypted_key_size = encrypted_key_size, + .free_encrypted_key = false + }; + + assert(pkcs11_uri); + assert(encrypted_key); + assert(ret_decrypted_key); + assert(ret_decrypted_key_size); + assert(params); + + data.friendly_name = params->friendly_name; + data.headless = params->headless; + data.askpw_flags = params->askpw_flags; + data.until = params->until; + + /* The functions called here log about all errors, except for EAGAIN which means "token not found right now" */ + r = pkcs11_find_token(pkcs11_uri, pkcs11_crypt_device_callback, &data); + if (r < 0) + return r; + + *ret_decrypted_key = TAKE_PTR(data.decrypted_key); + *ret_decrypted_key_size = data.decrypted_key_size; + + return 0; +} + +int acquire_luks2_key( + struct crypt_device *cd, + const char *json, + void *userdata, + const void *pin, + size_t pin_size, + char **ret_password, + size_t *ret_password_size) { + + int r; + size_t decrypted_key_size, encrypted_key_size; + _cleanup_(erase_and_freep) void *decrypted_key = NULL; + _cleanup_(erase_and_freep) char *base64_encoded = NULL; + _cleanup_free_ char *pkcs11_uri = NULL; + _cleanup_free_ void *encrypted_key = NULL; + systemd_pkcs11_plugin_params *pkcs11_params = userdata; + ssize_t base64_encoded_size; + + assert(json); + assert(ret_password); + assert(ret_password_size); + + r = parse_luks2_pkcs11_data(cd, json, &pkcs11_uri, &encrypted_key, &encrypted_key_size); + if (r < 0) + return r; + + if (pkcs11_params && pin) + crypt_log_verbose(cd, "PIN parameter ignored in interactive mode."); + + if (pkcs11_params) /* systemd based activation with interactive pin query callbacks */ + r = acquire_luks2_key_systemd( + pkcs11_uri, + pkcs11_params, + encrypted_key, encrypted_key_size, + &decrypted_key, &decrypted_key_size); + else /* default activation that provides single PIN if needed */ + r = acquire_luks2_key_by_pin( + cd, pkcs11_uri, pin, pin_size, + encrypted_key, encrypted_key_size, + &decrypted_key, &decrypted_key_size); + if (r < 0) + return r; + + base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); + if (base64_encoded_size < 0) + return crypt_log_error_errno(cd, (int) base64_encoded_size, "Cannot base64 encode key: %m"); + + *ret_password = TAKE_PTR(base64_encoded); + *ret_password_size = base64_encoded_size; + + return 0; +} + +int parse_luks2_pkcs11_data( + struct crypt_device *cd, + const char *json, + char **ret_uri, + void **ret_encrypted_key, + size_t *ret_encrypted_key_size) { + + int r; + size_t key_size; + _cleanup_free_ char *uri = NULL; + _cleanup_free_ void *key = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + JsonVariant *w; + + assert(json); + assert(ret_uri); + assert(ret_encrypted_key); + assert(ret_encrypted_key_size); + + r = json_parse(json, 0, &v, NULL, NULL); + if (r < 0) + return r; + + w = json_variant_by_key(v, "pkcs11-uri"); + if (!w) + return -EINVAL; + + uri = strdup(json_variant_string(w)); + if (!uri) + return -ENOMEM; + + w = json_variant_by_key(v, "pkcs11-key"); + if (!w) + return -EINVAL; + + r = unbase64mem(json_variant_string(w), SIZE_MAX, &key, &key_size); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded key: %m."); + + *ret_uri = TAKE_PTR(uri); + *ret_encrypted_key = TAKE_PTR(key); + *ret_encrypted_key_size = key_size; + + return 0; +} diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.h b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.h new file mode 100644 index 0000000..41ce9f0 --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#pragma once + +struct crypt_device; + +int acquire_luks2_key( + struct crypt_device *cd, + const char *json, + void *userdata, + const void *pin, + size_t pin_size, + char **password, + size_t *password_size); + +int parse_luks2_pkcs11_data( + struct crypt_device *cd, + const char *json, + char **ret_uri, + void **ret_encrypted_key, + size_t *ret_encrypted_key_size); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c new file mode 100644 index 0000000..72be5cc --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "ask-password-api.h" +#include "env-util.h" +#include "hexdecoct.h" +#include "json.h" +#include "log.h" +#include "luks2-tpm2.h" +#include "parse-util.h" +#include "random-util.h" +#include "sha256.h" +#include "strv.h" +#include "tpm2-util.h" + +int acquire_luks2_key( + const char *device, + uint32_t hash_pcr_mask, + uint16_t pcr_bank, + const void *pubkey, + size_t pubkey_size, + uint32_t pubkey_pcr_mask, + const char *signature_path, + const char *pin, + const char *pcrlock_path, + uint16_t primary_alg, + const void *key_data, + size_t key_data_size, + const void *policy_hash, + size_t policy_hash_size, + const void *salt, + size_t salt_size, + const void *srk_buf, + size_t srk_buf_size, + TPM2Flags flags, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size) { + + _cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL; + _cleanup_free_ char *auto_device = NULL; + _cleanup_(erase_and_freep) char *b64_salted_pin = NULL; + int r; + + assert(salt || salt_size == 0); + assert(ret_decrypted_key); + assert(ret_decrypted_key_size); + + if (!device) { + r = tpm2_find_device_auto(&auto_device); + if (r == -ENODEV) + return -EAGAIN; /* Tell the caller to wait for a TPM2 device to show up */ + if (r < 0) + return log_error_errno(r, "Could not find TPM2 device: %m"); + + device = auto_device; + } + + if ((flags & TPM2_FLAGS_USE_PIN) && !pin) + return -ENOANO; + + if (pin && salt_size > 0) { + uint8_t salted_pin[SHA256_DIGEST_SIZE] = {}; + CLEANUP_ERASE(salted_pin); + r = tpm2_util_pbkdf2_hmac_sha256(pin, strlen(pin), salt, salt_size, salted_pin); + if (r < 0) + return log_error_errno(r, "Failed to perform PBKDF2: %m"); + + r = base64mem(salted_pin, sizeof(salted_pin), &b64_salted_pin); + if (r < 0) + return log_error_errno(r, "Failed to base64 encode salted pin: %m"); + pin = b64_salted_pin; + } + + if (pubkey_pcr_mask != 0) { + r = tpm2_load_pcr_signature(signature_path, &signature_json); + if (r < 0) + return log_error_errno(r, "Failed to load PCR signature: %m"); + } + + _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy pcrlock_policy = {}; + if (FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK)) { + r = tpm2_pcrlock_policy_load(pcrlock_path, &pcrlock_policy); + if (r < 0) + return r; + } + + _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; + r = tpm2_context_new(device, &tpm2_context); + if (r < 0) + return log_error_errno(r, "Failed to create TPM2 context: %m"); + + r = tpm2_unseal(tpm2_context, + hash_pcr_mask, + pcr_bank, + pubkey, pubkey_size, + pubkey_pcr_mask, + signature_json, + pin, + FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL, + primary_alg, + key_data, key_data_size, + policy_hash, policy_hash_size, + srk_buf, srk_buf_size, + ret_decrypted_key, ret_decrypted_key_size); + if (r < 0) + return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); + + return r; +} diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h new file mode 100644 index 0000000..d84e5a3 --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#pragma once + +#include "tpm2-util.h" + +struct crypt_device; + +int acquire_luks2_key( + const char *device, + uint32_t pcr_mask, + uint16_t pcr_bank, + const void *pubkey, + size_t pubkey_size, + uint32_t pubkey_pcr_mask, + const char *signature_path, + const char *pcrlock_path, + const char *pin, + uint16_t primary_alg, + const void *key_data, + size_t key_data_size, + const void *policy_hash, + size_t policy_hash_size, + const void *salt, + size_t salt_size, + const void *srk_buf, + size_t srk_buf_size, + TPM2Flags flags, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size); diff --git a/src/cryptsetup/cryptsetup-tokens/meson.build b/src/cryptsetup/cryptsetup-tokens/meson.build new file mode 100644 index 0000000..b26940c --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/meson.build @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +lib_cryptsetup_token_common = static_library( + 'cryptsetup-token-common', + 'cryptsetup-token-util.c', + include_directories : includes, + dependencies : userspace, + link_with : libshared, + build_by_default : false) + +cryptsetup_token_systemd_tpm2_sources = files( + 'cryptsetup-token-systemd-tpm2.c', + 'luks2-tpm2.c', +) + +cryptsetup_token_systemd_fido2_sources = files( + 'cryptsetup-token-systemd-fido2.c', + 'luks2-fido2.c', +) + +cryptsetup_token_systemd_pkcs11_sources = files( + 'cryptsetup-token-systemd-pkcs11.c', + 'luks2-pkcs11.c', +) + +template = { + 'include_directories' : includes, + 'link_with' : [ + lib_cryptsetup_token_common, + libshared, + ], + 'version-script' : meson.current_source_dir() / 'cryptsetup-token.sym', + 'install_rpath' : pkglibdir, + 'install' : true, + 'install_dir' : libcryptsetup_plugins_dir, +} + +modules += [ + template + { + 'name' : 'cryptsetup-token-systemd-tpm2', + 'conditions' : [ + 'HAVE_LIBCRYPTSETUP_PLUGINS', + 'HAVE_TPM2', + ], + 'sources' : cryptsetup_token_systemd_tpm2_sources, + 'dependencies' : [ + libcryptsetup, + tpm2, + ], + }, + template + { + 'name' : 'cryptsetup-token-systemd-fido2', + 'conditions' : [ + 'HAVE_LIBCRYPTSETUP_PLUGINS', + 'HAVE_LIBFIDO2', + ], + 'sources' : cryptsetup_token_systemd_fido2_sources, + 'dependencies' : [ + libcryptsetup, + libfido2, + ], + }, + template + { + 'name' : 'cryptsetup-token-systemd-pkcs11', + 'conditions' : [ + 'HAVE_LIBCRYPTSETUP_PLUGINS', + 'HAVE_P11KIT', + ], + 'sources' : cryptsetup_token_systemd_pkcs11_sources, + 'dependencies' : [ + libcryptsetup, + libp11kit_cflags, + ], + }, +] |