Merge pull request #28658 from H5117/enroll_with_ec

cryptsetup: Add support for EC keys in PKCS#11 tokens
This commit is contained in:
Lennart Poettering
2023-12-21 22:45:40 +01:00
committed by GitHub
12 changed files with 755 additions and 173 deletions

View File

@@ -104,10 +104,14 @@
see above and below.</para></listitem>
<listitem><para>The key may be acquired via a PKCS#11 compatible hardware security token or
smartcard. In this case an encrypted key is stored on disk/removable media, acquired via
<constant>AF_UNIX</constant>, or stored in the LUKS2 JSON token metadata header. The encrypted key is
then decrypted by the PKCS#11 token with an RSA key stored on it, and then used to unlock the encrypted
volume. Use the <option>pkcs11-uri=</option> option described below to use this mechanism.</para></listitem>
smartcard. In this case a saved key used in unlock process is stored on disk/removable media, acquired via
<constant>AF_UNIX</constant>, or stored in the LUKS2 JSON token metadata header. For RSA, the saved key
is an encrypted volume key. The encrypted volume key is then decrypted by the PKCS#11 token with an RSA
private key stored on it, and used to unlock the encrypted volume. For elliptic-curve (EC) cryptography,
the saved key is the public key generated in enrollment process. The public key is then used to derive
a shared secret with a private key stored in the PKCS#11 token. The derived shared secret is then used
to unlock the volume. Use the <option>pkcs11-uri=</option> option described below to use this mechanism.
</para></listitem>
<listitem><para>Similarly, the key may be acquired via a FIDO2 compatible hardware security token
(which must implement the "hmac-secret" extension). In this case a key generated randomly during
@@ -643,7 +647,7 @@
<term><option>pkcs11-uri=</option></term>
<listitem><para>Takes either the special value <literal>auto</literal> or an <ulink
url="https://tools.ietf.org/html/rfc7512">RFC7512 PKCS#11 URI</ulink> pointing to a private RSA key
url="https://tools.ietf.org/html/rfc7512">RFC7512 PKCS#11 URI</ulink> pointing to a private key
which is used to decrypt the encrypted key specified in the third column of the line. This is useful
for unlocking encrypted volumes through PKCS#11 compatible security tokens or smartcards. See below
for an example how to set up this mechanism for unlocking a LUKS2 volume with a YubiKey security
@@ -653,16 +657,16 @@
security token metadata in its LUKS2 JSON token section. In this mode the URI and the encrypted key
are automatically read from the LUKS2 JSON token header. Use
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
as simple tool for enrolling PKCS#11 security tokens or smartcards in a way compatible with
as a simple tool for enrolling PKCS#11 security tokens or smartcards in a way compatible with
<literal>auto</literal>. In this mode the third column of the line should remain empty (that is,
specified as <literal>-</literal>).</para>
<para>The specified URI can refer directly to a private RSA key stored on a token or alternatively
just to a slot or token, in which case a search for a suitable private RSA key will be performed. In
this case if multiple suitable objects are found the token is refused. The encrypted key configured
in the third column of the line is passed as is (i.e. in binary form, unprocessed) to RSA
decryption. The resulting decrypted key is then Base64 encoded before it is used to unlock the LUKS
volume.</para>
<para>The specified URI can refer directly to a private key stored on a token or alternatively
just to a slot or token, in which case a search for a suitable private key will be performed. In
this case if multiple suitable objects are found the token is refused. The keyfile configured
in the third column of the line is used as is (i.e. in binary form, unprocessed). The resulting
decrypted key (for RSA) or derived shared secret (for ECC) is then Base64 encoded before it is used
to unlock the LUKS volume.</para>
<para>Use <command>systemd-cryptenroll --pkcs11-token-uri=list</command> to list all suitable PKCS#11
security tokens currently plugged in, along with their URIs.</para>
@@ -969,8 +973,8 @@ external /dev/sda3 keyfile:LABEL=keydev keyfile-timeout=10s,cipher=xchac
<title>Yubikey-based PKCS#11 Volume Unlocking Example</title>
<para>The PKCS#11 logic allows hooking up any compatible security token that is capable of storing RSA
decryption keys for unlocking an encrypted volume. Here's an example how to set up a Yubikey security
token for this purpose on a LUKS2 volume, using <citerefentry
or EC cryptographic keys for unlocking an encrypted volume. Here's an example how to set up a Yubikey
security token for this purpose on a LUKS2 volume, using <citerefentry
project='debian'><refentrytitle>ykmap</refentrytitle><manvolnum>1</manvolnum></citerefentry> from the
yubikey-manager project to initialize the token and
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>

View File

@@ -36,8 +36,8 @@
supports tokens and credentials of the following kind to be enrolled:</para>
<orderedlist>
<listitem><para>PKCS#11 security tokens and smartcards that may carry an RSA key pair (e.g. various
YubiKeys)</para></listitem>
<listitem><para>PKCS#11 security tokens and smartcards that may carry an RSA or EC key pair (e.g.
various YubiKeys)</para></listitem>
<listitem><para>FIDO2 security tokens that implement the <literal>hmac-secret</literal> extension (most
FIDO2 keys, including YubiKeys)</para></listitem>
@@ -317,9 +317,16 @@
smartcard URI referring to the token. Alternatively the special value <literal>auto</literal> may
be specified, in order to automatically determine the URI of a currently plugged in security token
(of which there must be exactly one). The special value <literal>list</literal> may be used to
enumerate all suitable PKCS#11 tokens currently plugged in. The security token must contain an RSA
key pair which is used to encrypt the randomly generated key that is used to unlock the LUKS2
volume. The encrypted key is then stored in the LUKS2 JSON token header area.</para>
enumerate all suitable PKCS#11 tokens currently plugged in.</para>
<para>The PKCS#11 token must contain an RSA or EC key pair which will be used to unlock a LUKS2 volume.
For RSA, a randomly generated volume key is encrypted with a public key in the token, and stored in
the LUKS2 JSON token header area. To unlock a volume, the stored encrypted volume key will be decrypted
with a private key in the token. For ECC, ECDH algorithm is used: we generate a pair of EC keys in
the same EC group, then derive a shared secret using the generated private key and the public key
in the token. The derived shared secret is used as a volume key. The generated public key is
stored in the LUKS2 JSON token header area. The generated private key is erased. To unlock a volume,
we derive the shared secret with the stored public key and a private key in the token.</para>
<para>In order to unlock a LUKS2 volume with an enrolled PKCS#11 security token, specify the
<option>pkcs11-uri=</option> option in the respective <filename>/etc/crypttab</filename> line:</para>

View File

@@ -138,6 +138,12 @@ const char* const systemd_features =
" -LIBCRYPTSETUP"
#endif
#if HAVE_LIBCRYPTSETUP_PLUGINS
" +LIBCRYPTSETUP_PLUGINS"
#else
" -LIBCRYPTSETUP_PLUGINS"
#endif
#if HAVE_LIBFDISK
" +LIBFDISK"
#else

View File

@@ -6,7 +6,6 @@
#include "memory-util.h"
#include "openssl-util.h"
#include "pkcs11-util.h"
#include "random-util.h"
int enroll_pkcs11(
struct crypt_device *cd,
@@ -18,12 +17,11 @@ int enroll_pkcs11(
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_free_ char *keyslot_as_string = NULL;
size_t decrypted_key_size, encrypted_key_size;
_cleanup_free_ void *encrypted_key = NULL;
size_t decrypted_key_size, saved_key_size;
_cleanup_free_ void *saved_key = NULL;
_cleanup_(X509_freep) X509 *cert = NULL;
ssize_t base64_encoded_size;
const char *node;
EVP_PKEY *pkey;
int keyslot, r;
assert_se(cd);
@@ -37,27 +35,9 @@ int enroll_pkcs11(
if (r < 0)
return r;
pkey = X509_get0_pubkey(cert);
if (!pkey)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract public key from X.509 certificate.");
r = rsa_pkey_to_suitable_key_size(pkey, &decrypted_key_size);
r = x509_generate_volume_keys(cert, &decrypted_key, &decrypted_key_size, &saved_key, &saved_key_size);
if (r < 0)
return log_error_errno(r, "Failed to determine RSA public key size.");
log_debug("Generating %zu bytes random key.", decrypted_key_size);
decrypted_key = malloc(decrypted_key_size);
if (!decrypted_key)
return log_oom();
r = crypto_random_bytes(decrypted_key, decrypted_key_size);
if (r < 0)
return log_error_errno(r, "Failed to generate random key: %m");
r = rsa_encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &encrypted_key, &encrypted_key_size);
if (r < 0)
return log_error_errno(r, "Failed to encrypt key: %m");
return log_error_errno(r, "Failed to generate volume keys: %m");
/* Let's base64 encode the key to use, for compat with homed (and it's easier to type it in by
* keyboard, if that might ever end up being necessary.) */
@@ -87,7 +67,7 @@ int enroll_pkcs11(
JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-pkcs11")),
JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))),
JSON_BUILD_PAIR("pkcs11-uri", JSON_BUILD_STRING(uri)),
JSON_BUILD_PAIR("pkcs11-key", JSON_BUILD_BASE64(encrypted_key, encrypted_key_size))));
JSON_BUILD_PAIR("pkcs11-key", JSON_BUILD_BASE64(saved_key, saved_key_size))));
if (r < 0)
return log_error_errno(r, "Failed to prepare PKCS#11 JSON token object: %m");

View File

@@ -8,7 +8,6 @@
#include "memory-util.h"
#include "openssl-util.h"
#include "pkcs11-util.h"
#include "random-util.h"
#include "strv.h"
static int add_pkcs11_encrypted_key(
@@ -158,11 +157,10 @@ static int acquire_pkcs11_certificate(
}
int identity_add_pkcs11_key_data(JsonVariant **v, const char *uri) {
_cleanup_(erase_and_freep) void *decrypted_key = NULL, *encrypted_key = NULL;
_cleanup_(erase_and_freep) void *decrypted_key = NULL, *saved_key = NULL;
_cleanup_(erase_and_freep) char *pin = NULL;
size_t decrypted_key_size, encrypted_key_size;
size_t decrypted_key_size, saved_key_size;
_cleanup_(X509_freep) X509 *cert = NULL;
EVP_PKEY *pkey;
int r;
assert(v);
@@ -171,27 +169,9 @@ int identity_add_pkcs11_key_data(JsonVariant **v, const char *uri) {
if (r < 0)
return r;
pkey = X509_get0_pubkey(cert);
if (!pkey)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract public key from X.509 certificate.");
r = rsa_pkey_to_suitable_key_size(pkey, &decrypted_key_size);
r = x509_generate_volume_keys(cert, &decrypted_key, &decrypted_key_size, &saved_key, &saved_key_size);
if (r < 0)
return log_error_errno(r, "Failed to extract RSA key size from X509 certificate.");
log_debug("Generating %zu bytes random key.", decrypted_key_size);
decrypted_key = malloc(decrypted_key_size);
if (!decrypted_key)
return log_oom();
r = crypto_random_bytes(decrypted_key, decrypted_key_size);
if (r < 0)
return log_error_errno(r, "Failed to generate random key: %m");
r = rsa_encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &encrypted_key, &encrypted_key_size);
if (r < 0)
return log_error_errno(r, "Failed to encrypt key: %m");
return log_error_errno(r, "Failed to generate volume keys: %m");
/* Add the token URI to the public part of the record. */
r = add_pkcs11_token_uri(v, uri);
@@ -202,7 +182,7 @@ int identity_add_pkcs11_key_data(JsonVariant **v, const char *uri) {
r = add_pkcs11_encrypted_key(
v,
uri,
encrypted_key, encrypted_key_size,
saved_key, saved_key_size,
decrypted_key, decrypted_key_size);
if (r < 0)
return r;

View File

@@ -4,11 +4,12 @@
#include "fd-util.h"
#include "hexdecoct.h"
#include "openssl-util.h"
#include "random-util.h"
#include "string-util.h"
#if HAVE_OPENSSL
/* For each error in the the OpenSSL thread error queue, log the provided message and the OpenSSL error
* string. If there are no errors in the OpenSSL thread queue, this logs the message with "No openssl
/* For each error in the OpenSSL thread error queue, log the provided message and the OpenSSL error
* string. If there are no errors in the OpenSSL thread queue, this logs the message with "No OpenSSL
* errors." This logs at level debug. Returns -EIO (or -ENOMEM). */
#define log_openssl_errors(fmt, ...) _log_openssl_errors(UNIQ, fmt, ##__VA_ARGS__)
#define _log_openssl_errors(u, fmt, ...) \
@@ -523,7 +524,6 @@ int rsa_encrypt_bytes(
*ret_encrypt_key = TAKE_PTR(b);
*ret_encrypt_key_size = l;
return 0;
}
@@ -989,7 +989,7 @@ int ecc_ecdh(const EVP_PKEY *private_pkey,
if (EVP_PKEY_derive(ctx, NULL, &shared_secret_size) <= 0)
return log_openssl_errors("Failed to get ECC shared secret size");
_cleanup_free_ void *shared_secret = malloc(shared_secret_size);
_cleanup_(erase_and_freep) void *shared_secret = malloc(shared_secret_size);
if (!shared_secret)
return log_oom_debug();
@@ -1128,6 +1128,170 @@ int string_hashsum(
return 0;
}
# endif
static int ecc_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) {
_cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_new = NULL;
_cleanup_(erase_and_freep) void *decrypted_key = NULL;
_cleanup_free_ unsigned char *saved_key = NULL;
size_t decrypted_key_size, saved_key_size;
int nid = NID_undef;
int r;
#if OPENSSL_VERSION_MAJOR >= 3
_cleanup_free_ char *curve_name = NULL;
size_t len = 0;
if (EVP_PKEY_get_group_name(pkey, NULL, 0, &len) != 1 || len == 0)
return log_openssl_errors("Failed to determine PKEY group name length");
len++;
curve_name = new(char, len);
if (!curve_name)
return log_oom_debug();
if (EVP_PKEY_get_group_name(pkey, curve_name, len, &len) != 1)
return log_openssl_errors("Failed to get PKEY group name");
nid = OBJ_sn2nid(curve_name);
#else
EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(pkey);
if (!ec_key)
return log_openssl_errors("PKEY doesn't have EC_KEY associated");
if (EC_KEY_check_key(ec_key) != 1)
return log_openssl_errors("EC_KEY associated with PKEY is not valid");
nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec_key));
#endif
r = ecc_pkey_new(nid, &pkey_new);
if (r < 0)
return log_debug_errno(r, "Failed to generate a new EC keypair: %m");
r = ecc_ecdh(pkey_new, pkey, &decrypted_key, &decrypted_key_size);
if (r < 0)
return log_debug_errno(r, "Failed to derive shared secret: %m");
#if OPENSSL_VERSION_MAJOR >= 3
/* EVP_PKEY_get1_encoded_public_key() always returns uncompressed format of EC points.
See https://github.com/openssl/openssl/discussions/22835 */
saved_key_size = EVP_PKEY_get1_encoded_public_key(pkey_new, &saved_key);
if (saved_key_size == 0)
return log_openssl_errors("Failed to convert the generated public key to SEC1 format");
#else
EC_KEY *ec_key_new = EVP_PKEY_get0_EC_KEY(pkey_new);
if (!ec_key_new)
return log_openssl_errors("The generated key doesn't have associated EC_KEY");
if (EC_KEY_check_key(ec_key_new) != 1)
return log_openssl_errors("EC_KEY associated with the generated key is not valid");
saved_key_size = EC_POINT_point2oct(EC_KEY_get0_group(ec_key_new),
EC_KEY_get0_public_key(ec_key_new),
POINT_CONVERSION_UNCOMPRESSED,
NULL, 0, NULL);
if (saved_key_size == 0)
return log_openssl_errors("Failed to determine size of the generated public key");
saved_key = malloc(saved_key_size);
if (!saved_key)
return log_oom_debug();
saved_key_size = EC_POINT_point2oct(EC_KEY_get0_group(ec_key_new),
EC_KEY_get0_public_key(ec_key_new),
POINT_CONVERSION_UNCOMPRESSED,
saved_key, saved_key_size, NULL);
if (saved_key_size == 0)
return log_openssl_errors("Failed to convert the generated public key to SEC1 format");
#endif
*ret_decrypted_key = TAKE_PTR(decrypted_key);
*ret_decrypted_key_size = decrypted_key_size;
*ret_saved_key = TAKE_PTR(saved_key);
*ret_saved_key_size = saved_key_size;
return 0;
}
static int rsa_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) {
_cleanup_(erase_and_freep) void *decrypted_key = NULL;
_cleanup_free_ void *saved_key = NULL;
size_t decrypted_key_size, saved_key_size;
int r;
r = rsa_pkey_to_suitable_key_size(pkey, &decrypted_key_size);
if (r < 0)
return log_debug_errno(r, "Failed to determine RSA public key size.");
log_debug("Generating %zu bytes random key.", decrypted_key_size);
decrypted_key = malloc(decrypted_key_size);
if (!decrypted_key)
return log_oom_debug();
r = crypto_random_bytes(decrypted_key, decrypted_key_size);
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);
if (r < 0)
return log_debug_errno(r, "Failed to encrypt random key: %m");
*ret_decrypted_key = TAKE_PTR(decrypted_key);
*ret_decrypted_key_size = decrypted_key_size;
*ret_saved_key = TAKE_PTR(saved_key);
*ret_saved_key_size = saved_key_size;
return 0;
}
int x509_generate_volume_keys(
X509 *cert,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size,
void **ret_saved_key,
size_t *ret_saved_key_size) {
assert(cert);
assert(ret_decrypted_key);
assert(ret_decrypted_key_size);
assert(ret_saved_key);
assert(ret_saved_key_size);
EVP_PKEY *pkey = X509_get0_pubkey(cert);
if (!pkey)
return log_openssl_errors("Failed to extract public key from X.509 certificate.");
#if OPENSSL_VERSION_MAJOR >= 3
int type = EVP_PKEY_get_base_id(pkey);
#else
int type = EVP_PKEY_base_id(pkey);
#endif
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);
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);
case NID_undef:
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine a type of public key");
default:
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported public key type: %s", OBJ_nid2sn(type));
}
}
#endif
int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) {

View File

@@ -108,6 +108,8 @@ int ecc_pkey_new(int curve_id, EVP_PKEY **ret);
int ecc_ecdh(const EVP_PKEY *private_pkey, const EVP_PKEY *peer_pkey, void **ret_shared_secret, size_t *ret_shared_secret_size);
int x509_generate_volume_keys(X509 *cert, 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);
int digest_and_sign(const EVP_MD *md, EVP_PKEY *privkey, const void *data, size_t size, void **ret, size_t *ret_size);

View File

@@ -586,114 +586,61 @@ int pkcs11_token_find_private_key(
P11KitUri *search_uri,
CK_OBJECT_HANDLE *ret_object) {
bool found_decrypt = false, found_class = false, found_key_type = false;
uint_fast8_t n_objects = 0;
bool found_class = false;
_cleanup_free_ CK_ATTRIBUTE *attributes_buffer = NULL;
CK_ULONG n_attributes, a, n_objects;
CK_ATTRIBUTE *attributes = NULL;
CK_OBJECT_HANDLE objects[2];
CK_RV rv, rv2;
int r;
CK_OBJECT_HANDLE object, candidate;
static const CK_OBJECT_CLASS class = CKO_PRIVATE_KEY;
CK_BBOOL decrypt_value, derive_value;
CK_ATTRIBUTE optional_attributes[] = {
{ CKA_DECRYPT, &decrypt_value, sizeof(decrypt_value) },
{ CKA_DERIVE, &derive_value, sizeof(derive_value) }
};
CK_RV rv;
assert(m);
assert(search_uri);
assert(ret_object);
r = dlopen_p11kit();
if (r < 0)
return r;
attributes = sym_p11_kit_uri_get_attributes(search_uri, &n_attributes);
for (a = 0; a < n_attributes; a++) {
CK_ULONG n_attributes;
CK_ATTRIBUTE *attributes = sym_p11_kit_uri_get_attributes(search_uri, &n_attributes);
for (CK_ULONG i = 0; i < n_attributes; i++) {
/* We use the URI's included match attributes, but make them more strict. This allows users
* to specify a token URL instead of an object URL and the right thing should happen if
* there's only one suitable key on the token. */
switch (attributes[a].type) {
switch (attributes[i].type) {
case CKA_CLASS: {
CK_OBJECT_CLASS c;
if (attributes[a].ulValueLen != sizeof(c))
if (attributes[i].ulValueLen != sizeof(c))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid PKCS#11 CKA_CLASS attribute size.");
memcpy(&c, attributes[a].pValue, sizeof(c));
memcpy(&c, attributes[i].pValue, sizeof(c));
if (c != CKO_PRIVATE_KEY)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Selected PKCS#11 object is not a private key, refusing.");
found_class = true;
break;
}
case CKA_DECRYPT: {
CK_BBOOL b;
if (attributes[a].ulValueLen != sizeof(b))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid PKCS#11 CKA_DECRYPT attribute size.");
memcpy(&b, attributes[a].pValue, sizeof(b));
if (!b)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Selected PKCS#11 object is not suitable for decryption, refusing.");
found_decrypt = true;
break;
}
case CKA_KEY_TYPE: {
CK_KEY_TYPE t;
if (attributes[a].ulValueLen != sizeof(t))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid PKCS#11 CKA_KEY_TYPE attribute size.");
memcpy(&t, attributes[a].pValue, sizeof(t));
if (t != CKK_RSA)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected PKCS#11 object is not an RSA key, refusing.");
found_key_type = true;
break;
}}
}
if (!found_decrypt || !found_class || !found_key_type) {
if (!found_class) {
/* Hmm, let's slightly extend the attribute list we search for */
attributes_buffer = new(CK_ATTRIBUTE, n_attributes + !found_decrypt + !found_class + !found_key_type);
attributes_buffer = new(CK_ATTRIBUTE, n_attributes + 1);
if (!attributes_buffer)
return log_oom();
memcpy(attributes_buffer, attributes, sizeof(CK_ATTRIBUTE) * n_attributes);
if (!found_decrypt) {
static const CK_BBOOL yes = true;
attributes_buffer[n_attributes++] = (CK_ATTRIBUTE) {
.type = CKA_DECRYPT,
.pValue = (CK_BBOOL*) &yes,
.ulValueLen = sizeof(yes),
};
}
if (!found_class) {
static const CK_OBJECT_CLASS class = CKO_PRIVATE_KEY;
attributes_buffer[n_attributes++] = (CK_ATTRIBUTE) {
.type = CKA_CLASS,
.pValue = (CK_OBJECT_CLASS*) &class,
.ulValueLen = sizeof(class),
};
}
if (!found_key_type) {
static const CK_KEY_TYPE type = CKK_RSA;
attributes_buffer[n_attributes++] = (CK_ATTRIBUTE) {
.type = CKA_KEY_TYPE,
.pValue = (CK_KEY_TYPE*) &type,
.ulValueLen = sizeof(type),
};
}
attributes_buffer[n_attributes++] = (CK_ATTRIBUTE) {
.type = CKA_CLASS,
.pValue = (CK_OBJECT_CLASS*) &class,
.ulValueLen = sizeof(class),
};
attributes = attributes_buffer;
}
@@ -703,26 +650,355 @@ int pkcs11_token_find_private_key(
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to initialize object find call: %s", sym_p11_kit_strerror(rv));
rv = m->C_FindObjects(session, objects, ELEMENTSOF(objects), &n_objects);
rv2 = m->C_FindObjectsFinal(session);
for (;;) {
CK_ULONG b;
rv = m->C_FindObjects(session, &candidate, 1, &b);
if (rv != CKR_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to find objects: %s", sym_p11_kit_strerror(rv));
if (b == 0)
break;
bool can_decrypt = false, can_derive = false;
optional_attributes[0].ulValueLen = sizeof(decrypt_value);
optional_attributes[1].ulValueLen = sizeof(derive_value);
rv = m->C_GetAttributeValue(session, candidate, optional_attributes, ELEMENTSOF(optional_attributes));
if (rv != CKR_OK && rv != CKR_ATTRIBUTE_TYPE_INVALID)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get attributes of a selected private key: %s", sym_p11_kit_strerror(rv));
if (optional_attributes[0].ulValueLen != CK_UNAVAILABLE_INFORMATION && decrypt_value == CK_TRUE)
can_decrypt = true;
if (optional_attributes[1].ulValueLen != CK_UNAVAILABLE_INFORMATION && derive_value == CK_TRUE)
can_derive = true;
if (can_decrypt || can_derive) {
n_objects++;
if (n_objects > 1)
break;
object = candidate;
}
}
rv = m->C_FindObjectsFinal(session);
if (rv != CKR_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to find objects: %s", sym_p11_kit_strerror(rv));
if (rv2 != CKR_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to finalize object find call: %s", sym_p11_kit_strerror(rv));
"Failed to finalize object find call: %s", sym_p11_kit_strerror(rv));
if (n_objects == 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
"Failed to find selected private key suitable for decryption on token.");
"Failed to find selected private key suitable for decryption or derivation on token.");
if (n_objects > 1)
return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
"Configured private key URI matches multiple keys, refusing.");
"Configured private key URI matches multiple keys, refusing.");
*ret_object = object;
return 0;
}
static const char* object_class_to_string(CK_OBJECT_CLASS class) {
switch (class) {
case CKO_CERTIFICATE:
return "CKO_CERTIFICATE";
case CKO_PUBLIC_KEY:
return "CKO_PUBLIC_KEY";
case CKO_PRIVATE_KEY:
return "CKO_PRIVATE_KEY";
case CKO_SECRET_KEY:
return "CKO_SECRET_KEY";
default:
return NULL;
}
}
/* Returns an object with the given class and the same CKA_ID or CKA_LABEL as prototype */
int pkcs11_token_find_related_object(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
CK_OBJECT_HANDLE prototype,
CK_OBJECT_CLASS class,
CK_OBJECT_HANDLE *ret_object ) {
_cleanup_free_ void *buffer = NULL;
CK_ATTRIBUTE attributes[] = {
{ CKA_ID, NULL_PTR, 0 },
{ CKA_LABEL, NULL_PTR, 0 }
};
CK_OBJECT_CLASS search_class = class;
CK_ATTRIBUTE search_attributes[2] = {
{ CKA_CLASS, &search_class, sizeof(search_class) }
};
CK_ULONG n_objects;
CK_OBJECT_HANDLE objects[2];
CK_RV rv;
rv = m->C_GetAttributeValue(session, prototype, attributes, ELEMENTSOF(attributes));
if (rv != CKR_OK && rv != CKR_ATTRIBUTE_TYPE_INVALID)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve length of attributes: %s", sym_p11_kit_strerror(rv));
if (attributes[0].ulValueLen != CK_UNAVAILABLE_INFORMATION) {
buffer = malloc(attributes[0].ulValueLen);
if (!buffer)
return log_oom();
attributes[0].pValue = buffer;
rv = m->C_GetAttributeValue(session, prototype, &attributes[0], 1);
if (rv != CKR_OK)
return log_debug_errno(SYNTHETIC_ERRNO(EIO),
"Failed to retrieve CKA_ID: %s", sym_p11_kit_strerror(rv));
search_attributes[1] = attributes[0];
} else if (attributes[1].ulValueLen != CK_UNAVAILABLE_INFORMATION) {
buffer = malloc(attributes[1].ulValueLen);
if (!buffer)
return log_oom();
attributes[1].pValue = buffer;
rv = m->C_GetAttributeValue(session, prototype, &attributes[1], 1);
if (rv != CKR_OK)
return log_debug_errno(SYNTHETIC_ERRNO(EIO),
"Failed to retrieve CKA_LABEL: %s", sym_p11_kit_strerror(rv));
search_attributes[1] = attributes[1];
} else
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "The prototype does not have CKA_ID or CKA_LABEL");
rv = m->C_FindObjectsInit(session, search_attributes, 2);
if (rv != CKR_OK)
return log_debug_errno(SYNTHETIC_ERRNO(EIO),
"Failed to initialize object find call: %s", sym_p11_kit_strerror(rv));
rv = m->C_FindObjects(session, objects, 2, &n_objects);
if (rv != CKR_OK)
return log_debug_errno(SYNTHETIC_ERRNO(EIO),
"Failed to find objects: %s", sym_p11_kit_strerror(rv));
rv = m->C_FindObjectsFinal(session);
if (rv != CKR_OK)
return log_debug_errno(SYNTHETIC_ERRNO(EIO),
"Failed to finalize object find call: %s", sym_p11_kit_strerror(rv));
if (n_objects == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENOENT),
"Failed to find a related object with class %s", object_class_to_string(class));
if (n_objects > 1)
log_warning("Found multiple related objects with class %s, using the first object.",
object_class_to_string(class));
*ret_object = objects[0];
return 0;
}
int pkcs11_token_decrypt_data(
#if HAVE_OPENSSL
static int ecc_convert_to_compressed(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
CK_OBJECT_HANDLE object,
const void *uncompressed_point,
size_t uncompressed_point_size,
void **ret_compressed_point,
size_t *ret_compressed_point_size) {
_cleanup_free_ void *ec_params_buffer = NULL;
CK_ATTRIBUTE ec_params_attr = { CKA_EC_PARAMS, NULL_PTR, 0 };
CK_RV rv;
int r;
rv = m->C_GetAttributeValue(session, object, &ec_params_attr, 1);
if (rv != CKR_OK && rv != CKR_ATTRIBUTE_TYPE_INVALID)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to retrieve length of CKA_EC_PARAMS: %s", sym_p11_kit_strerror(rv));
if (ec_params_attr.ulValueLen != CK_UNAVAILABLE_INFORMATION) {
ec_params_buffer = malloc(ec_params_attr.ulValueLen);
if (!ec_params_buffer)
return log_oom();
ec_params_attr.pValue = ec_params_buffer;
rv = m->C_GetAttributeValue(session, object, &ec_params_attr, 1);
if (rv != CKR_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to retrieve CKA_EC_PARAMS from a private key: %s", sym_p11_kit_strerror(rv));
} else {
CK_OBJECT_HANDLE public_key;
r = pkcs11_token_find_related_object(m, session, object, CKO_PUBLIC_KEY, &public_key);
if (r < 0)
return log_error_errno(r, "Failed to find a public key for compressing a EC point");
ec_params_attr.ulValueLen = 0;
rv = m->C_GetAttributeValue(session, public_key, &ec_params_attr, 1);
if (rv != CKR_OK && rv != CKR_ATTRIBUTE_TYPE_INVALID)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to retrieve length of CKA_EC_PARAMS: %s", sym_p11_kit_strerror(rv));
if (ec_params_attr.ulValueLen == CK_UNAVAILABLE_INFORMATION)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"The public key does not have CKA_EC_PARAMS");
ec_params_buffer = malloc(ec_params_attr.ulValueLen);
if (!ec_params_buffer)
return log_oom();
ec_params_attr.pValue = ec_params_buffer;
rv = m->C_GetAttributeValue(session, public_key, &ec_params_attr, 1);
if (rv != CKR_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to retrieve CKA_EC_PARAMS from a public key: %s", sym_p11_kit_strerror(rv));
}
_cleanup_(EC_GROUP_freep) EC_GROUP *group = NULL;
_cleanup_(EC_POINT_freep) EC_POINT *point = NULL;
_cleanup_(BN_CTX_freep) BN_CTX *bnctx = NULL;
_cleanup_free_ void *compressed_point = NULL;
size_t compressed_point_size;
const unsigned char *ec_params_value = ec_params_attr.pValue;
group = d2i_ECPKParameters(NULL, &ec_params_value, ec_params_attr.ulValueLen);
if (!group)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_PARAMS");
point = EC_POINT_new(group);
if (!point)
return log_oom();
bnctx = BN_CTX_new();
if (!bnctx)
return log_oom();
if (EC_POINT_oct2point(group, point, uncompressed_point, uncompressed_point_size, bnctx) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode an uncompressed EC point");
compressed_point_size = EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED, NULL, 0, bnctx);
if (compressed_point_size == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine size of a compressed EC point");
compressed_point = malloc(compressed_point_size);
if (!compressed_point)
return log_oom();
compressed_point_size = EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED, compressed_point, compressed_point_size, bnctx);
if (compressed_point_size == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert a EC point to compressed format");
*ret_compressed_point = TAKE_PTR(compressed_point);
*ret_compressed_point_size = compressed_point_size;
return 0;
}
#endif
/* Since EC keys doesn't support encryption directly, we use ECDH protocol to derive shared secret here.
* We use PKCS#11 C_DeriveKey function to derive a shared secret with a private key stored in the token and
* a public key saved on enrollment. */
static int pkcs11_token_decrypt_data_ecc(
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) {
static const CK_BBOOL yes = CK_TRUE, no = CK_FALSE;
static const CK_OBJECT_CLASS shared_secret_class = CKO_SECRET_KEY;
static const CK_KEY_TYPE shared_secret_type = CKK_GENERIC_SECRET;
static const CK_ATTRIBUTE shared_secret_template[] = {
{ CKA_TOKEN, (void*) &no, sizeof(no) },
{ CKA_CLASS, (void*) &shared_secret_class, sizeof(shared_secret_class) },
{ CKA_KEY_TYPE, (void*) &shared_secret_type, sizeof(shared_secret_type) },
{ CKA_SENSITIVE, (void*) &no, sizeof(no) },
{ CKA_EXTRACTABLE, (void*) &yes, sizeof(yes) }
};
CK_ECDH1_DERIVE_PARAMS params = {
.kdf = CKD_NULL,
.pPublicData = (void*) encrypted_data,
.ulPublicDataLen = encrypted_data_size
};
CK_MECHANISM mechanism = {
.mechanism = CKM_ECDH1_DERIVE,
.pParameter = &params,
.ulParameterLen = sizeof(params)
};
CK_OBJECT_HANDLE shared_secret_handle;
CK_SESSION_INFO session_info;
CK_MECHANISM_INFO mechanism_info;
CK_RV rv, rv2;
#if HAVE_OPENSSL
_cleanup_free_ void *compressed_point = NULL;
int r;
#endif
rv = m->C_GetSessionInfo(session, &session_info);
if (rv != CKR_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get information about the PKCS#11 session: %s", sym_p11_kit_strerror(rv));
rv = m->C_GetMechanismInfo(session_info.slotID, CKM_ECDH1_DERIVE, &mechanism_info);
if (rv != CKR_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get information about CKM_ECDH1_DERIVE: %s", sym_p11_kit_strerror(rv));
if (!(mechanism_info.flags & CKF_EC_UNCOMPRESS)) {
if (mechanism_info.flags & CKF_EC_COMPRESS) {
#if HAVE_OPENSSL
log_debug("CKM_ECDH1_DERIVE accepts compressed EC points only, trying to convert.");
size_t compressed_point_size;
r = ecc_convert_to_compressed(m, session, object, encrypted_data, encrypted_data_size, &compressed_point, &compressed_point_size);
if (r < 0)
return r;
params.pPublicData = compressed_point;
params.ulPublicDataLen = compressed_point_size;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"CKM_ECDH1_DERIVE does not support uncompressed format of EC points");
#endif
} else
log_debug("Both CKF_EC_UNCOMPRESS and CKF_EC_COMPRESS are false for CKM_ECDH1_DERIVE, ignoring.");
}
rv = m->C_DeriveKey(session, &mechanism, object, (CK_ATTRIBUTE*) shared_secret_template, ELEMENTSOF(shared_secret_template), &shared_secret_handle);
if (rv != CKR_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to derive a shared secret: %s", sym_p11_kit_strerror(rv));
CK_ATTRIBUTE shared_secret_attr = { CKA_VALUE, NULL_PTR, 0};
rv = m->C_GetAttributeValue(session, shared_secret_handle, &shared_secret_attr, 1);
if (rv != CKR_OK) {
rv2 = m->C_DestroyObject(session, shared_secret_handle);
if (rv2 != CKR_OK)
log_warning("Failed to destroy a shared secret, ignoring: %s", sym_p11_kit_strerror(rv2));
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve shared secret length: %s", sym_p11_kit_strerror(rv));
}
shared_secret_attr.pValue = malloc(shared_secret_attr.ulValueLen);
if (!shared_secret_attr.pValue)
return log_oom();
rv = m->C_GetAttributeValue(session, shared_secret_handle, &shared_secret_attr, 1);
rv2 = m->C_DestroyObject(session, shared_secret_handle);
if (rv2 != CKR_OK)
log_warning("Failed to destroy a shared secret, ignoring: %s", sym_p11_kit_strerror(rv2));
if (rv != CKR_OK) {
erase_and_free(shared_secret_attr.pValue);
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve a shared secret: %s", sym_p11_kit_strerror(rv));
}
log_info("Successfully derived key with security token.");
*ret_decrypted_data = shared_secret_attr.pValue;
*ret_decrypted_data_size = shared_secret_attr.ulValueLen;
return 0;
}
static int pkcs11_token_decrypt_data_rsa(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
CK_OBJECT_HANDLE object,
@@ -737,17 +1013,6 @@ int pkcs11_token_decrypt_data(
_cleanup_(erase_and_freep) CK_BYTE *dbuffer = NULL;
CK_ULONG dbuffer_size = 0;
CK_RV rv;
int r;
assert(m);
assert(encrypted_data);
assert(encrypted_data_size > 0);
assert(ret_decrypted_data);
assert(ret_decrypted_data_size);
r = dlopen_p11kit();
if (r < 0)
return r;
rv = m->C_DecryptInit(session, (CK_MECHANISM*) &mechanism, object);
if (rv != CKR_OK)
@@ -780,6 +1045,42 @@ int pkcs11_token_decrypt_data(
return 0;
}
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) {
CK_KEY_TYPE key_type;
CK_ATTRIBUTE key_type_template = { CKA_KEY_TYPE, &key_type, sizeof(key_type) };
CK_RV rv;
assert(m);
assert(encrypted_data);
assert(encrypted_data_size > 0);
assert(ret_decrypted_data);
assert(ret_decrypted_data_size);
rv = m->C_GetAttributeValue(session, object, &key_type_template, 1);
if (rv != CKR_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve private key type");
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);
case CKK_EC:
return pkcs11_token_decrypt_data_ecc(m, session, object, encrypted_data, encrypted_data_size, ret_decrypted_data, ret_decrypted_data_size);
default:
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported private key type: %lu", key_type);
}
}
int pkcs11_token_acquire_rng(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session) {

View File

@@ -1,6 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#if HAVE_OPENSSL
# include <openssl/x509.h>
#endif
#include <stdbool.h>
#if HAVE_P11KIT
@@ -10,7 +13,6 @@
#include "ask-password-api.h"
#include "macro.h"
#include "openssl-util.h"
#include "time-util.h"
bool pkcs11_uri_valid(const char *uri);
@@ -50,6 +52,7 @@ char *pkcs11_token_model(const CK_TOKEN_INFO *token_info);
int pkcs11_token_login_by_pin(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, const CK_TOKEN_INFO *token_info, const char *token_label, const void *pin, size_t pin_size);
int pkcs11_token_login(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slotid, const CK_TOKEN_INFO *token_info, const char *friendly_name, const char *icon_name, const char *key_name, const char *credential_name, usec_t until, AskPasswordFlags ask_password_flags, bool headless, char **ret_used_pin);
int pkcs11_token_find_related_object(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE prototype, CK_OBJECT_CLASS class, CK_OBJECT_HANDLE *ret_object);
int pkcs11_token_find_x509_certificate(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, P11KitUri *search_uri, CK_OBJECT_HANDLE *ret_object);
#if HAVE_OPENSSL
int pkcs11_token_read_x509_certificate(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, X509 **ret_cert);

View File

@@ -0,0 +1,8 @@
dn = "cn = systemd"
expiration_days = 30
signing_key
encryption_key
tls_www_client
email_protection_key

View File

@@ -39,6 +39,117 @@ check_result_qemu() {
return $ret
}
can_test_pkcs11() {
if ! command -v "softhsm2-util" >/dev/null; then
ddebug "softhsm2-util not available, skipping the PKCS#11 test"
return 1
fi
if ! command -v "pkcs11-tool" >/dev/null; then
ddebug "pkcs11-tool not available, skipping the PKCS#11 test"
return 1
fi
if ! command -v "certtool" >/dev/null; then
ddebug "certtool not available, skipping the PKCS#11 test"
return 1
fi
if ! "${SYSTEMCTL:?}" --version | grep -q "+P11KIT"; then
ddebug "Support for p11-kit is disabled, skipping the PKCS#11 test"
return 1
fi
if ! "${SYSTEMCTL:?}" --version | grep -q "+LIBCRYPTSETUP\b"; then
ddebug "Support for libcryptsetup is disabled, skipping the PKCS#11 test"
return 1
fi
if ! "${SYSTEMCTL:?}" --version | grep -q "+LIBCRYPTSETUP_PLUGINS"; then
ddebug "Support for libcryptsetup plugins is disabled, skipping the PKCS#11 test"
return 1
fi
return 0
}
setup_pkcs11_token() {
dinfo "Setup PKCS#11 token"
local P11_MODULE_CONFIGS_DIR P11_MODULE_DIR SOFTHSM_MODULE
export SOFTHSM2_CONF="/tmp/softhsm2.conf"
mkdir -p "$initdir/var/lib/softhsm/tokens/"
cat >${SOFTHSM2_CONF} <<EOF
directories.tokendir = $initdir/var/lib/softhsm/tokens/
objectstore.backend = file
slots.removable = false
slots.mechanisms = ALL
EOF
export GNUTLS_PIN="1234"
export GNUTLS_SO_PIN="12345678"
softhsm2-util --init-token --free --label "TestToken" --pin ${GNUTLS_PIN} --so-pin ${GNUTLS_SO_PIN}
if ! P11_MODULE_CONFIGS_DIR=$(pkg-config --variable=p11_module_configs p11-kit-1); then
echo "WARNING! Cannot get p11_module_configs from p11-kit-1.pc, assuming /usr/share/p11-kit/modules" >&2
P11_MODULE_CONFIGS_DIR="/usr/share/p11-kit/modules"
fi
if ! P11_MODULE_DIR=$(pkg-config --variable=p11_module_path p11-kit-1); then
echo "WARNING! Cannot get p11_module_path from p11-kit-1.pc, assuming /usr/lib/pkcs11" >&2
P11_MODULE_DIR="/usr/lib/pkcs11"
fi
SOFTHSM_MODULE=$(grep -F 'module:' "$P11_MODULE_CONFIGS_DIR/softhsm2.module"| cut -d ':' -f 2| xargs)
if [[ "$SOFTHSM_MODULE" =~ ^[^/] ]]; then
SOFTHSM_MODULE="$P11_MODULE_DIR/$SOFTHSM_MODULE"
fi
# RSA #####################################################
pkcs11-tool --module "$SOFTHSM_MODULE" --token-label "TestToken" --pin "env:GNUTLS_PIN" --so-pin "env:GNUTLS_SO_PIN" --keypairgen --key-type "RSA:2048" --label "RSATestKey" --usage-decrypt
certtool --generate-self-signed \
--load-privkey="pkcs11:token=TestToken;object=RSATestKey;type=private" \
--load-pubkey="pkcs11:token=TestToken;object=RSATestKey;type=public" \
--template "$TEST_BASE_DIR/$TESTNAME/template.cfg" \
--outder --outfile "/tmp/rsa_test.crt"
pkcs11-tool --module "$SOFTHSM_MODULE" --token-label "TestToken" --pin "env:GNUTLS_PIN" --so-pin "env:GNUTLS_SO_PIN" --write-object "/tmp/rsa_test.crt" --type cert --label "RSATestKey"
rm "/tmp/rsa_test.crt"
# prime256v1 ##############################################
pkcs11-tool --module "$SOFTHSM_MODULE" --token-label "TestToken" --pin "env:GNUTLS_PIN" --so-pin "env:GNUTLS_SO_PIN" --keypairgen --key-type "EC:prime256v1" --label "ECTestKey" --usage-derive
certtool --generate-self-signed \
--load-privkey="pkcs11:token=TestToken;object=ECTestKey;type=private" \
--load-pubkey="pkcs11:token=TestToken;object=ECTestKey;type=public" \
--template "$TEST_BASE_DIR/$TESTNAME/template.cfg" \
--outder --outfile "/tmp/ec_test.crt"
pkcs11-tool --module "$SOFTHSM_MODULE" --token-label "TestToken" --pin "env:GNUTLS_PIN" --so-pin "env:GNUTLS_SO_PIN" --write-object "/tmp/ec_test.crt" --type cert --label "ECTestKey"
rm "/tmp/ec_test.crt"
###########################################################
rm ${SOFTHSM2_CONF}
unset SOFTHSM2_CONF
inst_libs "$SOFTHSM_MODULE"
inst_library "$SOFTHSM_MODULE"
inst_simple "$P11_MODULE_CONFIGS_DIR/softhsm2.module"
cat >"$initdir/etc/softhsm2.conf" <<EOF
directories.tokendir = /var/lib/softhsm/tokens/
objectstore.backend = file
slots.removable = false
slots.mechanisms = ALL
log.level = INFO
EOF
mkdir -p "$initdir/etc/systemd/system/systemd-cryptsetup@.service.d"
cat >"$initdir/etc/systemd/system/systemd-cryptsetup@.service.d/PKCS11.conf" <<EOF
[Service]
Environment="SOFTHSM2_CONF=/etc/softhsm2.conf"
Environment="PIN=$GNUTLS_PIN"
EOF
unset GNUTLS_PIN
unset GNUTLS_SO_PIN
}
test_create_image() {
create_empty_image_rootdir
@@ -57,6 +168,10 @@ test_create_image() {
install_dmevent
generate_module_dependencies
if can_test_pkcs11; then
setup_pkcs11_token
fi
# Create a keydev
dd if=/dev/zero of="${STATEDIR:?}/keydev.img" bs=1M count=16
mkfs.ext4 -L varcrypt_keydev "$STATEDIR/keydev.img"
@@ -83,7 +198,7 @@ EOF
if command -v dracut >/dev/null; then
dracut --force --verbose --add crypt "$INITRD"
elif command -v mkinitcpio >/dev/null; then
mkinitcpio --addhooks sd-encrypt --generate "$INITRD"
mkinitcpio -S autodetect --addhooks sd-encrypt --generate "$INITRD"
elif command -v mkinitramfs >/dev/null; then
# The cryptroot hook is provided by the cryptsetup-initramfs package
if ! dpkg-query -s cryptsetup-initramfs; then

View File

@@ -5,8 +5,6 @@ set -o pipefail
# TODO:
# - /proc/cmdline parsing
# - figure out token support (apart from TPM2, as that's covered by TEST-70-TPM2)
# - this might help https://www.qemu.org/docs/master/system/devices/ccid.html
# - expect + interactive auth?
# We set up an encrypted /var partition which should get mounted automatically
@@ -185,6 +183,7 @@ empty0 $IMAGE_EMPTY - headless=1,
empty1 $IMAGE_EMPTY - headless=1,try-empty-password=1
# This one expects the key to be under /{etc,run}/cryptsetup-keys.d/empty_nokey.key
empty_nokey $IMAGE_EMPTY - headless=1
empty_pkcs11_auto $IMAGE_EMPTY - headless=1,pkcs11-uri=auto
detached $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE headless=1,header=$IMAGE_DETACHED_HEADER,keyfile-offset=32,keyfile-size=16
detached_store0 $IMAGE_DETACHED $IMAGE_DETACHED_KEYFILE headless=1,header=/header:LABEL=header_store,keyfile-offset=32,keyfile-size=16
@@ -229,6 +228,19 @@ mkdir -p /run/cryptsetup-keys.d
cp "$IMAGE_EMPTY_KEYFILE" /run/cryptsetup-keys.d/empty_nokey.key
cryptsetup_start_and_check empty_nokey
if [[ -r /etc/softhsm2.conf ]]; then
# Test unlocking with a PKCS#11 token
export SOFTHSM2_CONF="/etc/softhsm2.conf"
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"
PIN="1234" systemd-cryptenroll --pkcs11-token-uri="pkcs11:token=TestToken;object=ECTestKey" --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"
fi
cryptsetup_start_and_check detached
cryptsetup_start_and_check detached_store{0..2}
cryptsetup_start_and_check -f detached_fail{0..4}