Java ISV Application Development Guide
Overview Of License Enabling Activities
Defining Your License Management Policies
Example Of Single-User License Key Check
Example Of Server License Key Check
Example Of License Key Check With Customization For OEM Key
Coding Against The Extended API
Enabling Deferred-mode Licensing With The Extended API
Checking Access Controlled Keys
Coding For Automatic License State Management
Using Digital Signatures For Anti-Spoofing
This document guides you through the specifics of the process of license-enabling your Java product with EasyLicenser. It adds detail to the overview of the EasyLicenser Java Run Time Library API that is presented in the Concepts document.
The activities encompass:
A key decision
you need to make at the onset is whether you wish to enforce locking your
license keys to specific hardware and / or operating system. Although locking
your key provides a high degree of protection, it is also a sales inhibitor,
as it subjects your end-customer to onerous procedures both at the time you
take their order and whenever customers want to relocate their product installation.
The approach is also less portable across platforms. A related decision
is what you want to lock your key to: proprietary hardware, or the
logical operating system runtime environment. If the former, you will need
to implement or obtain hardware and operating system specific code and plug
it into the EasyLicenser runtime framework. If the latter, you can utilize
EasyLicenser's built-in enforcement facilities.
If you are going to node-lock your license keys, another decision you
need to make is the tradeoff between convenience and security: whether
security requirements override convenience, or whether a short time window
of vulnerability is acceptable for the purpose of eliminating the process
of explicitly acquiring node lock information from the customer.
When your product is licensed for time limited or metered use, you can decide on what you want to have happen when a license key check indicates an expiring or expired key. You can define thresholds for generating warnings on expiring keys and grace periods or quotas to suppress generating errors when a quota or expiration date is exceeded. For time limited licenses, you will also need to decide whether the licenses are to be individually issued and tracked, or whether a single relative-time-limited license is to be included with the product distribution for convenience. For metered licenses, you will also need to decide whether to let the EasyLicenser runtime automatically manage metered state.
When your product is licensed according to paid-for options and features, you will accordingly define product options in your license key generation environment, and process the option values in your application.
EasyLicenser has built-in license management models, types and parameters that meet most requirements directly. In the event that these do not directly meet your specific requirements and you have a specialized need for your own variation to the built-in policies, you can accomplish this by defining your own custom handler.
The specific mechanics of accomplishing these tasks are described below.
The EasyLicenser runtime library provides two levels of API's:
Both API's are threadsafe.
Four API calls are available with the EzLicenseInfo class for checking a license key:
All core API calls are available with
three signatures: a standard API call that is backward compatible with all previous versions
of EasyLicenser, an access-control API, new in EasyLicenser 2.0, that accepts
two additional parameters - a product name and an application password public key that
is backward compatible with EasyLicenser 2.0, and a strict access-control API that
accepts a "strict" parameter, which is implicitly false for the older signature for
backward compatibility.
The access control API is required to be used with access-controlled keys and will
also work with keys that are not based on products having application passwords
defined for them. The sole purpose of the standard API call signature is backward
compatibility. The access control API with strict password check should be used for
all new application development, for security.
If your application does not require customized or highly secure license management, you can use either of the first two API’s according to whether your product is a user or server product. Otherwise, you will find it convenient to use one of the functionally complete APIs.
Three key API calls are available with the EzLicenseExtendedAPI class for managing the lifecycle of a license key at an application installation:
The API is an access-control API, and application password checking is implicitly strict.
All application categories can use the extended API. That is, it is not a requirement that the application require deferred time limited licensing, deferred node locking or automatic metering.
The specific details of the API
are available in the Java Run Time Library
API Reference Java documentation. Some simple examples will
be illustrated below that show how the API’s are used in typical application
scenarios. Source and binary code
for additional examples are available in the demo and samples directories.
In particular,
the jdemo.java application is a simple Hello World
application that performs a simple check on a license key provided as an
input parameter based on a specified user / host name. For details on the
demo program
and how to run it, see the description of the directory structure in the Setup,
Management And Deployment Guide. The jdemosecure.java application
is similar to jdemo.java, but illustrates the use of the secure
API. Use of the secure API for automatic state management is illustrated with
the jdemostate.java application.
Suppose you have a desktop application such as a file management utility program that you sell to end consumers.
In your environment, you create a product definition for your product, which you call FileManagerProduct and with which you associate a secret password MySecret. The License Manager displays a corresponding application password public key rTxWsuxjr4/s3J=. Your application developer embeds the public key into your application and uses the product name and public key at the time of a license key check, as illustrated below.
Whenever a new customer visits your web site and downloads your application and pays by credit card for a perpetual license to use it, your web site defines a serial number or user id, which is also used as the EasyLicenser user name input to a key your web site programmatically generates and emails to your customer. The key that you generate is a User license model, of User license type. It is not time limited and does not have a quota limit defined on it.
At product installation time, your installer asks the user for the key, which is then stored in the operating system registry. Subsequently, each time the user runs your application, it prompts your user for the user id authentication information. Your code can take that user name input, retrieve the license key from the registry, and perform the license key check as follows:
// imports
import com.vs.ezlicrun.*;
// First, prompt for the login name
String userName =
(String) JOptionPane.showInputDialog(
null,
"Enter User Name:", "Login",
JOptionPane.PLAIN_MESSAGE);
userName = userName.trim();
:
// Check the license key that is already obtained
// from the registry into a "config" class.
// (note: "config" is not part of EasyLicenser)
EzLicenseInfo licenseInfo = new EzLicenseInfo();
try {
int warnings =
licenseInfo.checkSingleUserLicenseKeyBasic(
config.getLicenseKey(),0, 0, 0, 0, 0,
"FileManagerProduct", "rTxWsuxjr4/s3J=");
// last two params are product name and app password public key
} catch (EzLicExceptionBase eek) {
JOptionPane.showMessageDialog(
(JComponent)null,
"You are not licensed to use this product. " +
"Please contact technical support.",
"Licensing Error",
JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
Suppose you have a Unix-based application server product Blogix having an application password public key ZrTxWs0xjr4/s3== that you wish to license by the number of concurrent sessions on the application server. You wish to lock the user into a specific host machine, therefore you adopt an enforced host naming policy that requires your customer to provide you with the DNS host name for their server machine at the time of accepting an order and generating a key.
The key that you generate is of a Server license model, and Concurrent User license type. At the time of generating the key, you specify an upper limit on the number of concurrent users according to what your customer paid for. It is not time limited, and you do not define a quota limit on it. The host name that you specify is based on what your customer tells you. Your customer obtains this information through a "uname -a" shell command on the server machine targeted for deployment of your application.
Your server application is designed to load at startup time an application property text file that includes, among other items, the license key and the host name, similar to the following:
:
LicenseKey=tFy67+h=…
HostName=licensedHostName1
:
At run time, either periodically or in response to specific events such as session initiation, your product code can interrogate its environment to obtain the number of active sessions and the application properties and check the license:
// imports
import com.vs.ezlicrun.*;
// First, get the host name and license key,
// and the active sessionCount
String hostName = AppProperty.getProperty("HostName");
String licenseKey = AppProperty.getProperty("LicenseKey");
long sessionCount = App.getActiveSessionCount(); // the current value
:
// Check the license key
EzLicenseInfo licenseInfo = new EzLicenseInfo();
try {
int warnings =
licenseInfo.checkMultiUserLicenseKeyBasic(
licenseKey, 0, 0, 0, 0, 0,
hostName, sessionCount,
"Blogix", "ZrTxWs0xjr4/s3==");
// last two params are product name and app password public key
} catch (EzLicExceptionBase eek) {
// log warning and / or throw exception
// according to your preference
}
Suppose you are an OEM reseller and your desktop product embeds infrastructure GUI technology obtained from an OEM. Your OEM would like to ensure that their embedded product can only be used in the context of your product. It should not be possible for your customer to decompose your product to extract the OEM’s product and make unlicensed use of the latter. At the same time, you do not wish to impose an additional license management burden on your customer – you wish to distribute a single key to your customer.
Your OEM can provide you with
their key, which may or may not be based on EasyLicenser. At the time
of key composition, you can go to the Custom tab and provide the OEM key value
in the Custom Key text area as follows:
oemkey=tFyU6h7G….
Your application code that checks for the license key will include a custom key handler that extracts the name-value pair and puts it into a hash table that is maintained by the EasyLicenser run time (in this example, we are assuming the key isn't access controlled):
// imports
import com.vs.ezlicrun.*;
EzLicenseInfo licenseInfo = new EzLicenseInfo();
try {
int warnings =
licenseInfo.checkLicenseKey(
licenseKey,
// Built-in custom key handler,
// defined in EasyLicenser Run Time Library
(new EzLicCustomKeyHandler()),
// No application context needed
for this handler
null,
0, 0, 0, 0, 0,
username, 0);
} catch (EzLicExceptionBase eek) {
JOptionPane.showMessageDialog(
(JComponent)null,
"You are not licensed to use
this product. " +
"Please contact technical support.",
"Licensing Error",
JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
You instruct your OEM to make the following call the first time it is invoked by your code (which necessarily follows the above code fragment in time):
String oemkey = (new EzLicCustomKeyHandler()).
getCustomKeyValue("oemkey");
At this point, your OEM can perform the necessary license key check according to the technology that was used to generate the key. Suppose the OEM used EasyLicenser to generate the OEM key. Then the OEM’s code would be similar to the following:
EzLicenseInfo oemLicenseInfo = new
EzLicenseInfo();
int oemwarnings =
oemLicenseInfo.checkSingleUserLicenseKeyBasic(
oemkey,0, 0, 0, 0, 0,
// User name is based on the product /
// company being OEM’ed to
"YourProductName");
The above approach can be extended to any number of embedded OEM keys, possibly in combination with other customizations. To accomplish this, simply provide a list of name-value pairs instead of a single name-value pair. The only constraint is that the names should be unique.
The specific details of the API
are available in the Java Run Time Library
API Reference Java documentation. The API is used in essentially
the same manner regardless of the license policy that is encoded in the keys,
with the difference that an additional auxiliary key license key
parameter, corresponding to an enabler key provided by Agilis, is supplied
in order to enable deferred-time-limited or deferred-node-locked
licensing functionality. If deferred-licensing functionality is not required
(for example, corresponding to the scenario where a trial license is upgraded to
a production license), a null value is provided for the auxiliary key at the time
of activation.
To activate a license for a desktop application installation that is currently not in an activated
state, your application's activation logic will invoke the activateLicense
API call (using the same example as the single-user license above), as follows:
// imports
import com.vs.ezlicrun.*;
// Activate the license key provided by you to your end customer,
// optionally enabled for deferred-mode licensing with an auxiliary key provided by Agilis.
try {
EzLicenseExtendedAPI
licenseInfo =
EasyLicenseExtendedAPI.activateLicense(
config.getLicenseKey(), auxKey, null, null, userName,
"FileManagerProduct", "rTxWsuxjr4/s3J=",
null, null, true, auxKey != null);
// deferred node locking only if auxiliary key is specified.
} catch (EzLicExceptionBase eek) {
:
:
}
// extract and save key cookie in persistent store:
String keycookie = licInfo.getKeyCookie();
To check the license in the steady state, your application's license checking logic will read the last-saved key cookie from persistent store and then invoke the checkLicense API call, following which it will save the new key cookie in persistent store, as follows:
// imports
import com.vs.ezlicrun.*;
// Check the last-obtained license activation record
// that you read from persistent store eg. a registry entry.
// Bump up usage count by one, by specifying -1 for the quota-increment parameter.
try {
EzLicenseExtendedAPI
licenseInfo =
EasyLicenseExtendedAPI.checkLicense(
licenseKeyCookie, null, null, 0,
-1, 0, 0, 0, userName, 0,
"FileManagerProduct", "rTxWsuxjr4/s3J=",
null, null, true);
} catch (EzLicExceptionBase eek) {
:
:
}
// extract and save key cookie in persistent store:
licenseKeyCookie = licInfo.getKeyCookie();
To deactivate the license (for example at the time of uninstallation), your application's deactivation logic will read the last-saved key cookie from persistent store and then invoke the deactivateLicense API call, as follows:
// imports
import com.vs.ezlicrun.*;
// Deactivate using the last-obtained license activation record
// that you read from persistent store eg. a registry entry.
try {
EasyLicenseExtendedAPI.deactivateLicense(
licenseKeyCookie,
-1, 0, 0, 0, userName, 0,
"FileManagerProduct", "rTxWsuxjr4/s3J=",
null, true);
} catch (EzLicExceptionBase eek) {
:
:
}
The jdemoextapi.java application, available in the demo directory,
is an application that performs normal or deferred-mode license checks according to
whether and what auxiliary license key is provided.
Deferred-mode functionality is turned on by specifying the appropriate pair of keys: the actual key generated by you, and the enabling auxiliary key generated by Agilis and provided to you, as follows:
Deferred time-limited licensing:
This the anonymous-trial-licensing use case, where you may want to provide
a single self-contained binary file, together with a single set of keys,
for all evaluators to access either by downloading or installing from a
a single distribution medium, such that each installation is time-limited
to a duration controlled by you relative to the time the user initiates
trial licensing, without any communication between you and the trial user.
You generate a single key, which is not node locked, and which
has a EZLM:KEY-EXPIRE-DAYS option whose value you define to be the
desired license duration in days from the time of activation.
Correspondingly, Agilis provides you an auxiliary key that has embedded in
it an upper limit on relative expiration days. The effective relative-time-limited
license duration is the lower of the two values. In your application
logic, you provide the auxiliary key parameter to the license activation
API call. If you want the license to node-lock to the application installation
at the time of activation, your application specifies a true value for the
deferredNodeLock parameter.
Deferred node-locked licensing:
This the production-licensing use case, where you want to issue
production license keys to end customers, such that you achieve the
best of all worlds: your keys are node-locked to the end-customer
installation and at the same time you don't need to collect node-locking
information from your customer - instead you ensure the key you
generate can only be installed within a narrow time window that
starts the moment you generate the key.
In response to each customer order, you generate a key, which is not node locked, and which
has a EZLM:KEY-EXPIRE-SHELF-LIFE-HRS option whose value you define to be the
desired shelf life in hours from the time of key generation, which
represents the period during which the key can be installed
by your end customer.
Correspondingly, Agilis provides you an auxiliary key that has embedded in
it the appropriate authorization to issue deferred node-locked licenses.
In the application integration logic, your application specifies
a true value for the deferredNodeLock parameter.
License key deployment for applications having visible activation
When the application provides an installer or other activation mechanism for prompting the user to enter a license key (whether trial or perpetual), the activation sequence invokes the extended API to deactivate an existing installation and then perform an activation with the newly supplied key, for example a perpetual key representing a replacement for a trial key. The enabling key parameter is ignored in this second activation call if the replacement key is perpetual.
Hot license key deployment for applications lacking visible activation
When a user interface is not available for explicitly controlling activation, and the process of upgrading a license key consists of replacing an existing key with a new key (for a 24x7 server application, for example), the above deployment logic is modified to occur on the fly as follows: at the time a license check would ordinarily be made during normal execution of the application, the license check is preceded by a "new key" check by comparing the relative timestamps of the license key and the key cookie. For example, if the license key and the key cookie are saved in files, the check is simply:
if ((new File(licensekeyfilename)).lastModified() >
(new File(keycookiefilename)).lastModified()) {
// enter deactivation-then-reactivation sequence
}
// continue with normal license check as usual
The objective behind coding for security is to thwart the following types of attacks:
Cross-product key use: when an end customer uses an EasyLicenser-generated key
intended for another licensed product with your product instead of paying for and
acquiring an appropriate license for your product as well.
The defense against this attack is to generate access controlled keys and code
your application to use the access control enabled license key checking API's.
Key cloning: when an end customer's programmer writes code to analyze a valid
license key obtained from you in order to utilize EasyLicenser for generating
additional keys for your product on unlicensed machines.
The access controlled key mechanism also serves as a defense against this attack,
since the encrypted key contents, including product options, custom cookie and
custom key, are encrypted with a private key that is not known to the customer or
to you, and decrypted with a corresponding public key that is known to you but not
to your customer.
Bypassing license key checks: when an end customer's programmer reverse
engineers and debugs your application and / or the EasyLicenser runtime library,
alters the license key checking logic in reverse engineered code and rebuilds the
application / runtime library so it can be used without a valid license key.
The EasyLicenser runtime provides the following defenses to protect your
application from this type of attack:
Verifying EasyLicenser run time's actual digital signature against one
provided by you or Agilis. Any changes to the library cause the signatures
to mismatch. Your application performs the signature verification upon
program startup or when a class is dynamically loaded the first time.
You can also use the same digital signature mechanism to protect your own
libraries from tampering. In this case, you will necessarily have to generate
your own signatures.
Utilizing EasyLicenser's secure API. The secure API forces a validation and
exchange of encrypted key cookies upon each license key check such that
the key cookies change in value each time. The API also enables you to
obfuscate flow control by causing an exception defined by your application to
be thrown in the event of a success.
Utilizing a custom key handler to implement a three-way shared secret and
throw a signal. The custom key can contain a secret known only to your application
which passes it in encrypted form as a run time context to the license key
checking API which in turn calls back to your handler. Your handler can also
signal a license key check event such that the main application code requires
the signal to be raised in order for the license checking to be declared to
be successful.
Utilizing Java reflection to dynamically invoke the license key check API.
Dynamic invocation makes it harder to reverse engineer your application through
decompilation. The technique works for all four types of API calls.
Dynamic invocation is a generic technique that is not specific to EasyLicenser.
This is illustrated in the previous examples. No programming is required beyond passing the product name and application password public keys as parameters to an access-control API. The product name exactly corresponds to the name of the product that you specified in the License Manager GUI. The application password public key is the value displayed in the Product Details screen in the License Manager GUI when you define an application password for the product, and the product definition is selected at the time of key generation. If the key is not access-controlled, you can pass null values for the parameters. If the key is access-controlled, the parameter values are mandatory. For maximum security, specify a value of true for the "strict password check" parameter.
The secure API accepts an encrypted key cookie parameter
that is initialized at product activation time using a create
key cookie seed API call, and saved by the application in persistent
store. Every time a license key check is performed, for example upon
application startup, the saved key cookie os provided to the secure
license key check API call, which validates it and produces and returns
an updated key cookie if the license key check is successful. The
application replaces the previous key cookie with the new key cookie.
If the key cookie is tampered with, or the system clock is turned back to
before the timestamp contained in the last-saved key cookie, the
license key check will fail regardless of the validity of the license key.
The secure API optionally accepts an application-defined custom
exception handler. If specified, the exception handler is thrown
upon successful execution and can be caught in your application at a location
arbitrarily distant from the location of the license key check, making it
more difficult to alter the application logic to bypass the license key
check. The exception carries with it the warning code and new key cookie.
The secure API's key cookie also securely manages application license state that changes
at run time with successive license key checks. This state information is protected
from tampering by the same mechanisms that protect the key cookie. The specifics
of using key cookies for secure state management are described below under
Coding For Automatic State Management.
For a detailed illustration of how the secure API is used in conjunction with
key cookies, exception based flow control and dynamic invocation, please refer to
the jdemosecure.java source code.
Note however that for automatic state management, the preferred approach is to use the
higher-level extended API instead.
The secure API and key cookie mechanism is also used in an enhanced mode to automatically manage
license state that changes with successive use at the end customer site. Quota consumption is
implicitly tracked in the key cookie as a function of a quota increment that is specified to
the license key check call. To differentiate the quota increment from the pre-EasyLicenser 2.0
usage where the quota value always represented a cumulative quota value maintained by the
application, a quota increment is represented by a negative value.
In addition to automatically managing quota consumption, the key cookie can also track arbitrary
application license state that is supplied at the time of key cookie creation or a secure license
key check.
The license state contents of the key cookie can be retrieved through a set of getter
methods provided for the purpose.
For a detailed illustration of how the secure API is used in conjunction with
key cookies in enhanced mode to automatically manage license state, please refer to
the jdemostate.java source code.
The above objectives can be met with less coding if you use the higher-level extended API instead of the secure API.
The jdemoextapi.java source code illustrates how to
use the extended API.
The mechanism for protection from spoofing of an EasyLicenser library
(or your own libraries) is a
dynamic digital signature: at your development environment,
you generate a digital signature for the EasyLicenser (and / or your own)
library that is unique to your application and is based on a password
specified by you. For the EasyLicenser library, you do not have to
use the digital signature provided to you by Agilis. You then
package or embed with your application the public-key-encrypted versions
of your password and signature, and then use these to securely double-
check the signature of the library at run time at your
end customer's site, as follows:
ISV development environment:
Outside of your actual application, invoke the
com.vs.ils.crypt.Signature.makeLibDigest method,
providing as input parameters the EasyLicenser runtime library jar file name, a password
unique to your application and known only to your developers, and the EasyLicenser
Java library product name EzlmJavaLibrary,
as follows:
// Invocation returns a vector of 3 strings:
// signature, public-key-encrypted signature, public-key-encrypted password
String[] signatureEtc =
com.vs.ils.crypt.Signature.makeLibDigest(
"ezlicrun20.jar", // version 2.0 EasyLicenser runtime eg.
"myezlmsecret", // your chosen password
"EzlmJavaLibrary", // EasyLicenser runtime library product name
true); // indicate it's an ISV environment
Follow the invocation by extracting the returned data:
String
signature = signatureEtc[0]; // The actual signature
String
pubEncrSig = signatureEtc[1]; // signature encr w/ prod pub key
String
pubEncrPass = signatureEtc[2]; // passwd encr w/ prod pub key
You can also use the makelibdigest command line utility instead of
coding against the API. In either case, you will embed the public key encrypted password
into your application, and you will embed or otherwise arrange your application to be
parameterized to accept either the signature or its public key encrypted form. For
security, it is recommend that you parameterize your application to accept the public key
encrypted form of the signature so you don't have to keep it secret and so you can change
a data file whenever the EasyLicenser runtime library is updated. The tradeoff is that
your application will need to make an extra call at run time to perform an additional
public key encryption call.
Runtime application environment:
Code your application invoke the com.vs.ils.crypt.Signature.makeLibDigest
method similar to the above, except that with this API call, provide the public-key-encrypted
password and indicate that this is a run time invocation:
// Invocation returns a vector of 2 strings similar to before:
// signature, public-key-encrypted signature, public-key-encrypted password
String[] signatureEtc =
com.vs.ils.crypt.Signature.makeLibDigest(
"ezlicrun20.jar", // version 2.0 EasyLicenser runtime library eg.
"OCwwLzk4JjA2JzAh", // pk-encrypted password
"EzlmJavaLibrary", // EasyLicenser runtime library product name
false); // indicate it's a runtime environment
Follow the invocation by extracting the returned data as before, except that the
a password value is not returned:
String
signature = signatureEtc[0]; // Alleged actual signature
String
pubEncrSig = signatureEtc[1]; // Alleged signature encr w/ prod pub key
String
If you stored the original actual signature in its public key encrypted form
as recommended, first reconstruct the encrypted signature and check it in
order to detect spoofing of the cryptographic library itself. Use the
EasyLicenser library product's public key for encryption:
// First generate the product's public key:
String prodPubKey = com.vs.ils.crypt.PkCrypt.makePublicKey("EzlmJavaLibrary");
// Next, generate the public-key encryption of the encrypted signature:
signature = PkCrypt.encryptWithPublicKey(signature,prodPubKey);
if (!signature.equals(pubEncrSig)) {
System.out.println("*** Spoofed signature / crypto library ***");
System.exit(1);
}
Next, compare the resulting signature with the saved signature:
if (!signature.equals(savedOriginalSignature)) {
System.out.println("*** Spoofed EasyLicenser runtime library ***");
System.exit(1);
}
You should perform the check on the library prior to using any of its
functions, for example upon program startup or prior to an explicit load
of any of its classes.
If possible, provide an explicit path specification for the library name
parameter to the makeLibDigest API call. The path is permitted to
be relative, for example ../lib/ezlicrun20.jar. Specifying an explicit
path eliminates the need to add the parent directory to your class path. An
explicit path specification is also required on Windows, in order to circumvent
a bug in the Java runtime when the directory path in a classpath entry
contains white spaces.
There is necessarily a negative consequence of verifying signatures:
a new release of the library is necessarily accompanied by an updated
signature that requires a new release of your application unless
you parameterize your application to accept a signature parameter,
for example in a configuration file.
For an example of how digital signatures are checked in an application,
refer to the jdemosecure.java
secure demo applicaton.
It is sufficient to copy lib/ezlicrunVV.jar and extlib/ilscrypt10.jar from the EasyLicenser installation root directory into your build environment and include it in your class path. If your application performs a digital signature check and does not provide an explicit path specification for the library name when invoking the makeLibDigest API call, your class path will also need to include the absolute directory path containing the run time library.
When you package your Java product, include the above jar files in your zip file or executable Jar in the usual fashion.
If your application utilizes deferred-mode functionality, your product packaging will include at a minimum the auxiliary key provided by Agilis. If the deferred-mode functionality is specifically deferred time-limited functionality, the packaging will also include the global relative-time-limited license key generated by you as described above.
Refer to the Java Run Time Library API Reference for specific information on the EasyLicenser Java Run Time Library API that you will use while writing your Java product’s license-enabling code.