size_t decrypted_key_size, saved_key_size;
_cleanup_free_ void *saved_key = NULL;
_cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL;
+ Pkcs11RsaPadding rsa_padding = _PKCS11_RSA_PADDING_INVALID;
ssize_t base64_encoded_size;
const char *node;
int r;
"cryptenroll.pkcs11-pin",
/* askpw_flags= */ 0,
&pkey,
+ &rsa_padding,
/* ret_pin_used= */ NULL);
if (r < 0)
return r;
- r = pkey_generate_volume_keys(pkey, &decrypted_key, &decrypted_key_size, &saved_key, &saved_key_size);
+ r = pkey_generate_volume_keys(pkey,
+ pkcs11_rsa_padding_to_oaep_hash(rsa_padding),
+ &decrypted_key, &decrypted_key_size, &saved_key, &saved_key_size);
if (r < 0)
return log_error_errno(r, "Failed to generate volume keys: %m");
SD_JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-pkcs11")),
SD_JSON_BUILD_PAIR("keyslots", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_STRING(keyslot_as_string))),
SD_JSON_BUILD_PAIR_STRING("pkcs11-uri", private_uri ?: uri),
- SD_JSON_BUILD_PAIR_BASE64("pkcs11-key", saved_key, saved_key_size));
+ SD_JSON_BUILD_PAIR_BASE64("pkcs11-key", saved_key, saved_key_size),
+ SD_JSON_BUILD_PAIR_CONDITION(rsa_padding > PKCS11_RSA_PADDING_PKCS1V15,
+ "pkcs11-padding", SD_JSON_BUILD_STRING(pkcs11_rsa_padding_to_string(rsa_padding))));
if (r < 0)
return log_error_errno(r, "Failed to prepare PKCS#11 JSON token object: %m");
const char *volume_name,
const char *friendly_name,
const char *pkcs11_uri,
+ Pkcs11RsaPadding rsa_padding,
const char *key_file, /* We either expect key_file and associated parameters to be set (for file keys) … */
size_t key_file_size,
uint64_t key_file_offset,
.friendly_name = friendly_name,
.askpw_flags = askpw_flags,
.until = until,
+ .rsa_padding = rsa_padding,
};
int r;
char **ret_uri,
void **ret_encrypted_key,
size_t *ret_encrypted_key_size,
+ Pkcs11RsaPadding *ret_rsa_padding,
int *ret_keyslot) {
#if HAVE_P11KIT
_cleanup_free_ void *key = NULL;
int r, keyslot = -1;
size_t key_size = 0;
+ Pkcs11RsaPadding rsa_padding = PKCS11_RSA_PADDING_PKCS1V15;
assert(cd);
assert(ret_uri);
assert(ret_encrypted_key);
assert(ret_encrypted_key_size);
+ assert(ret_rsa_padding);
assert(ret_keyslot);
/* Loads PKCS#11 metadata from LUKS2 JSON token headers. */
r = sd_json_variant_unbase64(w, &key, &key_size);
if (r < 0)
return log_error_errno(r, "Failed to decode base64 encoded key: %m");
+
+ /* Optional padding-scheme tag. Absent in legacy tokens (which used PKCS#1 v1.5). */
+ w = sd_json_variant_by_key(v, "pkcs11-padding");
+ if (w) {
+ Pkcs11RsaPadding p;
+
+ if (!sd_json_variant_is_string(w))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "PKCS#11 token field 'pkcs11-padding' is not a string.");
+ p = pkcs11_rsa_padding_from_string(sd_json_variant_string(w));
+ if (p < 0)
+ return log_error_errno(p,
+ "PKCS#11 token field 'pkcs11-padding' has unsupported value '%s'.",
+ sd_json_variant_string(w));
+ rsa_padding = p;
+ }
}
if (!uri)
*ret_uri = TAKE_PTR(uri);
*ret_encrypted_key = TAKE_PTR(key);
*ret_encrypted_key_size = key_size;
+ *ret_rsa_padding = rsa_padding;
*ret_keyslot = keyslot;
return 0;
#else
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#include "pkcs11-padding.h"
#include "shared-forward.h"
int decrypt_pkcs11_key(
const char *volume_name,
const char *friendly_name,
const char *pkcs11_uri,
+ Pkcs11RsaPadding rsa_padding,
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
char **ret_uri,
void **ret_encrypted_key,
size_t *ret_encrypted_key_size,
+ Pkcs11RsaPadding *ret_rsa_padding,
int *ret_keyslot);
size_t pkcs11_key_size;
_cleanup_free_ char *pkcs11_uri = NULL, *key_str = NULL;
_cleanup_free_ void *pkcs11_key = NULL;
+ Pkcs11RsaPadding rsa_padding = PKCS11_RSA_PADDING_PKCS1V15;
if (dlopen_cryptsetup(LOG_DEBUG) < 0)
return;
- r = parse_luks2_pkcs11_data(cd, json, &pkcs11_uri, &pkcs11_key, &pkcs11_key_size);
+ r = parse_luks2_pkcs11_data(cd, json, &pkcs11_uri, &pkcs11_key, &pkcs11_key_size, &rsa_padding);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m.");
crypt_log(cd, "\tpkcs11-uri: %s\n", pkcs11_uri);
crypt_log(cd, "\tpkcs11-key: %s\n", key_str);
+ crypt_log(cd, "\tpkcs11-padding: %s\n", pkcs11_rsa_padding_to_string(rsa_padding));
}
/*
if (r < 0)
return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded key: %m.");
+ /* Optional 'pkcs11-padding' field: must be a known padding scheme string if present. Older systemd
+ * versions will just ignore this field entirely and assume PKCS#1 v1.5. */
+ w = sd_json_variant_by_key(v, "pkcs11-padding");
+ if (w) {
+ if (!sd_json_variant_is_string(w)) {
+ crypt_log_debug(cd, "PKCS#11 token field 'pkcs11-padding' is not a string.");
+ return 1;
+ }
+ if (pkcs11_rsa_padding_from_string(sd_json_variant_string(w)) < 0) {
+ crypt_log_debug(cd, "PKCS#11 token field 'pkcs11-padding' has unsupported value.");
+ return 1;
+ }
+ }
+
return 0;
}
size_t pin_size;
void *encrypted_key;
size_t encrypted_key_size;
+ Pkcs11RsaPadding rsa_padding;
void *decrypted_key;
size_t decrypted_key_size;
};
object,
data->encrypted_key,
data->encrypted_key_size,
+ data->rsa_padding,
&data->decrypted_key,
&data->decrypted_key_size);
if (r < 0)
size_t pin_size,
void *encrypted_key,
size_t encrypted_key_size,
+ Pkcs11RsaPadding rsa_padding,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size) {
.pin_size = pin_size,
.encrypted_key = encrypted_key,
.encrypted_key_size = encrypted_key_size,
+ .rsa_padding = rsa_padding,
};
assert(pkcs11_uri);
systemd_pkcs11_plugin_params *params,
void *encrypted_key,
size_t encrypted_key_size,
+ Pkcs11RsaPadding rsa_padding,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size) {
_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
+ .free_encrypted_key = false,
+ .rsa_padding = rsa_padding,
};
assert(pkcs11_uri);
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_free_ char *pkcs11_uri = NULL;
_cleanup_free_ void *encrypted_key = NULL;
+ Pkcs11RsaPadding rsa_padding = PKCS11_RSA_PADDING_PKCS1V15;
systemd_pkcs11_plugin_params *pkcs11_params = userdata;
ssize_t base64_encoded_size;
assert(ret_password);
assert(ret_password_size);
- r = parse_luks2_pkcs11_data(cd, json, &pkcs11_uri, &encrypted_key, &encrypted_key_size);
+ r = parse_luks2_pkcs11_data(cd, json, &pkcs11_uri, &encrypted_key, &encrypted_key_size, &rsa_padding);
if (r < 0)
return r;
pkcs11_uri,
pkcs11_params,
encrypted_key, encrypted_key_size,
+ rsa_padding,
&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,
+ rsa_padding,
&decrypted_key, &decrypted_key_size);
if (r < 0)
return r;
const char *json,
char **ret_uri,
void **ret_encrypted_key,
- size_t *ret_encrypted_key_size) {
+ size_t *ret_encrypted_key_size,
+ Pkcs11RsaPadding *ret_rsa_padding) {
int r;
size_t key_size;
_cleanup_free_ void *key = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
sd_json_variant *w;
+ Pkcs11RsaPadding rsa_padding = PKCS11_RSA_PADDING_PKCS1V15;
assert(json);
assert(ret_uri);
if (r < 0)
return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded key: %m.");
+ /* Optional padding-scheme tag. Absent in legacy tokens (which used PKCS#1 v1.5). */
+ w = sd_json_variant_by_key(v, "pkcs11-padding");
+ if (w) {
+ Pkcs11RsaPadding p;
+
+ if (!sd_json_variant_is_string(w))
+ return crypt_log_debug_errno(cd, -EINVAL,
+ "LUKS2 token field 'pkcs11-padding' is not a string.");
+ p = pkcs11_rsa_padding_from_string(sd_json_variant_string(w));
+ if (p < 0)
+ return crypt_log_debug_errno(cd, p,
+ "LUKS2 token field 'pkcs11-padding' has unsupported value '%s'.",
+ sd_json_variant_string(w));
+ rsa_padding = p;
+ }
+
*ret_uri = TAKE_PTR(uri);
*ret_encrypted_key = TAKE_PTR(key);
*ret_encrypted_key_size = key_size;
+ if (ret_rsa_padding)
+ *ret_rsa_padding = rsa_padding;
return 0;
}
#pragma once
+#include "pkcs11-util.h"
#include "shared-forward.h"
int acquire_luks2_key(
const char *json,
char **ret_uri,
void **ret_encrypted_key,
- size_t *ret_encrypted_key_size);
+ size_t *ret_encrypted_key_size,
+ Pkcs11RsaPadding *ret_rsa_padding);
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
_cleanup_free_ void *discovered_key = NULL;
struct iovec discovered_key_data = {};
+ Pkcs11RsaPadding rsa_padding = PKCS11_RSA_PADDING_PKCS1V15;
int keyslot = arg_key_slot, r;
const char *uri = NULL;
bool use_libcryptsetup_plugin = use_token_plugins();
if (arg_pkcs11_uri_auto) {
if (!use_libcryptsetup_plugin) {
- r = find_pkcs11_auto_data(cd, &discovered_uri, &discovered_key, &discovered_key_size, &keyslot);
+ r = find_pkcs11_auto_data(cd, &discovered_uri, &discovered_key, &discovered_key_size, &rsa_padding, &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.");
name,
friendly,
uri,
+ rsa_padding,
key_file, arg_keyfile_size, arg_keyfile_offset,
key_data,
until,
static int add_pkcs11_encrypted_key(
sd_json_variant **v,
const char *uri,
+ Pkcs11RsaPadding rsa_padding,
const void *encrypted_key, size_t encrypted_key_size,
const void *decrypted_key, size_t decrypted_key_size) {
r = sd_json_buildo(&e,
SD_JSON_BUILD_PAIR_STRING("uri", uri),
SD_JSON_BUILD_PAIR_BASE64("data", encrypted_key, encrypted_key_size),
- SD_JSON_BUILD_PAIR_STRING("hashedPassword", hashed));
+ SD_JSON_BUILD_PAIR_STRING("hashedPassword", hashed),
+ SD_JSON_BUILD_PAIR_CONDITION(rsa_padding > PKCS11_RSA_PADDING_PKCS1V15,
+ "padding", SD_JSON_BUILD_STRING(pkcs11_rsa_padding_to_string(rsa_padding))));
if (r < 0)
return log_error_errno(r, "Failed to build encrypted JSON key object: %m");
_cleanup_(erase_and_freep) char *pin = NULL;
size_t decrypted_key_size, saved_key_size;
_cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL;
+ Pkcs11RsaPadding rsa_padding = _PKCS11_RSA_PADDING_INVALID;
int r;
assert(v);
"home.token-pin",
/* askpw_flags= */ 0,
&pkey,
+ &rsa_padding,
&pin);
if (r < 0)
return r;
- r = pkey_generate_volume_keys(pkey, &decrypted_key, &decrypted_key_size, &saved_key, &saved_key_size);
+ r = pkey_generate_volume_keys(pkey,
+ pkcs11_rsa_padding_to_oaep_hash(rsa_padding),
+ &decrypted_key, &decrypted_key_size, &saved_key, &saved_key_size);
if (r < 0)
return log_error_errno(r, "Failed to generate volume keys: %m");
r = add_pkcs11_encrypted_key(
v,
uri,
+ rsa_padding,
saved_key, saved_key_size,
decrypted_key, decrypted_key_size);
if (r < 0)
if (r < 0)
return r;
- r = pkcs11_token_decrypt_data(m, session, object, data->encrypted_key->data, data->encrypted_key->size, &decrypted_key, &decrypted_key_size);
+ r = pkcs11_token_decrypt_data(m, session, object, data->encrypted_key->data, data->encrypted_key->size, data->encrypted_key->padding, &decrypted_key, &decrypted_key_size);
if (r < 0)
return r;
DLSYM_PROTOTYPE(EVP_PKEY_fromdata) = NULL;
DLSYM_PROTOTYPE(EVP_PKEY_fromdata_init) = NULL;
static DLSYM_PROTOTYPE(EVP_PKEY_get1_encoded_public_key) = NULL;
-static DLSYM_PROTOTYPE(EVP_PKEY_get_base_id) = NULL;
+DLSYM_PROTOTYPE(EVP_PKEY_get_base_id) = NULL;
static DLSYM_PROTOTYPE(EVP_PKEY_get_bits) = NULL;
static DLSYM_PROTOTYPE(EVP_PKEY_get_bn_param) = NULL;
static DLSYM_PROTOTYPE(EVP_PKEY_get_group_name) = NULL;
return 0;
}
-int rsa_encrypt_bytes(
- EVP_PKEY *pkey,
- const void *decrypted_key,
- size_t decrypted_key_size,
- void **ret_encrypt_key,
- size_t *ret_encrypt_key_size) {
-
- _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = NULL;
- _cleanup_free_ void *b = NULL;
- size_t l;
- int r;
-
- assert(ret_encrypt_key);
- assert(ret_encrypt_key_size);
-
- r = dlopen_libcrypto(LOG_DEBUG);
- if (r < 0)
- return r;
-
- ctx = sym_EVP_PKEY_CTX_new(pkey, NULL);
- if (!ctx)
- return log_openssl_errors("Failed to allocate public key context");
-
- if (sym_EVP_PKEY_encrypt_init(ctx) <= 0)
- return log_openssl_errors("Failed to initialize public key context");
-
- if (sym_EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
- return log_openssl_errors("Failed to configure PKCS#1 padding");
-
- if (sym_EVP_PKEY_encrypt(ctx, NULL, &l, decrypted_key, decrypted_key_size) <= 0)
- return log_openssl_errors("Failed to determine encrypted key size");
-
- b = malloc(l);
- if (!b)
- return -ENOMEM;
-
- if (sym_EVP_PKEY_encrypt(ctx, b, &l, decrypted_key, decrypted_key_size) <= 0)
- return log_openssl_errors("Failed to determine encrypted key size");
-
- *ret_encrypt_key = TAKE_PTR(b);
- *ret_encrypt_key_size = l;
- return 0;
-}
-
-/* Encrypt the key data using RSA-OAEP with the provided label and specified digest algorithm. Returns 0 on
- * success, -EOPNOTSUPP if the digest algorithm is not supported, or < 0 for any other error. */
int rsa_oaep_encrypt_bytes(
const EVP_PKEY *pkey,
const char *digest_alg,
assert(pkey);
assert(digest_alg);
- assert(label);
assert(decrypted_key);
assert(decrypted_key_size > 0);
assert(ret_encrypt_key);
if (sym_EVP_PKEY_CTX_set_rsa_oaep_md(ctx, md) <= 0)
return log_openssl_errors("Failed to configure RSA-OAEP MD");
- _cleanup_free_ char *duplabel = strdup(label);
- if (!duplabel)
- return log_oom_debug();
+ if (label) {
+ _cleanup_free_ char *duplabel = strdup(label);
+ if (!duplabel)
+ return log_oom_debug();
- if (sym_EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, duplabel, strlen(duplabel) + 1) <= 0)
- return log_openssl_errors("Failed to configure RSA-OAEP label");
- /* ctx owns this now, don't free */
- TAKE_PTR(duplabel);
+ if (sym_EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, duplabel, strlen(duplabel) + 1) <= 0)
+ return log_openssl_errors("Failed to configure RSA-OAEP label");
+ /* ctx owns this now, don't free */
+ TAKE_PTR(duplabel);
+ }
size_t size = 0;
if (sym_EVP_PKEY_encrypt(ctx, NULL, &size, decrypted_key, decrypted_key_size) <= 0)
bits = sym_EVP_PKEY_get_bits(pkey);
log_debug("Bits in RSA key: %i", bits);
- /* We use PKCS#1 padding for the RSA cleartext, hence let's leave some extra space for it, hence only
- * generate a random key half the size of the RSA length */
+ /* We use RSA-OAEP for the cleartext, which has 2*hash_len + 2 bytes of overhead (e.g. 66 bytes
+ * for SHA-256, 42 for SHA-1). Leave plenty of headroom by only generating a key half the size of
+ * the RSA modulus. */
suitable_key_size = bits / 8 / 2;
if (suitable_key_size < 1)
static int rsa_pkey_generate_volume_keys(
EVP_PKEY *pkey,
+ const char *digest_alg,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size,
void **ret_saved_key,
size_t decrypted_key_size, saved_key_size;
int r;
+ assert(digest_alg);
assert(ret_decrypted_key);
assert(ret_decrypted_key_size);
assert(ret_saved_key);
if (r < 0)
return log_debug_errno(r, "Failed to generate random key: %m");
- r = rsa_encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &saved_key, &saved_key_size);
+ r = rsa_oaep_encrypt_bytes(
+ pkey,
+ digest_alg,
+ /* label= */ NULL, /* matches PKCS#11 CKZ_DATA_SPECIFIED with empty source */
+ decrypted_key, decrypted_key_size,
+ &saved_key, &saved_key_size);
if (r < 0)
- return log_debug_errno(r, "Failed to encrypt random key: %m");
+ return log_debug_errno(r, "Failed to RSA-OAEP encrypt random key: %m");
*ret_decrypted_key = TAKE_PTR(decrypted_key);
*ret_decrypted_key_size = decrypted_key_size;
int pkey_generate_volume_keys(
EVP_PKEY *pkey,
+ const char *rsa_oaep_digest_alg,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size,
void **ret_saved_key,
switch (type) {
case EVP_PKEY_RSA:
- return rsa_pkey_generate_volume_keys(pkey, ret_decrypted_key, ret_decrypted_key_size, ret_saved_key, ret_saved_key_size);
+ return rsa_pkey_generate_volume_keys(pkey, rsa_oaep_digest_alg ?: "SHA-1", ret_decrypted_key, ret_decrypted_key_size, ret_saved_key, ret_saved_key_size);
case EVP_PKEY_EC:
return ecc_pkey_generate_volume_keys(pkey, ret_decrypted_key, ret_decrypted_key_size, ret_saved_key, ret_saved_key_size);
extern DLSYM_PROTOTYPE(EVP_PKEY_free);
extern DLSYM_PROTOTYPE(EVP_PKEY_fromdata_init);
extern DLSYM_PROTOTYPE(EVP_PKEY_fromdata);
+extern DLSYM_PROTOTYPE(EVP_PKEY_get_base_id);
extern DLSYM_PROTOTYPE(EVP_PKEY_get_id);
extern DLSYM_PROTOTYPE(EVP_PKEY_keygen_init);
extern DLSYM_PROTOTYPE(EVP_PKEY_keygen);
int kdf_kb_hmac_derive(const char *mode, const char *digest, const void *key, size_t key_size, const void *salt, size_t salt_size, const void *info, size_t info_size, const void *seed, size_t seed_size, size_t derive_size, void **ret);
-int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size);
-
int rsa_oaep_encrypt_bytes(const EVP_PKEY *pkey, const char *digest_alg, const char *label, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size);
int rsa_pkey_to_suitable_key_size(EVP_PKEY *pkey, size_t *ret_suitable_key_size);
int ecc_ecdh(const EVP_PKEY *private_pkey, const EVP_PKEY *peer_pkey, void **ret_shared_secret, size_t *ret_shared_secret_size);
-int pkey_generate_volume_keys(EVP_PKEY *pkey, void **ret_decrypted_key, size_t *ret_decrypted_key_size, void **ret_saved_key, size_t *ret_saved_key_size);
+int pkey_generate_volume_keys(EVP_PKEY *pkey, const char *rsa_oaep_digest_alg, void **ret_decrypted_key, size_t *ret_decrypted_key_size, void **ret_saved_key, size_t *ret_saved_key_size);
int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "basic-forward.h"
+
+typedef enum Pkcs11RsaPadding {
+ PKCS11_RSA_PADDING_PKCS1V15, /* CKM_RSA_PKCS, RFC 8017 PKCS#1 v1.5 (legacy) */
+ PKCS11_RSA_PADDING_OAEP_SHA1, /* CKM_RSA_PKCS_OAEP with SHA-1/MGF1-SHA-1 (more supported) */
+ PKCS11_RSA_PADDING_OAEP_SHA256, /* CKM_RSA_PKCS_OAEP with SHA-256/MGF1-SHA-256 (preferred if supported) */
+ _PKCS11_RSA_PADDING_MAX,
+ _PKCS11_RSA_PADDING_INVALID = -EINVAL,
+} Pkcs11RsaPadding;
+
+const char* pkcs11_rsa_padding_to_string(Pkcs11RsaPadding i);
+Pkcs11RsaPadding pkcs11_rsa_padding_from_string(const char *s);
+
+static inline const char* pkcs11_rsa_padding_to_oaep_hash(Pkcs11RsaPadding p) {
+ switch (p) {
+ case PKCS11_RSA_PADDING_OAEP_SHA1:
+ return "SHA-1";
+ case PKCS11_RSA_PADDING_OAEP_SHA256:
+ return "SHA-256";
+ default:
+ return NULL;
+ }
+}
#include "memory-util.h"
#include "pkcs11-util.h"
#include "random-util.h"
+#include "string-table.h"
#include "string-util.h"
#include "strv.h"
return true;
}
+static const char* const pkcs11_rsa_padding_table[_PKCS11_RSA_PADDING_MAX] = {
+ [PKCS11_RSA_PADDING_PKCS1V15] = "pkcs1",
+ [PKCS11_RSA_PADDING_OAEP_SHA1] = "oaep-sha1",
+ [PKCS11_RSA_PADDING_OAEP_SHA256] = "oaep-sha256",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(pkcs11_rsa_padding, Pkcs11RsaPadding);
+
#if HAVE_P11KIT
DLSYM_PROTOTYPE(p11_kit_module_get_name) = NULL;
CK_OBJECT_HANDLE object,
const void *encrypted_data,
size_t encrypted_data_size,
+ Pkcs11RsaPadding rsa_padding,
void **ret_decrypted_data,
size_t *ret_decrypted_data_size) {
- static const CK_MECHANISM mechanism = {
- .mechanism = CKM_RSA_PKCS
+ /* For RSA-OAEP we use SHA-256 or SHA-1 with the matching MGF1 and an empty (zero-length) label.
+ * SHA-256 is preferred when the token supports it, but for example SoftHSM doesn't support it. */
+ CK_RSA_PKCS_OAEP_PARAMS oaep_params_sha1 = {
+ .hashAlg = CKM_SHA_1,
+ .mgf = CKG_MGF1_SHA1,
+ .source = CKZ_DATA_SPECIFIED,
+ };
+ CK_RSA_PKCS_OAEP_PARAMS oaep_params_sha256 = {
+ .hashAlg = CKM_SHA256,
+ .mgf = CKG_MGF1_SHA256,
+ .source = CKZ_DATA_SPECIFIED,
};
+ CK_MECHANISM mechanism;
_cleanup_(erase_and_freep) CK_BYTE *dbuffer = NULL;
CK_ULONG dbuffer_size = 0;
CK_RV rv;
assert(ret_decrypted_data);
assert(ret_decrypted_data_size);
- rv = m->C_DecryptInit(session, (CK_MECHANISM*) &mechanism, object);
+ switch (rsa_padding) {
+
+ case PKCS11_RSA_PADDING_PKCS1V15:
+ mechanism = (CK_MECHANISM) {
+ .mechanism = CKM_RSA_PKCS,
+ };
+ break;
+
+ case PKCS11_RSA_PADDING_OAEP_SHA1:
+ mechanism = (CK_MECHANISM) {
+ .mechanism = CKM_RSA_PKCS_OAEP,
+ .pParameter = &oaep_params_sha1,
+ .ulParameterLen = sizeof(oaep_params_sha1),
+ };
+ break;
+
+ case PKCS11_RSA_PADDING_OAEP_SHA256:
+ mechanism = (CK_MECHANISM) {
+ .mechanism = CKM_RSA_PKCS_OAEP,
+ .pParameter = &oaep_params_sha256,
+ .ulParameterLen = sizeof(oaep_params_sha256),
+ };
+ break;
+
+ default:
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Unknown RSA padding scheme requested for PKCS#11 decryption.");
+ }
+
+ rv = m->C_DecryptInit(session, &mechanism, object);
if (rv != CKR_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to initialize decryption on security token: %s", sym_p11_kit_strerror(rv));
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to decrypt key on security token: %s", sym_p11_kit_strerror(rv));
- log_info("Successfully decrypted key with security token.");
+ log_info("Successfully decrypted key with security token (%s padding).",
+ pkcs11_rsa_padding_to_string(rsa_padding));
*ret_decrypted_data = TAKE_PTR(dbuffer);
*ret_decrypted_data_size = dbuffer_size;
CK_OBJECT_HANDLE object,
const void *encrypted_data,
size_t encrypted_data_size,
+ Pkcs11RsaPadding rsa_padding,
void **ret_decrypted_data,
size_t *ret_decrypted_data_size) {
switch (key_type) {
case CKK_RSA:
- return pkcs11_token_decrypt_data_rsa(m, session, object, encrypted_data, encrypted_data_size, ret_decrypted_data, ret_decrypted_data_size);
+ return pkcs11_token_decrypt_data_rsa(m, session, object, encrypted_data, encrypted_data_size, rsa_padding, ret_decrypted_data, ret_decrypted_data_size);
case CKK_EC:
return pkcs11_token_decrypt_data_ecc(m, session, object, encrypted_data, encrypted_data_size, ret_decrypted_data, ret_decrypted_data_size);
struct pkcs11_acquire_public_key_callback_data {
char *pin_used;
EVP_PKEY *pkey;
+ Pkcs11RsaPadding rsa_padding;
const char *askpw_friendly_name, *askpw_icon, *askpw_credential;
AskPasswordFlags askpw_flags;
};
sym_EVP_PKEY_free(data->pkey);
}
+static int pkcs11_token_try_oaep_mechanism(
+ CK_FUNCTION_LIST *m,
+ CK_SESSION_HANDLE session,
+ CK_OBJECT_HANDLE private_key,
+ CK_MECHANISM_TYPE hash_alg,
+ CK_RSA_PKCS_MGF_TYPE mgf) {
+
+ CK_RSA_PKCS_OAEP_PARAMS params = {
+ .hashAlg = hash_alg,
+ .mgf = mgf,
+ .source = CKZ_DATA_SPECIFIED,
+ };
+ CK_MECHANISM mechanism = {
+ .mechanism = CKM_RSA_PKCS_OAEP,
+ .pParameter = ¶ms,
+ .ulParameterLen = sizeof(params),
+ };
+ CK_RV rv;
+
+ rv = m->C_DecryptInit(session, &mechanism, private_key);
+ if (rv != CKR_OK)
+ return -EIO;
+
+ /* Accepted: terminate the operation. Per PKCS#11 spec section 5.2 any cryptographic function
+ * returning an error other than CKR_BUFFER_TOO_SMALL terminates the active operation, so feed
+ * deliberately invalid (too-short) ciphertext to C_Decrypt to trigger a cancellation that's
+ * likely to be portable across token implementations. */
+ CK_BYTE in[1] = {};
+ CK_BYTE out[1];
+ CK_ULONG out_len = sizeof(out);
+ (void) m->C_Decrypt(session, in, sizeof(in), out, &out_len);
+
+ return 0;
+}
+
+static int pkcs11_token_probe_rsa_oaep_padding(
+ CK_FUNCTION_LIST *m,
+ CK_SESSION_HANDLE session,
+ CK_OBJECT_HANDLE private_key,
+ Pkcs11RsaPadding *ret) {
+
+ CK_KEY_TYPE key_type;
+ CK_ATTRIBUTE key_type_template = { CKA_KEY_TYPE, &key_type, sizeof(key_type) };
+ CK_RV rv;
+ int r;
+
+ assert(m);
+ assert(ret);
+
+ /* Probes whether the token will accept the OAEP-SHA256 or OAEP-SHA1 mechanism for decryption with
+ * the given private key. On any failure (wrong key type, related-object lookup ambiguity yielded
+ * an EC private key, both mechanisms rejected, etc.) we bail out, this can only be best-effort. */
+ rv = m->C_GetAttributeValue(session, private_key, &key_type_template, /* ulCount= */ 1);
+ if (rv != CKR_OK)
+ return log_debug_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to retrieve private key type: %s", sym_p11_kit_strerror(rv));
+ if (key_type != CKK_RSA)
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Located private key is not RSA, skipping OAEP padding probe.");
+
+ /* For RSA-OAEP we use SHA-256 or SHA-1 with the matching MGF1 and an empty (zero-length) label.
+ * SHA-256 is preferred when the token supports it, but for example SoftHSM doesn't support it, so
+ * fall back to SHA-1 if it gets rejected. */
+ r = pkcs11_token_try_oaep_mechanism(m, session, private_key, CKM_SHA256, CKG_MGF1_SHA256);
+ if (r >= 0) {
+ *ret = PKCS11_RSA_PADDING_OAEP_SHA256;
+ log_debug("Token accepts OAEP-SHA256 for decryption.");
+ return 0;
+ }
+
+ r = pkcs11_token_try_oaep_mechanism(m, session, private_key, CKM_SHA_1, CKG_MGF1_SHA1);
+ if (r >= 0) {
+ *ret = PKCS11_RSA_PADDING_OAEP_SHA1;
+ log_debug("Token accepts OAEP-SHA1 for decryption.");
+ return 0;
+ }
+
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Token rejected both OAEP-SHA256 and OAEP-SHA1.");
+}
+
static int pkcs11_acquire_public_key_callback(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
* kernel's and the token's pool */
(void) pkcs11_token_acquire_rng(m, session);
+ /* Decide whether the enrolled key needs RSA padding metadata at all based on the public key we
+ * extracted (RSA vs EC). For RSA, default to OAEP-SHA1 which works on all known tokens (e.g.
+ * SoftHSM only accepts SHA-1), then try to upgrade to OAEP-SHA256 by probing the matching private
+ * key on the token. The probe is best-effort: if we cannot find the private key (e.g. token holds
+ * only the certificate, or the related-object lookup is ambiguous because CKA_ID is missing or
+ * doesn't match) we keep the OAEP-SHA1 default. For non-RSA keys (EC) we leave rsa_padding as
+ * _INVALID, so no padding tag is recorded and the decrypt path uses ECDH instead. */
+ data->rsa_padding = _PKCS11_RSA_PADDING_INVALID;
+
+ if (sym_EVP_PKEY_get_base_id(pkey) == EVP_PKEY_RSA) {
+ CK_OBJECT_HANDLE prototype = public_key != CK_INVALID_HANDLE ? public_key : certificate;
+ CK_OBJECT_HANDLE private_key = CK_INVALID_HANDLE;
+ Pkcs11RsaPadding padding = PKCS11_RSA_PADDING_OAEP_SHA1; /* default to SHA1 unless we can do better */
+
+ if (pkcs11_token_find_related_object(m, session, prototype, CKO_PRIVATE_KEY, &private_key) >= 0) {
+ r = pkcs11_token_probe_rsa_oaep_padding(m, session, private_key, &padding);
+ if (r < 0)
+ log_info_errno(r, "Token rejected both OAEP-SHA256 and OAEP-SHA1, defaulting RSA enrollment to OAEP-SHA1.");
+ } else
+ log_info("No matching PKCS#11 private key found, OAEP padding probe skipped, defaulting RSA enrollment to OAEP-SHA1.");
+
+ data->rsa_padding = padding;
+ }
+
data->pin_used = TAKE_PTR(pin_used);
data->pkey = TAKE_PTR(pkey);
return 0;
const char *askpw_credential,
AskPasswordFlags askpw_flags,
EVP_PKEY **ret_pkey,
+ Pkcs11RsaPadding *ret_rsa_padding,
char **ret_pin_used) {
_cleanup_(pkcs11_acquire_public_key_callback_data_release) struct pkcs11_acquire_public_key_callback_data data = {
.askpw_icon = askpw_icon,
.askpw_credential = askpw_credential,
.askpw_flags = askpw_flags,
+ .rsa_padding = _PKCS11_RSA_PADDING_INVALID,
};
int r;
return r;
*ret_pkey = TAKE_PTR(data.pkey);
+ if (ret_rsa_padding)
+ *ret_rsa_padding = data.rsa_padding;
if (ret_pin_used)
*ret_pin_used = TAKE_PTR(data.pin_used);
return 0;
object,
data->encrypted_key,
data->encrypted_key_size,
+ data->rsa_padding,
&data->decrypted_key,
&data->decrypted_key_size);
if (r < 0)
#include "ask-password-api.h"
#include "dlfcn-util.h"
+#include "pkcs11-padding.h"
#include "shared-forward.h"
bool pkcs11_uri_valid(const char *uri);
#endif
int pkcs11_token_find_private_key(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, P11KitUri *search_uri, CK_OBJECT_HANDLE *ret_object);
-int pkcs11_token_decrypt_data(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, const void *encrypted_data, size_t encrypted_data_size, void **ret_decrypted_data, size_t *ret_decrypted_data_size);
+int pkcs11_token_decrypt_data(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, const void *encrypted_data, size_t encrypted_data_size, Pkcs11RsaPadding rsa_padding, void **ret_decrypted_data, size_t *ret_decrypted_data_size);
int pkcs11_token_acquire_rng(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session);
int pkcs11_find_token(const char *pkcs11_uri, pkcs11_find_token_callback_t callback, void *userdata);
#if HAVE_OPENSSL
-int pkcs11_acquire_public_key(const char *uri, const char *askpw_friendly_name, const char *askpw_icon, const char *askpw_credential, AskPasswordFlags askpw_flags, EVP_PKEY **ret_pkey, char **ret_pin_used);
+int pkcs11_acquire_public_key(const char *uri, const char *askpw_friendly_name, const char *askpw_icon, const char *askpw_credential, AskPasswordFlags askpw_flags, EVP_PKEY **ret_pkey, Pkcs11RsaPadding *ret_rsa_padding, char **ret_pin_used);
#endif
typedef struct {
bool free_encrypted_key;
const char *askpw_credential;
AskPasswordFlags askpw_flags;
+ Pkcs11RsaPadding rsa_padding;
} pkcs11_crypt_device_callback_data;
void pkcs11_crypt_device_callback_data_release(pkcs11_crypt_device_callback_data *data);
return 0;
}
+static int dispatch_pkcs11_padding(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ Pkcs11RsaPadding *p = ASSERT_PTR(userdata);
+ Pkcs11RsaPadding v;
+
+ if (sd_json_variant_is_null(variant)) {
+ *p = PKCS11_RSA_PADDING_PKCS1V15;
+ return 0;
+ }
+
+ if (!sd_json_variant_is_string(variant))
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL),
+ "JSON field '%s' is not a string.", strna(name));
+
+ v = pkcs11_rsa_padding_from_string(sd_json_variant_string(variant));
+ if (v < 0)
+ return json_log(variant, flags, v,
+ "JSON field '%s' has unsupported value '%s'.",
+ strna(name), sd_json_variant_string(variant));
+
+ *p = v;
+ return 0;
+}
+
static int dispatch_pkcs11_key(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
UserRecord *h = userdata;
sd_json_variant *e;
{ "uri", SD_JSON_VARIANT_STRING, dispatch_pkcs11_uri, offsetof(Pkcs11EncryptedKey, uri), SD_JSON_MANDATORY },
{ "data", SD_JSON_VARIANT_STRING, dispatch_pkcs11_key_data, 0, SD_JSON_MANDATORY },
{ "hashedPassword", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Pkcs11EncryptedKey, hashed_password), SD_JSON_MANDATORY },
+ { "padding", SD_JSON_VARIANT_STRING, dispatch_pkcs11_padding, offsetof(Pkcs11EncryptedKey, padding), 0 },
{},
};
return log_oom();
Pkcs11EncryptedKey *k = h->pkcs11_encrypted_key + h->n_pkcs11_encrypted_key;
- *k = (Pkcs11EncryptedKey) {};
+ *k = (Pkcs11EncryptedKey) {
+ .padding = PKCS11_RSA_PADDING_PKCS1V15, /* legacy default if field is absent */
+ };
r = sd_json_dispatch(e, pkcs11_key_dispatch_table, flags, k);
if (r < 0) {
#include "sd-id128.h"
#include "bitfield.h"
+#include "pkcs11-padding.h"
#include "rlimit-util.h"
#include "shared-forward.h"
/* Where to find the private key to decrypt the encrypted passphrase above */
char *uri;
+ /* Which RSA padding scheme was used to wrap the encrypted passphrase. Defaults to PKCS#1 v1.5 for
+ * legacy records that omit the field; new enrollments use RSA-OAEP with SHA-256 or SHA-1. */
+ Pkcs11RsaPadding padding;
+
/* What to test the decrypted passphrase against to allow access (classic UNIX password hash). Note
* that the decrypted passphrase is also used for unlocking LUKS and fscrypt, and if the account is
* backed by LUKS or fscrypt the hashed password is only an additional layer of authentication, not
cryptsetup_start_and_check empty_pkcs11_auto
cryptsetup luksKillSlot -q "$IMAGE_EMPTY" 2
cryptsetup token remove --token-id 0 "$IMAGE_EMPTY"
+
+ # Verify that new RSA enrollments tag the LUKS2 token with the OAEP padding scheme. Old systemd
+ # versions ignore this field and fall back to PKCS#1 v1.5, which causes the token to refuse to
+ # decrypt the OAEP-wrapped blob, so this metadata is what makes graceful rejection on old systems
+ # possible. The actual OAEP hash variant (SHA-1 vs SHA-256) is probed at enrollment time and
+ # depends on what the token supports, but SoftHSM through at least 2.7.0 only accepts SHA-1
+ PIN="1234" systemd-cryptenroll --pkcs11-token-uri="pkcs11:token=TestToken;object=RSATestKey" --unlock-key-file="$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY"
+ cryptsetup luksDump --dump-json-metadata "$IMAGE_EMPTY" |
+ jq -e '.tokens | with_entries(select(.value.type == "systemd-pkcs11"))[]["pkcs11-padding"] | select(. == "oaep-sha256" or . == "oaep-sha1")' >/dev/null
+
+ # Forward-compat negative check: tampering with the padding tag must cause unlocking to fail
+ # cleanly (the token rejects the OAEP blob when asked to do PKCS#1 v1.5, and vice versa).
+ TOKEN_ID=$(cryptsetup luksDump --dump-json-metadata "$IMAGE_EMPTY" |
+ jq -r '.tokens | to_entries[] | select(.value.type == "systemd-pkcs11") | .key' | head -n1)
+ cryptsetup token export --token-id "$TOKEN_ID" "$IMAGE_EMPTY" |
+ jq '. + {"pkcs11-padding": "pkcs1"}' >"$WORKDIR/tampered-token.json"
+ cryptsetup token remove --token-id "$TOKEN_ID" "$IMAGE_EMPTY"
+ cryptsetup token import --json-file="$WORKDIR/tampered-token.json" --token-id "$TOKEN_ID" "$IMAGE_EMPTY"
+ cryptsetup_start_and_check -f empty_pkcs11_auto
+ # Restore the correct token and confirm unlock works again.
+ cryptsetup token remove --token-id "$TOKEN_ID" "$IMAGE_EMPTY"
+ cryptsetup luksKillSlot -q "$IMAGE_EMPTY" 2
+
+ PIN="1234" systemd-cryptenroll --pkcs11-token-uri="pkcs11:token=TestToken;object=RSATestKey" --unlock-key-file="$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY"
+ cryptsetup_start_and_check empty_pkcs11_auto
+ cryptsetup luksKillSlot -q "$IMAGE_EMPTY" 2
+ cryptsetup token remove --token-id 0 "$IMAGE_EMPTY"
+
+ # Backward-compat check: mock a legacy LUKS2 token wrapped with PKCS#1 v1.5 (no
+ # 'pkcs11-padding' field) and verify the new code can still decrypt it
+ SOFTHSM_MODULE=$(awk -F: '/^[[:space:]]*module[[:space:]]*:/ { gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit }' /usr/share/p11-kit/modules/softhsm2.module)
+ [[ -n "$SOFTHSM_MODULE" ]] || { echo "Could not locate libsofthsm2.so via /usr/share/p11-kit/modules/softhsm2.module" >&2; exit 1; }
+ # Read the RSA public key out of the matching certificate object on the token, generate random key and
+ # wrap it
+ pkcs11-tool --module "$SOFTHSM_MODULE" --token-label TestToken --pin 1234 \
+ --read-object --type cert --label RSATestKey --output-file "$WORKDIR/rsa.der"
+ openssl x509 -inform DER -in "$WORKDIR/rsa.der" -pubkey -noout >"$WORKDIR/rsa-pub.pem"
+ openssl rand -out "$WORKDIR/decrypted.bin" 32
+ openssl pkeyutl -encrypt -pubin -inkey "$WORKDIR/rsa-pub.pem" \
+ -pkeyopt rsa_padding_mode:pkcs1 \
+ -in "$WORKDIR/decrypted.bin" -out "$WORKDIR/encrypted.bin"
+ base64 -w0 "$WORKDIR/decrypted.bin" >"$WORKDIR/passphrase"
+ cryptsetup luksAddKey --batch-mode --key-file="$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY" "$WORKDIR/passphrase"
+ LEGACY_SLOT=$(cryptsetup luksDump --dump-json-metadata "$IMAGE_EMPTY" |
+ jq -r '.keyslots | keys | map(tonumber) | max')
+ ENCRYPTED_B64=$(base64 -w0 "$WORKDIR/encrypted.bin")
+ cat >"$WORKDIR/legacy-token.json" <<EOF
+{
+ "type": "systemd-pkcs11",
+ "keyslots": ["$LEGACY_SLOT"],
+ "pkcs11-uri": "pkcs11:token=TestToken;object=RSATestKey;type=private",
+ "pkcs11-key": "$ENCRYPTED_B64"
+}
+EOF
+ cryptsetup token import --json-file="$WORKDIR/legacy-token.json" "$IMAGE_EMPTY"
+ cryptsetup_start_and_check empty_pkcs11_auto
+ cryptsetup luksKillSlot -q "$IMAGE_EMPTY" "$LEGACY_SLOT"
+ cryptsetup token remove --token-id 0 "$IMAGE_EMPTY"
fi
cryptsetup_start_and_check detached