From 10c223a4bddf3bc8b938daef3839ee654d873063 Mon Sep 17 00:00:00 2001 From: Brad Hjelmar Date: Tue, 8 Sep 2020 11:43:23 -0400 Subject: [PATCH 1/5] fix: duplicate backend names cause key collision exception, issue: https://github.com/Appdynamics/AppDynamics.DEXTER/issues/114 --- ProcessingSteps/Index/IndexAPMMetricsList.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ProcessingSteps/Index/IndexAPMMetricsList.cs b/ProcessingSteps/Index/IndexAPMMetricsList.cs index cacbb0e..adae1e9 100644 --- a/ProcessingSteps/Index/IndexAPMMetricsList.cs +++ b/ProcessingSteps/Index/IndexAPMMetricsList.cs @@ -107,7 +107,9 @@ public override bool Execute(ProgramOptions programOptions, JobConfiguration job Dictionary backendDictionary = new Dictionary(); if (backendsList != null) { - backendDictionary = backendsList.ToDictionary(e => e.BackendName, e => e); + backendDictionary = backendsList + .GroupBy(e => e.BackendName, StringComparer.OrdinalIgnoreCase) + .ToDictionary(e => e.Key, e => e.First(), StringComparer.OrdinalIgnoreCase); } Dictionary informationPointDictionary = new Dictionary(); if (informationPointsList != null) From 2512b26a0c1e3709cc384a0d3cf724bf0a6fe394 Mon Sep 17 00:00:00 2001 From: Brad Hjelmar Date: Wed, 23 Sep 2020 10:11:45 -0400 Subject: [PATCH 2/5] updated LicenseSign to not require locally installed cert, updated documentation for LicenseSign steps --- LicenseSign/LicenseSign/Program.cs | 18 ++++++------------ .../Properties/launchSettings.json | 2 +- LicenseSign/Readme.txt | 19 ++++++++++++++++++- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/LicenseSign/LicenseSign/Program.cs b/LicenseSign/LicenseSign/Program.cs index 185f86f..d9599fe 100644 --- a/LicenseSign/LicenseSign/Program.cs +++ b/LicenseSign/LicenseSign/Program.cs @@ -16,16 +16,17 @@ class Program { static void Main(string[] args) { - if (args.Length != 2) + if (args.Length != 3) { - Console.WriteLine("path_to_license_file name_of_certificate"); + Console.WriteLine("path_to_license_file path_to_private_certificate password_of_private_certificate"); Console.WriteLine("Not enough parameters passed"); return; } string licenseFilePath = args[0]; //string certificateFilePath = args[1]; - string certificateName = args[1]; + string certificatePath = args[1]; + string certificatePassword = args[2]; JObject licenseFile = JObject.Parse(File.ReadAllText(licenseFilePath)); JObject licensedFeatures = (JObject)licenseFile["LicensedFeatures"]; @@ -45,15 +46,8 @@ static void Main(string[] args) //X509Certificate2 privateCert = new X509Certificate2(certificateFilePath, securePassword); // Instead it needs to be present in the MyStore - X509Store store = new X509Store(StoreLocation.CurrentUser); - X509Certificate2 privateCert = null; - store.Open(OpenFlags.ReadOnly); - X509Certificate2Collection cers = store.Certificates.Find(X509FindType.FindBySubjectName, certificateName, false); - if (cers.Count > 0) - { - privateCert = cers[0]; - }; - store.Close(); + X509Certificate2 privateCert = new X509Certificate2(certificatePath, certificatePassword, X509KeyStorageFlags.Exportable); + Console.WriteLine("Certificate: {0}", privateCert); var bytesSigned = SignData(privateCert, bytesToSign); diff --git a/LicenseSign/LicenseSign/Properties/launchSettings.json b/LicenseSign/LicenseSign/Properties/launchSettings.json index 77b6032..1755016 100644 --- a/LicenseSign/LicenseSign/Properties/launchSettings.json +++ b/LicenseSign/LicenseSign/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "LicenseSign": { "commandName": "Project", - "commandLineArgs": "\"C:\\appdynamics\\AppDynamics.DEXTER\\LicenseSign\\LicenseFiles\\LicensedFeatures.standard.json\" \"AppDynamics DEXTER Licensing\"" + "commandLineArgs": "\"path_to_license_file\" \"path_to_private_certificate\" \"password_of_private_certificate\"" } } } \ No newline at end of file diff --git a/LicenseSign/Readme.txt b/LicenseSign/Readme.txt index 7d11322..0b82a74 100644 --- a/LicenseSign/Readme.txt +++ b/LicenseSign/Readme.txt @@ -12,4 +12,21 @@ Third, sign the license file via LicenseSign This is .NET Core 3.1 Uses private key from the cert in My personal store to sign the license file -Fourth, read the license file and validate signature from DEXTER \ No newline at end of file +Fourth, read the license file and validate signature from DEXTER + + + +------Updated Instructions 9/23/2020--------- +There should never be a need to issue a new cert with the IssueCertificate program. +- Valid public cert is available in Dexter root project directory AppDynamics.DEXTER.public.cer +- Valid private cert is hosted on AppDynamics Google Drive. + - email: leon.muntingh@appdynamics.com for access + +Signing a new license +- Run LicenseSign with the following arguments [path_to_license_file path_to_private_certificate password_of_private_certificate] + - path_to_license_file: license file on which to overwrite the "Signature" field + - default choose from any json from LicenseSign/LicenseFiles/* + - make sure to verify "ExpirationDateTime" + - new license will OVERWRITE old license + - path_to_private_certificate: from Google Drive located in LicenseSign/IssueCertificate/bin/Debug/AppDynamics.DEXTER.private.pfx + - password_of_private_certificate: from Google Drive located in privatekeypassword.txt \ No newline at end of file From e0a23938c9884ce6bac77fa302eef8d89ed63429 Mon Sep 17 00:00:00 2001 From: Brad Hjelmar Date: Wed, 23 Sep 2020 10:13:07 -0400 Subject: [PATCH 3/5] undid previous commit --- ProcessingSteps/Index/IndexAPMMetricsList.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ProcessingSteps/Index/IndexAPMMetricsList.cs b/ProcessingSteps/Index/IndexAPMMetricsList.cs index adae1e9..cacbb0e 100644 --- a/ProcessingSteps/Index/IndexAPMMetricsList.cs +++ b/ProcessingSteps/Index/IndexAPMMetricsList.cs @@ -107,9 +107,7 @@ public override bool Execute(ProgramOptions programOptions, JobConfiguration job Dictionary backendDictionary = new Dictionary(); if (backendsList != null) { - backendDictionary = backendsList - .GroupBy(e => e.BackendName, StringComparer.OrdinalIgnoreCase) - .ToDictionary(e => e.Key, e => e.First(), StringComparer.OrdinalIgnoreCase); + backendDictionary = backendsList.ToDictionary(e => e.BackendName, e => e); } Dictionary informationPointDictionary = new Dictionary(); if (informationPointsList != null) From 37abc425a4daba632f81a75cf02e36d97e214b9e Mon Sep 17 00:00:00 2001 From: Anoop Date: Fri, 25 Sep 2020 05:12:09 -0700 Subject: [PATCH 4/5] Created a new project for the local cert signing --- .../LicenseSign.LocalCert.csproj | 12 ++ LicenseSign/LicenseSign.LocalCert/Program.cs | 143 ++++++++++++++++++ .../Properties/launchSettings.json | 8 + LicenseSign/LicenseSign.sln | 8 +- LicenseSign/LicenseSign/Program.cs | 18 ++- .../Properties/launchSettings.json | 2 +- global.json | 2 +- 7 files changed, 184 insertions(+), 9 deletions(-) create mode 100644 LicenseSign/LicenseSign.LocalCert/LicenseSign.LocalCert.csproj create mode 100644 LicenseSign/LicenseSign.LocalCert/Program.cs create mode 100644 LicenseSign/LicenseSign.LocalCert/Properties/launchSettings.json diff --git a/LicenseSign/LicenseSign.LocalCert/LicenseSign.LocalCert.csproj b/LicenseSign/LicenseSign.LocalCert/LicenseSign.LocalCert.csproj new file mode 100644 index 0000000..0eaee1b --- /dev/null +++ b/LicenseSign/LicenseSign.LocalCert/LicenseSign.LocalCert.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp3.1 + + + + + + + diff --git a/LicenseSign/LicenseSign.LocalCert/Program.cs b/LicenseSign/LicenseSign.LocalCert/Program.cs new file mode 100644 index 0000000..9aafb6f --- /dev/null +++ b/LicenseSign/LicenseSign.LocalCert/Program.cs @@ -0,0 +1,143 @@ +using Newtonsoft.Json.Linq; +using System; +using System.IO; +using Newtonsoft.Json; +using System.Text; +using System.Security; +using System.Net; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Security.Cryptography; + +namespace LicenseSign +{ + class Program + { + static void Main(string[] args) + { + if (args.Length != 3) + { + Console.WriteLine("path_to_license_file path_to_private_certificate password_of_private_certificate"); + Console.WriteLine("Not enough parameters passed"); + return; + } + + string licenseFilePath = args[0]; + //string certificateFilePath = args[1]; + string certificatePath = args[1]; + string certificatePassword = args[2]; + + JObject licenseFile = JObject.Parse(File.ReadAllText(licenseFilePath)); + JObject licensedFeatures = (JObject)licenseFile["LicensedFeatures"]; + + //string dataToSign = licensedFeatures.ToString(Formatting.Indented); + string dataToSign = licensedFeatures.ToString(Formatting.None); + Console.WriteLine("Signing {0}", dataToSign); + + var bytesToSign = Encoding.UTF8.GetBytes(dataToSign); + Console.WriteLine("Data to sign array length: {0}", dataToSign.Length); + + // Can't get this certificate to load the private key when it is loaded from local file system pfx + //Console.WriteLine("Specify password for {0}", certificateFilePath); + //String password = ReadPassword('*'); + //SecureString securePassword = new NetworkCredential("", password).SecurePassword; + + //X509Certificate2 privateCert = new X509Certificate2(certificateFilePath, securePassword); + + // Instead it needs to be present in the MyStore + X509Certificate2 privateCert = new X509Certificate2(certificatePath, certificatePassword, X509KeyStorageFlags.Exportable); + + Console.WriteLine("Certificate: {0}", privateCert); + + var bytesSigned = SignData(privateCert, bytesToSign); + Console.WriteLine("Signed data array length: {0}", bytesSigned.Length); + + bool validated = ValidateData(privateCert, bytesSigned, bytesToSign); + Console.WriteLine("Is Validated: {0}", validated); + + string signedString = Convert.ToBase64String(bytesSigned); + Console.WriteLine("Signature {0}", signedString); + + licenseFile["Signature"] = signedString; + + Console.WriteLine("Saving {0}", licenseFile); + using (StreamWriter sw = File.CreateText(licenseFilePath)) + { + JsonSerializer serializer = new JsonSerializer(); + serializer.NullValueHandling = NullValueHandling.Include; + serializer.Formatting = Newtonsoft.Json.Formatting.Indented; + serializer.Serialize(sw, licenseFile); + } + } + + public static byte[] SignData(X509Certificate2 certificate, byte[] dataToSign) + { + var rsaPrivateKey = certificate.GetRSAPrivateKey(); + var parameters = rsaPrivateKey.ExportParameters(true); + Console.WriteLine("Certificate private key RSA parameters were successfully exported."); + + var privateKey = certificate.PrivateKey; + Console.WriteLine("Certificate private key is accessible."); + + // generate new private key in correct format + var cspParams = new CspParameters() + { + ProviderType = 24, + ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider" + }; + var rsaCryptoServiceProvider = new RSACryptoServiceProvider(cspParams); + rsaCryptoServiceProvider.ImportParameters(parameters); + + // sign data + var signedBytes = rsaCryptoServiceProvider.SignData(dataToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + return signedBytes; + } + + public static bool ValidateData(X509Certificate2 certificate, byte[] signature, byte[] dataToValidate) + { + var rsaPublicKey = certificate.GetRSAPublicKey(); + + bool validationResult = rsaPublicKey.VerifyData(dataToValidate, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + + return validationResult; + } + + public static string ReadPassword(char mask) + { + const int ENTER = 13, BACKSP = 8, CTRLBACKSP = 127; + int[] FILTERED = { 0, 27, 9, 10 /*, 32 space, if you care */ }; // const + + var pass = new Stack(); + char chr = (char)0; + + while ((chr = System.Console.ReadKey(true).KeyChar) != ENTER) + { + if (chr == BACKSP) + { + if (pass.Count > 0) + { + System.Console.Write("\b \b"); + pass.Pop(); + } + } + else if (chr == CTRLBACKSP) + { + while (pass.Count > 0) + { + System.Console.Write("\b \b"); + pass.Pop(); + } + } + else if (FILTERED.Count(x => chr == x) > 0) { } + else + { + pass.Push((char)chr); + System.Console.Write(mask); + } + } + + return new string(pass.Reverse().ToArray()); + } + } +} diff --git a/LicenseSign/LicenseSign.LocalCert/Properties/launchSettings.json b/LicenseSign/LicenseSign.LocalCert/Properties/launchSettings.json new file mode 100644 index 0000000..cbea827 --- /dev/null +++ b/LicenseSign/LicenseSign.LocalCert/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "LicenseSign.LocalCert": { + "commandName": "Project", + "commandLineArgs": "\"path_to_license_file\" \"path_to_private_certificate\" \"password_of_private_certificate\"" + } + } +} \ No newline at end of file diff --git a/LicenseSign/LicenseSign.sln b/LicenseSign/LicenseSign.sln index be204db..193bae3 100644 --- a/LicenseSign/LicenseSign.sln +++ b/LicenseSign/LicenseSign.sln @@ -12,7 +12,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\src\LicensedFeatures.json = ..\src\LicensedFeatures.json EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LicenseValidate", "LicenseValidate\LicenseValidate.csproj", "{A9E80274-7E9A-4EB7-9360-BE54F95DD854}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LicenseValidate", "LicenseValidate\LicenseValidate.csproj", "{A9E80274-7E9A-4EB7-9360-BE54F95DD854}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LicenseSign.LocalCert", "LicenseSign.LocalCert\LicenseSign.LocalCert.csproj", "{707B8596-7E21-4BBF-9EFB-825D4D17832A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -32,6 +34,10 @@ Global {A9E80274-7E9A-4EB7-9360-BE54F95DD854}.Debug|Any CPU.Build.0 = Debug|Any CPU {A9E80274-7E9A-4EB7-9360-BE54F95DD854}.Release|Any CPU.ActiveCfg = Release|Any CPU {A9E80274-7E9A-4EB7-9360-BE54F95DD854}.Release|Any CPU.Build.0 = Release|Any CPU + {707B8596-7E21-4BBF-9EFB-825D4D17832A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {707B8596-7E21-4BBF-9EFB-825D4D17832A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {707B8596-7E21-4BBF-9EFB-825D4D17832A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {707B8596-7E21-4BBF-9EFB-825D4D17832A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/LicenseSign/LicenseSign/Program.cs b/LicenseSign/LicenseSign/Program.cs index d9599fe..185f86f 100644 --- a/LicenseSign/LicenseSign/Program.cs +++ b/LicenseSign/LicenseSign/Program.cs @@ -16,17 +16,16 @@ class Program { static void Main(string[] args) { - if (args.Length != 3) + if (args.Length != 2) { - Console.WriteLine("path_to_license_file path_to_private_certificate password_of_private_certificate"); + Console.WriteLine("path_to_license_file name_of_certificate"); Console.WriteLine("Not enough parameters passed"); return; } string licenseFilePath = args[0]; //string certificateFilePath = args[1]; - string certificatePath = args[1]; - string certificatePassword = args[2]; + string certificateName = args[1]; JObject licenseFile = JObject.Parse(File.ReadAllText(licenseFilePath)); JObject licensedFeatures = (JObject)licenseFile["LicensedFeatures"]; @@ -46,8 +45,15 @@ static void Main(string[] args) //X509Certificate2 privateCert = new X509Certificate2(certificateFilePath, securePassword); // Instead it needs to be present in the MyStore - X509Certificate2 privateCert = new X509Certificate2(certificatePath, certificatePassword, X509KeyStorageFlags.Exportable); - + X509Store store = new X509Store(StoreLocation.CurrentUser); + X509Certificate2 privateCert = null; + store.Open(OpenFlags.ReadOnly); + X509Certificate2Collection cers = store.Certificates.Find(X509FindType.FindBySubjectName, certificateName, false); + if (cers.Count > 0) + { + privateCert = cers[0]; + }; + store.Close(); Console.WriteLine("Certificate: {0}", privateCert); var bytesSigned = SignData(privateCert, bytesToSign); diff --git a/LicenseSign/LicenseSign/Properties/launchSettings.json b/LicenseSign/LicenseSign/Properties/launchSettings.json index 1755016..6b0ebf6 100644 --- a/LicenseSign/LicenseSign/Properties/launchSettings.json +++ b/LicenseSign/LicenseSign/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "LicenseSign": { "commandName": "Project", - "commandLineArgs": "\"path_to_license_file\" \"path_to_private_certificate\" \"password_of_private_certificate\"" + "commandLineArgs": "\"path_to_license_file\" \"certificate_name_in_local_store\" " } } } \ No newline at end of file diff --git a/global.json b/global.json index 67fa600..13b2c4c 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "3.1.100" + "version": "3.1.401" } } \ No newline at end of file From 54359ff05c1abb99b1a7b367226f8d24b608eb5d Mon Sep 17 00:00:00 2001 From: Anoop Date: Fri, 25 Sep 2020 05:50:08 -0700 Subject: [PATCH 5/5] Updated Readme on two different ways of signing --- LicenseSign/Readme.txt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/LicenseSign/Readme.txt b/LicenseSign/Readme.txt index 0b82a74..07c8e22 100644 --- a/LicenseSign/Readme.txt +++ b/LicenseSign/Readme.txt @@ -22,8 +22,16 @@ There should never be a need to issue a new cert with the IssueCertificate progr - Valid private cert is hosted on AppDynamics Google Drive. - email: leon.muntingh@appdynamics.com for access -Signing a new license -- Run LicenseSign with the following arguments [path_to_license_file path_to_private_certificate password_of_private_certificate] +Signing a new license can be done in two ways +1. If private cert is installed in your local PC with export parameters enabled. + - path_to_license_file: license file on which to overwrite the "Signature" field + - default choose from any json from LicenseSign/LicenseFiles/*. + - make sure to verify "ExpirationDateTime". + - new license will OVERWRITE old license. + - Name of the private cert installed in your PC cert store. + +2. If you have private cert file located in your PC and you know the password of the cert file +Run LicenseSign.LocalCert with the following arguments [path_to_license_file path_to_private_certificate password_of_private_certificate] - path_to_license_file: license file on which to overwrite the "Signature" field - default choose from any json from LicenseSign/LicenseFiles/* - make sure to verify "ExpirationDateTime"