Shelf Life Check
A Shelf Life Check is a type of Check that detects if an application is being run after a certain date. This gives the application a shelf life - a limited period of time for which it can be run.
Shelf Life Checks are particularly useful for beta or evaluation software. If the user attempts to run the application beyond the expiration date, a Shelf Life Check can detect this and react by notifying the application and causing the application to exit. In other words, a Shelf Life Check detects and reacts to unauthorized use of your application based on the current date.
Configuring Shelf Life Checks
To have Dotfuscator Professional inject Shelf Life Checks into your application, you must first obtain a Shelf Life Activation Key from PreEmptive Solutions.
Once you have an Activation Key, enable Checks.
Then, configure the Checks in the Config Editor or by annotating your source code with ShelfLifeCheckAttribute
.
Both of these methods allow you to specify various properties that determine how the Check operates; for a full listing, see the ShelfLifeCheckAttribute
section of the Check Attributes page.
Activation Key
A Shelf Life Activation Key is a data file that is required to inject Shelf Life Checks. To obtain an Activation Key, contact PreEmptive Solutions.
Once issued, you must specify the path to the Activation Key in each Shelf Life Check's ActivationKeyFile property. We recommend keeping a copy of the Activation Key on each build machine.
The Activation Key is only needed when Dotfuscator processes the application. The application does not need to access the Activation Key at runtime, so it should not be distributed outside of the development organization.
Tokens
A Shelf Life Token is a file containing information about an application's shelf life, such as the expiration date. Rather than embedding the expiration information directly into the application, Dotfuscator generates a Token containing this information. At runtime, the Shelf Life Check uses the Token to determine if the application has expired.
In Dotfuscator Professional, there are two ways of providing the Token to a Shelf Life Check:
Embed the Token with the Check in the application. This is more convenient, but once the application has shipped, the Token (and therefore the expiration date) cannot be changed or customized.
Generate the Token separately, then provide it to the Check at runtime. For instance, if you provide the Token to the application using a web service, you can change the Token without redistributing the application, allowing for expiration date extensions and per-user expiration dates.
In either scenario, you may also ensure the authenticity of a Token by using a public / private key pair. The Token includes the public key; after the Token is generated, it is signed with the private key. At runtime, the Shelf Life Check verifies the signature matches the public key. This ensures that the Token has not been modified, preventing certain kinds of expiration date manipulation.
Embedding a Token with the Check
You can have Dotfuscator automatically generate and embed a Token with a Shelf Life Check. This is the simpler of the two options.
To do this, when configuring the Check, specify values for the Check's ExpirationDate and (optionally) WarningDate properties.
If you want the generated token to be signed, also specify values for PrivateKeyFile and, if needed, PrivateKeyFilePassword.
For details on these and other properties of Shelf Life Checks, see the ShelfLifeCheckAttribute
section of the Check Attributes page.
Generating a Token Separately
You can instead generate the Token manually and have the application itself provide the Token to the Shelf Life Check at runtime. This allows you to, for instance, store the Token in a database or web service that the application accesses. You can change the Token provided without redistributing the application, allowing for expiration date extensions and per-user expiration dates.
To generate a Token manually, see the Generate New Shelf Life Token section for details.
To have the Check retrieve the Token dynamically at runtime, when configuring the Check, specify values for the ShelfLifeTokenSource properties.
These values specify a source in the application code that provides the Token as a string
.
For details on these and other properties of Shelf Life Checks, see the ShelfLifeCheckAttribute
section of the Check Attributes page.
Expiration and Warning Dates
Each Shelf Life Token has an associated expiration date. When a Shelf Life Check using the Token runs, it compares the expiration date to the current time (using an algorithm more complex than just checking the system time). If the expiration date is in the past, the Check determines the application to be expired. What happens as a result of the application being expired depends on the application notification properties.
Each Shelf Life Token may optionally specify a warning date. When a Shelf Life Check using the Token runs, if the warning date is in the past, the Check determines the application to be in the warning period. What happens as a result of the application being in the warning period depends on the application notification properties.
For a Token embedded with a Shelf Life Check, the expiration and warning dates are specified by the Check's ExpirationDate and WarningDate properties, respectively. For Tokens provided at runtime, these dates are specified when the Token is generated.
Dates for a Shelf Life Tokens are configured as strings, in one of two formats:
An absolute date, specified in the form
YYYY-MM-DD
.A date relative to the date Dotfuscator generated the Token, specified as an integer indicating the number of days.
For instance, if Dotfuscator generates the Token on August 1, 2017, and the application should expire on August 31, 2017, the expiration date can be written as either 2017-08-31
or 30
.
Application Notification
Application Notification with Shelf Life Checks differs somewhat from that of other Check types.
Notification Kinds
While other Checks provide only one notification, saying whether the Check found an unauthorized application state or not, Shelf Life Checks can have two notifications:
Expiration Notification notifies the application code whether or not the application has expired (is being run after its expiration date).
This notification will always be used when a Check runs, including if the application is expired, is in the warning period, or is neither.
The following properties of a Shelf Life Check determine how that Check notifies the application whether or not it is expired:
- ExpirationNotificationSinkElement
- ExpirationNotificationSinkName
- ExpirationNotificationSinkOwner
These properties specify a sink in the application code that receives a
bool
value:true
if the application is expired, andfalse
otherwise.
Warning Notification notifies the application code whether or not the application is in the warning period (is being run after its warning date).
This notification is skipped if no warning date is specified for the Check, but otherwise will always be used when a Check runs, just like the expiration notification.
The following properties of a Shelf Life Check determine how that Check notifies the application whether or not it is in the warning period:
- WarningNotificationSinkElement
- WarningNotificationSinkName
- WarningNotificationSinkOwner
These properties specify a sink in the application code that receives a
bool
value:true
if the application is in the warning state, andfalse
otherwise.
Using a String Sink
As an alternative to the bool
sinks specified in the previous subsection, either notification may instead use a Method, Delegate, or MethodArgument sink where the method or delegate has the signature void(string, string)
.
In this case, the Shelf Life Check calls the method or delegate with the following two parameters:
- Warning Date (or
null
if no warning date was specified) - Expiration Date
This allows the application code to be more specific in its reaction, such as by displaying the expiration date to the user.
Like a bool
sink, a string
sink is called even if the application is not in the warning period nor has expired.
However, a sink for a warning notification will never be called if no warning date is specified.
Note that both expiration and warning notifications call string
sinks with the same arguments, so specifying both as string
sinks is redundant.
You can use one of the two notifications with a string
sink, and the other with a bool
sink.
For example, consider an evaluation software with the following Shelf Life Check and code snippet:
internal class ShelfLifeApplicationNotificationExample
{
private bool applicationHasExpired; // set by Shelf Life Check
internal void SetupApplication()
{
LoadDataFiles();
EnableFreeFeatures();
if (!applicationHasExpired)
{
EnablePaidFeatures();
}
}
private void LoadDataFiles() // location of Shelf Life Check
{
Console.WriteLine("Simulating startup logic...");
}
// EnableFreeFeatures() and EnablePaidFeatures() omitted for brevity
// called by Shelf Life Check
private void LogEvaluationNotice(string warnDateString, string expireDateString)
{
// Use arguments directly as strings...
Console.WriteLine($"This evaluation software has an expiration date of {expireDateString}.");
Console.WriteLine("To purchase the full version, please contact <sales@example.com>.");
// ...or parse to DateTime objects for calculations.
DateTime warnDate = DateTime.Parse(warnDateString);
DateTime expireDate = DateTime.Parse(expireDateString);
int daysUntilWarn = warnDate.Subtract(DateTime.Today).Days;
int daysUntilExpire = expireDate.Subtract(DateTime.Today).Days;
if (daysUntilExpire <= 0)
{
Console.WriteLine("WARNING: The software has expired. Paid features are no longer available.");
}
else if (daysUntilWarn <= 0)
{
Console.WriteLine($"WARNING: The software will expire in {daysUntilExpire} days.");
}
}
}
After Dotfuscator's injection, when other application code calls SetupApplication()
:
SetupApplication()
callsLoadDataFiles()
.Before
LoadDataFiles()
executes, the Shelf Life Check runs:The Check determines if the application is expired, in the warning period, or neither.
As its warning notification, the Check calls the
LogEvaluationNotice(string, string)
method, providing the warning date and expiration date as strings.The
LogEvaluationNotice(string, string)
writes information about the evaluation software to the console, then returns control to the Check.As its expiration notification, the Check sets the
applicationHasExpired
field totrue
if the application has expired, andfalse
otherwise.The Check finishes running and returns control to the
LoadDataFiles()
method.
The
LoadDataFiles()
method runs, then returns control toSetupApplication()
.SetupApplication()
callsEnableFreeFeatures()
, which then returns control toSetupApplication()
.If
applicationHasExpired
isfalse
(i.e., the evaluation has not expired),SetupApplication()
callsEnablePaidFeatures()
, which then returns control toSetupApplication()
.SetupApplication()
returns control to its caller.
Exit Behavior
Shelf Life Checks do not support Check Actions. A configuration equivalent to the use of the "Exit" Action can be done by setting the ExpirationNotificationSinkElement property of the Check to DefaultAction.
When a Shelf Life Check with this configuration detects expired application usage, it will cause the application to exit immediately with exit code 0.
Unsupported Application Types
Dotfuscator can inject Shelf Life Checks into all .NET assemblies except for the following:
- Managed C++ assemblies containing native and managed code
- Multi-module assemblies
- .NET Core assemblies
- .NET 5 assemblies
- UWP assemblies
- Xamarin assemblies
- MAUI assemblies
Testing
To test how the Shelf Life Checks injected into your application react to expired application usage or usage during the warning period, the simplest method is to temporarily configure the Checks with the expiration dates and warning dates in the past. Be sure to exercise all locations of your Shelf Life Checks. Once you have tested that the application acts as it should when it is expired or in the warning period, you will need to change the dates back to their intended values before building the application for distribution.