Introduction
[Update Highlights]
2016.11 Thanks for the comments pointing out the missing part for validating UID in the demo. The bug was fixed, you may get the latest codes from CodeProject or GitHub. No changes to the core library, keep core lib's version as 1.1, and Demo App was updated to version 1.2.
2016.9 Thanks to Member 12119377's comments on public key & private key files' protection, so I've modified the library and let the public key & private key files as embedded resource of the assembly to ensure they are unable to be replaced easily. Also, updated library version to 1.1.
First, I'd like to show you a live demo for this solution.
A Simple Example
I had an application having features 1, 2 and 3, and I wanted the user to pay for each feature they like instead of paying for the whole package. So I designed a license solution to make this possible.
Current Solution
- My application launches and finds itself not activated:
- It displays the activation form and shows the current device UID for purchasing a license:
- User shall give this device UID to our vendor to purchase the license, via either mail or phone.
- At our vendor side, there is a license issuer program to issue the license based on the device UID and those features the user bought.
- After license is generated, vendor shall paste the license string into a text file or mail to our user for activation.
- User use paste the license string into the activation form to activate the application.
Well, if this gets you interested, please read on.
The highlights of this solution are:
- Digital signature technology is used to protect the license file.
- XML based license file allows it to contain rich information needed by the application.
- Supports both single license mode and volume license mode.
- For single license mode, PC's unique key is generated based on PC's hardware, and BASE36 algorithm is used to generate the unique key for easier usage.
As I learnt, the key concept of such a license solution has been introduced years ago, even there are similar articles on CodeProject as well. However, it seems to be no ready-to-use solution or library out there. So it is a chance for me to do it!
Background
I was working on a project that is a system having many key features, which are expected to be activated or deactivated based on the license provided. Bosses want the feature on/off mechanism of the licensing system to be highly flexible, so the idea of pre-defined different editions is not suitable for this purpose.
The benefit is that the end user can just pay for those features which he/she wants. E.g., user A can only pay for feature A, B & C; while user B only want feature A & C, and user C may only pay for feature B.
A good idea is to put those active features into a XML list and use this list as the license file. However, the next question is how to protect this license file since it is just a plain text XML. Finally, the digital signature solution answers the question.
Major Ideas
Main Workflow
The below chart describes the main workflow of this licensing solution:
Notes
- End User Application generates the Unique ID for the current PC.
- Unique ID will be passed to License Issuer. This shall be an offline step which may include additional actions such as purchase and payment.
- License Issuer issues the license file based on the Unique ID and license options, i.e., decides to enable which features are based on the purchase and payment.
- Send back the license file to end user. This is also an offline step, maybe by mail or USB drivers.
- End User Application verifies the license file and startup.
Unique ID for the Device
For now, this solution is only used on PC, also including servers & laptops. It has not been used in mobile devices yet. I think the algorithm to get unique ID for mobile devices shall be different. Here, we just talk about PC first.
The unique ID for a PC contains 4 parts:
- Application Name
- Processor ID
- Motherboard Serial Number
- Disk Volume Serial Number of Drive C
The four parts are concatenated as string
s, then checksum
is generated via MD5. And BASE36 encoding is used to format the checksum
into a string
like "XXXX-XXXX-XXXX-XXXX
" for easier reading and transferring.
public static string GenerateUID(string appName)
{
string _id = string.Concat(appName, GetProcessorId(),
GetMotherboardID(), GetDiskVolumeSerialNumber());
byte[] _byteIds = Encoding.UTF8.GetBytes(_id);
MD5CryptoServiceProvider _md5 = new MD5CryptoServiceProvider();
byte[] _checksum = _md5.ComputeHash(_byteIds);
string _part1Id = BASE36.Encode(BitConverter.ToUInt32(_checksum, 0));
string _part2Id = BASE36.Encode(BitConverter.ToUInt32(_checksum, 4));
string _part3Id = BASE36.Encode(BitConverter.ToUInt32(_checksum, 8));
string _part4Id = BASE36.Encode(BitConverter.ToUInt32(_checksum, 12));
return string.Format("{0}-{1}-{2}-{3}", _part1Id, _part2Id, _part3Id, _part4Id);
}
XML Based License File
So, in order to let the license file contain more information, an XML based license file is necessary. In C#, we can easily use XML Serializer to serialize the object into XML and vice verca.
The basic license entity is defined as below: (Some codes may contain Chinese, will do multiple languages later)
public abstract class LicenseEntity
{
[Browsable(false)]
[XmlIgnore]
[ShowInLicenseInfo(false)]
public string AppName { get; protected set; }
[Browsable(false)]
[XmlElement("UID")]
[ShowInLicenseInfo(false)]
public string UID { get; set; }
[Browsable(false)]
[XmlElement("Type")]
[ShowInLicenseInfo(true, "Type",
ShowInLicenseInfoAttribute.FormatType.EnumDescription)]
public LicenseTypes Type { get; set; }
[Browsable(false)]
[XmlElement("CreateDateTime")]
[ShowInLicenseInfo(true, "Creation Time",
ShowInLicenseInfoAttribute.FormatType.DateTime)]
public DateTime CreateDateTime { get; set; }
public abstract LicenseStatus DoExtraValidation(out string validationMsg);
}
It contains three default properties:
UID
, which is the device's unique ID (used to identify the specified device for single license. Not used for volume license) Type
, indicates it is a single license or volume license CreateDateTime
, indicates when the license is created
Since this is an abstract
class, you can just extend it for additional properties by inheriting it.
Protect the License File
OK, now we have the license file and enough information we need. The problem is that an XML file for licensing is very weak because anyone can modify it. Thus we need to introduce digital signature solution.
Generate a Certificate
To use digital signature, first of all, we need a pair of RSA keys. Private key for system owner to sign the XML file. And the public key for end user's application to verify the signed XML license file.
A easy way is to use "makecert
" command to generate the certificates which we need to sign and verify the XML license file. Details will be covered in the later part of this article, please keep reading!
Sign the License File
C# provides native support for digital signature, so it is very easy to sign the XML license file as below: (These codes are copied from Microsoft samples):
private static void SignXML(XmlDocument xmlDoc, RSA Key)
{
if (xmlDoc == null)
throw new ArgumentException("xmlDoc");
if (Key == null)
throw new ArgumentException("Key");
SignedXml signedXml = new SignedXml(xmlDoc);
signedXml.SigningKey = Key;
Reference reference = new Reference();
reference.Uri = "";
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(env);
signedXml.AddReference(reference);
signedXml.ComputeSignature();
XmlElement xmlDigitalSignature = signedXml.GetXml();
xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));
}
Now we are ready to publish the license for end users. The last 'optimization' is to flat the XML so I encoded it with BASE64 and put all the output into a plain text file as the license.
Verify the License File
When the license file file is installed on the end user's PC, the end user's application shall populate the public key file and verify the license file using it.
private static Boolean VerifyXml(XmlDocument Doc, RSA Key)
{
if (Doc == null)
throw new ArgumentException("Doc");
if (Key == null)
throw new ArgumentException("Key");
SignedXml signedXml = new SignedXml(Doc);
XmlNodeList nodeList = Doc.GetElementsByTagName("Signature");
if (nodeList.Count <= 0)
{
throw new CryptographicException
("Verification failed: No Signature was found in the document.");
}
if (nodeList.Count >= 2)
{
throw new CryptographicException
("Verification failed: More that one signature was found for the document.");
}
signedXml.LoadXml((XmlElement)nodeList[0]);
return signedXml.CheckSignature(Key);
}
If the above verification is successful, the application starts to read the XML contents and can turn related features on/off accordingly.
About the Library
The whole .NET solution is built based on .NET Framework Version 4.0, in order to provide maximum compatibility even for retired Windows XP.
What I Have Provided
There are five projects inside this solution, which are split into two parts:
Core Libraries
It contains two projects. One is QLicense which contains the core objects and logic. The other is ActivationControls4Win
which contains the related Windows Form Controls for easy usage.
Demo
This part contains a demo application which uses the above license solution and a demo activation tool which issues the license.
What You Need To Do
The steps below outline how to use this library. I will use those demo projects as a sample to describe how to create your own application that integrate this license solution.
1. Create Your Certificates for Your Application
It is recommended that you create a new set of certificates for each of your new applications, this is for security consideration.
- You can just use
makecert
command to do this like:
makecert -pe -ss My -sr CurrentUser -$ commercial -n "CN=<YourCertName>" -sky Signature
Replace "<YourCertName>
" with your own certificate name.
- After executing the above command, open your Certificate Management window by running "certmgr.msc".
- Find the created certificate under "
Personal
" category with the name you specified in <YourAppName>
above. - Right click on the certificate and select "All Tasks"->"Export"
- On the Export dialogue, select "Yes, export the private key" and leave the other settings as default.
- On the password diaglogue, input a password to protect the private key. You need to copy this password in your code when using this certificate. Example, we use password "demo" here.
- For the file name, we may use "LicenseSign.pfx" for this demo. Now we have the certificate with private key on hand.
- Do step 4) to step 7) again to export the public key, just the difference is to choose "No, do not export the private key" and use file name as "LicenseVerify.cer" instead. Leave all the other options as default.
Well, we now have both the private key and public key generated for our solution.
2. Create your own license entity class - "DemoLicense"
- You need to create a Class Library project which contains your own license entity, naming it "
DemoLicense
" for example. - Add Reference to QLicense library
- Create a new class named "
MyLicense
" and inherits "QLicense.LicenseEntity
" - Add your own properties as you need
And the final code shall look like:
using QLicense;
using System.ComponentModel;
using System.Xml.Serialization;
namespace DemoLicense
{
public class MyLicense : QLicense.LicenseEntity
{
[DisplayName("Enable Feature 01")]
[Category("License Options")]
[XmlElement("EnableFeature01")]
[ShowInLicenseInfo(true, "Enable Feature 01",
ShowInLicenseInfoAttribute.FormatType.String)]
public bool EnableFeature01 { get; set; }
[DisplayName("Enable Feature 02")]
[Category("License Options")]
[XmlElement("EnableFeature02")]
[ShowInLicenseInfo(true, "Enable Feature 02",
ShowInLicenseInfoAttribute.FormatType.String)]
public bool EnableFeature02 { get; set; }
[DisplayName("Enable Feature 03")]
[Category("License Options")]
[XmlElement("EnableFeature03")]
[ShowInLicenseInfo(true, "Enable Feature 03",
ShowInLicenseInfoAttribute.FormatType.String)]
public bool EnableFeature03 { get; set; }
public override LicenseStatus DoExtraValidation(out string validationMsg)
{
validationMsg = string.Empty;
return LicenseStatus.VALID;
}
}
}
Notes
XmlElement
attribute indicates the element name when the object is serilized into XML. ShowInLicenseInfo
attribute indicates whether this property shall be displayed in LicenseInfoControl
which is a WinForm control contained in ActivationControls4Win
project.
3. Integrate QLicense library with your application - "DemoWinFormApp"
- Add reference to
DemoLicense
, QLicense
and ActivationControls4Win
- Add LicenseVerify.cer generated in step 1 into the project, make it as Embedded Resource and Do not copy to Output Directory in the file property settings.
- Put the
LicenseInfoControl
from ActivationControls4Win
onto the form you'd like to show license details, such as About form. For the demo, I just put it on the main form.
- The main logic for the main form is to validate the license and inform user to activate the application if needed. I put the logic in
Form_Shown
event in order to let main form shown in the backgound for better user experience. You may put this logic into a splash form or other place.
private void frmMain_Shown(object sender, EventArgs e)
{
MyLicense _lic = null;
string _msg = string.Empty;
LicenseStatus _status = LicenseStatus.UNDEFINED;
Assembly _assembly = Assembly.GetExecutingAssembly();
using (MemoryStream _mem = new MemoryStream())
{
_assembly.GetManifestResourceStream("DemoWinFormApp.LicenseVerify.cer").CopyTo(_mem);
_certPubicKeyData = _mem.ToArray();
}
if (File.Exists("license.lic"))
{
_lic = (MyLicense)LicenseHandler.ParseLicenseFromBASE64String(
typeof(MyLicense),
File.ReadAllText("license.lic"),
_certPubicKeyData,
out _status,
out _msg);
}
else
{
_status = LicenseStatus.INVALID;
_msg = "Your copy of this application is not activated";
}
switch (_status)
{
case LicenseStatus.VALID:
licInfo.ShowLicenseInfo(_lic);
return;
default:
MessageBox.Show(_msg, string.Empty, MessageBoxButtons.OK,
MessageBoxIcon.Warning);
using (frmActivation frm = new frmActivation())
{
frm.CertificatePublicKeyData = _certPubicKeyData;
frm.ShowDialog();
Application.Exit();
}
break;
}
}
- Add a new form named
frmActivation
and put LicenseActivateControl
from ActivationControls4Win
onto it. This is the form user needed to enter license content and activate your application.
- Here is the main logic for
frmActivation
. It will display the calculated device UID once the form popup:
public partial class frmActivation : Form
{
public byte[] CertificatePublicKeyData { private get; set; }
public frmActivation()
{
InitializeComponent();
}
private void btnCancel_Click(object sender, EventArgs e)
{
if (MessageBox.Show("Are you sure to cancel?",
string.Empty, MessageBoxButtons.YesNo, MessageBoxIcon.Warning,
MessageBoxDefaultButton.Button2) == DialogResult.Yes)
{
this.Close();
}
}
private void frmActivation_Load(object sender, EventArgs e)
{
licActCtrl.AppName = "DemoWinFormApp";
licActCtrl.LicenseObjectType = typeof(DemoLicense.MyLicense);
licActCtrl.CertificatePublicKeyData = this.CertificatePublicKeyData;
licActCtrl.ShowUID();
}
private void btnOK_Click(object sender, EventArgs e)
{
if (licActCtrl.ValidateLicense())
{
File.WriteAllText(Path.Combine(Application.StartupPath, "license.lic"),
licActCtrl.LicenseBASE64String);
MessageBox.Show("License accepted, the application will be close.
Please restart it later", string.Empty,
MessageBoxButtons.OK, MessageBoxIcon.Information);
this.Close();
}
}
}
4. Create Your License Issuer Tool - "DemoActivationTool"
- Create a new Windows Form Application project, named "
DemoActivationTool
". - Add References to
DemoLicense
, QLicense
and ActivationControls4Win
- Add LicenseSign.pfx generated in step 1 into the project, make it as Embedded Resource and Do not copy to Output Directory in the file property settings.
- Draw the control
LicenseSettingsControl
and LicenseStringContainer
on the main form, it may look like:
- Add the following codes for main form. Details are explained inline with the codes as comments below:
public partial class frmMain : Form
{
private byte[] _certPubicKeyData;
private SecureString _certPwd = new SecureString();
public frmMain()
{
InitializeComponent();
_certPwd.AppendChar('d');
_certPwd.AppendChar('e');
_certPwd.AppendChar('m');
_certPwd.AppendChar('o');
}
private void frmMain_Load(object sender, EventArgs e)
{
Assembly _assembly = Assembly.GetExecutingAssembly();
using (MemoryStream _mem = new MemoryStream())
{
_assembly.GetManifestResourceStream
("DemoActivationTool.LicenseSign.pfx").CopyTo(_mem);
_certPubicKeyData = _mem.ToArray();
}
licSettings.CertificatePrivateKeyData = _certPubicKeyData;
licSettings.CertificatePassword = _certPwd;
licSettings.License = new MyLicense();
}
private void licSettings_OnLicenseGenerated
(object sender, QLicense.Windows.Controls.LicenseGeneratedEventArgs e)
{
licString.LicenseString = e.LicenseBASE64String;
}
private void btnGenSvrMgmLic_Click(object sender, EventArgs e)
{
licString.LicenseString = LicenseHandler.GenerateLicenseBASE64String(
new MyLicense(),
_certPubicKeyData,
_certPwd);
}
}
That's all for this guide, the running result has already been shown at the beginning of this article.
Feel free to leave your comments and good ideas below.
History
- May 2015: Released Version 1.0
- September 2016: Released Version 1.1. For security consideration, embedded public key and private key file as embedded resource of the assembly instead of single local files.