This post is a product of not reading the full documentation. Lesson #1: read the docs thoroughly, or at least don’t stop reading when you think you’re done.
Publishing an ISV solution
One of the steps when you want to publish an ISV solution is generating the license for your customers. This license has to be signed using an Authenticode certificate that will enable the solution, limit the number of users or set an expiration date.
If you check the docs you can see the certificate must meet some minimum requirements:
- Authenticode certificate (X.509) from a certificate authority (CA)
- The certificate key size must be either of 1024-bit or 2048-bit. 4096-bit keys are not supported
And the idiot writing this stopped reading there, contacted the certificate reseller and ordered a CodeSign certificate with the above requirements. After some weeks I received the USB token and downloaded the certificate.
I was ready to finish developing the solutions and generate the licenses, so I continued reading the docs until I got to the “Certificate import and export“ section and I saw this:
- Personal Information Exchange (PFX, also known as PKCS #12) – The PKCS #12 format, which uses the .pfx file name extension, supports secure storage of certificates, private keys, and all certificates in a certification path. The PKCS #12 format is the only file format that can be used to export a certificate and its private key.
And now the best part: the purpose of a USB eToken is to hardware protect the private key, which makes exporting the private key impossible! I’m a stupid with an expensive private key inside a USB!
I’m NOT ordering another certificate
After contacting the reseller and the CA and confirming there’s no way of exporting the private key I started looking for a workaround.
How is the license created? Using the axutil command which also existed to manage models in AX2012. The excutable is found in the PackagesLocalDirectory’s bin folder, and there’s an AXUtilLib.dll library too.
Probably what I’ve done is not allowed by the license, but the available tools would not solve my issue and I’ve had to make my own 🤷♂️
I went on and disassembled the DLL and started checking the classes. The executable tool expects a certificatepath parameter, this should be the private key PFX file, but I don’t have it.
We have a class called AXUtil with a GenerateLicense method:
public bool GenerateLicense() { return new LicenseGenerator(this.config.LicenseInfo, this.context).GenerateLicense(); }
The this.Config.LicenseInfo parameter passed includes the arguments from the command line. So, instead of using the certificate from that path I’ll do the following:
public bool GenerateLicense() { X509Store store = new X509Store("My", StoreLocation.CurrentUser); store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates; X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false); X509Certificate2Collection scollection = X509Certificate2UI.SelectFromCollection(fcollection, "Certificate Select", "Select a certificate from the following list to sign the license", X509SelectionFlag.SingleSelection); if (scollection.Count == 0) { throw new System.NullReferenceException("No certificate loaded."); } foreach (X509Certificate2 x509 in scollection) { X509Certificate2 certificate = x509; return new LicenseGenerator(this.config.LicenseInfo, this.context).GenerateLicense(certificate); } return false; }
This will allow us to select which certificate to use (from out certificates) and will pass it to LicenseGenerator’s GenerateLicense method. This method accepts no parameters, but thanks to C#’s method overloading I can do this:
internal bool GenerateLicense() { X509Certificate2 certificate = this.LoadCertificate(); if (certificate == null) return false; return this.GenerateLicenseFile(this.GenerateSignature(certificate), certificate); } internal bool GenerateLicense(X509Certificate2 usbCertificate) { X509Certificate2 certificate = usbCertificate; if (certificate == null) return false; return this.GenerateLicenseFile(this.GenerateSignature(certificate), certificate); }
And now I can use my method. I’ve had to do one more change to the original AXUtilLib library: changing thee LicenseInfo setters to public.
ISVLicenseGenerator
I’ve created a graphic tool that makes use of this modified library and allows us to create a valid license file with a USB eToken:
The source code of this tool (and the modified DLL) and an executable file ready to use can be downloaded from GitHub, and you can do whatever you want with it. I just hope I don’t receive a notice from Microsoft…
As you can see all you need to do is select the license path and complete the data, like it’s done in the command line tool.
Click on “Generate” and the prompt to select your certificate will appear:
After selecting the certificate, the USB software will prompt for the password:
And done! We have a valid license file generated and ready to be deployed to the customer environments:
I hope that my stupidity will help someone!
14 Comments
Nice article ,
I have question , the X509Certificate2 certificate which we have to select after generate button , is this a self signed? if not then how to get that ,and same with .cert file which we attached with certificate in application object tree.
Thanks
The self-signed certificate is used only for testing purposes. To publish an ISV solution to AppSource you need to buy an Authenticode certificate. Once you got it you can use it’s public key to add it to the AOT as a .cert file, and the private key to sign the license.
The docs have some more info about the process.
Thanks so much for this. I’m looking at the project now.
One thing I don’t understand. How do you install the certificate on a cloud-hosted environment. It is my understanding the the eTokens don’t work through RDP.
That’s correct. You need to install the certificate and run the tool on your local PC, then take the txt license file to the cloud-hosted environment and you can install the license as described here: https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/dev-tools/isv-licensing#create-a-package-and-generate-a-customer-specific-license
Wouldn’t that mean you need D365 F&O installed on your local machine?
No, ISVLicenseGenerator is a standalone application. It only generates the txt license file that you need to enable the ISV solutions on a dev, sandbox or production environment.
Thanks, I thought the license generated connected to the AOS to verify the license and cert. My bad…
Which of these methods for storing the PK are supported by the tool (from Digicert cert request form):
– Digicert provided hardware token
– Use existing token
– Install on an HSM
– Digicert Keylocker (cloud HSM)
The tool will load certificates from the local PC certificate store and from hardware tokens connected to that PC if any is connected. So I guess that the first option is good, and the second one too if you already have a hardware USB token and will install the new certificate there.
Thanks for the reply and the tool. It helps a lot.
Hello Adria, I’m trying to deal with the cloud HSM from DigiCert. Do you have ideas regarding that instead of using the hardware USB token ?
Hi Cedric,
I haven’t worked with Digicert’s cloud HSM. Is it something hosting the certificates on a cloud service? If it is, maybe it has an API where you can request the keys, and changing the code of the ISVLicenseGenerator tool to call that API and sign the license?
Dear Adria, the blog is very helpful for us as we recently renewed and got certificate in USB non-exportable format. Before we use this app to generate license file from USB token. Do we need to also custom the code in our solution like you mentioned in the link. Please help
https://community.dynamics.com/blogs/post/?postid=b7db1c8d-8285-4d59-83b7-e9da755b7762
Pingback: ISVLicenseGenerator v0.9: a farewell? - ariste.info