PLUSManaged: Using Self-Signed Writable License Files

Some environments, including ones like remote locations (such as in a field or on the road), do not offer any Internet connectivity.  Meanwhile, others are highly restrictive with Internet connectivity (often places such as hospitals, government and military offices, etc...). In these types of environments, it can be difficult or impractical to require Internet connectivity. Additionally, there might be other reasons why your application would need to be able to write to its license files freely as well (depending on your licensing requirements). In these situations, it is possible to use writable license files, which are license files that are encrypted and signed by your application (read more about license files). Although this means all key data needed to write license files is known to your application (which is less secure than using read-only licenses issued by SOLO Server), this affords you extra flexibility when needed.

Keep in mind that you need to make the best choices about what type of license file your application should use, and when it should use it.  Our sample applications show you how you can use read-only licenses, writable licenses, and even a hybrid of both.

Inheriting from PLUSManaged

When creating your license implementation class, the one major thing you need to do differently is to inherit from the WritableLicense class (instead of License, which is for read-only licenses signed by SOLO Server). Your class would be defined using code similar to that which is shown below.

C#
public class SampleLicense : WritableLicense
{
}
VB.NET
Public Class SampleLicense
Inherits WritableLicense
End Class

Using License File Aliases

Aliases are hidden copies of your application's license file, which are designed to prevent your license file from being arbitrarily written to or deleted.  Using aliases is necessary when using writable license files, as this prevents users from being able to easily restore the license file to a prior state simply by restoring backup copy of the file.

Important

Although it is critical to use license file aliases to protect your writable licenses, they cannot prevent your application from being returned to a prior state when the entire system is restored to a snapshot or image (using something like Norton Ghost, for example). Using periodic background checking with SOLO Server whenever possible is best for preventing the license from being compromised in this manner.

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 example code is provided below to show how you can configure some user-specific alias locations.

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.

C#
//Add an alias in the user's %APPDATA% folder.
this.AddAlias(new LicenseFileSystemAlias(Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "XyzProduct"), "XyzProductUserAlias1.xml"), encryptionKey, true));
//Add an alias in the user's registry area.
this.AddAlias(new LicenseWindowsRegistryAlias("Software\\XyzProduct\\License", encryptionKey, true, Microsoft.Win32.RegistryHive.CurrentUser, "XyzProductUserAlias2"));
//Add an alias to the user's classes\clsid area. Generate and use a new GUID (or a value formatted like "f8e46dfc-2500-45ba-bc1a-760e0b386147") manually using System.Guid.NewGuid().ToString(). It is important to use your own GUID instead of the example provided below!
this.AddAlias(new LicenseWindowsRegistryAlias("Software\\Classes\\CLSID\\{f8e46dfc-2500-45ba-bc1a-760e0b386147}\\Programmable", encryptionKey, true, Microsoft.Win32.RegistryHive.CurrentUser, "Version"));
//Add an alias within an existing Bitmap image located in the user's %APPDATA% folder.
this.AddAlias(new LicenseImageAlias(Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "XyzProduct"), "Image.bmp"), encryptionKey, true));
VB.NET
'Add an alias in the user's %APPDATA% folder.
Me.AddAlias(New LicenseFileSystemAlias(Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "XyzProduct"), "XyzProductUserAlias1.xml"), encryptionKey, True))
'Add an alias in the user's registry area.
Me.AddAlias(New LicenseWindowsRegistryAlias("Software\\XyzProduct\\License", encryptionKey, True, Microsoft.Win32.RegistryHive.CurrentUser, "XyzProductUserAlias2"))
'Add an alias to the user's classes\clsid area. Generate and use a new GUID (or a value formatted like "f8e46dfc-2500-45ba-bc1a-760e0b386147") manually using System.Guid.NewGuid().ToString(). It is important to use your own GUID instead of the example provided below!
Me.AddAlias(New LicenseWindowsRegistryAlias("Software\Classes\CLSID\{f8e46dfc-2500-45ba-bc1a-760e0b386147}\Programmable", encryptionKey, true, Microsoft.Win32.RegistryHive.CurrentUser, "Version"))
'Add an alias within an existing Bitmap image located in the user's %APPDATA% folder.
Me.AddAlias(New LicenseImageAlias(Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "XyzProduct"), "Image.bmp"), encryptionKey, True))

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 on how you can configure some global alias locations is provided in the code below.

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.

C#
//Add an alias file in the same folder as this application/.exe file.
this.AddAlias(new LicenseFileSystemAlias(Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "XyzProductAlias1.xml"), encryptionKey, true));
//Add an alias in the common application data folder (usually "C:\Program Data").
this.AddAlias(new LicenseFileSystemAlias(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "XyzProductAlias2.xml"), encryptionKey, true));
//Add an alias in the system folder (usually C:\Windows\System32).
this.AddAlias(new LicenseFileSystemAlias(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "XyzProductAlias3.xml"), encryptionKey, true));
//Add an alias in a global classes\clsid registry area. Generate and use a new GUID (or a value formatted like "f8e46dfc-2500-45ba-bc1a-760e0b386147") manually using System.Guid.NewGuid().ToString(). It is important to use your own GUID instead of the example provided below!
this.AddAlias(new LicenseWindowsRegistryAlias("CLSID\\{f8e46dfc-2500-45ba-bc1a-760e0b386147}\\Programmable", encryptionKey, true, Microsoft.Win32.RegistryHive.ClassesRoot, "Version"));
//Add an alias file within an existing Bitmap image in the same folder as this application/.exe file.
this.AddAlias(new LicenseImageAlias(Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "Image.bmp"), encryptionKey, true));
VB.NET
'Add an alias file in the same folder as this application/.exe file.
Me.AddAlias(New LicenseFileSystemAlias(Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "XyzProductGlobalAlias1.xml"), encryptionKey, True))
'Add an alias in the common application data folder (usually "C:\Program Data").
Me.AddAlias(New LicenseFileSystemAlias(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "XyzProductGlobalAlias2.xml"), encryptionKey, True))
'Add an alias in the system folder (usually C:\Windows\System32).
Me.AddAlias(New LicenseFileSystemAlias(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "XyzProductGlobalAlias3.xml"), encryptionKey, True))
'Add an alias in a global classes\clsid registry area. Generate and use a new GUID (or a value formatted like "f8e46dfc-2500-45ba-bc1a-760e0b386147") manually using System.Guid.NewGuid().ToString(). It is important to use your own GUID instead of the example provided below!
Me.AddAlias(New LicenseWindowsRegistryAlias("CLSID\{f8e46dfc-2500-45ba-bc1a-760e0b386147}\Programmable", encryptionKey, True, Microsoft.Win32.RegistryHive.ClassesRoot, "Version"))
'Add an alias file within an existing Bitmap image in the same folder as this application/.exe file.
Me.AddAlias(New LicenseImageAlias(Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "Image.bmp"), encryptionKey, True))

PLUSManaged for .NET Standard 2.0 License file storage

When storing license data, you will need to be mindful of the platform's data storage guidelines.

When using writable license files, you may need to store the licenses in a location where they will be retained even when the application is uninstalled.

Validating Aliases

Adding logic to your application to validate its configured aliases is pivotal to using aliases properly. The code below illustrates how you can validate aliases in your license implementation class.

C#
LicenseAliasValidation aliasValidation = new LicenseAliasValidation(this);
if (!aliasValidation.Validate())
{
this.LastError = aliasValidation.LastError;
return false;
}
VB.NET
Dim aliasValidation As New LicenseAliasValidation()
If Not aliasValidation.Validate() Then
Me.LastError = aliasValidation.LastError
Return False
End If
Important

Your license validation logic also needs to verify the system clock when using aliases to help ensure you can trust the time the system clock reports to your application.

Self-Issued Evaluations

Evaluations are one of the most common license models, and it is often convenient (if not necessary) to allow your application to automatically issue evaluation periods for your prospective customers (learn more).

Identifying a Self-Issued Evaluation

If your application uses a writable license for all types of licenses, then it needs to be able to distinguish evaluations from other types of licenses. Since all other types of licenses will generally require activation with SOLO Server, the easiest way to identify self-issued evaluations is to check for an empty string value in the InstallationID property (as an Installation ID would be present for any licenses activated with SOLO Server). Otherwise, if your application only uses writable licenses for evaluations, then this can easily be determined by which license implementation is being used.

Creating Fresh Evaluations

When your application is run on a computer for the first time, it is common practice to automatically issue a fresh evaluation period. You can implement this functionality in your license implementation class using a method similar to the one shown below.

C#
public bool CreateFreshEvaluation()
{
//Start by loading and checking all of the aliases.
int numAliases, numValidAliases;
this.CheckAliases(out numAliases, out numValidAliases);

//If we found any aliases, write the most recent one as the license file, and use it instead of creating a fresh evaluation.
LicenseAlias mostRecent = LicenseAlias.GetMostCurrentAlias(this.Aliases);
if (mostRecent.LastUpdated != DateTime.MinValue)
{
//Write the most recent alias as the new, primary license file and load its contents.
this.WriteAliasToLicenseFile(mostRecent, "[License File Path]");
this.Load(mostRecent.Contents);

//Now update all the other aliases as well.
int aliasesToWrite, aliasesWritten;
this.WriteAliases(out aliasesToWrite, out aliasesWritten);

return true;
}

//Since evaluations are not activated, they have no Installation ID.
this.InstallationID = "";
//Set the license's effective start date to the current date.
this.EffectiveStartDate = DateTime.UtcNow.Date;
//Set the license's effective end date (the expiration date) to 30 days after the current date.
this.EffectiveEndDate = DateTime.UtcNow.Date.AddDays(30);

int filesToWrite, filesWritten;
this.WriteAliases(out filesToWrite, out filesWritten);

//TODO: you can add your own logic here to set your own requirements for how many aliases must be written
// ...for this example, we only require 1.
if (filesWritten < 1)
{
return false;
}

return this.WriteLicenseFile("[License File Path]");
}
VB.NET
Public Function CreateFreshEvaluation() As Boolean
'Start by loading and checking all the aliases.
Dim numAliases As Integer, numValidAliases As Integer
Me.CheckAliases(numAliases, numValidAliases)

'If we found any aliases, write the most recent one as the license file, and use it instead of creating a fresh evaluation.
Dim mostRecent As LicenseAlias = LicenseAlias.GetMostCurrentAlias(Me.Aliases)
If mostRecent.LastUpdated <> DateTime.MinValue Then
Me.WriteAliasToLicenseFile(mostRecent, "[License File Path]")
'Write the most recent alias as the new, primary license file and load its contents.
Me.Load(mostRecent.Contents)

'Now update all the other aliases as well.
Dim aliasesToWrite As Integer, aliasesWritten As Integer
Me.WriteAliases(aliasesToWrite, aliasesWritten)

Return True
End If

'Since evaluations are not activated, they have no Installation ID.
Me.InstallationID = ""
'Set the license's effective start date to the current date.
Me.EffectiveStartDate = DateTime.Now.[Date]
'Set the license's effective end date (the expiration date) to 30 days after the current date.
Me.EffectiveEndDate = DateTime.Now.[Date].AddDays(30)

Dim filesToWrite As Integer, filesWritten As Integer
Me.WriteAliases(filesToWrite, filesWritten)

'TODO: you can add your own logic here to set your own requirements for how many aliases must be written
' ...for this example, we only require 1.
If filesWritten < 1 Then
Return False
End If

Return Me.WriteLicenseFile("[License File Path]")
End Function

Then, you can update your application to check to see whether or not the license file exists. When no license file is present (or when the LoadFile method inherited from WritableLicense fails), you can then call the new CreateFreshEvaluation method.

Creating Expired Evaluations

If your application uses writable license files exclusively (and not a hybrid of writable and read-only license files), then there may be some cases where you need to create an expired evaluation. This can include situations such as when a customer deactivates a license that was previously activated, or when a background check finds that the License ID or the Installation ID is no longer valid (or has been deactivated or revoked). Below is an example showing how you can create a method that creates an expired evaluation license, which you can then have your application call under these kinds of circumstances.

C#
public bool CreateExpiredEvaluation()
{
//Since evaluations are not activated, they have no Installation ID.
this.InstallationID = "";
//Set the license's effective start date to yesterday.
this.EffectiveStartDate = DateTime.UtcNow.Date.AddDays(-1);
//Set the license's effective start date to yesterday so it is expired.
this.EffectiveEndDate = DateTime.UtcNow.Date.AddDays(-1);

int filesToWrite, filesWritten;
this.WriteAliases(out filesToWrite, out filesWritten);

//TODO: you can add your own logic here to set your own requirements for how many aliases must be written
// ...for this example, we only require 1.
if (filesWritten < 1)
{
return false;
}

return this.WriteLicenseFile("[License File Path]");
}
VB.NET
Public Function CreateExpiredEvaluation() As Boolean
'Since evaluations are not activated, they have no Installation ID.
Me.InstallationID = ""
'Set the license's effective start date to yesterday.
Me.EffectiveStartDate = DateTime.UtcNow.[Date].AddDays(-1)
'Set the license's effective start date to yesterday so it is expired.
Me.EffectiveEndDate = DateTime.UtcNow.[Date].AddDays(-1)

Dim filesToWrite As Integer, filesWritten As Integer
Me.WriteAliases(filesToWrite, filesWritten)

'TODO: you can add your own logic here to set your own requirements for how many aliases must be written
' ...for this example, we only require 1.
If filesWritten < 1 Then
Return False
End If

Return Me.WriteLicenseFile("[License File Path]")
End Function