-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ECDiffieHellmanCng derivekey methods fail if the private key isn't a MicrosoftSoftwareKeyStorageProvider key #71009
Comments
@blowdart can you please move this to the right area / repo? Thanks! |
Tagging subscribers to this area: @dotnet/area-system-security, @vcsjones Issue DetailsIn the latest dotnet, the
|
This is right :) |
Do you mean this is a regression in behavior from a previous version of .NET? Did this used to work in .NET Core 3.1 or .NET 5? Do you have a quick example program that you can show to demonstrate the behavior, assuming |
I have only tested this with the latest .NET. I don't know if the same issue exists in older versions but I expect it will. I should be able to get you a minimal test case shortly. |
Here's a very simple piece of example code. If the provider is changed, this code will fail. It generates a new ECDH private key and uses a default public key. |
@MattR-entrust Can you run your sample program and include the exception that you get (remove the |
The supplied handle is invalid. |
The actual error is bubbling up from the native provider dll so the precise behaviour might vary depending on the provider being used. StackTrace " at System.Security.Cryptography.NCryptNative.UnsafeNativeMethods.NCryptSecretAgreement(SafeNCryptKeyHandle hPrivKey, SafeNCryptKeyHandle hPubKey, SafeNCryptSecretHandle& phSecret, Int32 dwFlags) This was a 3rd party bug being triggered by the .NET bug |
Thanks for the repro and the stack trace. I can reproduce it, and I think I have a work around that will work for now. Using your example program, if you replace ecdhkey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
ecdhkey.HashAlgorithm = CngAlgorithm.Sha256;
ecdhkey.SecretPrepend = prepend.ToString().HexToByteArray();
ecdhkey.SecretAppend = append.ToString().HexToByteArray();
key = ecdhkey.DeriveKeyMaterial(cngpub); Does that work with your CNG Provider? |
Yes, that works for me. Thanks. |
On Windows, yes. Not on other platforms. Setting properties on an instance of ECDiffieHellmanCng was the original design of the ECDiffieHellman class, despite the fact that it made the base class and the only implementing class have unclear semantics of what |
Thanks again. That covers everything I need at the moment. |
What's the action here, @vcsjones? Change our simple import+call into doing a CngKey import into the same provider as Lines 28 to 39 in 607aa00
There'd then be an argument that we should check the provider when And, of course, if that provider fails to import the blob (such as not allowing ephemeral/public-only keys) then I guess we can fall back to the default KSP and assume that the provider understands cross-KSP keys. |
@bartonjs I was probably thinking that Something like: CngKey importedKey;
try
{
importedKey = otherKey.Import(Key.Provider);
}
catch (CryptographicException)
{
importedKey = otherKey.Import();
} I haven't spiked anything out though. Since there is a reasonable work around I was not going to push to get this fixed for .NET 7. |
@bartonjs the thing that I am most curious about your input is how, if at all, we can test this. I can test it on my machine with a different CNG provider; but to my understanding, CI only has the Software KSP so we can't test this easily and prevent it from regressing. |
The platform crypto provider doesn't seem to like creating ephemeral private keys, and the smart card provider seems to "make" them but they don't work, so I'm assuming your testing thus far has involved named keys. My thoughts then are, in the CNG test library [ConditionalFact]
public static void EcdhWithPlatformProvider()
{
CngAlgorithm alg = CngAlgorithm.ECDiffieHellmanP256;
CngKeyCreationParameters creationParams = new CngKeyCreationParameters
{
Provider = CngProvider.MicrosoftPlatformCryptoProvider,
KeyCreationOptions = CngKeyCreationOptions.OverwriteExistingKey,
};
const string AliceKeyName = "__dotnetTestECDH-A";
const string BobKeyName = "__dotnetTestECDH-B";
CngKey aliceKey;
CngKey bobKey = null;
try
{
aliceKey = CngKey.Create(alg, AliceKeyName, creationParams);
}
catch (CryptographicException)
{
throw new SkipException("Could not create the Alice key in the platform KSP");
}
try
{
try
{
bobKey = CngKey.Create(alg, BobKeyName, creationParams);
}
catch (CryptographicException)
{
throw new SkipException("Could not create the Alice key in the platform KSP");
}
using (ECDiffieHellmanCng alice = new ECDiffieHellmanCng(aliceKey))
using (ECDiffieHellmanCng bob = new ECDiffieHellmanCng(bobKey))
using (ECDiffieHellman charlie = new ECDiffieHellmanCng(ECCurve.NamedCurves.nistP256))
using (ECDiffieHellman dave = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256))
{
byte[] ab = alice.DeriveKeyFromHash(bob.PublicKey, HashAlgorithmName.SHA384);
byte[] ba = bob.DeriveKeyFromHash(alice.PublicKey, HashAlgorithmName.SHA384);
AssertExtensions.SequenceEqual(ab, ba);
byte[] ac = alice.DeriveKeyFromHash(charlie.PublicKey, HashAlgorithmName.SHA384);
byte[] ca = charlie.DeriveKeyFromHash(alice.PublicKey, HashAlgorithmName.SHA384);
AssertExtensions.SequenceEqual(ac, ca);
byte[] bd = bob.DeriveKeyFromHash(dave.PublicKey, HashAlgorithmName.SHA384);
byte[] db = dave.DeriveKeyFromHash(bob.PublicKey, HashAlgorithmName.SHA384);
AssertExtensions.SequenceEqual(bd, db);
}
}
finally
{
aliceKey.Delete();
bobKey?.Delete();
}
}
|
Is this issue resolved? I am getting the error message "The supplied handle is invalid." with the following stack trace when I use HSM as a provider. It is working fine when I used default Microsoft provider. Here is the code:
var derivedSecret = eccCurve.DeriveKeyMaterial(remoteEcDiffHellmanPublicKey);
var derivedSecret = eccCurve.DeriveKeyFromHmac(remoteEcDiffHellmanPublicKey, HashAlgorithmName.SHA256, Stack Trace: |
@tegobena no, the issue is still open. A few pull requests have been made to work toward fixing using ECC with ECDiffieHellman, but a few more remain.
Are you using the overload that accepts a |
@vcsjones Thank you for your quick response! The error is showing up in both cases. I don't have the remote key created in HSM, so I am using the default Microsoft provider for that for the public key. |
The public key and the private key need to be in the same provider. This is not a limitation of .NET, but rather from CNG. From the
So you can't derive a secret key with two different storage providers. You should be able to export the public key and create an ephemeral static CngKey CopyToStorageProvider(CngKey key, CngProvider targetProvider)
{
byte[] publicBlob = key.Export(CngKeyBlobFormat.EccPublicBlob);
return CngKey.Import(publicBlob, CngKeyBlobFormat.EccPublicBlob, targetProvider);
} |
And the issue is that the .NET implementation forces the public key into the MS provider regardless of which provider it is originally in so when the public key handle is passed down into the private key's provider, there are failures unless the private key is held in the MS provider.
Matt
…________________________________
From: Kevin Jones ***@***.***>
Sent: 15 November 2022 16:29
To: dotnet/runtime ***@***.***>
Cc: Matthew D. Reid ***@***.***>; Mention ***@***.***>
Subject: [EXTERNAL] Re: [dotnet/runtime] ECDiffieHellmanCng derivekey methods fail if the private key isn't a MicrosoftSoftwareKeyStorageProvider key (Issue #71009)
WARNING: This email originated outside of Entrust.
DO NOT CLICK links or attachments unless you trust the sender and know the content is safe.
________________________________
I don't have the remote key created in HSM, so I am using the default Microsoft provider for that for the public key.
The public key and the private key need to be in the same provider. This is not a limitation of .NET, but rather from CNG. From the NCryptSecretAgreement docs<https://urldefense.com/v3/__https://learn.microsoft.com/en-us/windows/win32/api/ncrypt/nf-ncrypt-ncryptsecretagreement__;!!FJ-Y8qCqXTj2!ZAEDeMvCKeXrE5bNRfjUczl6IdYBfEac_gQzbks-0ryzv-IwqUZJ3c7vBvnW3KMUCsYlgGL8sLeni7rIs-9WQ7y1V2z9$>:
This key and the hPubKey key must come from the same key storage provider.
This key and the hPrivKey key must come from the same key storage provider.
So you can't derive a secret key with two different storage providers.
You should be able to export the public key and create an ephemeral CngKey with it in the HSM's provider. Maybe something like this:
static CngKey CopyToStorageProvider(CngKey key, CngProvider targetProvider)
{
byte[] publicBlob = key.Export(CngKeyBlobFormat.EccPublicBlob);
return CngKey.Import(publicBlob, CngKeyBlobFormat.EccPublicBlob, targetProvider);
}
—
Reply to this email directly, view it on GitHub<https://urldefense.com/v3/__https://github.com/dotnet/runtime/issues/71009*issuecomment-1315563107__;Iw!!FJ-Y8qCqXTj2!ZAEDeMvCKeXrE5bNRfjUczl6IdYBfEac_gQzbks-0ryzv-IwqUZJ3c7vBvnW3KMUCsYlgGL8sLeni7rIs-9WQ8GvZZhT$>, or unsubscribe<https://urldefense.com/v3/__https://github.com/notifications/unsubscribe-auth/AY2AWRSJ2G2WWXYJVEKVZTLWIO25FANCNFSM5ZJPT3SQ__;!!FJ-Y8qCqXTj2!ZAEDeMvCKeXrE5bNRfjUczl6IdYBfEac_gQzbks-0ryzv-IwqUZJ3c7vBvnW3KMUCsYlgGL8sLeni7rIs-9WQ8n0m77Y$>.
You are receiving this because you were mentioned.Message ID: ***@***.***>
Any email and files/attachments transmitted with it are confidential and are intended solely for the use of the individual or entity to whom they are addressed. If this message has been sent to you in error, you must not copy, distribute or disclose of the information it contains. Please notify Entrust immediately and delete the message from your system.
|
Right, the only way to do that right now is to use This issue is open so when we transport the public key we will do it for the right provider. |
Thank you so much for your help @vcsjones and @MattR-entrust! Using CngKey.Import as you motioned above I don't see the error related to "The supplied key is invalid". It works fine when I use Hash Derivative Function. I am seeing another error when I use HMAC. It looks when I add HmacKey it doesn't set the SecretAgreementFlags correctly. Do you know if there is any work around related to this? Code modified: var cngKey = CngKey.Open("MyKeyName", hsmProvider); eccCurve.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hmac; Error Message: "Invalid flags specified." |
I can't reproduce that behavior using the Microsoft Software provider.
Is it possible that your CNG provider does not support support the |
Thank you @vcsjones! As you mentioned above HMAC derivation is not supported by the provider. |
I am having a similar but different issue. I use CngKey.Import() to create the public imported CngKey with a valid HSM provider handle. I had to create a ECC_PUBLIC_BLOB to import the data. In the examples above, that blob creation stuff handles inside the .Net code. Inside of the Import method, there are 2 native NCrypt API calls. The first is to NCryptImportKey() and that is succeeding. The second is to NCryptSetProperty() where the "CLR IsEphemeral" property is set to 1. |
This sounds similar to the issue described in #71310. |
In the latest dotnet, the
ECDiffieHellmanCng
DeriveKeyFromHash
andDeriveKeyMaterial
methods will fail if the ECDH key isn't aMicrosoftSoftwareKeyStorageProvider
key. This is because these methods take the public key from any KSP provider and convert them to aMicrosoftSoftwareKeyStorageProvider
public key before sending them to the native dll from the provider of the private key. This breaks the contract of NCryptSecretAgreement unless the private key provider is alsoMicrosoftSoftwareKeyStorageProvider
.This issue came up when attempting to use a Hardware Security Module (HSM) protected non-exportable private ECDH key. The only work-around I found is to make an unsafe call to
NCryptSecretAgreement
etc. in my own code, by-passing these CNG methods.I think that the code fix would be to ensure that the CNG wrapper functions import the public key into the private key's KSP provider before calling down to the ncrypt dll.
The text was updated successfully, but these errors were encountered: