licensing is a .NET Standard class library for licensing and managing permissions in desktop applications.
The license key is produced by the accompanying backend service. The license key scheme prescribed for this project is based on the principles of asymmetric cryptography, signature and verification. The license key comprises of two parts; a data string, and a RSA private key signature of that data. The public key is to be shipped with the software and is included with this project.
Read an overview of the licensing scheme here.
The license data will be in csv format and encoded as a base 64 string.
email, epochTime, expiry, subscriptionTier
The email
and epochTime
values are of little use on the client, but they serve to provide a data string unique to each license. The epochTime
value is the time of license purchase/creation. The expiry
value is also crucial to the implementation of the licensing scheme. Any subsequent values added on are to convey information about the user's license and make decisions within the application. For this project, I have included subscriptionTier
. The subscription tier value is an integer representing the different access levels or tiers of the application's usage. There is no restriction on what this value can be, as long as it matches the Licensing.Enums.SubscriptionTier
Enum values.
You can extend this implementation to include values unique to your use case. For this, you will need to modify Licensing.Models.LicenseKeyDataModel
and Licensing.LicenseChecker.ParseLicenseKeyData()
. You can also extend the subscription tier information by modifying Licensing.Enums.SubscriptionTier
,
The license signature is also encoded as a base 64 string.
Verifying this signature against the data is what confers validity on the license key. You verify the license by making a call to Licensing.LicenseChecker.IsServerLicenseValid()
or Licensing.LicenseChecker.IsClientLicenseValid()
. The former accepts as a parameter, the license key from the server [1], while the latter accepts the license stored to persistent storage. Both methods have a bool
return type.
The expiry value is an integer representing the number of months a license is valid for. If you have a different use case, say weeks or years, you could modify the Licensing.LicenseChecker.IsLicenseExpired()
method to use one of Licensing.LicenseChecker.Year
, Licensing.LicenseChecker.Month
, or Licensing.LicenseChecker.Week
constant properties.
To check the expiration status of a license, make a call to Licensing.LicenseChecker.IsLicenseExpired()
. This method takes the license stored to persistent storage as a parameter.
Use the Licensing.LicenseChecker.ParseLicenseKeyPrefs()
method to convert your server gotten license key into a Licensing.Models.LicenseKeyPrefsModel
[2] object which can be serialized to disk and stores the license key data on the user machine.
Licensing.LicenseCore
provides static methods for saving and retrieving the license key to and from disk.
There is a collection of custom exception classes in Licensing.Exceptions
. These exceptions serve to report on the unique type of error and allows your application more robust exception handling.
How you handle these exceptions and where is entirely up to you.
You are to set the RSA public key key-string in Licensing.Constants.LicensePublicKey.Initialize()
. This is best done as a string assignment. You could alternatively set up a config or env file that feeds into this assignment. Protection of the public key is not priority, or even practical. This project is meant for client side deployment and anyone with time and a decompiler can extract that key-string[3].
"user@example.com, 1600163621.4321098, 12, 2"
using System;
using Licensing;
using Licensing.Models;
var licenseKey = new LicenseKeyModel {
Signature="license-signature-string-fkwPBfjdcwQmspfokGDWewdFGfkmclwdfopewgihVJnHGempofdiDSewfSFh",
Data="license-data-string-sfnDdljfe3GbwoK9sLmfJiurhbgfbDSunw"
};
Console.WriteLine(LicenseChecker.IsServerLicenseValid(licenseKey));
var licenseKeyPrefs = LicenseChecker.ParseLicenseKeyPrefs(licenseKey);
LicenseCore.SaveLicensePrefs(licenseKeyPrefs);
var prefs = LicenseCore.GetCurrentLicensePrefs();
Console.WriteLine(prefs.Data.Email);
Console.WriteLine(prefs.Data.EpochTime);
Console.WriteLine(prefs.Data.Subscription.ToString());
Console.WriteLine(prefs.Data.Expiry);
Console.WriteLine(LicenseChecker.IsClientLicenseValid(prefs));
Console.WriteLine(LicenseChecker.IsLicenseExpired(prefs));
True
user@example.com
1600163621.4321098
Plus
12
True
False
Download the repo as zip. Extract the contents into your solution directory. Add the licensing project to your solution.
$ dotnet sln <SLN_FILE> add src/Licensing/Licensing.csproj
licensing uses the Bouncy Castle cryptography library. A DLL is included with this repo in the /lib directory.
You could deliver the license key to the client through two channels. One channel would be over http/https through a web API. The other is as a plain text license file containing the signature and data downloaded from the server.
Your choice will depend on your constraints and use cases. Either way, you will need to implement your own wrapper to retrieve the license key and convert it to an object of Licensing.Models.LicenseKeyModel
.
In certain scenarios, there is a lag between the time a license is purchased and when it is first activated. If you are only interested in the purchase date, then Licensing.Models.LicenseKeyPrefsModel.Data
's EpochTime
property holds that information. You can convert this value to System.DateTime
and assign it to Licensing.Models.LicenseKeyPrefsModel.ActivatedAt
before calling Licensing.LicenseCore.SaveLicensePrefs()
.
In other scenarios, you might be interested in the activation date of the license instead. For instance, where a reseller program is in place. In this case, you would want the validity of a license to begin at activation and not at purchase. You could set up a one-time internet connection to your server for this purpose. One way would be to store the "ActivatedAt" value for each license on the server. Subsequent use of the same license would have an attached "ActivatedAt" value and you will be able to implement validity properly across devices.
Now, this server "ActivatedAt" value is not private key signed and could be subject to tampering. It is up to you to establish a secure channel of communication between your server and the client.
You can set up a system that allows updating the asymmetric key pair for each different version of your application. The private keys of all the different versions will be managed by the server, and the corresponding public keys deployed with each respective version. It is important that you get this match right. It is also important that you protect your private key(s). Anyone with a private key can generate limitless amounts of license keys for that version of your software (keygen).
Your application can be cracked if decompiled and your access control statements are bypassed. I recommend looking into obfuscation of critical code sections to make the process a little more difficult.
Read this article by Michael Herrmann about generating license keys on the server side.