diff --git a/man/crypttab.xml b/man/crypttab.xml index eb742be0582..c35d782c8dd 100644 --- a/man/crypttab.xml +++ b/man/crypttab.xml @@ -104,10 +104,14 @@ see above and below. 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 - AF_UNIX, 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 described below to use this mechanism. + smartcard. In this case a saved key used in unlock process is stored on disk/removable media, acquired via + AF_UNIX, 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 described below to use this mechanism. + 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 @@ Takes either the special value auto or an RFC7512 PKCS#11 URI pointing to a private RSA key + url="https://tools.ietf.org/html/rfc7512">RFC7512 PKCS#11 URI 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 systemd-cryptenroll1 - 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 auto. In this mode the third column of the line should remain empty (that is, specified as -). - 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. + 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. Use systemd-cryptenroll --pkcs11-token-uri=list to list all suitable PKCS#11 security tokens currently plugged in, along with their URIs. @@ -969,8 +973,8 @@ external /dev/sda3 keyfile:LABEL=keydev keyfile-timeout=10s,cipher=xchac Yubikey-based PKCS#11 Volume Unlocking Example 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 ykmap1 from the yubikey-manager project to initialize the token and systemd-cryptenroll1 diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml index 8fd885cb264..40e07ce24d9 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -36,8 +36,8 @@ supports tokens and credentials of the following kind to be enrolled: - PKCS#11 security tokens and smartcards that may carry an RSA key pair (e.g. various - YubiKeys) + PKCS#11 security tokens and smartcards that may carry an RSA or EC key pair (e.g. + various YubiKeys) FIDO2 security tokens that implement the hmac-secret extension (most FIDO2 keys, including YubiKeys) @@ -317,9 +317,16 @@ smartcard URI referring to the token. Alternatively the special value auto 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 list 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. + enumerate all suitable PKCS#11 tokens currently plugged in. + + 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. In order to unlock a LUKS2 volume with an enrolled PKCS#11 security token, specify the option in the respective /etc/crypttab line: diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c index f94df54c207..d4a689cf852 100644 --- a/src/shared/openssl-util.c +++ b/src/shared/openssl-util.c @@ -8,8 +8,8 @@ #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, ...) \ @@ -524,7 +524,6 @@ int rsa_encrypt_bytes( *ret_encrypt_key = TAKE_PTR(b); *ret_encrypt_key_size = l; - return 0; } @@ -990,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(); @@ -1130,6 +1129,95 @@ int string_hashsum( } # 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, @@ -1194,6 +1282,9 @@ int x509_generate_volume_keys( 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"); diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c index 6e88dc38038..22cb794633d 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -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,127 @@ 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 = objects[0]; + *ret_object = object; return 0; } -int pkcs11_token_decrypt_data( +/* 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 = ¶ms, + .ulParameterLen = sizeof(params) + }; + CK_OBJECT_HANDLE shared_secret_handle; + CK_RV rv, rv2; + + 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 +785,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 +817,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) {