Description
Background and motivation
.NET 9 added support for OpenSSL 3.x providers:
This is helpful but I learned the hard way, it has some limitations. A provider might implement decoders/encoders which allows to load/store additional file types. However, the basic file handling and finding appropriate decoders/encoders is done as part of the default provider. The current API only allows to load a single specific provider.
To give a concrete example of what currently cannot be done with the .NET API: The tpm2 provider supports direct TPM keys (via the handle URI prefix) but also TSS2 files (a key file encrypted with an TPM key, useful since a TPM has limit key storage). Details can be found here:
https://github.com/tpm2-software/tpm2-openssl/blob/master/docs/keys.md#loading-a-private-key
However, TSS2 files do not work since this would require loading the default provider along with tpm2.
Thus, I would suggest to provide an overload for the method OpenKeyFromProvider which supports a list of providers and to give full flexibility also one for the propertyQuery. The last one helps OpenSSL to choose the right provider for individual operations.
Implementation would be straight-forward and end up in:
https://github.com/dotnet/runtime/blob/5f97ddfff8074355964e6d13a690c236dd818050/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey.c
The single OSSL_PROVIDER_load can be replaced with a loop of several calls. And OSSL_STORE_open_ex has already the necessary property query parameter (currently a NULL is passed).
I tested this and directly in C# using the appropriate DllImports.
What also speaks for this API extension: OpenSSL engines are deprecated. However, for TPM based TSS key files there is no migration path. With OpenSSL 1.x and the tpm2tss engine one could use SafeEvpPKeyHandle.OpenPrivateKeyFromEngine in .NET. As explained above this is not possible wich a switch to providers at the moment.
API Proposal
namespace System.Security.Cryptography
{
public partial class SafeEvpPKeyHandle
{
// existing OpenSSL Providers API (new plugin model for OSSL 3.0+)
public static SafeEvpPKeyHandle OpenKeyFromProvider(string providerName, string keyUri) => throw null;
// more flexible overload which supports loading multiple providers and a provider query string
public static SafeEvpPKeyHandle OpenKeyFromProvider(IEnumerable<string> providerList, string keyUri) => throw null;
public static SafeEvpPKeyHandle OpenKeyFromProvider(IEnumerable<string> providerList, string keyUri, string propertyQuery) => throw null;
}
}
API Usage
byte[] data = ...;
// For TPM settings refer to provider documentation you're using, i.e. https://github.com/tpm2-software/tpm2-openssl/tree/master
// specific values are just example
string[] providers = { "default", "tpm2" };
const string propQuery = "?provider!=tpm2";
using (SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider(providers, "file:/path/to/key.tss", propQuery))
using (ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle))
{
byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256);
// do stuff with signature
// note: tpm2 does not allow Verify operations, public key needs to be exported and re-imported into new instance
}
Alternative Designs
I currently have not use case that requires loading more than 2 providers and one being the default one. So one option could be to add a boolean flag as a third parameter to the existing API function, stating whether the default provider should be loaded and setting it to false (so that the current behaviour does not change). But why not give full flexibility and avoid further change/extension for future use cases?
Risks
I have designed the new functionality as an extension to the existing one without any breaking change.