From fa4a929907beac69f64cb1ce775acd9262cac61b Mon Sep 17 00:00:00 2001 From: John Simons Date: Wed, 10 Sep 2014 09:46:53 +1000 Subject: [PATCH] Committing Particular.Licensing so we know what we ship --- .gitignore | 1 - .../Particular.Licensing/License.cs | 51 ++++++++++ .../LicenseDeserializer.cs | 74 +++++++++++++++ .../LicenseExpirationChecker.cs | 38 ++++++++ .../Particular.Licensing/LicenseVerifier.cs | 92 +++++++++++++++++++ .../RegistryLicenseStore.cs | 90 ++++++++++++++++++ .../Particular.Licensing/ReleaseDateReader.cs | 23 +++++ .../TrialStartDateStore.cs | 40 ++++++++ .../UniversalDateParser.cs | 13 +++ .../Particular.Licensing/UserSidChecker.cs | 15 +++ 10 files changed, 436 insertions(+), 1 deletion(-) create mode 100644 src/NServiceBus.Core/App_Packages/Particular.Licensing/License.cs create mode 100644 src/NServiceBus.Core/App_Packages/Particular.Licensing/LicenseDeserializer.cs create mode 100644 src/NServiceBus.Core/App_Packages/Particular.Licensing/LicenseExpirationChecker.cs create mode 100644 src/NServiceBus.Core/App_Packages/Particular.Licensing/LicenseVerifier.cs create mode 100644 src/NServiceBus.Core/App_Packages/Particular.Licensing/RegistryLicenseStore.cs create mode 100644 src/NServiceBus.Core/App_Packages/Particular.Licensing/ReleaseDateReader.cs create mode 100644 src/NServiceBus.Core/App_Packages/Particular.Licensing/TrialStartDateStore.cs create mode 100644 src/NServiceBus.Core/App_Packages/Particular.Licensing/UniversalDateParser.cs create mode 100644 src/NServiceBus.Core/App_Packages/Particular.Licensing/UserSidChecker.cs diff --git a/.gitignore b/.gitignore index 81e89fb6c01..1a2e20de589 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,3 @@ _NCrunch_NServiceBus/* logs run-git.cmd src/Chocolatey/Build/* -App_Packages diff --git a/src/NServiceBus.Core/App_Packages/Particular.Licensing/License.cs b/src/NServiceBus.Core/App_Packages/Particular.Licensing/License.cs new file mode 100644 index 00000000000..fba1325d01f --- /dev/null +++ b/src/NServiceBus.Core/App_Packages/Particular.Licensing/License.cs @@ -0,0 +1,51 @@ +namespace Particular.Licensing +{ + using System; + using System.Collections.Generic; + + class License + { + public static License TrialLicense(DateTime trialStartDate) + { + return new License + { + LicenseType = "Trial", + ExpirationDate = trialStartDate.AddDays(14), + IsExtendedTrial = false, + ValidApplications = new List { "All"} + }; + } + + public License() + { + ValidApplications = new List(); + } + + public DateTime? ExpirationDate { get; set; } + + public bool IsTrialLicense + { + get { return !IsCommercialLicense; } + } + + public bool IsExtendedTrial { get; set; } + + public bool IsCommercialLicense + { + get { return LicenseType.ToLower() != "trial"; } + } + + public string LicenseType { get; set; } + + public string RegisteredTo { get; set; } + + public DateTime? UpgradeProtectionExpiration { get; internal set; } + + public List ValidApplications{ get; internal set; } + + public bool ValidForApplication(string applicationName) + { + return ValidApplications.Contains(applicationName) || ValidApplications.Contains("All"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/App_Packages/Particular.Licensing/LicenseDeserializer.cs b/src/NServiceBus.Core/App_Packages/Particular.Licensing/LicenseDeserializer.cs new file mode 100644 index 00000000000..37b4f75e727 --- /dev/null +++ b/src/NServiceBus.Core/App_Packages/Particular.Licensing/LicenseDeserializer.cs @@ -0,0 +1,74 @@ +namespace Particular.Licensing +{ + using System; + using System.Linq; + using System.Xml; + + static class LicenseDeserializer + { + public static License Deserialize(string licenseText) + { + var license = new License(); + var doc = new XmlDocument(); + doc.LoadXml(licenseText); + + + var applications = doc.SelectSingleNode("/license/@Applications"); + + + if (applications != null) + { + license.ValidApplications.AddRange(applications.Value.Split(';')); + } + + var upgradeProtectionExpiration = doc.SelectSingleNode("/license/@UpgradeProtectionExpiration"); + + if (upgradeProtectionExpiration != null) + { + license.UpgradeProtectionExpiration = Parse(upgradeProtectionExpiration.Value); + } + else + { + var expirationDate = doc.SelectSingleNode("/license/@expiration"); + + if (expirationDate != null) + { + license.ExpirationDate = Parse(expirationDate.Value); + + } + } + + var licenseType = doc.SelectSingleNode("/license/@LicenseType"); + + if (licenseType == null) + { + licenseType = doc.SelectSingleNode("/license/@type"); + } + + if (licenseType != null) + { + license.LicenseType = licenseType.Value; + } + + var name = doc.SelectSingleNode("/license/name"); + + if (name != null) + { + license.RegisteredTo = name.InnerText; + } + + return license; + } + + static DateTime Parse(string dateStringFromLicense) + { + if (string.IsNullOrEmpty(dateStringFromLicense)) + { + throw new Exception("Invalid datestring found in xml"); + } + + return UniversalDateParser.Parse(dateStringFromLicense.Split('T').First()); + } + + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/App_Packages/Particular.Licensing/LicenseExpirationChecker.cs b/src/NServiceBus.Core/App_Packages/Particular.Licensing/LicenseExpirationChecker.cs new file mode 100644 index 00000000000..2ae74998764 --- /dev/null +++ b/src/NServiceBus.Core/App_Packages/Particular.Licensing/LicenseExpirationChecker.cs @@ -0,0 +1,38 @@ +namespace Particular.Licensing +{ + using System; + + static class LicenseExpirationChecker + { + public static bool HasLicenseExpired(License license) + { + if (license.ExpirationDate.HasValue && HasLicenseDateExpired(license.ExpirationDate.Value)) + { + return true; + } + + + if (license.UpgradeProtectionExpiration != null) + { + var buildTimeStamp = ReleaseDateReader.GetReleaseDate(); + if (buildTimeStamp > license.UpgradeProtectionExpiration) + { + return true; + } + } + return false; + } + + static bool HasLicenseDateExpired(DateTime licenseDate) + { + var oneDayGrace = licenseDate; + + if (licenseDate < DateTime.MaxValue.AddDays(-1)) + { + oneDayGrace = licenseDate.AddDays(1); + } + + return oneDayGrace < DateTime.UtcNow.Date; + } + } +} diff --git a/src/NServiceBus.Core/App_Packages/Particular.Licensing/LicenseVerifier.cs b/src/NServiceBus.Core/App_Packages/Particular.Licensing/LicenseVerifier.cs new file mode 100644 index 00000000000..7d33a5441a7 --- /dev/null +++ b/src/NServiceBus.Core/App_Packages/Particular.Licensing/LicenseVerifier.cs @@ -0,0 +1,92 @@ +namespace Particular.Licensing +{ + using System; + using System.Security.Cryptography; + using System.Security.Cryptography.Xml; + using System.Xml; + + class LicenseVerifier + { + public static bool TryVerify(string licenseText, out Exception failure) + { + try + { + Verify(licenseText); + + failure = null; + + return true; + } + catch (Exception ex) + { + failure = ex; + return false; + } + + } + + public static void Verify(string licenseText) + { + if (string.IsNullOrEmpty(licenseText)) + { + throw new Exception("Empty license string"); + } + + var xmlVerifier = new SignedXmlVerifier(PublicKey); + + xmlVerifier.VerifyXml(licenseText); + } + + public const string PublicKey = @"5M9/p7N+JczIN/e5eObahxeCIe//2xRLA9YTam7zBrcUGt1UlnXqL0l/8uO8rsO5tl+tjjIV9bOTpDLfx0H03VJyxsE8BEpSVu48xujvI25+0mWRnk4V50bDZykCTS3Du0c8XvYj5jIKOHPtU//mKXVULhagT8GkAnNnMj9CvTc=AQAB"; + + class SignedXmlVerifier + { + readonly string publicKey; + + public SignedXmlVerifier(string publicKey) + { + this.publicKey = publicKey; + } + + public void VerifyXml(string xml) + { + var doc = LoadXmlDoc(xml); + + using (var rsa = new RSACryptoServiceProvider()) + { + rsa.FromXmlString(publicKey); + + var nsMgr = new XmlNamespaceManager(doc.NameTable); + nsMgr.AddNamespace("sig", "http://www.w3.org/2000/09/xmldsig#"); + + var signedXml = new SignedXml(doc); + var signature = (XmlElement)doc.SelectSingleNode("//sig:Signature", nsMgr); + if (signature == null) + { + throw new Exception("Xml is invalid as it has no XML signature"); + } + signedXml.LoadXml(signature); + + if (!signedXml.CheckSignature(rsa)) + { + throw new Exception("Xml is invalid as it failed signature check."); + } + } + } + + static XmlDocument LoadXmlDoc(string xml) + { + try + { + var doc = new XmlDocument(); + doc.LoadXml(xml); + return doc; + } + catch (XmlException exception) + { + throw new Exception("The text provided could not be parsed as XML.", exception); + } + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/App_Packages/Particular.Licensing/RegistryLicenseStore.cs b/src/NServiceBus.Core/App_Packages/Particular.Licensing/RegistryLicenseStore.cs new file mode 100644 index 00000000000..a2fccc9274d --- /dev/null +++ b/src/NServiceBus.Core/App_Packages/Particular.Licensing/RegistryLicenseStore.cs @@ -0,0 +1,90 @@ +namespace Particular.Licensing +{ + using System; + using System.Security; + using Microsoft.Win32; + + class RegistryLicenseStore + { + public RegistryLicenseStore() + { + keyPath = DefaultKeyPath; + keyName = DefaultKeyName; + regKey = Registry.CurrentUser; + + } + + public RegistryLicenseStore(RegistryKey regKey, string keyPath = DefaultKeyPath, string keyName = DefaultKeyName) + { + this.keyPath = keyPath; + this.keyName = keyName; + this.regKey = regKey; + } + + public bool TryReadLicense(out string license) + { + try + { + using (var registryKey = regKey.OpenSubKey(keyPath)) + { + if (registryKey == null) + { + license = null; + return false; + } + + var licenseValue = registryKey.GetValue("License", null); + + if (licenseValue is string[]) + { + license = string.Join(" ", (string[]) licenseValue); + } + else + { + license = (string)licenseValue; + } + + return !string.IsNullOrEmpty(license); + } + } + catch (SecurityException exception) + { + throw new Exception(string.Format("Failed to access '{0}'. Do you have permission to read this key?", FullPath), exception); + } + } + + + public void StoreLicense(string license) + { + try + { + using (var registryKey = regKey.CreateSubKey(keyPath)) + { + if (registryKey == null) + { + throw new Exception(string.Format("CreateSubKey for '{0}' returned null. Do you have permission to write to this key", keyPath)); + } + + registryKey.SetValue(keyName, license, RegistryValueKind.String); + } + } + catch (UnauthorizedAccessException exception) + { + throw new Exception(string.Format("Failed to access '{0}'. Do you have permission to write to this key?", FullPath), exception); + } + } + + string FullPath + { + get { return string.Format("{0} : {1} : {2}", regKey.Name, keyPath, keyName); } + } + + string keyPath; + string keyName; + RegistryKey regKey; + + + const string DefaultKeyPath = @"SOFTWARE\ParticularSoftware"; + const string DefaultKeyName = "License"; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/App_Packages/Particular.Licensing/ReleaseDateReader.cs b/src/NServiceBus.Core/App_Packages/Particular.Licensing/ReleaseDateReader.cs new file mode 100644 index 00000000000..e29151cf0b1 --- /dev/null +++ b/src/NServiceBus.Core/App_Packages/Particular.Licensing/ReleaseDateReader.cs @@ -0,0 +1,23 @@ +namespace Particular.Licensing +{ + using System; + using System.Linq; + using System.Reflection; + + static class ReleaseDateReader + { + public static DateTime GetReleaseDate() + { + var attribute = (dynamic)Assembly.GetExecutingAssembly() + .GetCustomAttributes(false) + .FirstOrDefault(x => x.GetType().Name == "ReleaseDateAttribute"); + + if (attribute == null) + { + throw new Exception("No ReleaseDateAttribute could be found in assembly, please make sure GitVersion is enabled"); + } + + return UniversalDateParser.Parse((string)attribute.OriginalDate); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/App_Packages/Particular.Licensing/TrialStartDateStore.cs b/src/NServiceBus.Core/App_Packages/Particular.Licensing/TrialStartDateStore.cs new file mode 100644 index 00000000000..176f492ebd5 --- /dev/null +++ b/src/NServiceBus.Core/App_Packages/Particular.Licensing/TrialStartDateStore.cs @@ -0,0 +1,40 @@ +namespace Particular.Licensing +{ + using System; + using System.Globalization; + using Microsoft.Win32; + + static class TrialStartDateStore + { + public static DateTime GetTrialStartDate() + { + var rootKey = Registry.LocalMachine; + + if (UserSidChecker.IsNotSystemSid()) + { + rootKey = Registry.CurrentUser; + } + using (var registryKey = rootKey.CreateSubKey(StorageLocation)) + { + // ReSharper disable PossibleNullReferenceException + //CreateSubKey does not return null http://stackoverflow.com/questions/19849870/under-what-circumstances-will-registrykey-createsubkeystring-return-null + var trialStartDateString = (string)registryKey.GetValue("TrialStart", null); + // ReSharper restore PossibleNullReferenceException + if (trialStartDateString == null) + { + var trialStart = DateTime.UtcNow; + trialStartDateString = trialStart.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); + registryKey.SetValue("TrialStart", trialStartDateString, RegistryValueKind.String); + + return trialStart; + } + + var trialStartDate = DateTimeOffset.ParseExact(trialStartDateString, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); + + return trialStartDate.Date; + } + } + + public static string StorageLocation = @"SOFTWARE\ParticularSoftware"; + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/App_Packages/Particular.Licensing/UniversalDateParser.cs b/src/NServiceBus.Core/App_Packages/Particular.Licensing/UniversalDateParser.cs new file mode 100644 index 00000000000..e6179f7eb90 --- /dev/null +++ b/src/NServiceBus.Core/App_Packages/Particular.Licensing/UniversalDateParser.cs @@ -0,0 +1,13 @@ +namespace Particular.Licensing +{ + using System; + using System.Globalization; + + static class UniversalDateParser + { + public static DateTime Parse(string value) + { + return DateTime.ParseExact(value, "yyyy-MM-dd", null, DateTimeStyles.AssumeUniversal).ToUniversalTime(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/App_Packages/Particular.Licensing/UserSidChecker.cs b/src/NServiceBus.Core/App_Packages/Particular.Licensing/UserSidChecker.cs new file mode 100644 index 00000000000..d121ef8d446 --- /dev/null +++ b/src/NServiceBus.Core/App_Packages/Particular.Licensing/UserSidChecker.cs @@ -0,0 +1,15 @@ +namespace Particular.Licensing +{ + using System.Security.Principal; + + static class UserSidChecker + { + public static bool IsNotSystemSid() + { + var windowsIdentity = WindowsIdentity.GetCurrent(); + return windowsIdentity != null && + windowsIdentity.User != null && + !windowsIdentity.User.IsWellKnown(WellKnownSidType.LocalSystemSid); + } + } +} \ No newline at end of file