openssl: PKEY-method override no longer working with OpenSSL 3.0
I have an application that uses a PKEY-method override to perform HSM-based secure key signing. In OpenSSL 1.1.1. this works well, but in OpenSSL 3.0 it does not work anymore.
I am aware that all the PKEY method functions are deprecated in OpenSSL 3.0, but I guess they ‘should’ still work the same as before…
However, it might be that my usage is somewhat unusual, and thus not covered by the OpenSSL 3.0 compatibility code. So it might be that you immediately tell me that this is no supposed to work anymore with OpenSSL 3.0. That would be totally OK for me, I do have a provider based solution for that use case that works with OpenSSL 3.0. However, maybe this situation is unintentionally not covered by the compatibility code and is worth to fix.
Here is what I do:
I setup my own PKEY method as follows. Note that this is not within an engine, but just within the application code.
pkey_meth = EVP_PKEY_meth_new(EVP_PKEY_id(pkey), 0);
EVP_PKEY_meth_set_init(pkey_meth, my_pkey_meth_init);
EVP_PKEY_meth_set_cleanup(pkey_meth, my_pkey_meth_cleanup);
EVP_PKEY_meth_set_copy(pkey_meth, my_pkey_meth_copy);
EVP_PKEY_meth_set_ctrl(pkey_meth, my_pkey_meth_ctrl, NULL);
EVP_PKEY_meth_set_sign(pkey_meth, NULL, my_pkey_meth_sign);
EVP_PKEY_meth_add0(pkey_meth);
Note that my_pkey_meth_ctrl() supports some own private control commands, i.e. EVP_PKEY_CTRL_MY_COMMAND.
Also note that the PKEY used in this application is a legacy PKEY, that has been produced via EVP_PKEY_assign_EC_KEY() (public key only, the private key is known to my PKEY method by other ways - i.e. is set via EVP_PKEY_CTX_ctrl() with a private command later on).
I then setup a digest sign context as follows:
EVP_PKEY_CTX *pctx = NULL;
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
EVP_DigestSignInit(ctx, &pctx, md, NULL, pkey);
EVP_PKEY_CTX_ctrl(pkey_ctx, -1, -1, EVP_PKEY_CTRL_MY_COMMAND, x, y);
The very last function (EVP_PKEY_CTX_ctrl()) fails.
I debugged this, and it seems that it is seeding the command to the (default) provider, and thus through the ctrl to parms translation, which of course fails to translate that command since ist my private one.
So evp_pkey_ctx_ctrl_to_param() fails with EVP_R_COMMAND_NOT_SUPPORTED.
It goes that way, because in evp_pkey_ctx_ctrl_int() it calls evp_pkey_ctx_state() which returns EVP_PKEY_STATE_PROVIDER for that PKEY context.
I think that the error actually happend way before that. EVP_DigestSignInit() internally creates the PKEY context. do_sigver_init() creates a new PKEY context using EVP_PKEY_CTX_new_from_pkey() which calls int_ctx_new(). At the end of int_ctx_new() the created PKEY context looks as follows:
*ret = {operation = 0, libctx = 0x0, propquery = 0x0, keytype = 0x3fffddb1d68 "id-ecPublicKey", keymgmt = 0x1113690, op = {
keymgmt = {genctx = 0x0}, kex = {exchange = 0x0, algctx = 0x0}, sig = {signature = 0x0, algctx = 0x0}, ciph = {cipher = 0x0,
algctx = 0x0}, encap = {kem = 0x0, algctx = 0x0}}, cached_parameters = {dist_id_name = 0x0, dist_id = 0x0, dist_id_len = 0,
dist_id_set = 0}, app_data = 0x0, pkey_gencb = 0x0, keygen_info = 0x0, keygen_info_count = 0, legacy_keytype = 408,
pmeth = 0x10b9070, engine = 0x0, pkey = 0x10bba80, peerkey = 0x0, data = 0x10b9350, flag_call_digest_custom = 0, rsa_pubexp = 0x0}
Note that the pmeth field is set, and it is my own PKEY method, as expected. It got it via pmeth = evp_pkey_meth_find_added_by_application(id);
Later in do_sigver_init() it uses the created PKEY context and checks if it is a legacy context:
if (evp_pkey_ctx_is_legacy(locpctx))
goto legacy;
And here is the problem, evp_pkey_ctx_is_legacy() returns false, so it is treated as provider context, not legacy.
evp_pkey_ctx_is_legacy is a define as follows:
#define evp_pkey_ctx_is_legacy(ctx) \
((ctx)->engine != NULL || (ctx)->keytype == NULL)
I would assume that it should also check (ctx)->pmeth != NULL, because a context with an application PKEY method should also be treated as a legacy context.
The legacy code within do_sigver_init() would then use the PKEY method to perform the sign operation.
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 26 (26 by maintainers)
Commits related to this issue
- EVP: Move setting |keytype| in int_ctx_new() in the legacy section The code that set |keytype| in the legacy section of int_ctx_new() did so a little too early, before other variables that should be ... — committed to levitte/openssl by levitte 3 years ago
- Fix custom EVP_PKEY_METHOD implementations where no engine is present It is possible to have a custom EVP_PKEY_METHOD implementation without having an engine. In those cases we were failing to use th... — committed to mattcaswell/openssl by mattcaswell 3 years ago
To ease investigations into this issue, I wrote a fully standalone reproducer:
When compiled against 1.1.1 this application prints “Success!”, but fails to do so against master.
I have reverted Richards fix and applied yours (only the one commit), and tested my application again. Your fix also fixes the problem.
Please note that I have tested only the legacy-PKEY case, since my application always uses legacy PKEYs. So I can’t tell if it also works with provided PKEYs. However, your original reproducer should cover that case.
I was first a bit confused if your fix wouldn’t also need the change from Richards fix about only setting
keytypefor provided keys, but I then realized that you also changedevp_pkey_ctx_is_legacy()to check forkeymgmtonly. So you fix looks good from my point of view.With a fix in my PKEY method sign function for the sig = NULL case it now works fine. So I can confirm that your fix in https://github.com/openssl/openssl/pull/16111 fixes the problem.
The consequence is that we will have to do pkey downgrading under these conditions, since pmeths rightfully assume legacy keys. That’s something we have explicitly said we shouldn’t have to do.
It’s a bit late to change that perspective now!
Why? With a provider key, ctrls of that sort are invalid. Remember, we do not automagically downgrade pkeys, and the ctrl<->params translation only handles ctrl cmds and param keys that libcrypto knows. Translating completely random cmds like
EVP_PKEY_CTRL_MY_COMMANDare too big an ask.