/// <summary> /// Securely exchanges a secret key /// </summary> /// <param name="stream"></param> /// <param name="ecParams">ECDSA public / private keypair used for signing</param> /// <returns>A tuple containing a 256 bit hashed secret key, and the fingerprint of the remote</returns> /// <exception cref="CryptographicException"></exception> /// <exception cref="InvalidDataException">Thrown when the remote sends invalid data</exception> public static async Task <(byte[], ECPoint)> ExchangeKeyAsync(this WsStream stream, ECParameters ecParams) { if (ecParams.D is null) { throw new CryptographicException("Private key must be provided"); } ECDsa ecDsa = ECDsa.Create(ecParams); // TODO: Harden security (prevent abuse, double check everything) // host authentication var pubBytes = ecDsa.ExportSubjectPublicKeyInfo(); // key exchange var ecdh = ECDiffieHellman.Create(); var kePubBytes = ecdh.ExportSubjectPublicKeyInfo(); // sign ecdh key to authenticate var signed = ecDsa.SignData(kePubBytes, HashAlgorithmName.SHA256); var bw = new AsyncBinaryWriter(stream); var br = new AsyncBinaryReader(stream); //1 await bw.WriteAsync(pubBytes.Length); await bw.WriteAsync(pubBytes); //2 await bw.WriteAsync(signed.Length); await bw.WriteAsync(signed); //3 await bw.WriteAsync(kePubBytes.Length); await bw.WriteAsync(kePubBytes); // read remote public key and verify signature //1 var remotePubKey = ECDsa.Create(); var remotePubBytes = await br.ReadBytesAsync(await br.ReadAssertAsync(120)); remotePubKey.ImportSubjectPublicKeyInfo(remotePubBytes, out _); //2 var remoteSignature = await br.ReadBytesAsync(await br.ReadAssertAsync(96)); //3 var remoteKePub = await br.ReadBytesAsync(await br.ReadAssertAsync(158)); var remoteEcdh = ECDiffieHellman.Create(); remoteEcdh.ImportSubjectPublicKeyInfo(remoteKePub, out _); // verify signed public key exchange key if (!remotePubKey.VerifyData(remoteKePub, remoteSignature, HashAlgorithmName.SHA256)) { throw new CryptographicException("Remote public key does not match hash!"); } // derive shared secret var sharedSecret = ecdh.DeriveKeyMaterial(remoteEcdh.PublicKey); // return the public key (fingerprint) of the remote, and the hashed shared secret return(SHA256.HashData(sharedSecret), remotePubKey.ExportParameters(false).Q); }