We are trying to get a browser based app using JS to exchange keys with a .Net Core 5.0 server using Eliptical Curve Diffie Hellman. Our app requires a shared secret on both ends for some specific processing we do (not encryption, but our own process), and we want that secret to be derived rather than transmitted for security purposes. We have searched around for a suitable solution and patching various ones together, we have come up with this, but it fails with an exception on the C# side. Basically, our pseudo code is as follows (we have only gotten to Step 3 so far):
- Client (Bob) generates a ECDH / P-256 key pair in the client using the window.crypto.subtle library.
- Bob exports the public key from this pair and issues a GET to the server (Alice) to get her public key (passing the bobPublicKeyB64 as a query arg).
- Alice takes the incoming request and calls a C# method to use Bob's public key to create a shared secret.
- Alice stores this shared secret in memcache and returns her public key to Bob.
- Bob then uses Alice's public key to get his own shared secret and stores it in "session storage" on the browser.
The code that sends Bob's public key to Alice is not shown here, it is a standard XMLHttpRequest. It does work, however, and the server is invoked as witnessed by the failures in the C# code. We have verified that the bobPublicKeyB64 is exactly the same between Bob when he sends it and Alice when she gets it.
Once this is working, we will harden the storage methodology on both ends, but first we need to get the exchange working. The comments in the Alice (C#) block of code show where it fails. the getDerivedKey method (commented out for now) came from this post - ECDH nodejs and C# key exchange and it fails with a different exception (I am sure that both failures are due to some mismatch between the JS library and the .Net implementation, but we cannot get a handle on it). Any and all help is greatly appreciated - the basic question is "How can we get Bob and Alice on speaking terms, when Bob is JS and Alice is C#?"
Following is the JS code on Bob:
async function setUpDFHKeys() {
let bobPublicKeyB64;
let bobPrivateKeyB64;
try {
const bobKey = await window.crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
true,
["deriveKey"]
);
const publicKeyData = await window.crypto.subtle.exportKey("raw", bobKey.publicKey);
const publicKeyBytes = new Uint8Array(publicKeyData);
const publicKeyB64 = btoa(publicKeyBytes);
bobPublicKeyB64 = publicKeyB64;
const privateKeyData = await window.crypto.subtle.exportKey("pkcs8", bobKey.privateKey);
const privateKeyBytes = new Uint8Array(privateKeyData);
const privateKeyB64 = btoa(String.fromCharCode.apply(null, privateKeyBytes));
bobPrivateKeyB64 = privateKeyB64;
}
catch (error) {
console.log("Could not setup DFH Keys " + error);
}};
Following is the C# code on Alice:
private string WorkWithJSPublicKey(string bobPublicKeyB64, out string alicePublicKey)
{
try
{
//
// Alice is this server.
// Bob is a browser that uses the window.crypto.subtle.generateKey with 'ECDH' and 'P-256' as parameters
// and window.crypto.subtle.exportKey of the key.publicKey in "raw" format (this is then converted to a B64 string using btoa)
//
using (ECDiffieHellman alice = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256))
{
//
// Get the public key info from Bob and convert to B64 string to return to Alice
//
Span<byte> exported = new byte[alice.KeySize];
int len = 0;
alice.TryExportSubjectPublicKeyInfo(exported, out len);
alicePublicKey = Convert.ToBase64String(exported.Slice(0, len));
//
// Get Alice's private key to use to generate a shared secret
//
byte[] alicePrivateKey = alice.ExportECPrivateKey();
//
// Import Bob's public key after converting it to bytes
//
var bobPubKeyBytes = Convert.FromBase64String(bobPublicKeyB64);
//
// TRY THIS... (Bombs with "The specified curve 'nistP256' or its parameters are not valid for this platform").
//
// getDerivedKey(bobPubKeyBytes, alice);
//
// This throws exception ("The provided data is tagged with 'Universal' class value '20', but it should have been 'Universal' class value '16'.")
//
alice.ImportSubjectPublicKeyInfo(bobPubKeyBytes, out len);
//
// Once Alice knows about Bob, create a shared secret and return it.
//
byte[] sharedSecret = alice.DeriveKeyMaterial(alice.PublicKey);
return Convert.ToBase64String(sharedSecret);
}
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
alicePublicKey = string.Empty;
return string.Empty;
}
}
And the code for the getDerivedKey (borrowed from ECDH nodejs and C# key exchange ) is shown below:
static byte[] getDerivedKey(byte[] key1, ECDiffieHellman alice)
{
byte[] keyX = new byte[key1.Length / 2];
byte[] keyY = new byte[keyX.Length];
Buffer.BlockCopy(key1, 1, keyX, 0, keyX.Length);
Buffer.BlockCopy(key1, 1 + keyX.Length, keyY, 0, keyY.Length);
ECParameters parameters = new ECParameters
{
Curve = ECCurve.NamedCurves.nistP256,
Q = {
X = keyX,
Y = keyY,
},
};
byte[] derivedKey;
using (ECDiffieHellman bob = ECDiffieHellman.Create(parameters))
using (ECDiffieHellmanPublicKey bobPublic = bob.PublicKey)
{
return derivedKey = alice.DeriveKeyFromHash(bobPublic, HashAlgorithmName.SHA256);
}
}