Add support for systemd-pkcs11 libcryptsetup plugin.

Add support for systemd-pkcs11 based LUKS2 device activation
via libcryptsetup plugin. This make the feature (pkcs11 sealed
LUKS2 keyslot passphrase) usable from both systemd utilities
and cryptsetup cli.

The feature is configured via -Dlibcryptsetup-plugins combo
with default value set to 'auto'. It get's enabled automatically
when cryptsetup 2.4.0 or later is installed in build system.
This commit is contained in:
Ondrej Kozina 2021-05-20 15:37:08 +02:00
parent 0ff605665a
commit 8186022c9d
8 changed files with 534 additions and 24 deletions

View file

@ -1801,6 +1801,20 @@ if conf.get('HAVE_LIBCRYPTSETUP_PLUGINS') == 1
install : true,
install_dir : libcryptsetup_plugins_dir)
endif
if conf.get('HAVE_P11KIT') == 1
cryptsetup_token_systemd_pkcs11 = shared_library(
'cryptsetup-token-systemd-pkcs11',
link_args : ['-shared',
'-Wl,--version-script=' + cryptsetup_token_sym_path],
dependencies : libshared_deps + [libcryptsetup, versiondep],
link_with : [libshared],
link_whole : [cryptsetup_token_systemd_pkcs11_static],
link_depends : cryptsetup_token_sym,
install_rpath : rootlibexecdir,
install : true,
install_dir : libcryptsetup_plugins_dir)
endif
endif
############################################################

View file

@ -0,0 +1,143 @@
/* 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);
assert(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 funtion (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, "Can not 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;
}

View file

@ -4,13 +4,15 @@
#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(cd, ...) crypt_logf(cd, CRYPT_LOG_NORMAL, __VA_ARGS__)
#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; \

View file

@ -0,0 +1,271 @@
/* 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 = userdata;
assert(m);
assert(slot_info);
assert(token_info);
assert(uri);
assert(data);
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) {
/* Referesh 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, 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.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;
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;
r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
if (r < 0)
return crypt_log_error_errno(cd, r, "Can not base64 encode key: %m");
*ret_password = TAKE_PTR(base64_encoded);
*ret_password_size = strlen(*ret_password);
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;
}

View file

@ -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);

View file

@ -43,4 +43,22 @@ if conf.get('HAVE_LIBFIDO2') == 1
c_args : cryptsetup_token_c_args)
endif
if conf.get('HAVE_P11KIT') == 1
cryptsetup_token_systemd_pkcs11_sources = files('''
cryptsetup-token-systemd-pkcs11.c
cryptsetup-token.h
cryptsetup-token-util.h
cryptsetup-token-util.c
luks2-pkcs11.c
luks2-pkcs11.h
'''.split())
cryptsetup_token_systemd_pkcs11_static = static_library(
'cryptsetup-token-systemd-pkcs11_static',
cryptsetup_token_systemd_pkcs11_sources,
include_directories : includes,
dependencies : libshared_deps + [libcryptsetup, versiondep],
c_args : cryptsetup_token_c_args)
endif
endif

View file

@ -996,6 +996,32 @@ static int attach_luks_or_plain_or_bitlk_by_fido2(
return 0;
}
static int attach_luks2_by_pkcs11(
struct crypt_device *cd,
const char *name,
const char *friendly_name,
usec_t until,
bool headless,
uint32_t flags) {
int r = -ENOTSUP;
#if HAVE_LIBCRYPTSETUP_PLUGINS
if (!crypt_get_type(cd) || strcmp(crypt_get_type(cd), CRYPT_LUKS2))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Automatic PKCS#11 metadata requires LUKS2 device.");
systemd_pkcs11_plugin_params params = {
.friendly_name = friendly_name,
.until = until,
.headless = headless
};
r = crypt_activate_by_token_pin(cd, name, "systemd-pkcs11", CRYPT_ANY_TOKEN, NULL, 0, &params, flags);
if (r > 0) /* returns unlocked keyslot id on success */
r = 0;
#endif
return r;
}
static int attach_luks_or_plain_or_bitlk_by_pkcs11(
struct crypt_device *cd,
const char *name,
@ -1013,23 +1039,26 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11(
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
_cleanup_free_ void *discovered_key = NULL;
int keyslot = arg_key_slot, r;
const char *uri;
const char *uri = NULL;
bool use_libcryptsetup_plugin = libcryptsetup_plugins_support();
assert(cd);
assert(name);
assert(arg_pkcs11_uri || arg_pkcs11_uri_auto);
if (arg_pkcs11_uri_auto) {
r = find_pkcs11_auto_data(cd, &discovered_uri, &discovered_key, &discovered_key_size, &keyslot);
if (IN_SET(r, -ENOTUNIQ, -ENXIO))
return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN),
"Automatic PKCS#11 metadata discovery was not possible because missing or not unique, falling back to traditional unlocking.");
if (r < 0)
return r;
if (!use_libcryptsetup_plugin) {
r = find_pkcs11_auto_data(cd, &discovered_uri, &discovered_key, &discovered_key_size, &keyslot);
if (IN_SET(r, -ENOTUNIQ, -ENXIO))
return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN),
"Automatic PKCS#11 metadata discovery was not possible because missing or not unique, falling back to traditional unlocking.");
if (r < 0)
return r;
uri = discovered_uri;
key_data = discovered_key;
key_data_size = discovered_key_size;
uri = discovered_uri;
key_data = discovered_key;
key_data_size = discovered_key_size;
}
} else {
uri = arg_pkcs11_uri;
@ -1044,17 +1073,22 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11(
for (;;) {
bool processed = false;
r = decrypt_pkcs11_key(
name,
friendly,
uri,
key_file, arg_keyfile_size, arg_keyfile_offset,
key_data, key_data_size,
until,
arg_headless,
&decrypted_key, &decrypted_key_size);
if (r >= 0)
break;
if (use_libcryptsetup_plugin && arg_pkcs11_uri_auto)
r = attach_luks2_by_pkcs11(cd, name, friendly, until, arg_headless, flags);
else {
r = decrypt_pkcs11_key(
name,
friendly,
uri,
key_file, arg_keyfile_size, arg_keyfile_offset,
key_data, key_data_size,
until,
arg_headless,
&decrypted_key, &decrypted_key_size);
if (r >= 0)
break;
}
if (r != -EAGAIN) /* EAGAIN means: token not found */
return r;
@ -1094,6 +1128,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11(
log_debug("Got one or more potentially relevant udev events, rescanning PKCS#11...");
}
assert(decrypted_key);
if (pass_volume_key)
r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);

View file

@ -74,5 +74,11 @@ int pkcs11_crypt_device_callback(
#endif
typedef struct {
const char *friendly_name;
usec_t until;
bool headless;
} systemd_pkcs11_plugin_params;
int pkcs11_list_tokens(void);
int pkcs11_find_token_auto(char **ret);