public override async Task PairAsync(string ipAddress, TextBox outputTextBox)
        {
            // Create SHA256 hash digest. This is not supported by server version < 7
            // (need to use SHA1 for those cases) but that doesn't really matter right now.
            IDigest hashAlgorithm  = new Sha256Digest();
            int     hashDigestSize = hashAlgorithm.GetDigestSize();

            // Create and salt pin
            byte[] salt = this.GenerateRandomBytes(16);
            string pin  = GenerateRandomPin();

            byte[] saltAndPin = SaltPin(salt, pin);

            // Asymmetric key pair
            RsaKeyPairGenerator keyPairGenerator = new RsaKeyPairGenerator();

            keyPairGenerator.Init(new KeyGenerationParameters(this.SecureRandom, 2048));
            AsymmetricCipherKeyPair keyPair = keyPairGenerator.GenerateKeyPair();

            // Certificate issuer and name
            X509Name name = new X509Name("CN=NVIDIA GameStream Client");

            // Certificate serial number
            byte[]     serialBytes = this.GenerateRandomBytes(8);
            BigInteger serial      = new BigInteger(serialBytes).Abs();

            // Expires in 20 years
            DateTime now        = DateTime.UtcNow;
            DateTime expiration = now.AddYears(20);

            X509V3CertificateGenerator generator = new X509V3CertificateGenerator();

            generator.SetSubjectDN(name);
            generator.SetIssuerDN(name);
            generator.SetSerialNumber(serial);
            generator.SetNotBefore(now);
            generator.SetNotAfter(expiration);
            generator.SetPublicKey(keyPair.Public);

            BouncyCastleX509Certificate certificate =
                generator.Generate(
                    new Asn1SignatureFactory("SHA1WithRSA", keyPair.Private));

            // Create PKCS12 certificate bytes.
            Pkcs12Store          store            = new Pkcs12Store();
            X509CertificateEntry certificateEntry = new X509CertificateEntry(certificate);
            string friendlyName = "Moonlight Xbox";
            string password     = "******";

            store.SetCertificateEntry(friendlyName, certificateEntry);
            store.SetKeyEntry(
                friendlyName,
                new AsymmetricKeyEntry(keyPair.Private),
                new X509CertificateEntry[] { certificateEntry });
            string pfxData;

            using (MemoryStream memoryStream = new MemoryStream(512))
            {
                store.Save(memoryStream, password.ToCharArray(), this.SecureRandom);
                pfxData = CryptographicBuffer.EncodeToBase64String(memoryStream.ToArray().AsBuffer());
            }

            await CertificateEnrollmentManager.ImportPfxDataAsync(
                pfxData,
                password,
                ExportOption.NotExportable,
                KeyProtectionLevel.NoConsent,
                InstallOptions.DeleteExpired,
                friendlyName);

            // Read the UWP cert from the cert store
            Certificate uwpCertificate =
                (await CertificateStores.FindAllAsync(
                     new CertificateQuery {
                FriendlyName = friendlyName
            }))[0];

            string keyString;

            using (StringWriter keyWriter = new StringWriter())
            {
                PemWriter pemWriter = new PemWriter(keyWriter);
                pemWriter.WriteObject(keyPair);
                keyString = keyWriter.ToString();

                // Line endings must be UNIX style for GFE to accept the certificate.
                keyString = keyString.Replace(Environment.NewLine, "\n");
            }

            string certString;

            using (StringWriter certWriter = new StringWriter())
            {
                PemWriter pemWriter = new PemWriter(certWriter);
                pemWriter.WriteObject(certificate);
                certString = certWriter.ToString();

                // Line endings must be UNIX style for GFE to accept the certificate.
                certString = certString.Replace(Environment.NewLine, "\n");
            }

            byte[] pemCertBytes = Encoding.UTF8.GetBytes(certString);
            byte[] uniqueId     = GenerateRandomBytes(8);

            // Create the HTTP client.
            HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter();

            filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
            filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.InvalidName);
            filter.ClientCertificate = uwpCertificate;

            HttpClient httpClient = new HttpClient(filter);

            // Unpair before doing anything else in this test app.
            string uriString =
                string.Format(
                    "http://{0}:47989/unpair?uniqueid={1}&uuid={2}",
                    ipAddress,
                    BytesToHex(uniqueId),
                    Guid.NewGuid().ToString("N"));

            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, new Uri(uriString)))
            {
                using (HttpResponseMessage response = await httpClient.SendRequestAsync(request))
                {
                    outputTextBox.Text = $"Unpair status code: {response.StatusCode}\n";
                    string responseContent = await response.Content.ReadAsStringAsync();

                    outputTextBox.Text += responseContent + "\n";
                }
            }

            await Task.Delay(2000);

            outputTextBox.Text = $"Enter pin: {pin}";

            // Get server certificate.
            // TODO: Call should have no timeout because it requires the user to enter a pin.
            PairResponse pairResponse = null;

            uriString =
                string.Format(
                    "http://{0}:47989/pair?uniqueid={1}&uuid={2}&devicename=roth&updateState=1&phrase=getservercert&salt={3}&clientcert={4}",
                    ipAddress,
                    BytesToHex(uniqueId),
                    Guid.NewGuid().ToString("N"),
                    BytesToHex(salt),
                    BytesToHex(pemCertBytes));
            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, new Uri(uriString)))
            {
                using (HttpResponseMessage response = await httpClient.SendRequestAsync(request))
                {
                    outputTextBox.Text = $"Get server cert status code: {response.StatusCode}\n";
                    string responseContent = await response.Content.ReadAsStringAsync();

                    outputTextBox.Text += responseContent + "\n";
                    using (StringReader reader = new StringReader(responseContent))
                    {
                        XmlSerializer serializer = new XmlSerializer(typeof(PairResponse));
                        pairResponse = serializer.Deserialize(new StringReader(responseContent)) as PairResponse;
                    }
                }
            }

            if (pairResponse == null || pairResponse.Paired != 1)
            {
                outputTextBox.Text += "Pairing failed.\n";
                return;
            }

            if (string.IsNullOrEmpty(pairResponse.PlainCert))
            {
                outputTextBox.Text += "Pairing already in progress.\n";
                return;
            }

            // Parse server certificate
            byte[] serverCertBytes = HexToBytes(pairResponse.PlainCert);
            BouncyCastleX509Certificate serverCertificate = new X509CertificateParser().ReadCertificate(serverCertBytes);

            // Hash the salt and pin and use it to generate an AES key.
            byte[]            hashedSaltAndPin = HashData(hashAlgorithm, saltAndPin);
            ICipherParameters aesKey           = GenerateCipherKey(hashedSaltAndPin);

            // Generate a random challenge and encrypt it using AES.
            byte[] challenge          = GenerateRandomBytes(16);
            byte[] encryptedChallenge = DoAesCipher(true, aesKey, challenge);

            await Task.Delay(2000);

            // Send the encrypted challenge to the server.
            // TODO: Call should have a timeout.
            uriString =
                string.Format(
                    "http://{0}:47989/pair?uniqueid={1}&uuid={2}&devicename=roth&updateState=1&clientchallenge={3}",
                    ipAddress,
                    BytesToHex(uniqueId),
                    Guid.NewGuid().ToString("N"),
                    BytesToHex(encryptedChallenge));
            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, new Uri(uriString)))
            {
                using (HttpResponseMessage response = await httpClient.SendRequestAsync(request))
                {
                    outputTextBox.Text = $"Send challenge status code: {response.StatusCode}\n";
                    string responseContent = await response.Content.ReadAsStringAsync();

                    outputTextBox.Text += responseContent + "\n";
                    using (StringReader reader = new StringReader(responseContent))
                    {
                        XmlSerializer serializer = new XmlSerializer(typeof(PairResponse));
                        pairResponse = serializer.Deserialize(new StringReader(responseContent)) as PairResponse;
                    }
                }
            }

            if (pairResponse == null || pairResponse.Paired != 1)
            {
                outputTextBox.Text += "Pairing failed.\n";
                return;
            }

            // Decode the server's response and subsequent challenge.
            byte[] encryptedServerChallengeResponse = HexToBytes(pairResponse.ChallengeResponse);
            byte[] decryptedServerChallengeResponse = DoAesCipher(false, aesKey, encryptedServerChallengeResponse);

            byte[] serverResponse  = new byte[hashDigestSize];
            byte[] serverChallenge = new byte[16];
            Array.Copy(decryptedServerChallengeResponse, serverResponse, hashDigestSize);
            Array.Copy(decryptedServerChallengeResponse, hashDigestSize, serverChallenge, 0, 16);

            // Using another 16 byte secret, compute a challenge response hash using the secret,
            // our certificate signature, and the challenge.
            byte[] clientSecret          = GenerateRandomBytes(16);
            byte[] challengeResponseHash =
                HashData(
                    hashAlgorithm,
                    ConcatenateByteArrays(serverChallenge, certificate.GetSignature(), clientSecret));
            byte[] encryptedChallengeResponse = DoAesCipher(true, aesKey, challengeResponseHash);

            await Task.Delay(2000);

            // Send the challenge response to the server.
            // TODO: Call should have a timeout.
            uriString =
                string.Format(
                    "http://{0}:47989/pair?uniqueid={1}&uuid={2}&devicename=roth&updateState=1&serverchallengeresp={3}",
                    ipAddress,
                    BytesToHex(uniqueId),
                    Guid.NewGuid().ToString("N"),
                    BytesToHex(encryptedChallengeResponse));
            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, new Uri(uriString)))
            {
                using (HttpResponseMessage response = await httpClient.SendRequestAsync(request))
                {
                    outputTextBox.Text = $"Send challenge response status code: {response.StatusCode}\n";
                    string responseContent = await response.Content.ReadAsStringAsync();

                    outputTextBox.Text += responseContent + "\n";
                    using (StringReader reader = new StringReader(responseContent))
                    {
                        XmlSerializer serializer = new XmlSerializer(typeof(PairResponse));
                        pairResponse = serializer.Deserialize(new StringReader(responseContent)) as PairResponse;
                    }
                }
            }

            if (pairResponse == null || pairResponse.Paired != 1)
            {
                outputTextBox.Text += "Pairing failed.\n";
                // TODO: Unpair here by calling http://<blah>/unpair?uniqueid={1}&uuid={2}.
                return;
            }

            // Get the server's signed secret.
            byte[] serverSecretResponse = HexToBytes(pairResponse.PairingSecret);
            byte[] serverSecret         = new byte[16];
            byte[] serverSignature      = new byte[256];
            Array.Copy(serverSecretResponse, serverSecret, serverSecret.Length);
            Array.Copy(serverSecretResponse, serverSecret.Length, serverSignature, 0, serverSignature.Length);

            if (!VerifySignature(serverSecret, serverSignature, serverCertificate.GetPublicKey()))
            {
                outputTextBox.Text += "Pairing failed.\n";
                // TODO: Unpair as above.
                return;
            }

            // Ensure the server challenge matched what we expected (the PIN was correct).
            byte[] serverChallengeResponseHash =
                HashData(
                    hashAlgorithm,
                    ConcatenateByteArrays(
                        challenge,
                        serverCertificate.GetSignature(),
                        serverSecret));
            if (!serverChallengeResponseHash.SequenceEqual(serverResponse))
            {
                outputTextBox.Text += "Pairing failed due to wrong pin.\n";
                // TODO: Unpair as above.
                return;
            }

            await Task.Delay(2000);

            // Send the server our signed secret
            // TODO: Call should have a timeout.
            byte[] signedSecret        = SignData(clientSecret, keyPair.Private);
            byte[] clientPairingSecret =
                ConcatenateByteArrays(
                    clientSecret,
                    signedSecret);
            uriString =
                string.Format(
                    "http://{0}:47989/pair?uniqueid={1}&uuid={2}&devicename=roth&updateState=1&clientpairingsecret={3}",
                    ipAddress,
                    BytesToHex(uniqueId),
                    Guid.NewGuid().ToString("N"),
                    BytesToHex(clientPairingSecret));
            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, new Uri(uriString)))
            {
                using (HttpResponseMessage response = await httpClient.SendRequestAsync(request))
                {
                    outputTextBox.Text = $"Send client pairing secret status code: {response.StatusCode}\n";
                    string responseContent = await response.Content.ReadAsStringAsync();

                    outputTextBox.Text += responseContent + "\n";
                    using (StringReader reader = new StringReader(responseContent))
                    {
                        XmlSerializer serializer = new XmlSerializer(typeof(PairResponse));
                        pairResponse = serializer.Deserialize(new StringReader(responseContent)) as PairResponse;
                    }
                }
            }

            if (pairResponse == null || pairResponse.Paired != 1)
            {
                outputTextBox.Text += "Pairing failed.\n";
                // TODO: Unpair as above.
                return;
            }

            await Task.Delay(2000);

            // Do the initial challenge (seems neccessary for us to show as paired).
            // TODO: Call should have a timeout.
            uriString =
                string.Format(
                    "https://{0}:47984/pair?uniqueid={1}&uuid={2}&devicename=roth&updateState=1&phrase=pairchallenge",
                    ipAddress,
                    BytesToHex(uniqueId),
                    Guid.NewGuid().ToString("N"));
            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, new Uri(uriString)))
            {
                using (HttpResponseMessage response = await httpClient.SendRequestAsync(request))
                {
                    outputTextBox.Text = $"Send pair challenge status code: {response.StatusCode}\n";
                    string responseContent = await response.Content.ReadAsStringAsync();

                    outputTextBox.Text += responseContent + "\n";
                    using (StringReader reader = new StringReader(responseContent))
                    {
                        XmlSerializer serializer = new XmlSerializer(typeof(PairResponse));
                        pairResponse = serializer.Deserialize(new StringReader(responseContent)) as PairResponse;
                    }
                }
            }

            if (pairResponse == null || pairResponse.Paired != 1)
            {
                outputTextBox.Text += "Pairing failed.\n";
                // TODO: Unpair as above.
                return;
            }

            await Task.Delay(2000);

            outputTextBox.Text = "Pairing succeeded!\n";
        }