public async Task QueryDataInsecure() { ServerInfo = await NvHttp.ServerInfo(); Online = true; InitializeSecureClient(); }
public NvStreamDevice(IPAddress ipAddress, CryptoProvider cryptoProvider) { IPAddress = ipAddress; CryptoProvider = cryptoProvider; NvHttp = new NvHttp(new Uri($"http://{IPAddress}:{HTTP_PORT}/")); Online = false; }
/// <summary> /// Create start HTTP request /// </summary> private async Task<bool> StartOrResumeApp(NvHttp nv, MoonlightStreamConfiguration streamConfig) { XmlQuery serverInfo = new XmlQuery(nv.BaseUrl + "/serverinfo?uniqueid=" + nv.GetUniqueId()); string currentGameString = await serverInfo.ReadXmlElement("currentgame"); if (currentGameString == null) { return false; } string versionString = await serverInfo.ReadXmlElement("appversion"); if (versionString == null) { return false; } serverMajorVersion = Convert.ToInt32(versionString.Substring(0, 1)); byte[] aesIv = streamConfig.GetRiAesIv(); int riKeyId = (int)(((aesIv[0] << 24) & 0xFF000000U) | ((aesIv[1] << 16) & 0xFF0000U) | ((aesIv[2] << 8) & 0xFF00U) | (aesIv[3] & 0xFFU)); string riConfigString = "&rikey=" + PairingCryptoHelpers.BytesToHex(streamConfig.GetRiAesKey()) + "&rikeyid=" + riKeyId; // Launch a new game if nothing is running if (currentGameString == null || currentGameString.Equals("0")) { XmlQuery x = new XmlQuery(nv.BaseUrl + "/launch?uniqueid=" + nv.GetUniqueId() + "&appid=" + context.appId + "&mode=" + streamConfig.GetWidth() + "x" + streamConfig.GetHeight() + "x" + streamConfig.GetFps() + "&additionalStates=1&sops=1" + // FIXME: make sops configurable riConfigString); string sessionStr = await x.ReadXmlElement("gamesession"); if (sessionStr == null || sessionStr.Equals("0")) { return false; } return true; } else { // A game was already running, so resume it // FIXME: Quit and relaunch if it's not the game we came to start XmlQuery x = new XmlQuery(nv.BaseUrl + "/resume?uniqueid=" + nv.GetUniqueId() + riConfigString); string resumeStr = await x.ReadXmlElement("resume"); if (resumeStr == null || resumeStr.Equals("0")) { return false; } return true; } }
public async Task <bool> Pair() { // Generate salt for hashing the pin byte[] salt = CryptoProvider.GenerateRandomBytes(16); // Combine sal and pin and generate aes key from them string pin = CryptoProvider.GeneratePin(); byte[] saltedPin = CryptoProvider.SaltPin(salt, pin); KeyParameter aesKey = CryptoProvider.GenerateAesKey(EnhancedSecurity, saltedPin); // Send the salt and get server cert. This doesn't have read timeout // because the user must enter the PIN before the server responds NvPair getServerCertResponse = await NvHttp.GetServerCert(salt, CryptoProvider.GetCertificatePem()); }
/// <summary> /// Quit Game Event Handler /// </summary> private async Task QuitGame_Common() { Computer selected = (Computer)computerPicker.SelectedItem; // User hasn't selected anything or selected the placeholder if (selected == null || selected.IpAddress == null) { DialogUtils.DisplayDialog(this.Dispatcher, "No machine selected", "Quit Failed"); return; } PairingManager p = new PairingManager(selected); if (await p.QueryPairState() != true) { DialogUtils.DisplayDialog(this.Dispatcher, "Device not paired", "Quit Failed"); return; } try { NvHttp nv = new NvHttp(selected.IpAddress); XmlQuery quit = new XmlQuery(nv.BaseUrl + "/cancel?uniqueid=" + nv.GetUniqueId()); string cancelled = await quit.ReadXmlElement("cancel"); if (cancelled == "1") { DialogUtils.DisplayDialog(this.Dispatcher, "Successfully Quit Game", "Quit Game"); return; } } catch (Exception ex) { Debug.WriteLine(ex.Message); } DialogUtils.DisplayDialog(this.Dispatcher, "Unable to quit", "Quit Game"); }
private void InitializeSecureClient() { SecureNvHttp = new NvHttp(new Uri($"https://{IPAddress}:{ServerInfo.HttpsPort}/"), NvHttp.Uuid); }
/// <summary> /// Constructor that sets nv /// </summary> /// <param name="nv">The NvHttp Object</param> public PairingManager(Computer computer) { this.nv = new NvHttp(computer.IpAddress); }
/// <summary> /// Unpair from the device /// </summary> private static async Task Unpair(NvHttp nv) { XmlQuery unpair; unpair = new XmlQuery(nv.BaseUrl + "/unpair?uniqueid=" + nv.GetUniqueId()); await unpair.Run(); }
public static async Task<bool> PerformPairingHandshake(CoreDispatcher uiDispatcher, WindowsCryptoProvider provider, NvHttp nv, string uniqueId) { string result; // Generate a salt for hashing the PIN byte[] salt = GenerateRandomBytes(16); string pin = new Random().Next(9999).ToString("D4"); // Combine the salt and pin, then create an AES key from them byte[] saltAndPin = SaltPin(salt, pin); CryptographicKey aesKey = GenerateAesKey(saltAndPin); // Send the salt and get the server cert DialogUtils.DisplayDialog(uiDispatcher, "Enter the following PIN on the host PC: " + pin, "Enter PIN"); // User will need to close dialog themselves XmlQuery getServerCert = new XmlQuery(nv.BaseUrl + "/pair?uniqueid=" + uniqueId + "&devicename=roth&updateState=1&phrase=getservercert&salt=" + BytesToHex(salt) + "&clientcert=" + BytesToHex(await provider.GetPemCertBytes())); result = await getServerCert.ReadXmlElement("paired"); if (result == null || !result.Equals("1")) { await Unpair(nv); return false; } X509Certificate serverCert = await ExtractPlainCert(getServerCert, "plaincert"); // Generate a random challenge and encrypt it with our AES key byte[] randomChallenge = GenerateRandomBytes(16); Debug.WriteLine("Client challenge: " + BytesToHex(randomChallenge)); byte[] encryptedChallenge = EncryptAes(randomChallenge, aesKey); // Send the encrypted challenge to the server XmlQuery challengeResp = new XmlQuery(nv.BaseUrl + "/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&clientchallenge="+BytesToHex(encryptedChallenge)); // If we're not paired, there's a problem. result = await challengeResp.ReadXmlElement("paired"); if (result == null || !result.Equals("1")) { await Unpair(nv); return false; } // Decode the server's response and subsequent challenge byte[] encServerChallengeResponse = HexToBytes(await challengeResp.ReadXmlElement("challengeresponse")); byte[] decServerChallengeResponse = DecryptAes(encServerChallengeResponse, aesKey); byte[] serverResponse = new byte[20], serverChallenge = new byte[16]; Array.Copy(decServerChallengeResponse, serverResponse, serverResponse.Length); Array.Copy(decServerChallengeResponse, 20, serverChallenge, 0, serverChallenge.Length); Debug.WriteLine("serverResponse: " + BytesToHex(serverResponse)); Debug.WriteLine("server challenge: " + BytesToHex(serverChallenge)); // Using another 16 bytes secret, compute a challenge response hash using the secret, our cert sig, and the challenge byte[] clientSecret = GenerateRandomBytes(16); Debug.WriteLine("Client secret: " + BytesToHex(clientSecret)); Debug.WriteLine("Client sig: " + BytesToHex((await provider.GetClientCertificate()).GetSignature())); byte[] challengeRespHash = ToSHA1Bytes(concatBytes(concatBytes(serverChallenge, (await provider.GetClientCertificate()).GetSignature()), clientSecret)); Debug.WriteLine("Challenge SHA 1: " + BytesToHex(challengeRespHash)); byte[] challengeRespEncrypted = EncryptAes(challengeRespHash, aesKey); XmlQuery secretResp = new XmlQuery(nv.BaseUrl + "/pair?uniqueid=" + uniqueId + "&devicename=roth&updateState=1&serverchallengeresp=" + BytesToHex(challengeRespEncrypted)); result = await secretResp.ReadXmlElement("paired"); if (result == null || !result.Equals("1")) { await Unpair(nv); return false; } // Get the server's signed secret byte[] serverSecretResp = HexToBytes(await secretResp.ReadXmlElement("pairingsecret")); byte[] serverSecret = new byte[16]; byte[] serverSignature = new byte[256]; Array.Copy(serverSecretResp, 0, serverSecret, 0, 16); Array.Copy(serverSecretResp, 16, serverSignature, 0, 256); // Ensure the authenticity of the data if (!VerifySignature(serverSecret, serverSignature, serverCert)) { // Cancel the pairing process await Unpair(nv); // Looks like a MITM return false; } // Ensure the server challenge matched what we expected (aka the PIN was correct) byte[] serverChallengeRespHash = ToSHA1Bytes(concatBytes(concatBytes(randomChallenge, serverCert.GetSignature()), serverSecret)); if (!serverChallengeRespHash.SequenceEqual(serverResponse)) { // Cancel the pairing process await Unpair(nv); // Probably got the wrong PIN return false; } // Send the server our signed secret byte[] clientPairingSecret = concatBytes(clientSecret, SignData(await provider.GetKeyPair(), clientSecret)); XmlQuery clientSecretResp = new XmlQuery(nv.BaseUrl + "/pair?uniqueid=" + uniqueId + "&devicename=roth&updateState=1&clientpairingsecret=" + BytesToHex(clientPairingSecret)); result = await clientSecretResp.ReadXmlElement("paired"); if (result == null || !result.Equals("1")) { await Unpair(nv); return false; } // Do the initial challenge (seems neccessary for us to show as paired) XmlQuery pairChallenge = new XmlQuery(nv.BaseUrl + "/pair?uniqueid=" + uniqueId + "&devicename=roth&updateState=1&phrase=pairchallenge"); result = await pairChallenge.ReadXmlElement("paired"); if (result == null || !result.Equals("1")) { await Unpair(nv); return false; } return true; }
/// <summary> /// Starts the connection by calling into Moonlight Common /// </summary> private async Task StartConnection(MoonlightStreamConfiguration streamConfig) { NvHttp nv = null; await SetStateText("Resolving hostname..."); try { nv = new NvHttp(context.computer.IpAddress); } catch (ArgumentNullException) { stageFailureText = "Error resolving hostname"; ConnectionFailed(); return; } String serverIp = null; try { serverIp = await nv.ResolveServerIPAddress(); } catch (Exception) { stageFailureText = "Error resolving hostname"; ConnectionFailed(); return; } // Set up callbacks MoonlightDecoderRenderer drCallbacks = new MoonlightDecoderRenderer(DrSetup, DrCleanup, DrSubmitDecodeUnit); MoonlightAudioRenderer arCallbacks = new MoonlightAudioRenderer(ArInit, ArCleanup, ArPlaySample); MoonlightConnectionListener clCallbacks = new MoonlightConnectionListener(ClStageStarting, ClStageComplete, ClStageFailed, ClConnectionStarted, ClConnectionTerminated, ClDisplayMessage, ClDisplayTransientMessage); // Launch Steam await SetStateText("Launching Steam"); if (await StartOrResumeApp(nv, streamConfig) == false) { Debug.WriteLine("Can't find app"); stageFailureText = "Error launching App"; ConnectionFailed(); return; } // Call into Common to start the connection Debug.WriteLine("Starting connection"); MoonlightCommonRuntimeComponent.StartConnection(serverIp, streamConfig, clCallbacks, drCallbacks, arCallbacks, serverMajorVersion); if (stageFailureText != null) { Debug.WriteLine("Stage failed"); ConnectionFailed(); return; } else { ConnectionSuccess(); } }
/// <summary> /// Create start HTTP request /// </summary> private async Task <bool> StartOrResumeApp(NvHttp nv, MoonlightStreamConfiguration streamConfig) { XmlQuery serverInfo = new XmlQuery(nv.BaseUrl + "/serverinfo?uniqueid=" + nv.GetUniqueId()); string currentGameString = await serverInfo.ReadXmlElement("currentgame"); if (currentGameString == null) { return(false); } string versionString = await serverInfo.ReadXmlElement("appversion"); if (versionString == null) { return(false); } serverMajorVersion = Convert.ToInt32(versionString.Substring(0, 1)); byte[] aesIv = streamConfig.GetRiAesIv(); int riKeyId = (int)(((aesIv[0] << 24) & 0xFF000000U) | ((aesIv[1] << 16) & 0xFF0000U) | ((aesIv[2] << 8) & 0xFF00U) | (aesIv[3] & 0xFFU)); string riConfigString = "&rikey=" + PairingCryptoHelpers.BytesToHex(streamConfig.GetRiAesKey()) + "&rikeyid=" + riKeyId; // Launch a new game if nothing is running if (currentGameString == null || currentGameString.Equals("0")) { XmlQuery x = new XmlQuery(nv.BaseUrl + "/launch?uniqueid=" + nv.GetUniqueId() + "&appid=" + context.appId + "&mode=" + streamConfig.GetWidth() + "x" + streamConfig.GetHeight() + "x" + streamConfig.GetFps() + "&additionalStates=1&sops=1" + // FIXME: make sops configurable riConfigString); string sessionStr = await x.ReadXmlElement("gamesession"); if (sessionStr == null || sessionStr.Equals("0")) { return(false); } return(true); } else { // A game was already running, so resume it // FIXME: Quit and relaunch if it's not the game we came to start XmlQuery x = new XmlQuery(nv.BaseUrl + "/resume?uniqueid=" + nv.GetUniqueId() + riConfigString); string resumeStr = await x.ReadXmlElement("resume"); if (resumeStr == null || resumeStr.Equals("0")) { return(false); } return(true); } }