Need Quality Code? Get Silver Backed

Remote Licensing

11thNov

0

by Gary H

Liberty is the right to do what I like; license, the right to do what you like

Bertrand Russell

In our previous post we looked at how we could use asymmetric cryptography to sign a license to detect tampering. In this post we will extend our sample application to request a license from a (simulated) remote server using asymmetric encryption to secure the request. We will also use symmetric encryption to secure the response from the server.

Overview

We begin by preparing a license request. Next we encrypt this with the applications public key for transmission to the server. The server decrypts the request with its private key, processes the license request and returns a signed, tamper-proof license encrypted with the agreed symmetric key. The final stage is the client verifying that the returned license is valid.

Why encrypt?

We encrypt our message to the server with our public key so that an attacker listening on the wire cannot see the contents of a license request. The license request message itself contains a pre-generated symmetric key prepared by the client. We encrypt the response form the server with this key to again protect the transmission from snooping by an attacker.

Why use symmetric encryption for the response?

  1. Negates the problem of key distribution - We send the key with the request and don't need to worry about private keys shipping with the application. If we used asymmetric encryption we would need either a seperate private key for each application (and a corresponding way of working out which public key to use) or every application would use the same private key which, if it were compromised, would allow an attacker to decrypt all license responses to any client.
  2. The key is unique to the client for that single request - If an attacker was to record all of the responses from our server to all clients to attack the encryption offline, each message would have to be attacked individually. Similarly, if a key is compromised for a single request only that request is vulnerable and no others.
  3. Faster - Asymmetric encryption is slower than symmetric. If we were handling a large number of requests this would reduce the load of the server.

The scheme that we are using is very similar in many ways to that used by SSL.

Implementation

We begin by defining the license request message that the client will issue. The message must contain the unique identifying information about the license along with any other data that needs to be sent to the server. In our case this means storing a license key along with a pre-generated symmetric key and initialisation vector.

[Serializable]
public class LicenseRequestMessage
{
    public byte[] SharedKey { get; set; }
    public byte[] SharedIv { get; set; }
    public string LicenseKey { get; set; }

    public byte[] SerializeAndEncrypt()
    {
        byte[] rawBytes;
        using (var msOut = new MemoryStream())
        {
            new BinaryFormatter().Serialize(msOut, this);
            rawBytes = msOut.ToArray();
        }

        return Utilities.AsymmetricEncrypt(Assembly.GetExecutingAssembly(), rawBytes);
    }
}

Next we will stub up a remote server. This will be a simulated remote server based on our prior work. You could just as easily implement this behind a web service call or some other communication method.

Our server needs methods for receiving a license request and for issuing a license. We will use the code for issuing a license from our prior work. In order to handle a license request we need to add a few steps of our own. The signature for this method receives a byte array - this is because we are receiving a binary blob for the license request. Within the method we need to decrypt the blob, perform a license lookup and issue then serialize and re-encrypt our response. Leaning on the utility methods from the previous encryption series this gives us:

public class RemoteLicenseServer
{
    private readonly string[] _goodLicenses = new [] { "TESTLIC001", 
                                                        "TESTLIC002" };

    private static string IssueLicense(DateTime expiry, string uniqueKey)
    {
        var sbXml = new StringBuilder();
        using (var swOut = new StringWriter(sbXml))
        using (var xmlOut = new XmlTextWriter(swOut))
        {
            xmlOut.WriteStartDocument();
            xmlOut.WriteStartElement("License");
                xmlOut.WriteStartElement("Key");
                xmlOut.WriteString(uniqueKey);
                xmlOut.WriteEndElement();
                xmlOut.WriteStartElement("IssueDate");
                xmlOut.WriteString(
                    DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss"));
                xmlOut.WriteEndElement();
                xmlOut.WriteStartElement("Expires");
                xmlOut.WriteString(expiry.ToString("dd/MM/yyyy HH:mm:ss"));
                xmlOut.WriteEndElement();
                xmlOut.WriteStartElement("IssuedBy");
                xmlOut.WriteString("Demo Licensing Server");
                xmlOut.WriteEndElement();
            xmlOut.WriteEndElement();
            xmlOut.WriteEndDocument();
            xmlOut.Close();
        }

        var privateKey = Utilities.GetRSAFromSnkFile("DemoPubPrivPair.snk");
        return SignAndVerify.SignXmlFile(sbXml.ToString(), privateKey);
    }

    public byte[] RequestLicense(byte[] licenseRequestMessage)
    {
        try
        {
            var rawMessageBytes = 
                Utilities.AsymmetricDecrypt(licenseRequestMessage, 
                    Path.Combine( 
                        Path.GetDirectoryName(
                            Assembly.GetExecutingAssembly().Location), 
                            "DemoPubPrivPair.snk"));
            var messageReader = new BinaryFormatter();
            LicenseRequestMessage message;
            
            using (var msIn = new MemoryStream(rawMessageBytes))
            {
                message = (LicenseRequestMessage)messageReader
                                .Deserialize(msIn);
            }
            
            if (!_goodLicenses.Contains(message.LicenseKey))
            {
                return null;
            }

            var license = IssueLicense(DateTime.Now.AddDays(1), 
                                message.LicenseKey);
            byte[] serializedLicense;
            using (var msOut = new MemoryStream())
            {
                messageReader.Serialize(msOut, license);
                serializedLicense = msOut.ToArray();
            }

            return Utilities.SymmetricEncrypt(serializedLicense, 
                        message.SharedKey, message.SharedIv);

        }
        catch (Exception)
        {
            throw;
            return null;
        }
    }
}

Finally we need to add code in the client to actually make the request. We need to create the request, populate its required fields, serialize it into a byte array for encryption, encrypt it with the servers public key and then make the call itself. We have simplified some of this by combining the serialization and encryption into a helper method on the license request itself. The remaining calls take the form of:

byte[] request;
byte[] response;
ClientLicense license;
LicenseRequestMessage messageFromClient;


var server = new RemoteLicenseServer();

var algo = RijndaelManaged.Create();
algo.GenerateKey();
algo.GenerateIV();

var validKey1 = "TESTLIC001";

messageFromClient = new LicenseRequestMessage
    {
        LicenseKey = validKey1,
        SharedIv = algo.IV,
        SharedKey = algo.Key
    };


request = messageFromClient.SerializeAndEncrypt();
response = server.RequestLicense(request);
license = new ClientLicense(response, messageFromClient);

And there we have it. A reasonably robust license request that cannot be snooped on by an attacker and which an attacker cannot mimic without some significant effort.

Conclusions

In this series we have looked at cryptography from the small building blocks of symmetric and asymmetric encryption through to a practical use of cryptographic techniques in order to thwart observation and to detect tampering with data. Is this licensing system perfect? No, not at all. This article has explored only a practical use of cryptography without addressing security in general. To make this system truly robust we would need to consider things like replay attacks, timing attacks, oracle attacks and many many more. Hopefully though this series has given you a taste of just how useful cryptography can be in the resolution of some commonly faced problems.

You can download a copy of the demo solution from http://media.leapinggorilla.com/Files/LeapingGorilla.Demo.Licensing.zip

C# , Security , Encryption

Comments are Locked for this Post