public LicenseBlob TryDeserialize(string license, string licenseSource, bool locallySourced)
        {
            LicenseBlob blob;

            try {
                blob = LicenseBlob.Deserialize(license);
            } catch (Exception ex) {
                AcceptIssue(new Issue("Failed to parse license (from " + licenseSource + "):",
                                      LicenseBlob.TryRedact(license) + "\n" + ex, IssueSeverity.Error));
                return(null);
            }
            if (!blob.VerifySignature(TrustedKeys, null))
            {
                sink.AcceptIssue(new Issue(
                                     "License " + blob.Fields.Id + " (from " + licenseSource +
                                     ") has been corrupted or has not been signed with a matching private key.", IssueSeverity.Error));
                return(null);
            }
            if (locallySourced && blob.Fields.MustBeFetched())
            {
                sink.AcceptIssue(new Issue(
                                     "This license cannot be installed directly; it must be fetched from a license server",
                                     LicenseBlob.TryRedact(license), IssueSeverity.Error));
                return(null);
            }
            return(blob);
        }
        static bool Validate(string licenseStr, BigInteger mod, BigInteger exp, StringBuilder log)
        {
            var blob = LicenseBlob.Deserialize(licenseStr);

            log?.AppendLine("---------------------------------------------");
            log?.AppendLine("Parsed info: " + blob.Fields);
            log?.AppendLine("Plaintext hash: " + BitConverter
                            .ToString(new SHA512Managed().ComputeHash(blob.Data))
                            .ToLower()
                            .Replace("-", ""));
            return(blob.VerifySignature(new[] { new RSADecryptPublic(mod, exp) }, log));
        }
        /// <summary>
        ///     We have a layer of caching by string. This does not need to be fast.
        /// </summary>
        /// <param name="b"></param>
        public void Add(LicenseBlob b)
        {
            // Prevent duplicate signatures
            if (dict.TryAdd(BitConverter.ToString(b.Signature), b))
            {
                //New/unique - ensure fetcher is created
                if (b.Fields.IsRemotePlaceholder())
                {
                    Secret   = b.Fields.GetSecret();
                    IsRemote = true;

                    TryUpdateLicenseServersInfo(b);
                    RecreateFetcher();
                }
                LocalLicenseChange();
            }
        }
        public static LicenseBlob Deserialize(string license)
        {
            var parts = license.Split(':').Select(s => s.Trim()).ToList();

            if (parts.Count < 2)
            {
                throw new ArgumentException(
                          "Not enough ':' delimited segments in license key; failed to deserialize: \"" + license + "\"",
                          nameof(license));
            }

            var dataBytes = Convert.FromBase64String(parts[parts.Count - 2]);
            var b         = new LicenseBlob {
                Original  = license,
                Signature = Convert.FromBase64String(parts[parts.Count - 1]),
                Data      = dataBytes,
                Fields    = new LicenseDetails(System.Text.Encoding.UTF8.GetString(dataBytes))
            };

            // b.Info = System.Text.Encoding.UTF8.GetString(b.Data);
            // b.Comments = parts.Take(parts.Count - 2);
            return(b);
        }
        public static bool Revalidate(this ILicenseBlob b, IEnumerable <RSADecryptPublic> trustedKeys)
        {
            var ourCopy = LicenseBlob.Deserialize(b.Original);

            return(ourCopy.VerifySignature(trustedKeys, null) && ourCopy.Fields.DataMatches(b.Fields));
        }
        void OnFetchResult(string body, IReadOnlyCollection <LicenseFetcher.FetchResult> results)
        {
            if (body != null)
            {
                Last200 = parent.Clock.GetUtcNow();
                var license = parent.TryDeserialize(body, "remote server", false);
                if (license != null)
                {
                    var newId = license.Fields.Id;
                    if (newId == Id)
                    {
                        remoteLicense = license;
                        // Victory! (we're ignoring failed writes/duplicates)
                        parent.Cache.TryPut(fetcher.CacheKey, body);

                        LastSuccess = parent.Clock.GetUtcNow();

                        lastWorkingUri = results.Last().FullUrl;
                    }
                    else
                    {
                        parent.AcceptIssue(new Issue(
                                               "Remote license file does not match. Please contact [email protected]",
                                               "Local: " + Id + "  Remote: " + newId, IssueSeverity.Error));
                    }
                }
                // TODO: consider logging a failed deserialization remotely
            }
            else
            {
                var licenseName = Id;

                if (results.All(r => r.HttpCode == 404 || r.HttpCode == 403))
                {
                    parent.AcceptIssue(new Issue("No such license (404/403): " + licenseName,
                                                 string.Join("\n", results.Select(r => "HTTP 404/403 fetching " + RedactSecret(r.ShortUrl))),
                                                 IssueSeverity.Error));
                    // No such subscription key.. but don't downgrade it if exists.
                    var cachedString = parent.Cache.Get(fetcher.CacheKey);
                    int temp;
                    if (cachedString == null || !int.TryParse(cachedString, out temp))
                    {
                        parent.Cache.TryPut(fetcher.CacheKey, results.First().HttpCode.ToString());
                    }
                    Last404 = parent.Clock.GetUtcNow();
                }
                else if (results.All(r => r.LikelyNetworkFailure))
                {
                    // Network failure. Make sure the server can access the remote server
                    parent.AcceptIssue(fetcher.FirewallIssue(licenseName, results.FirstOrDefault()));
                    LastTimeout = parent.Clock.GetUtcNow();
                }
                else
                {
                    parent.AcceptIssue(new Issue("Exception(s) occurred fetching license " + licenseName,
                                                 RedactSecret(string.Join("\n",
                                                                          results.Select(r =>
                                                                                         $"{r.HttpCode} {r.FullUrl}  LikelyTimeout: {r.LikelyNetworkFailure} Error: {r.FetchError?.ToString()}"))),
                                                 IssueSeverity.Error));
                    LastException = parent.Clock.GetUtcNow();
                }
            }
            LocalLicenseChange();
        }