예제 #1
0
        public async Task ExternalWebContentSource_TestAllServers()
        {
            var urls = await ExternalWebContentSource.LoadInternalServerListAsync();

            var failedUrls = new List <Tuple <Uri, object> >();

            foreach (var url in urls)
            {
                // Make 3 attempts to successfully connect to each server.
                for (int i = 0; i < 3; i++)
                {
                    try
                    {
                        var hc     = HttpClientHelpers.Create(userAgent: UnitTestUserAgent());
                        var result = await hc.GetByteArrayAsync(url);

                        break;
                    }
                    catch (Exception ex)
                    {
                        await Task.Delay(1000);

                        if (i == 2)
                        {
                            failedUrls.Add(Tuple.Create(url, (object)ex));
                        }
                    }
                }
            }

            if (failedUrls.Any())
            {
                throw new Exception("Urls failed to GET: " + String.Join("\n", failedUrls.Select(x => x.Item1.ToString() + " - " + x.Item2.ToString())));
            }
        }
예제 #2
0
            public async Task <byte[]> ResetAndRun()
            {
                // TODO: timeout.
                var hash = SHA256.Create();
                var hc   = HttpClientHelpers.Create(userAgent: UserAgent);
                var sw   = Stopwatch.StartNew();

                try
                {
                    var responseBytes = await hc.GetByteArrayAsync(Url);

                    sw.Stop();
                    Log.Trace("GET from '{0}' in {1:N2}ms, received {2:N0} bytes", Url, sw.Elapsed.TotalMilliseconds, responseBytes.Length);
                    var result = hash.ComputeHash(
                        responseBytes
                        .Concat(BitConverter.GetBytes(sw.ElapsedTicks))
                        .Concat(StaticEntropy)
                        .ToArray()
                        );
                    return(result);
                }
                catch (Exception ex)
                {
                    Log.WarnException("Exception when trying to GET from {0}", ex, Url);
                    return(null);
                }
            }
예제 #3
0
        public void TestHttpClientDefaults()
        {
            var http = HttpClientHelpers.Create();

            Assert.AreEqual(HttpClientHelpers.DefaultTimeout, http.Timeout);
            Assert.AreEqual(HttpClientHelpers.UserAgentString(), String.Join(" ", http.DefaultRequestHeaders.UserAgent.Select(x => x.ToString())));
        }
예제 #4
0
        /// <summary>
        /// Create a user-agent string to use with HTTP requests.
        /// </summary>
        /// <param name="usageIdentifier">An email address, website, or other identifying mark to include.</param>
        /// <exception cref="System.Exception">May throw if the usageIdentifier has invalid characters.</exception>
        public static string UserAgent(string usageIdentifier)
        {
            var id   = (usageIdentifier ?? "unconfigured").Replace("@", ".AT.");
            var ua   = HttpClientHelpers.UserAgentString(id);
            var http = HttpClientHelpers.Create(userAgent: ua);

            http.Dispose();
            return(ua);
        }
        private async Task <byte[]> GetPublicEntropyAsync(EntropyPriority priority)
        {
            // https://random.org

            Log.Trace("Beginning to gather entropy.");

            // Fetch data.
            var response = "";
            var sw       = Stopwatch.StartNew();

            if (!_UseDiskSourceForUnitTests)
            {
                var apiUri = new Uri("https://www.random.org/cgi-bin/randbyte?nbytes=" + _BytesPerRequest + "&format=h");
                var hc     = HttpClientHelpers.Create(userAgent: _UserAgent);
                try
                {
                    response = await hc.GetStringAsync(apiUri);
                }
                catch (Exception ex)
                {
                    Log.Warn(ex, "Unable to GET from {0}", apiUri);
                    return(null);
                }
                Log.Trace("Read {0:N0} characters of html in {1:N2}ms.", response.Length, sw.Elapsed.TotalMilliseconds);
            }
            else
            {
                using (var stream = File.OpenRead(HttpClientHelpers._BasePathToUnitTestData + "www.random.org.html"))
                {
                    response = await new StreamReader(stream).ReadToEndAsync();
                }
            }
            sw.Stop();


            // The entire content is random hex bytes.
            // Albeit with a bunch of whitespace.
            var randomString = response.Replace("\r", "").Replace("\n", "").Replace(" ", "");

            var randomBytes = randomString.ParseFromHexString()
                              .Concat(BitConverter.GetBytes(unchecked ((uint)sw.Elapsed.Ticks)))       // Don't forget to include network timing!
                              .ToArray();

            Log.Trace("Read {0:N0} bytes of entropy (including 4 bytes of timing info).", randomBytes.Length);

            return(randomBytes);
        }
        protected override async Task <byte[]> GetInternalEntropyAsync(EntropyPriority priority)
        {
            Log.Trace("Beginning to gather entropy.");

            // This supports SSL, but the cert isn't valid (it's for the uni, rather than the correct domain).
            // http://www.randomnumbers.info/content/Download.htm
            // This returns HTML, which means I'm doing some hacky parsing here.
            const int rangeOfNumbers = 4096 - 1;      // 12 bits per number (1.5 bytes).

            // Fetch data.
            var response = "";
            var sw       = Stopwatch.StartNew();

            if (!_UseDiskSourceForUnitTests)
            {
                var apiUri = new Uri("http://www.randomnumbers.info/cgibin/wqrng.cgi?amount=" + _NumberOfNumbers.ToString() + "&limit=" + rangeOfNumbers.ToString());
                var hc     = HttpClientHelpers.Create(userAgent: _UserAgent);
                try
                {
                    response = await hc.GetStringAsync(apiUri);
                }
                catch (Exception ex)
                {
                    Log.Warn(ex, "Unable to GET from {0}", apiUri);
                    return(null);
                }
                Log.Trace("Read {0:N0} characters of html in {1:N2}ms.", response.Length, sw.Elapsed.TotalMilliseconds);
            }
            else
            {
                using (var stream = File.OpenRead(HttpClientHelpers._BasePathToUnitTestData + "www.randomnumbers.info.html"))
                {
                    response = await new StreamReader(stream).ReadToEndAsync();
                }
            }
            sw.Stop();

            // Locate some pretty clear boundaries around the random numbers returned.
            var startIdxString = "Download random numbers from quantum origin";
            var startIdx       = response.IndexOf(startIdxString, StringComparison.OrdinalIgnoreCase);

            if (startIdx == -1)
            {
                Log.Error("Cannot locate start string in html of randomnumbers.info result: source will return nothing. Looking for '{0}'. Actual result in next message.", startIdxString);
                Log.Error(response);
                return(null);
            }
            var hrIdxString = "<hr>";

            startIdx = response.IndexOf(hrIdxString, startIdx, StringComparison.OrdinalIgnoreCase);
            if (startIdx == -1)
            {
                Log.Error("Cannot locate start string in html of randomnumbers.info result: source will return nothing. Looking for '{0}'. Actual result in next message.", hrIdxString);
                Log.Error(response);
                return(null);
            }
            var endIdxString = "</td>";
            var endIdx       = response.IndexOf("</td>", startIdx, StringComparison.OrdinalIgnoreCase);

            if (endIdx == -1)
            {
                Log.Error("Cannot locate end string in html of randomnumbers.info result: source will return nothing. Looking for '{0}'. Actual result in next message.", endIdxString);
                Log.Error(response);
                return(null);
            }
            Log.Trace("Parsed beginning and end of useful entropy.");

            var haystack            = response.Substring(startIdx, endIdx - startIdx);
            var numbersAndOtherJunk = haystack.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); // Numbers are space separated.
            var numbers             = numbersAndOtherJunk
                                      .Where(x => x.All(Char.IsDigit))                                           // Remove non-numeric junk.
                                      .Select(x => Int16.Parse(x))                                               // Parse to an int16.
                                      .ToList();
            var result = new byte[(numbers.Count() * 2) + sizeof(uint)];

            for (int i = 0; i < numbers.Count; i++)
            {
                // Take the Int16s in the range 0..4095 (4096 possibilities) and write them into the result array.
                // The top 4 bits will always be empty, but that doesn't matter too much as they will be hashed when added to a Pool.
                // This means only 75% of bits are truly random, so 16 bytes is only equivalent to 12 bytes.
                // TODO: some bit bashing to pack things more efficiently
                var twoBytes = BitConverter.GetBytes(numbers[i]);
                result[i * 2]       = twoBytes[0];
                result[(i * 2) + 1] = twoBytes[1];
            }
            var timingBytes = BitConverter.GetBytes(unchecked ((uint)sw.Elapsed.Ticks));

            result[result.Length - 4] = timingBytes[0];
            result[result.Length - 3] = timingBytes[1];
            result[result.Length - 2] = timingBytes[2];
            result[result.Length - 1] = timingBytes[3];

            Log.Trace("Read {0:N0} bytes of entropy (including 4 bytes of timing info).", result.Length);

            return(result);
        }
예제 #7
0
        protected override async Task <byte[]> GetInternalEntropyAsync(EntropyPriority priority)
        {
            // https://beacon.nist.gov/
            // Note that this will return the same result for 60 second period.
            // We must mix in some local entropy to ensure differnt computers end up with different entropy.
            // Yes, this reduces the effectiveness of this source, but it will still contribute over time.

            Log.Trace("Beginning to gather entropy.");

            // Fetch data.
            var response = "";
            var sw       = Stopwatch.StartNew();

            if (!_UseDiskSourceForUnitTests)
            {
                var apiUri = new Uri("https://beacon.nist.gov/rest/record/last");
                var hc     = HttpClientHelpers.Create(userAgent: _UserAgent);
                try
                {
                    response = await hc.GetStringAsync(apiUri);
                }
                catch (Exception ex)
                {
                    Log.Warn(ex, "Unable to GET from {0}", apiUri);
                    return(null);
                }
                Log.Trace("Read {0:N0} characters of html in {1:N2}ms.", response.Length, sw.Elapsed.TotalMilliseconds);
            }
            else
            {
                using (var stream = File.OpenRead(HttpClientHelpers._BasePathToUnitTestData + "beacon.nist.gov-last.xml"))
                {
                    response = await new StreamReader(stream).ReadToEndAsync();
                }
            }
            sw.Stop();

            var localEntropy = (await StaticLocalEntropy.Get32()).Concat(CheapEntropy.Get16())
                               .Concat(BitConverter.GetBytes((uint)sw.Elapsed.Ticks))
                               .ToArray();

            Log.Trace("Got {0:N0} bytes of local entropy to mix.", localEntropy.Length);

            // Parse out the useful parts of the response.
            // Keeping away from XML parsing to minimise dependencies. At least for now.

            // The first two return 64 random bytes each, the signature is 256 bytes. All are hashed to 64 bytes when combined with local entropy.
            var lastOutputBytes  = GetWithinXmlTags(response, "previousOutputValue").ParseFromHexString();
            var outputValueBytes = GetWithinXmlTags(response, "outputValue").ParseFromHexString();
            var signatureBytes   = GetWithinXmlTags(response, "signatureValue").ParseFromHexString();

            Log.Trace("Got {0:N0} output bytes, {1:N0} last output bytes, {2:N0} signature bytes.", outputValueBytes.Length, lastOutputBytes.Length, signatureBytes.Length);

            // Mix in some local entropy.
            var hasher = SHA512.Create();
            var result = hasher.ComputeHash(lastOutputBytes.Concat(localEntropy).ToArray())
                         .Concat(hasher.ComputeHash(outputValueBytes.Concat(localEntropy).ToArray()))
                         .Concat(hasher.ComputeHash(signatureBytes.Concat(localEntropy).ToArray()))
                         .Concat(BitConverter.GetBytes(unchecked ((uint)sw.Elapsed.Ticks)))    // Don't forget to include network timing!
                         .ToArray();

            Log.Trace("Read {0:N0} bytes of entropy.", result.Length);

            return(result);
        }
예제 #8
0
        protected override async Task <byte[]> GetInternalEntropyAsync(EntropyPriority priority)
        {
            // http://qrng.anu.edu.au/index.php

            Log.Trace("Beginning to gather entropy.");

            // Fetch data.
            // This always returns 1024 bytes!!
            var response = "";
            var sw       = Stopwatch.StartNew();

            if (!_UseDiskSourceForUnitTests)
            {
                var apiUri = new Uri("https://qrng.anu.edu.au/RawHex.php");
                var hc     = HttpClientHelpers.Create(userAgent: _UserAgent);
                try
                {
                    response = await hc.GetStringAsync(apiUri);
                }
                catch (Exception ex)
                {
                    Log.Warn(ex, "Unable to GET from {0}", apiUri);
                    return(null);
                }
                Log.Trace("Read {0:N0} characters of html in {1:N2}ms.", response.Length, sw.Elapsed.TotalMilliseconds);
            }
            else
            {
                using (var stream = File.OpenRead(HttpClientHelpers._BasePathToUnitTestData + "qrng.anu.edu.au-RawHex.php.html"))
                {
                    response = await new StreamReader(stream).ReadToEndAsync();
                }
            }
            sw.Stop();


            // Locate some pretty clear boundaries around the random numbers returned.
            var startIdxString = "1024 bytes of randomness in hexadecimal form";
            var startIdx       = response.IndexOf(startIdxString, StringComparison.OrdinalIgnoreCase);

            if (startIdx == -1)
            {
                Log.Error("Cannot locate start string in html of anu result: source will return nothing. Looking for '{0}', actual result in next message.", startIdxString);
                Log.Error(response);
                return(null);
            }

            var startIdxString2 = "<td>";

            startIdx = response.IndexOf(startIdxString2, startIdx, StringComparison.OrdinalIgnoreCase) + startIdxString2.Length;
            if (startIdx == -1)
            {
                Log.Error("Cannot locate start string in html of anu result: source will return nothing. Looking for '{0}', actual result in next message.", startIdxString2);
                Log.Error(response);
                return(null);
            }

            var endIdxString = "</td>";
            var endIdx       = response.IndexOf(endIdxString, startIdx, StringComparison.OrdinalIgnoreCase);

            if (endIdx == -1)
            {
                Log.Error("Cannot locate end string in html of anu result: source will return nothing. Looking for '{0}', actual result in next message.", endIdxString);
                Log.Error(response);
                return(null);
            }
            Log.Trace("Parsed beginning and end of useful entropy.");

            // Trim and parse.
            var randomString = response.Substring(startIdx, endIdx - startIdx).Trim();
            var randomBytes  = randomString.ParseFromHexString()
                               .Concat(BitConverter.GetBytes(unchecked ((uint)sw.Elapsed.Ticks)))      // Don't forget to include network timing!
                               .ToArray();

            Log.Trace("Read {0:N0} bytes of entropy (including 4 bytes of timing info).", randomBytes.Length);

            return(randomBytes);
        }
        private async Task <byte[]> GetApiEntropyAsync(EntropyPriority priority)
        {
            // http://www.random.org/

            // https://api.random.org/json-rpc/2/introduction
            // https://api.random.org/json-rpc/2/basic
            // https://api.random.org/json-rpc/2/request-builder

            // Fetch data.
            var response = "";
            var sw       = Stopwatch.StartNew();

            if (!_UseDiskSourceForUnitTests)
            {
                var apiUri      = new Uri("https://api.random.org/json-rpc/2/invoke");
                var hc          = HttpClientHelpers.Create(userAgent: _UserAgent);
                var requestBody = "{\"jsonrpc\":\"2.0\",\"method\":\"generateBlobs\",\"params\":{\"apiKey\":\"" + _ApiKey.ToString("D") + "\",\"n\":1,\"size\":" + (_BytesPerRequest * 8) + ",\"format\":\"base64\"},\"id\":1}";
                try
                {
                    response = await hc.PostStringAsync(apiUri, requestBody, "application/json");
                }
                catch (Exception ex)
                {
                    Log.Warn(ex, "Unable to POST to {0} with body {1}", apiUri, requestBody);
                    return(null);
                }
                Log.Trace("Read {0:N0} characters of html in {1:N2}ms.", response.Length, sw.Elapsed.TotalMilliseconds);
            }
            else
            {
                using (var stream = File.OpenRead(HttpClientHelpers._BasePathToUnitTestData + "api.random.org.html"))
                {
                    response = await new StreamReader(stream).ReadToEndAsync();
                }
            }
            sw.Stop();

            // To avoid using dynamic or a Json library, we do hacky string parsing!

            // Check for error.
            if (response.IndexOf("\"error\":") != -1)
            {
                Log.Error("Random.org API returned error result. Full result in next message.");
                Log.Error(response);
                return(null);
            }
            int dataIdx = response.IndexOf("\"data\":[\"");

            if (dataIdx == -1)
            {
                Log.Error("Cannot locate random result in random.org API result: source will return nothing. Actual result in next message.");
                Log.Error(response);
                return(null);
            }
            dataIdx = dataIdx + "\"data\":[\"".Length;
            int endIdx = response.IndexOf("\"]", dataIdx);

            if (endIdx == -1)
            {
                Log.Error("Cannot locate end of random result in random.org API result: source will return nothing. Actual result in next message.");
                Log.Error(response);
                return(null);
            }
            Log.Trace("Parsed Json result.");

            // Trim and parse.
            var randomString = response.Substring(dataIdx, endIdx - dataIdx).Trim();
            var randomBytes  = Convert.FromBase64String(randomString)
                               .Concat(BitConverter.GetBytes(unchecked ((uint)sw.Elapsed.Ticks))) // Don't forget to include network timing!
                               .ToArray();

            Log.Trace("Read {0:N0} bytes of entropy (including 4 bytes of timing info).", randomBytes.Length);

            return(randomBytes);
        }
예제 #10
0
        protected override async Task <byte[]> GetInternalEntropyAsync(EntropyPriority priority)
        {
            // http://www.fourmilab.ch/hotbits/
            Log.Trace("Beginning to gather entropy.");

            string pseudoSource, apiKey;

            if (String.IsNullOrWhiteSpace(_ApiKey))
            {
                pseudoSource = "&pseudo=pseudo";
                apiKey       = "&apikey=";
            }
            else
            {
                pseudoSource = "";
                apiKey       = "&apikey=" + _ApiKey;
            }

            // Fetch data.
            var response = "";
            var sw       = Stopwatch.StartNew();

            if (!_UseDiskSourceForUnitTests)
            {
                var apiUri = new Uri("https://www.fourmilab.ch/cgi-bin/Hotbits.api?nbytes=" + _BytesPerRequest + "&fmt=hex&npass=1&lpass=8&pwtype=3" + apiKey + pseudoSource);
                var hc     = HttpClientHelpers.Create(userAgent: _UserAgent);
                try
                {
                    response = await hc.GetStringAsync(apiUri);
                }
                catch (Exception ex)
                {
                    Log.Warn(ex, "Unable to GET from {0}", apiUri);
                    return(null);
                }
                Log.Trace("Read {0:N0} characters of html in {1:N2}ms.", response.Length, sw.Elapsed.TotalMilliseconds);
            }
            else
            {
                using (var stream = File.OpenRead(HttpClientHelpers._BasePathToUnitTestData + "hotbits.html"))
                {
                    response = await new StreamReader(stream).ReadToEndAsync();
                }
            }
            sw.Stop();


            // Locate some pretty clear boundaries around the random numbers returned.
            var startIdx = response.IndexOf("<pre>", StringComparison.OrdinalIgnoreCase) + "<pre>".Length;

            if (startIdx == -1)
            {
                Log.Error("Cannot locate start string in html of hotbits result: source will return nothing. Actual result in next message.");
                Log.Error(response);
                return(null);
            }
            var endIdx = response.IndexOf("</pre>", startIdx, StringComparison.OrdinalIgnoreCase);

            if (endIdx == -1)
            {
                Log.Error("Cannot locate end string in html of hotbits result: source will return nothing. Actual result in next message.");
                Log.Error(response);
                return(null);
            }
            var randomString = response.Substring(startIdx, endIdx - startIdx).Trim().Replace("\r", "").Replace("\n", "");

            Log.Trace("Parsed beginning and end of useful entropy.");

            var randomBytes = randomString.ParseFromHexString()
                              .Concat(BitConverter.GetBytes(unchecked ((uint)sw.Elapsed.Ticks)))       // Don't forget to include network timing!
                              .ToArray();

            Log.Trace("Read {0:N0} bytes of entropy (including 4 bytes of timing info).", randomBytes.Length);

            return(randomBytes);
        }
예제 #11
0
        protected override async Task <byte[]> GetInternalEntropyAsync(EntropyPriority priority)
        {
            // http://qrng.ethz.ch/http_api/

            Log.Trace("Beginning to gather entropy.");

            // Fetch data.
            var response = "";
            var sw       = Stopwatch.StartNew();

            if (!_UseDiskSourceForUnitTests)
            {
                var apiUri = new Uri("http://qrng.ethz.ch/api/randint?min=0&max=255&size=" + _BytesPerRequest);
                var hc     = HttpClientHelpers.Create(userAgent: _UserAgent);
                try
                {
                    response = await hc.GetStringAsync(apiUri);
                }
                catch (Exception ex)
                {
                    Log.Warn(ex, "Unable to GET from {0}", apiUri);
                    return(null);
                }
                Log.Trace("Read {0:N0} characters of json in {1:N2}ms.", response.Length, sw.Elapsed.TotalMilliseconds);
            }
            else
            {
                using (var stream = File.OpenRead(HttpClientHelpers._BasePathToUnitTestData + "qrng.ethz.ch-randint.txt"))
                {
                    response = await new StreamReader(stream).ReadToEndAsync();
                }
            }
            sw.Stop();


            // To avoid using dynamic or a Json library, we do hacky string parsing!

            // Check for valid result.
            if (response.IndexOf("\"result\":") == -1)
            {
                Log.Error("qrng.ethz.ch returned unknown result. Full result in next message.");
                Log.Error(response);
                return(null);
            }
            int dataIdx = response.IndexOf("[");

            if (dataIdx == -1)
            {
                Log.Error("Cannot locate random result in qrng.ethz.ch response: source will return nothing. Actual result in next message.");
                Log.Error(response);
                return(null);
            }
            dataIdx = dataIdx + 1;
            int endIdx = response.IndexOf("]", dataIdx);

            if (endIdx == -1)
            {
                Log.Error("Cannot locate end of random result in qrng.ethz.ch response: source will return nothing. Actual result in next message.");
                Log.Error(response);
                return(null);
            }
            Log.Trace("Parsed Json result.");

            // Trim and parse.
            var randomInts          = response.Substring(dataIdx, endIdx - dataIdx).Trim();
            var numbersAndOtherJunk = randomInts.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            var result = numbersAndOtherJunk
                         .Select(x => (x ?? "").Trim())
                         .Where(x => x.All(Char.IsDigit))                                   // Remove non-numeric junk.
                         .Select(x => Byte.Parse(x))                                        // Parse to byte.
                         .Concat(BitConverter.GetBytes(unchecked ((uint)sw.Elapsed.Ticks))) // Don't forget to include network timing!
                         .ToArray();

            Log.Trace("Read {0:N0} bytes of entropy (including 4 bytes of timing info).", result.Length);

            return(result);
        }