openssl: Cannot use key and ciphers in different providers

For an external key (non-exportable) I use a custom provider with a minimal store, keymgmt and signature implemented. This works, the key can be loaded, and signing using EVP_PKEY_sign() calls the method in my provider. The latter requires the EVP_PKEY_CTX created with the same propq as the key, though.

Now, to use the corresponding certificate in TLS, I create SSL_CTX in default context so that cipher and digest algorithms are available. And load the key as the private key. This errors during signature as it wants to export the key which is not possible. Is there a way out of this?

An edited snippet of the relevant code:

OSSL_PROVIDER_load(libctx, "ovpn.xkey");

EVP_PKEY *pkey = my_custom_loader(libctx, "provider=ovpn.xkey", uri); /* this loads an opaque pointer as keydata */ 

SSL_CTX *ctx = SSL_CTX_new_ex(NULL, NULL, method); /* using default context */
SSL_CTX_use_certificate(ctx, cert);
SSL_CTX_use_Privatekey(ctx, pkey);
..

In this case, all is good until signature is needed which fails with calls to provider’s keymgmt_export() with PRIVATE_KEY in selection, not possible for the key. So, it looks like the sign op is tried in the context for SSL_CTX which needs the key exported.

If I use SSL_CTX_new_ex(libctx, "provider=ovpn.xkey", method) it fails quickly with “library has no ciphers”. Is it possible to get the sign operation called in the context of the key’s provider, without losing access to algorithms in the default provider?

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 46 (26 by maintainers)

Commits related to this issue

Most upvoted comments

Somewhere in the end, this appears to be a conflict of what the caller actually wants, and this comes all the way down to where operations are initialized.

I simply don’t see what the conflict is?

Load a key from provider “ovpn.xkey” and tell your SSL_CTX to use it. Also tell your SSL_CTX to prefer the default provider (propquery “?provider=default”). Where is the conflict? I expect algorithms to always be fetched from the default provider, except in the case where we need to use the (unexportable) key. In that case our preference to use the default provider doesn’t apply because we cannot use the default provider.

So far, the priority has been that whatever the caller asks for rules

It is perfectly possible for EVP to respect what the caller has asked for, and still be able to operate without failing in the above scenario. The fact that it currently fails is clearly a bug in my eyes.

I would rather think that the fix should be there, rather than making the EVP functionality less precise.

I fail to see what is making EVP less precise here.

If we fix these two things then I think it goes a long way to solving @selvanair’s problems (you would not need to implement keygen, or anything to do with keyexch, or tls capabilities). You probably would still need to handle the keymgmt_operation_name with id = OP_KEYEXCH thing though.

Basically if we fixed those bugs then you could fetch the key as now:

EVP_PKEY *pkey = my_custom_loader(libctx, "provider=ovpn.xkey", uri); /* this loads an opaque pointer as keydata */ 

And then setup SSL_CTX to prefer the default provider over yours:

SSL_CTX *ctx = SSL_CTX_new_ex(libctx, "?provider=default", method);
SSL_CTX_use_certificate(ctx, cert);
SSL_CTX_use_Privatekey(ctx, pkey);

Doing it this way, libssl would always use algorithms from default whenever possible. However the private key you loaded would have a keymgmt from your customer provider, and (with the bugs mentioned above fixed) then, because your provider cannot export, it would only fetch a SIGNATURE algorithm from your provider (overriding the preference in the propquery for the default provider). I think this even works for the verify stuff, i.e. when verifying you would not be using a key from your custom provider and therefore the SIGNATURE algorithm fetched would be from the default provider and therefore you should be able to get away without having to implement the verify code in your provider.

It looks clear to me that in 3.1 we need to be provide a better way of saying - its the same as that algorithm but with these differences, e.g. some kind of “filter” provider.