PLUSNative: Using Self-Signed Writable License Files

Using a read-only license file issued by SOLO Server is the recommended approach since it is inherently more secure. However, it may be desirable to use a self-signed writable license file for a variety of reasons (read more about license files). This is often the case when implementing unmanaged evaluations/trials that do not require activation, or if the application must keep track of usage data while offline. When using writable license files it is recommended to use redundant copies of the license file, known as license file aliases. These are used to help prevent an end-user from backing up and restoring a license file or from reinstalling an evaluation to gain additional unauthorized usage. The last date and time the application was ran is generally stored in the LastUpdated field in the license file. When the license file and aliases are loaded their LastUpdated date/times are compared to determine which one is newer, and older copies will automatically be overwritten.

The following example demonstrates loading a license file and it's aliases and updating the LastUpdated date/time with the current system time.

Loading a License File and License File Aliases

The license file aliases are automatically loaded when the main license file is loaded. If the main license file has been backed up and restored, it will be automatically overwritten by the newer aliases. If the main license file has been deleted, it will be restored from an aliases file if present.

Important

The steganography feature is presently a beta, and changes may be applied which could require minor source code modifications to be made in order to use a future release. Please keep in mind that your testing should be very thorough. Also, use your own images distributed with your application such as a splash screen image. Do not use an existing image such as the desktop wallpaper since another product could share that license location.

If the license file or alias files are a supported image file then steganography will automatically be used to read and write the license to the specified image.

The following code snippet loads a license file and several license file aliases from disk:

C/C++
//declare variables
SK_ResultCode result = SK_ERROR_NONE;
SK_ApiContext context = NULL;
char *filename = ...;
char *alias1 = ...;
char *alias2 = ...;
SK_XmlDoc license = NULL;
char *lastUpdated = NULL;
char *now = NULL;
int comparison = 0;

//initialize API context (usually called during application start-up)
//refer to the Configuring the API Context topic for instructions

//define our license file aliases
result = SK_PLUS_LicenseAliasAdd(context, SK_FLAGS_NONE, alias1);
result = SK_PLUS_LicenseAliasAdd(context, SK_FLAGS_NONE, alias2);

//load the license file (and aliases)
result = SK_PLUS_LicenseFileLoad(context, SK_FLAGS_NONE, filename);

Since there are numerous locations where the license file could be saved, and since acceptable locations vary from platform to platform, the code snippet above omits the actual path to the license file and license file aliases. This code snippet also omits many of the parameters necessary to initialize the API context (the context variable). Refer to the Configuring the API Context topic for instructions and a complete example of calling the SK_ApiContextInitialize function.

Configuring Aliases

The first step is to add some code to your license implementation class constructor.  The code added will vary depending on what locations you use for your aliases.

Important

The example code below and in any sample application code shows locations that are only meant to provide some guidance on selecting your application's alias locations.  Your application must use different alias names and/or locations than any of the examples given by this documentation and any of the sample applications.

Additionally, it is equally important that any two applications (which do not share a license) use separate license file and alias locations.

User-Specific Locations

User-specific alias and license file locations are convenient because they can help you to avoid problems that need to be addressed when users with limited access to a system try to use your software.  More specifically, if your application were to use user-specific locations exclusively, the primary benefit would be that users would never need elevated access to run and activate your application. However, the significant drawback is that because the locations used are unique to each user which signs-on to a system, each user will have his or her own license file and aliases, meaning each user will need to activate to be able to use the application. Some examples of user-specific alias locations are described below.

Global Locations

Important

Microsoft Office applications (such as Word, Excel, Access, etc...) may run in a ClickToRun environment.  This environment has known limitations that make it problematic for licensed Office add-ins and macros to use global locations. Consequently, licensed add-ins and macros that target these environments should only use user-specific locations for licenses and aliases. See this knowledge-base article for more details.

Global alias and license file locations are locations which are shared by all users on a system.  The advantage to using global locations is that the application only needs to be activated once on a given system. However, the drawback to this is that you typically need to consider setting permissions on these locations when deploying your application. Some examples for global alias locations are provided below.

Writing License Fields

After making any changes to the license file the LastUpdated date/time field should be updated prior to saving. When saving the license file all license file aliases are automatically updated as well.

The following code snippet demonstrates reading the LastUpdated date/time field and updating it with the current date/time, provided the LastUpdated date/time is in the past. This helps prevent the user from backdating the system clock.

C/C++
//get the decrypted license document
result = SK_PLUS_LicenseGetXmlDocument(context, SK_FLAGS_NONE, &license);

//read the LastUpdated date/time field
result = SK_XmlNodeGetValueDateTimeString(SK_FLAGS_NONE, license, "/SoftwareKey/PrivateData/License/LastUpdated", &lastUpdated);

//get the current system date/time
result = SK_DateTimeGetCurrentString(SK_FLAGS_NONE, &now);

//make sure the LastUpdated date/time is in the past or the current system time.
result = SK_DateTimeCompareStrings(SK_FLAGS_NONE, lastUpdated, now, &comparison);
if (comparison <= 0)
{
//update the LastUpdated date/time field
result = SK_XmlNodeSetValueDateTimeString(SK_FLAGS_NONE, license, "/SoftwareKey/PrivateData/License/LastUpdated", now);

//save our changes
result = SK_PLUS_LicenseFileSave(context, SK_FLAGS_NONE, filename, license);
}

//free our license document
SK_XmlDocumentDispose(SK_FLAGS_NONE, &license);

//free our date-time strings
SK_StringDispose(SK_FLAGS_NONE, &lastUpdated);
SK_StringDispose(SK_FLAGS_NONE, &now);
Important

These basic source code examples omit important and necessary error checking for the sake of clarity. Many of the functions used could fail for one reason or another, and it is important that you make sure each function call succeeds before passing the result from one function to another. Otherwise, you may not be able to tell exactly where a problem originated if and when one occurs.