using System; using System.Text; using System.Collections.Generic; using System.IO; using Org.BouncyCastle.Security; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.OpenSsl; /* Sample key: [KEY { "Key": { "LicenseFormat": "8", "Id": "1660594773", "RegisteredTo": "AAA TEST DEVELOPMENT", "DBID": "1q3Q8MedzUQ/VQVWjQiLs3o/7nSbNFUpF5KzkM/Ny0M=", "Perpetual": true, "LicenseExpiration": "5555-01-01T00:00:00", "MaintenanceExpiration": "2023-08-15T00:00:00", "Features": [ { "Name": "ServiceTechs", "Count": 100 } ] } } KEY] [SIGNATURE VaVGBaXjnh1A7etqYp9bu5XkGaLxD2AplT+DERaIL8gHqSEj+1s3seEZpuRBJAvof5j++Si3ZCuesYL2M6IQUwmimYMwbQPH07MixOYxgc4kKMO53AwTmtBxwEzUYdhMn5i3g/2xOpJoXTsyqOJ0v1ZidFswQs8vVtxBO8uanlMKzFpXZE8K0cI8ZII9nRdBoUERO+JoEE16l/UAgi6O7KT8l1r225RodFhAThrdAT5ntIJ0JaTHPaMgnYb25VGS9LYQUWU7nkA+IsfddjxZW70a8IHXvQ1B5oV6srFVdkS4wY0dgbT+k7VJBDhMmltRDdOUH */ namespace rockfishCore.Util { //Key generator controller public static class RavenKeyFactory { public const int PERPETUAL_TRIAL_PERIOD_DAYS = 5; public const int SUBSCRIPTION_TRIAL_PERIOD_HOURS = 48; //Unlicensed token private const string UNLICENSED_TOKEN = "UNLICENSED"; //REVOKED token public const string REVOKED_TOKEN = "REVOKED"; //LICENSE USER COUNT FEATURES //PERPETUAL Scheduleable users private const string ACTIVE_SERVICE_TECHS_FEATURE_NAME = "ServiceTechs"; //SUBSCRIPTION private const string ACTIVE_INTERNAL_USERS_FEATURE_NAME = "ActiveInternalUsers"; private const string ACTIVE_CUSTOMER_USERS_FEATURE_NAME = "ActiveCustomerUsers"; //Add-on's / integrations //This feature name means it's a trial key private const string TRIAL_FEATURE_NAME = "TrialMode"; #region license classes //DTO object returned on license query public class LicenseFeature { //name of feature / product public string Feature { get; set; } //Optional count for items that require it public long Count { get; set; } } //DTO object for parsed key internal class AyaNovaLicenseKey { public AyaNovaLicenseKey() { Features = new List(); RegisteredTo = UNLICENSED_TOKEN; //Id = RegisteredTo; LicenseFormat = "8"; var vv = Math.Truncate((DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds); string sId = vv.ToString(); if (sId.Contains(",")) sId = sId.Split('.')[0]; Id = sId; } public override string ToString() { System.Text.StringBuilder sb = new StringBuilder(); sb.AppendLine("LICENSE DETAILS"); sb.AppendLine($"Registered to: {RegisteredTo}"); sb.AppendLine($"Database id: {DbId}"); sb.AppendLine($"Type: {(Perpetual ? "Perpetual" : "Subscription")}"); if (WillExpire) sb.AppendLine($"License valid until: {LicenseExpiration.ToLongDateString()}"); sb.AppendLine($"Maintenance until: {MaintenanceExpiration.ToLongDateString()}"); foreach (LicenseFeature f in Features) { if (f.Feature == TRIAL_FEATURE_NAME) { sb.AppendLine("Temporary license for evaluation"); continue; } if (f.Feature == ACTIVE_SERVICE_TECHS_FEATURE_NAME) { sb.AppendLine($"Scheduleable resources: {f.Count}"); continue; } //default for items added later not tokenized if (f.Count > 0) sb.AppendLine($"{f.Feature}: {f.Count}"); else sb.AppendLine($"{f.Feature}"); } return sb.ToString(); } public bool IsEmpty { get { //Key is empty if it's not registered to anyone or there are no features in it return string.IsNullOrWhiteSpace(RegisteredTo) || (Features == null || Features.Count == 0); } } /// /// Fetch the license status of the feature in question /// /// /// LicenseFeature object or null if there is no license public LicenseFeature GetLicenseFeature(string Feature) { if (IsEmpty) return null; string lFeature = Feature.ToLowerInvariant(); foreach (LicenseFeature l in Features) { if (l.Feature.ToLowerInvariant() == lFeature) { return l; } } return null; } /// /// Check for the existance of license feature /// /// /// bool public bool HasLicenseFeature(string Feature) { if (IsEmpty) return false; string lFeature = Feature.ToLowerInvariant(); foreach (LicenseFeature l in Features) { if (l.Feature.ToLowerInvariant() == lFeature) { return true; } } return false; } public bool WillExpire { get { return LicenseExpiration < DateUtil.EmptyDateValue; } } public bool LicenseExpired { get { return LicenseExpiration > DateTime.Now; } } public bool MaintenanceExpired { get { return MaintenanceExpiration > DateTime.Now; } } public bool TrialLicense { get { return HasLicenseFeature(TRIAL_FEATURE_NAME); } } public string LicenseFormat { get; set; } public string Id { get; set; } public string RegisteredTo { get; set; } public string DbId { get; set; } public bool Perpetual { get; set; } public DateTime LicenseExpiration { get; set; } public DateTime MaintenanceExpiration { get; set; } public List Features { get; set; } } #endregion public static string GetRavenTrialKey(string dbid, string CompanyName, bool Perpetual) { //Build a sample test key, sign it and return it AyaNovaLicenseKey k = new AyaNovaLicenseKey(); k.LicenseFormat = "8"; k.RegisteredTo = CompanyName; k.DbId = dbid; k.Perpetual = Perpetual; //flag as trial key not regular key k.Features.Add(new LicenseFeature() { Feature = TRIAL_FEATURE_NAME, Count = 0 }); if (Perpetual) { //trial period time limit k.MaintenanceExpiration = k.LicenseExpiration = DateTime.UtcNow.AddDays(PERPETUAL_TRIAL_PERIOD_DAYS); //enough techs to easily cover huge amounts of seeding (it's actually only 15 at the moment but you never know this is some buffer) k.Features.Add(new LicenseFeature() { Feature = ACTIVE_SERVICE_TECHS_FEATURE_NAME, Count = 250 }); } else { //SUBSCRIPTION //trial period time limit k.MaintenanceExpiration = k.LicenseExpiration = DateTime.UtcNow.AddHours(SUBSCRIPTION_TRIAL_PERIOD_HOURS); //20k customer contacts will cover huge seeding level easily //5k inside staff users will cover huge seeding level easily k.Features.Add(new LicenseFeature() { Feature = ACTIVE_INTERNAL_USERS_FEATURE_NAME, Count = 5000 }); k.Features.Add(new LicenseFeature() { Feature = ACTIVE_CUSTOMER_USERS_FEATURE_NAME, Count = 20000 }); } return GenerateRavenKey(k); } //TESTING ONLY this is for development purposes only //No external usage public static string GetRavenTestKey(string dbid, bool Perpetual) { //Build a sample test key, sign it and return it AyaNovaLicenseKey k = new AyaNovaLicenseKey(); k.LicenseFormat = "8"; k.RegisteredTo = "GZ TestCo Inc."; k.DbId = dbid; k.Perpetual = Perpetual; if (Perpetual) { k.MaintenanceExpiration = k.LicenseExpiration = DateTime.UtcNow.AddDays(PERPETUAL_TRIAL_PERIOD_DAYS); k.Features.Add(new LicenseFeature() { Feature = ACTIVE_SERVICE_TECHS_FEATURE_NAME, Count = 250 }); } else { //SUBSCRIPTION k.MaintenanceExpiration = k.LicenseExpiration = DateTime.UtcNow.AddHours(SUBSCRIPTION_TRIAL_PERIOD_HOURS); //20k customer contacts will cover huge seeding level easily //5k inside staff users will cover huge seeding level easily k.Features.Add(new LicenseFeature() { Feature = ACTIVE_INTERNAL_USERS_FEATURE_NAME, Count = 5000 }); k.Features.Add(new LicenseFeature() { Feature = ACTIVE_CUSTOMER_USERS_FEATURE_NAME, Count = 20000 }); } k.Features.Add(new LicenseFeature() { Feature = TRIAL_FEATURE_NAME, Count = 0 }); return GenerateRavenKey(k); } /// /// RAVEN key generator /// /// internal static string GenerateRavenKey(AyaNovaLicenseKey k) { if (string.IsNullOrWhiteSpace(k.RegisteredTo)) throw new ArgumentException("RegisteredTo is required", "RegisteredTo"); // if (k.DbId == Guid.Empty) if (string.IsNullOrWhiteSpace(k.DbId)) throw new ArgumentException("DBId is required", "RegisteredTo"); try { StringBuilder sbKey = new StringBuilder(); StringWriter sw = new StringWriter(sbKey); using (Newtonsoft.Json.JsonWriter w = new Newtonsoft.Json.JsonTextWriter(sw)) { w.Formatting = Newtonsoft.Json.Formatting.Indented; //outer object start w.WriteStartObject(); w.WritePropertyName("Key"); w.WriteStartObject();//start of key object w.WritePropertyName("LicenseFormat"); w.WriteValue(k.LicenseFormat); w.WritePropertyName("Id"); w.WriteValue(k.Id); w.WritePropertyName("RegisteredTo"); w.WriteValue(k.RegisteredTo); w.WritePropertyName("DBID"); w.WriteValue(k.DbId); w.WritePropertyName("Perpetual"); w.WriteValue(k.Perpetual); w.WritePropertyName("LicenseExpiration"); w.WriteValue(k.LicenseExpiration); w.WritePropertyName("MaintenanceExpiration"); w.WriteValue(k.MaintenanceExpiration); //FEATURES // w.WritePropertyName("Features"); // w.WriteStartObject(); w.WritePropertyName("Features"); w.WriteStartArray(); foreach (LicenseFeature lf in k.Features) { w.WriteStartObject(); w.WritePropertyName("Name"); w.WriteValue(lf.Feature); if (lf.Count > 0) { w.WritePropertyName("Count"); w.WriteValue(lf.Count); } w.WriteEndObject(); } //end of features array w.WriteEnd(); //end of features object // w.WriteEndObject(); //end of AyaNova/AyaNovaLite key object w.WriteEndObject(); //close outer 'wrapper' object brace } w.WriteEndObject(); }//end of using statement // ## CALCULATE SIGNATURE //GET JSON as a string with whitespace stripped outside of delimited strings //http://stackoverflow.com/questions/8913138/minify-indented-json-string-in-net string keyNoWS = System.Text.RegularExpressions.Regex.Replace(sbKey.ToString(), "(\"(?:[^\"\\\\]|\\\\.)*\")|\\s+", "$1"); //**** Note this is our real 2016 private key var privatePEM = @"-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAz7wrvLDcKVMZ31HFGBnLWL08IodYIV5VJkKy1Z0n2snprhSi u3izxTyz+SLpftvKHJpky027ii7l/pL9Bo3JcjU5rKrxXavnE7TuYPjXn16dNLd0 K/ERSU+pXLmUaVN0nUWuGuUMoGJMEXoulS6pJiG11yu3BM9fL2Nbj0C6a+UwzEHF mns3J/daZOb4gAzMUdJfh9OJ0+wRGzR8ZxyC99Na2gDmqYglUkSMjwLTL/HbgwF4 OwmoQYJBcET0Wa6Gfb17SaF8XRBV5ZtpCsbStkthGeoXZkFriB9c1eFQLKpBYQo2 DW3H1MPG2nAlQZLbkJj5cSh7/t1bRF08m6P+EQIDAQABAoIBAQCGvTpxLRXgB/Kk EtmQBEsMx9EVZEwZeKIqKuDsBP8wvf4/10ql5mhT6kehtK9WhSDW5J2z8DtQKZMs SBKuCZE77qH2CPp9E17SPWzQoRbaW/gDlWpYhgf8URs89XH5zxO4XtXKw/4omRlV zLYiNR2pifv0EHqpOAg5KGzewdEo4VgXgtRWpHZLMpH2Q0/5ZIKMhstI6vFHP1p7 jmU4YI6uxiu7rVrZDmIUsAGoTdMabNqK/N8hKaoBiIto0Jn1ck26g+emLg8m160y Xciu5yFUU+PP1SJMUs+k1UnAWf4p46X9jRLQCBRue9o0Ntiq/75aljRoDvgdwDsR mg4ZANqxAoGBAPBoM5KoMZ4sv8ZFv8V+V8hgL5xiLgGoiwQl91mRsHRM/NQU5A/w tH8nmwUrJOrksV7kX9228smKmoliTptyGGyi1NPmSkA7cN9YYnENoOEBHCVNK9vh P+bkbMYUDNMW4fgOj09oXtQtMl5E2B3OTGoNwZ2w13YQJ8RIniLPsX7nAoGBAN01 eQNcUzQk9YrFGTznOs8udDLBfigDxaNnawvPueulJdBy6ZXDDrKmkQQA7xxl8YPr dNtBq2lOgnb6+smC15TaAfV/fb8BLmkSwdn4Fy0FApIXIEOnLq+wjkte98nuezl8 9KXDzaqNI9hPuk2i36tJuLLMH8hzldveWbWjSlRHAoGBAKRPE7CQtBjfjNL+qOta RrT0yJWhpMANabYUHNJi+K8ET2jEPnuGkFa3wwPtUPYaCABLJhprB9Unnid3wTIM 8RSO1ddd9jGgbqy3w9Bw+BvQnmQAMpG9iedNB+r5mSpM4XSgvuIO+4EYwuwbMXpt nVx+um4Eh75xnDxTRYGVYkLRAoGAaZVpUlpR+HSfooHbPv+bSWKB4ewLPCw4vHrT VErtEfW8q9b9eRcmP81TMFcFykc6VN4g47pfh58KlKHM7DwAjDLWdohIy89TiKGE V3acEUfv5y0UoFX+6ara8Ey+9upWdKUY3Lotw3ckoc3EPeQ84DQK7YSSswnAgLaL mS/8fWcCgYBjRefVbEep161d2DGruk4X7eNI9TFJ278h6ydW5kK9aTJuxkrtKIp4 CYf6emoB4mLXFPvAmnsalkhN2iB29hUZCXXSUjpKZrpijL54Wdu2S6ynm7aT97NF oArP0E2Vbow3JMxq/oeXmHbrLMLQfYyXwFmciLFigOtkd45bfHdrbA== -----END RSA PRIVATE KEY-----"; PemReader pr = new PemReader(new StringReader(privatePEM)); AsymmetricCipherKeyPair keys = (AsymmetricCipherKeyPair)pr.ReadObject(); var encoder = new UTF8Encoding(false, true); var inputData = encoder.GetBytes(keyNoWS); var signer = SignerUtilities.GetSigner("SHA256WITHRSA"); signer.Init(true, keys.Private); signer.BlockUpdate(inputData, 0, inputData.Length); var sign = signer.GenerateSignature(); var signature = Convert.ToBase64String(sign); System.Text.StringBuilder sbOut = new StringBuilder(); sbOut.AppendLine("[KEY"); sbOut.AppendLine(sbKey.ToString()); sbOut.AppendLine("KEY]"); sbOut.AppendLine("[SIGNATURE"); sbOut.AppendLine(signature); sbOut.AppendLine("SIGNATURE]"); return sbOut.ToString(); } catch (Exception ex) { return (ex.Message); } } //eoc } //eons }