/// <summary>
        /// Query the server to get the device pair state
        /// </summary>
        /// <returns>True if device is already paired, false if not, null if failure</returns>
        public async Task<bool?> QueryPairState()
        {
            XmlQuery pairState;

            pairState = new XmlQuery(nv.BaseUrl + "/serverinfo?uniqueid=" + nv.GetUniqueId());

            string statusCode = await pairState.ReadXmlRootAttribute("status_code");
            if (statusCode == null)
            {
                return null;
            }

            // Status code 401 means we're not paired
            if (Convert.ToInt32(statusCode) == 401)
            {
                return false;
            }

            string pairStatus = await pairState.ReadXmlElement("PairStatus");
            if (pairStatus == null)
            {
                // Request failed
                return null;
            }

            // Check if the device is paired by checking the XML attribute within the <paired> tag
            if (String.Compare(pairStatus, "1") != 0)
            {
                Debug.WriteLine("Not paired");
                return false;
            }

            // We're already paired if we get here!
            return true;
        }
        /// <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;
            }
        }
Beispiel #3
0
        private static async Task<X509Certificate> ExtractPlainCert(XmlQuery q, String tag)
        {
            String certHexString = await q.ReadXmlElement(tag);
            byte[] certBytes = PairingCryptoHelpers.HexToBytes(certHexString);
            String certText = Encoding.UTF8.GetString(certBytes, 0, certBytes.Length);

            PemReader certReader = new PemReader(new StringReader(certText));
            return (X509Certificate)certReader.ReadObject();
        }
Beispiel #4
0
        /// <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");
        }
Beispiel #5
0
        /// <summary>
        /// Query the server to get the device pair state
        /// </summary>
        /// <returns>True if device is already paired, false if not, null if failure</returns>
        public async Task <bool?> QueryPairState()
        {
            XmlQuery pairState;

            pairState = new XmlQuery(nv.BaseUrl + "/serverinfo?uniqueid=" + nv.GetUniqueId());

            string statusCode = await pairState.ReadXmlRootAttribute("status_code");

            if (statusCode == null)
            {
                return(null);
            }

            // Status code 401 means we're not paired
            if (Convert.ToInt32(statusCode) == 401)
            {
                return(false);
            }

            string pairStatus = await pairState.ReadXmlElement("PairStatus");

            if (pairStatus == null)
            {
                // Request failed
                return(null);
            }

            // Check if the device is paired by checking the XML attribute within the <paired> tag
            if (String.Compare(pairStatus, "1") != 0)
            {
                Debug.WriteLine("Not paired");
                return(false);
            }

            // We're already paired if we get here!
            return(true);
        }
Beispiel #6
0
 /// <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();
 }
Beispiel #7
0
        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>
        /// 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");
        }
        /// <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);
            }
        }