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/Properties/launchSettings.json b/LicenseSign/LicenseSign/Properties/launchSettings.json
index 77b6032..6b0ebf6 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\" \"certificate_name_in_local_store\" "
}
}
}
\ No newline at end of file
diff --git a/LicenseSign/Readme.txt b/LicenseSign/Readme.txt
index 7d11322..07c8e22 100644
--- a/LicenseSign/Readme.txt
+++ b/LicenseSign/Readme.txt
@@ -12,4 +12,29 @@ 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 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"
+ - 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
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