Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Using XML Digital Signatures for Application Licensing

0.00/5 (No votes)
23 Nov 2005 11  
Use XML Digital Signatures for a request- and signing-based licensing mechanism for your applications.

Introduction

At some point in most developers' lives, application licensing becomes a problem. Many solutions exist but are often expensive and difficult to implement. Few offer seamless integration with an existing code base. In such a case, writing an in-house licensing mechanism may be desired. Such a task, however, is faced with many problems.

One of the biggest challenges of any licensing mechanism is to create a scheme that can't be hacked by the user. Some schemes rely on the Internet to provide an authentication system. Obviously, such a scheme requires an Internet connection which can be a burden on the user. Putting information on the user's computer, however, also gives the user an opportunity to hack the licensing scheme. What's needed is a solution that can reside on a user's computer but one that can't be changed.

Such a solution does exist: XML Digital Signatures. Actually, public-key cryptography in general can solve the problem, but the ease and flexibility of both XML[1] and XML Digital Signatures[2] make using XML Digital Signatures an attractive solution.

This article will explain the basic premise public-key cryptography and how it relates to XML Digital Signatures, detail the creation of a key pair, give examples for signing and verifying an XML document, and discuss different implementations to provide reliable application licensing.

Note: this is not a full solution that you can simply add to your application, but a tutorial about how to sign XML using XML Digital Signatures. I offer a simple implementation only as an example. There are many third-party libraries that use similar techniques that are ready for integration into your product(s) and ready for deployment.

Cryptography

To put it simply, cryptography is merely the means of hiding text. Plain text is run through algorithms of varying complexity to produce cipher text, or unreadable text[4]. Converting plain text to cipher text is known as encryption, and converted cipher text back to plain text is known as decryption. While many developers may be inundated with the details, this doesn't have to be a complex process. Cryptography has been around circa 1900 BC. At first, cryptography was nothing more than "non-standard" glyphs. Even relatively modern cryptographic algorithms aren't always complex. ROT13 was - and perhaps still is - used in USENET to hide plain text from indexers by shifting alphabetical characters 13 spaces. So, an "A" becomes an "N", a "B" becomes an "O", and so on.

Admittedly, many modern cryptographic algorithms are complex, but you most often only need to know the basics to implement and use cryptography in your applications. The simplest form of cryptography uses symmetrical algorithms. This means that two or more parties share the same key (or passphrase) that is used to encrypt and decrypt information. ROT13, which was mentioned early, is just one of many examples of symmetrical keys, where the key is simply the rule that characters should be shifted by 13 spaces. Most modern cryptographic algorithms use asymmetrical algorithms, when each party has both a private key and a public key. This is also known as public-key cryptography. The most popular algorithm is RSA[5] and examples can be found in SSL, PGP, S/MIME, and many other secure standards.

With asymmetrical algorithms, a private and public key - known as a key pair - are generated by a user using private or random data. The private key is kept secret and secure, but the public key is given freely. The sender will use the recipient's public key to encrypt information, while the recipient will use their private key to decrypt the information. Information can also be signed in a similar fashion, except that the sender will use their private key to encrypt a hash of the plain or cipher text, and the recipient will use the sender's public key to decrypt and verify the hash. If the information was changed in any way, the hash will be invalid and the signature verification will fail. Using this technology with XML produces the XML Digital Signatures that we will use to license applications.

XML Digital Signatures

XML Digital Signatures, using public-key cryptography which was discussed earlier in this article, solves the problem of verifying that information came from a particular source, and that the information has not changed. This standard is just one of many included in Microsoft's WS-Security Specification[6] and can be used to verify the source of a Web Service response, or to verify that any XML data has not changed since it was signed.

Signatures are created by linking references to several transformations together over the content of the XML document, either in whole or in part. One of these transformations is a hash - a one-way checksum that is unique to the source content; even changing a single character will result in an entirely different hash. The most popular hash algorithms are MD5[7] and SHA1[8]. Because XML documents can have different indentation and amounts of white space, typically a canonicalization method is applied. A canonicalization method removes white space and other formatting, thereby reducing XML data to its simplest form. This is important because generating a hash over XML documents with the same data but different formats will result in two different hashes. Finally, a signature transformation is applied to encrypt the hash.

The XML Digital Signature - a composite of the things mentioned earlier, and usually containing the public key that signed it - can take on different forms in relation to the XML document which was signed. An enveloped signature is contained under the document element of the XML document which was signed, while an enveloping signature contains the XML document within the signature. Signatures can also be detached. Because an enveloped schema preserves the original XML schema[9] with only a reference to the qualified Signature element in the "http://www.w3.org/2000/09/xmldsig#" namespace:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="license"
  targetNamespace="http://www.codeproject.com/dotnet/xmldsiglic.asp"
  elementFormDefault="qualified"
  xmlns="http://www.codeproject.com/dotnet/xmldsiglic.asp"
  xmlns:mstns="http://www.codeproject.com/dotnet/xmldsiglic.asp"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
  version="1.0">
  <xs:import id="schema" namespace="http://www.w3.org/2000/09/xmldsig#"
  schemaLocation="xmldsig-core-schema.xsd" />
  <xs:element name="license">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="computerName" type="xs:string"
          minoccurs="1" maxoccurs="1" nillable="false" />
        <xs:element name="expires" type="xs:dateTime"
          minOccurs="1" maxOccurs="1" nillable="false" />
        <xs:element ref="dsig:Signature"
          minOccurs="0" maxOccurs="1" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Because the dsig:Signature element is optional, this schema applies to both signed and unsigned licenses. It also allows applications to parse the license document without validating the signature or extracting content from an otherwise different schema (such as in the case of an enveloping schema). I prefer this type of signature and will use it in the examples provided later in this article, and in the sample source code.

The Code

The following code will demonstrate creating key pairs, and signing and verifying XML content. The principles presented can be applied to many solutions that require - or that would benefit from - XML Digital Signatures. While the sample source does sign a license request file and verify a signed license against a specific public key, the specifics of this solution as it applies to licenses are explained later.

Key Creation

Key creation is a straight-forward process of creating a public and private key, or a key pair. While many utilities exist to create a key pair, the sn.exe[10] utility provided in the Microsoft .NET Framework SDK will be used. This utility allows you perform many tasks related to strong-named assemblies, including generating key pairs and saving them to a binary file.

First, the key pair is created from the command line:

sn.exe -k KeyFile.snk

In the sample source, the same key pair is used for license request signing and for signing strong-named assemblies. In production environments, you may want to have separate keys to prevent a cracked key from being used to sign modified assemblies or to sign falsified license requests.

Because the public key shouldn't be added to the signature output - which will be discussed later - it should be obtainable from the verification code. The .NET classes can easily create a crypto key from an XML fragment that represents a public key, so the public key will be embedded as a resource into the sample Verify.exe utility. To allow for this step and to make signing easier, the public key shall be imported into a key container on your machine:

sn.exe -m y
sn.exe -i KeyFile.snk CodeProject

The first command merely tells following container commands to act on a container for the machine instead of the user. Specify 'n' to have commands enacted on user containers. The second command installs the key pair into a key container named 'CodeProject' which provide easy access to the key container from a variety of crypto utilities. The sample Sign.exe signature utility can easily access this key container - much more easily than reading an XML document and importing the inner XML of the document element. While that and many other ideas are viable options for such a solution, the use of key containers in all but signature verification will be used throughout this article and sample source.

Finally, the public key must be extracted from the key pair and exported to an XML document. Because the key pair is now in a key container, this code is made simple:

CspParameters parms = new CspParameters(1);
parms.Flags = CspProviderFlags.UseMachineKeyStore;
parms.KeyContainerName = "CodeProject";
parms.KeyNumber = 2;
RSACryptoServiceProvider csp = new RSACryptoServiceProvider(parms);
Console.WriteLine(csp.ToXmlString(false));

Thanks go out to Pol Degryse for pointing out that I must explicitly set which store to use despite to what sn.exe set the default store, and to Michel Gallant, Security MVP, on microsoft.public.dotnet.security for pointing out that each container uses two key pairs, and that I must specify AT_SIGNATURE (2) instead of the default AT_KEYEXCHANGE (1), which caused the key pair to be unique on different machines.

Since this code writes the XML fragment to standard out, simply redirect standard out to a file that will be embedded into the Verify.exe project:

ExtractPubKey.exe > PubKey.xml

Add the file PubKey.xml as an embedded resource into the Verify project for later use.

Signing

To sign the XML document with an enveloped XML Digital Signature, we'll need to take into account various factors. Because the content to be signed contains the signature that contains the hash of the content, we obviously can't sign the entire content because the signature isn't computed until the hash is computed. We must also ensure that white space is ignored when computing the signature otherwise even a change in white space due to various transport issues could render the signed document invalid.

Transform algorithms[11] solve these and many other problems by applying specific algorithms to the original content or to output from other transforms. One particular transform eliminates white space and optionally comments in the computation of the signature: Canonicalization[12]. Canonicalization uses the character set to determine which characters shouldn't be included in the signature computation. Optionally, comments can be removed from the computed signature depending on which canonicalization method is used.

Another transform algorithm of interest is the Enveloped Signature Transform[13]. This transform uses XPath expression "not(ancestor-or-self::dsig:Signature)" to filter the signature during computation. The end result is that only the content is signed with or verified against the hash. The signature - which would change when the hash changes - is not included in the hash.

Once the necessary transforms have been chosen, they are applied to the content to sign:

// Load the license request file.

XmlDocument xmldoc = new XmlDocument();
xmldoc.Load(args[0]);

// Get the key pair from the key store.

CspParameters parms = new CspParameters(1);
parms.Flags = CspProviderFlags.UseMachineKeyStore;
parms.KeyContainerName = "CodeProject";
parms.KeyNumber = 2;
RSACryptoServiceProvider csp = new RSACryptoServiceProvider(parms);

// Creating the XML signing object.

SignedXml sxml = new SignedXml(xmldoc);
sxml.SigningKey = csp;

// Set the canonicalization method for the document.

sxml.SignedInfo.CanonicalizationMethod = 
  SignedXml.XmlDsigCanonicalizationUrl; // No comments.


// Create an empty reference (not enveloped) for the XPath

// transformation.

Reference r = new Reference("");

// Create the XPath transform and add it to the reference list.

r.AddTransform(new XmlDsigEnvelopedSignatureTransform(false));

// Add the reference to the SignedXml object.

sxml.AddReference(r);

// Compute the signature.

sxml.ComputeSignature();

// Get the signature XML and add it to the document element.

XmlElement sig = sxml.GetXml();
xmldoc.DocumentElement.AppendChild(sig);

// Write-out formatted signed XML to console (allow for redirection).

XmlTextWriter writer = new XmlTextWriter(Console.Out);
writer.Formatting = Formatting.Indented;

try
{
  xmldoc.WriteTo(writer);
}
finally
{
  writer.Flush();
  writer.Close();
}

In the code, the public key of signing key pair is not included in the output for reasons that will be covered in better detail in the Licensing topic.

Verifying

To verify the signed XML document, we simply load the signed XML document, reconstitute the cryptographic service provider (CSP) from the embedded XML public key resource, and verify the Signature node in the signed XML document against the public key in the CSP. The SignedXml object that we used to sign the XML will automatically determine which standard transforms were applied and will compute and verify the hash based on the transforms used when the document was signed:

// Get the XML content from the embedded XML public key.

Stream s = null;
string xmlkey = string.Empty;
try
{
  s = typeof(Verify).Assembly.GetManifestResourceStream(
    "CodeProject.XmlDSigLic.PubKey.xml");

  // Read-in the XML content.

  StreamReader reader = new StreamReader(s);
  xmlkey = reader.ReadToEnd();
  reader.Close();
}
catch (Exception e)
{
  Console.Error.WriteLine("Error: could not import public key: {0}",
    e.Message);
  return 1;
}

// Create an RSA crypto service provider from the embedded

// XML document resource (the public key).

RSACryptoServiceProvider csp = new RSACryptoServiceProvider();
csp.FromXmlString(xmlkey);

// Load the signed XML license file.

XmlDocument xmldoc = new XmlDocument();
xmldoc.Load(args[0]);

// Create the signed XML object.

SignedXml sxml = new SignedXml(xmldoc);

try
{
  // Get the XML Signature node and load it into the signed XML object.

  XmlNode dsig = xmldoc.GetElementsByTagName("Signature",
    SignedXml.XmlDsigNamespaceUrl)[0];
  sxml.LoadXml((XmlElement)dsig);
}
catch
{
  Console.Error.WriteLine("Error: no signature found.");
  return 1;
}

// Verify the signature.

if (sxml.CheckSignature(csp))
  Console.WriteLine("SUCCESS: Signature valid.");
else
  Console.WriteLine("FAILED: Signature invalid.");

Licensing

Using the described concepts for application licensing covers the technical aspects of protecting your licensed code while providing the user a simple licensing mechanism that can be Web- or email-based merely to obtain the license. These concepts alone, however, are not enough to provide an effective licensing toolset. There are a number of things you must consider when designing your application licensing model. Some concepts discussed here are implemented and are simplified to give you a simple example.

One thing you must consider is that license files can be transferred from one computer to another. If a single individual were to purchase a license and license had no unique identifier tied to the individual's machine or personal identity, he or she could easily post or resell the license.

To combat this problem, you should include some unique identifier in the license file. If you want to limit the license to a single machine, you could include the MAC address or computer SID in the license and - after verifying the signature - compare the stored unique identifier with that of the computer. While the signed XML document may be valid, two different identifiers would render the license invalid. There are a number of things you can use to identify the machine, but you should make sure that the identifier would be unique from computer to computer.

You could - depending on your license model - also use a unique personal identifier but it must be automatically obtained. If you ask the user or query an application for information that could be changed without affecting a user's environment - such as an email address from Outlook without authentication - the user could easily lie. One example would be to use the user's Microsoft Passport PID. Because the user is forced to authenticate in order to obtain this information, the user identity could be trusted within reason depending on your policy.

For this example, I simply get the computer name from the Environment class. In practice, I have written a Managed C++ assembly which grabs the computer SID which is unique to every computer in a given domain.

You should also consider implementing an expiration date or a combination of a signing date and certain number of days after that date before the signature is invalidated. This practice is common in cryptography systems and provides a means of discontinuing products and services to license violators or clients who no longer exist and who might sell their equipment whole-sale. You could even provide a revocation list (a list of forcibly-expired licenses) - which can be found in SSL[14] implementations - but that would force a dependency on the Internet in order to periodically check the revocation list.

Last in this topic - but certainly not the final consideration - is that the client code should not containing any private key information and should not allow for identity or key spoofing.

In the code earlier, the XML public key file was embedded into the verification application. It just as easily could have been encoded directly into the source code. Because the code contains this public key, it can verify the signature against that key instead of using a public key in the XML Signature element. If the verification code were to trust the public key in the signed document, then anyone could sign a new license with their private key, and their public key would be included and used to verify the document.

Embedding the private key (or the entire key pair) would be, perhaps, even worse because they could not only sign a new license with your private key, but they could extract the key pair and use it to sign assemblies that look like your or your organization's assemblies. They could easily decompile, change, and recompile and sign your assemblies while maintaining their validity, while effectively replacing your code with possible malicious code and could distribute it to clients. The assembly is signed, after all, with your key pair.

Even keeping your private key secure - another good argument for key containers over key pair files - this mechanism can be circumvented. Because .NET assemblies can be decompiled even with tools like ildasm.exe[15] in the Microsoft .NET Framework SDK, changed, and recompiled with a new signing key, you should take great care of obfuscating your code, burying the verification assembly deep in a chain of assemblies that automatically verify dependent assemblies based on their strong names while the CLR verifies each assembly's hash, and maintaining your hold on your code. No cryptography system is perfect, but you can take measures to make it fool-proof; that is, protected from "fools" who don't know any better.

How is this information obtained?

This information can be obtained through a request process. This is common in some cryptographic systems like SSL where an individual requests that a license be signed with information the individual has assigned. In our example, a separate client application - such as a small Windows or command-line utility, or even an ActiveX control or Java applet on a Web site - or even a trial version of your application, would gather information about the user that is unique to their machine or identity. The user or the applet would then send that request to you or your organization to be signed and returned to the user. In this way, you get the information that is required to validate the identity of the client machine or personal identity while controlling the private information and, thus, the ability to sign the request. If you implemented an expiration date or the user changes pertinent information, they would have to resubmit their request with the updated data for you to sign - if you or your organization wishes to renew the license. This gives you ultimate control over your licensed application and can be as flexible as you design it.

To continue our example, let's take our simple license schema and view an example request file generate on the client's machine:

<license>
  <computerName>MYCOMPUTER</computerName>
  <expires>2003-09-31T00:00:00</expires>
</license>

The request file contains the computer name (remember that this is only an example and a computer name would not be guaranteed to be unique and could easily be changed) and an expiration date. The user would then send the license request file or fragment to you or your organization. After reviewing and, possibly, generating a purchase order, you would run the signing application on the request file and send it back to the user through an automated manner, or with instructions for installing the file on their machine.

If the above request was passed to the sample Sign.exe application, the following output would be generated:

<license>
  <computerName>MYCOMPUTER</computerName>
  <expires>2003-09-31T00:00:00</expires>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod
        Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
      <SignatureMethod
        Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
      <Reference URI="">
        <Transforms>
          <Transform
          Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
        <DigestValue>eU3Par59M28X1c1DNORnhmW0Z2Y=</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>epyuHLJbmyscoVMg2pZZAtZJbBHsZFUCwE4Udv+u3TfiAms2HpLgN3cL
      NtRlxyQpvWt1FKAB/SCk1jr0IaeE7oEjCp2mDOOHhTUTyiv2vMJgCRecC1PLcrmR9ABhqk
      itsjzrCt7V3eF5SpObdUFqcj+n9gjuFnPQtlQeWcvKEcg=</SignatureValue>
  </Signature>
</license>

Note: The SignatureValue value has been wrapped for your convenience but should be a on a single line.

If you pass the signed document through the Verify.exe application, you will see that the signature is valid and that the document was not changed. If you change any content within the document - such as the expiration date or computer name - and run the verification application again, the signature will be invalid since the encrypted hash value is different from the computed hash over the changed content. Add to that checks against the computer name, expiration date, and even whether or not the file exists, and you will have effective license validation.

Summary

XML Digital Signatures use industry-standard encryption to sign XML documents. Using XML documents for license request files allows you to control what your clients can do with your application and for how long they may do it, and allows you to provide a disconnected license mechanism that is both secure and unique to a particular machine or user. The APIs that provide this level of functionality already exist in the Microsoft .NET Framework base class library and are easy to use and provide the same object-oriented programming model as the rest of your .NET applications.

Licensing your application doesn't have to be difficult. With XML Digital Signatures and well-designed validation, you can have a simple yet effective licensing mechanism that is both extensible and that uses industry standards.

References

Below you will find a list of reference that were either used during the writing of this document, or that you may find helpful in gaining a better understand of the topics to which these references apply. Other references used over the years for general research into the topic matter discussed here are not included.

  1. Extensible Markup Language (XML): http://www.w3.org/XML/
  2. XML-Signature Syntax and Processing: http://www.w3.org/TR/xmldsig-core/
  3. RSA Laboratories | Cryptography FAQ: http://www.rsasecurity.com/rsalabs/faq/index.html
  4. Counterpane Labs: Applied Cryptography (Bruce Schneier): http://www.schneier.com/book-applied.html
  5. RSA Security: http://www.rsasecurity.com/
  6. WS-Security Specification Index Page: http://msdn.microsoft.com/library/en-us/dnglobspec/html/wssecurspecindex.asp
  7. MD5: http://www.ietf.org/rfc/rfc1321.txt
  8. SHA1: http://csrc.nist.gov/publications/fips/fips180-1/fip180-1.txt
  9. XML Schema: http://www.w3.org/XML/Schema
  10. Strong Name Tool (sn.exe): http://msdn.microsoft.com/library/en-us/cptools/html/cpgrfstrongnameutilitysnexe.asp
  11. Transform Algorithms: http://www.w3.org/TR/xmldsig-core/#sec-TransformAlg
  12. Canonicalization Algorithms: http://www.w3.org/TR/xmldsig-core/#sec-c14nAlg
  13. Enveloped Signature Transform: http://www.w3.org/2000/09/xmldsig#enveloped-signature
  14. SSL 3.0 Specification: http://wp.netscape.com/eng/ssl3/
  15. MSIL Disassembler (ildasm.exe): http://msdn.microsoft.com/library/en-us/cptools/html/cpconmsildisassemblerildasmexe.asp

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here