Licensing Sever using Azure Function and Portable.Licensing

Licensing is a requirement for any paid software to ensure permitted usage of the software. A simple software licensing can be implemented locally but it doesn’t prevent use of the same software in multiple computers; This requires the license to be locked on to a single machine which can be achieved through a licensing server. There are multiple services that would handle this for you, or if you want to get your hands dirty – I have given a brief description of the licencing server I have implemented.

Licensing flow

1. User receives the Serial Key for the software
2. User enters the Serial Key for registration on the client
3. The Serial Key and a unique computer ID is registered on the serverr
4. License is generated on the server and stored in the user’s computer
5. License is validated locally every time

Stack

Looking into multiple opensource licensing libraries, Portable.Licesning library seemed to be the best solution. The library generates a private and public key based on a password phrase. The private key is used to generate the license and the public key is used by the user for validation. Follow the Pluralsight course here for better understanding of how the library works.
The licensing server is hosted on Microsoft Azure using Azure Functions and Azure Table Storage.

Server

Azure Functions provides us a server-less architecture which can be triggered using web-hooks and contains the code to receive, process and generate the license. The Azure Table Storage contains the details of each license provided to the user. The below picture shows an example of a license.
Licensing Server - Azure Table Storage Example
Licensing Server – Azure Table Storage Example
The server is accessed by an HTTP request which contains a JSON string with the Serial Key and the CPU ID of the machine to be licensed. On the server the  Serial Key is used to retrieve the details from the table and used to generate a license that is sent as HTTP reply. The licensing details in the table is updated to the CPU registered. Updating the details of the license on the server will reflect on the software when re-verification is performed.
Here is the code for licensing server,
#r "Microsoft.WindowsAzure.Storage"
#r "Newtonsoft.Json"

using System.Net;
using Newtonsoft.Json;
using Microsoft.WindowsAzure.Storage.Table;
using Portable.Licensing;

private static string privateKey = "MIICITAjBgoqhkiG9w0BDAEDMBUEEBg0LQE4xWGOjLpiHC6GJ+kCAQoEggH4TrHH7v5kvPQEgq6QQLM9sGi2TgWH6uTEgvYM1WmQ9v01NfWbuBx2OCFlpdt8e+ZpdkmznGeiHH6B837h5BuJlk333bfZQbJ3prjDrfpnerAg3+qF6UdFP7sfj7qur7hN8ZD2Etde2NQ6ij45LHwX6gx4+ZGF/8lvwvVXEgBFuwtPg+MHHKAMcxw/QQt3T1S/HfahGV+I8kDEnLcugBniSFDUjCMnMGDT6nqtfu0DKWouu5s62aZrk+wUSUYinZcbgd2zMbuAPn5o+udUrNJMrDj1eC0UtiNduzw5aXOmUF/9gb4pTIQ96aBPSQQwkQYoDOsbQGPkYnM+FLQ1YYGGUYQAPcTlyUAIXOuE7DMMH3p7kRLiwhE18EAwzjPJXbtqBpb3ISScUEi96oxU4maUArZhiFWkuGXXTTLpmSBdYao2tkErWvX0GifTJ0b+LB5i5+PRGTkbAw2Y4EVMqLBXmniRFvmpayiLWurF1DnnXLErmHQ9CSLKzMQO/udkGk+9cenErG8WGHahaiNTnEXfSnB1ueXreM7tRXBCrAoRfS7g2lgmXeTQKanwamp2mdp4r4CjZeoy4P8WiPmBZ36nF5EeDV+7ZgBSMGHNRr/pXVcsZM9gXknaF/VUIJ0SD2E1ay/ExM6mZC/VRKV/hMmKVrYyj6lJZyHj";
private static string passPhrase = "foo";

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, IQueryable<LicenseEntity> inputTable, CloudTable outputTable, TraceWriter log)
{
    string parseData = await req.Content.ReadAsStringAsync();
    dynamic data = await req.Content.ReadAsAsync<object>();
    Data input = JsonConvert.DeserializeObject<Data>(parseData);

    LicenseEntity licenseEntity = inputTable.Where(l => (l.Serial == input.Serial)).SingleOrDefault();
   
    if(licenseEntity == null)
        return req.CreateResponse(HttpStatusCode.BadRequest, "Invalid Serial");
    else
    {
        if(licenseEntity.Registered)
        {
            if(licenseEntity.CPU != input.CPU)
                return req.CreateResponse(HttpStatusCode.BadRequest, "Serial already registered");
        }

        LicenseType licenseType;

        if(licenseEntity.LicenseType == "Standard")
            licenseType = LicenseType.Standard;
        else
            licenseType = LicenseType.Trial;

        var license = License.New()
                .WithUniqueIdentifier(new Guid(licenseEntity.Serial))
                .As(licenseType)
                .WithMaximumUtilization(licenseEntity.MaximumDevices)
                .LicensedTo(licenseEntity.User, licenseEntity.Email)
                .WithAdditionalAttributes(new Dictionary<string,string>{
                    {"CPU", input.CPU}
                })
                .CreateAndSignWithPrivateKey(privateKey, passPhrase);
        
        licenseEntity.CPU = input.CPU;
        licenseEntity.Registered = true;

        var operation = TableOperation.Replace(licenseEntity);
        await outputTable.ExecuteAsync(operation);

        return req.CreateResponse(HttpStatusCode.OK, license.ToString());
    } 
}

public class LicenseEntity : TableEntity
{
    public bool Registered { get; set; }
    public string User { get; set; }
    public string Email { get; set; }
    public string Serial { get; set; }
    public string LicenseType { get; set; }
    public int MaximumDevices { get; set; }
    public string CPU { get; set; }
}

public class Data
{
    public string Serial { get; set; }
    public string CPU { get; set; }
}

Client

The client calls the Azure Functions’ HTTP web-hook with a JSON string containing the Serial Key and CPU ID to generate a license for the software. The server generated license is stored locally for validation using the public key every time the application startups. Modifying the license file will make it invalid.
Here is the code for the licensing client,
using Newtonsoft.Json;
using Portable.Licensing;
using Portable.Licensing.Validation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Management;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace LicensedSoftware
{
    public static class Licensing
    {
        private static string PublicKey = "MIIBKjCB4wYHKoZIzj0CATCB1wIBATAsBgcqhkjOPQEBAiEA/////wAAAAEAAAAAAAAAAAAAAAD///////////////8wWwQg/////wAAAAEAAAAAAAAAAAAAAAD///////////////wEIFrGNdiqOpPns+u9VXaYhrxlHQawzFOw9jvOPD4n0mBLAxUAxJ02CIbnBJNqZnjhE50mt4GffpAEIQNrF9Hy4SxCR/i85uVjpEDydwN9gS3rM6D0oTlF2JjClgIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABKunTp+/M49vBtPnB6zqV/PfocZJkcarOqfBFfzXvnaefUhnJCYGi4ZuzHXKC7mAd/4hYo7wf9Ejtmflt0FvgMM=";
        private static string ServerURI = "Azure Function URL";
		
        public static License GetLicense()
        {
            if (!File.Exists(Directory.GetCurrentDirectory() + @"\License.lic"))
                return null;

            try
            {
                License license = License.Load(File.ReadAllText(Directory.GetCurrentDirectory() + @"\License.lic"));
                return license;
            }
            catch(Exception ex)
            {
                return null;
            }
        }

        public static bool ValidateLicense()
        {
            License license = GetLicense();

            var validationFailures = license.Validate()
                                .Signature(PublicKey)
                                .AssertValidLicense();

            foreach (var failure in validationFailures)
                return false;

            return true;
        }
        
        public static async Task GenerateLicense(LicenseData data)
        {
            HttpClient httpClient = new HttpClient();
            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain"));
            httpClient.Timeout = new TimeSpan(0, 2, 30);

            string responseString = string.Empty;
            string JSONString = JsonConvert.SerializeObject(data);

            try
            {
                HttpResponseMessage apiResponse = await httpClient.PostAsync(ServerURI, 
                    new StringContent(JSONString, Encoding.UTF8, "application/json"));

                responseString = await apiResponse.Content.ReadAsStringAsync();

                try
                {
                    License license = License.Load(responseString);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(responseString);
                    return;
                }

                File.WriteAllText(Directory.GetCurrentDirectory() + @"\License.lic", responseString);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Unable to access server");
            }
        }
        
        public static List<string> CPUID()
        {
            List<string> cpuInfo = new List<string>();
            ManagementClass mc = new ManagementClass("win32_processor");
            ManagementObjectCollection moc = mc.GetInstances();

            foreach (ManagementObject mo in moc)
            {
                cpuInfo.Add(mo.Properties["processorID"].Value.ToString());
            }
            return cpuInfo;
        }

        public class LicenseData
        {
            public string Serial { get; set; }
            public string CPU { get; set; }
        }
    }
}
Hope that was helpful I will soon implement a public key generation and licensing server to test and play with.