Steps
This section outlines a set of basic steps that a software publisher might follow to incorporate the LSAPI calls into an application.
1. Select a unique publisher (company) name. This name, which may be up to 32 characters long, should include a trademarked name and be the same for all applications that your company develops.
2. Select a unique product (application) name within the company (publisher) domain. This name must be unique within the first 32 characters. Over the life of an application, the product name usually remains unchanged.
3. Select a unique version string within the application (product) domain. The version string is used to differentiate multiple versions.
4. Determine the number of different sets of secrets that are likely to be issued. For purposes of example, four sets are chosen:
Set 1
Set 2
Set 3
Set 4
5. Now it is time to select the individual secrets which make up the sets. For each set, choose one or more random secrets:
Secret Set | Secret 1 | Secret 2 | Secret 3 | Secret 4 |
Set 1 | 0x84FE31 | 0x24178 | 0x36EAB | 0xAB213 |
Set 2 | 0x84FE31 | 0x1012A | 0x34784 | 0xAB213 |
Set 3 | 0x84FE31 | 0xA6170 | 0x62834 | 0x1074E |
Set 4 | 0x84FE31 | 0x71F45 | 0xD6189 | 0x86753 |
In this example, the same secret value was chosen for Secret 1 for all four sets. This allows the application to always challenge Secret 1 in the LSRequest(), yet differentiate subsequent challenges through LSUpdate(). The use of the sets is up to the software developer (e.g., each set can represent a different licensing policy if desired).
6. Embed the challenge algorithms in the application. Some effort will be required to obscure the code and associated data (see the Anti-Tampering Guidelines on page 25).
7. Add the LSRequest() and LSRelease() calls to the application.. Also include periodic calls to LSUpdate(), as appropriate.
8. Select the specific LSAPI-compliant license systems for which you wish to issue licenses. For operating system platforms which allow shared or dynamically linked libraries, the set of supported license systems may be expanded after the software product has shipped, provided that the software application does not require license system specific functionality.
Those are the basic steps.
It is not the intent of this specification to offer recommendations on how to handle various error codes. However, the software publisher should carefully consider the implications of the decisions that he or she makes. For example, should an application be allowed to run if the network is unavailable? What if there are no more licenses available? Organizations such as the Microcomputer Managers Association and the Software Publishers Association can offer recommendations to these and other important questions.
Application Code Example
The following code provides an example of licensing an application with the LSAPI. This code demonstrates several useful approaches to various situations, utilizing the LSAPI.
First, GetMyLicense(), the section of code which obtains the required units, demonstrates how error codes can be displayed for each individual provider when a license is not obtainable. This allows the user the capability of determining what caused the failure to obtain the required units, for each individual license provider. This function will display the error for each provider, as they occur.
Second, the functions which create the challenge query/response, are designed to work on both big endian machines (ex. Sun ) and little endian machines (ex. intel 80x86 family). If your target machine has the little endian architecture, LITTLE_ENDIAN needs to be defined.
Finally, the update portion of code, PerformUpdate(), could be placed into an automatic scheduler, if one is available on your specific platform, to simplify the update process.
Please note that this code is meant for clarity. It is a little more verbose than might otherwise be required for your application. Further, since the code is example code, it is very straight forward in its operations. This should not be a goal in the creation of the licensing code for your applications. The licensing code should be obfuscated so as to hide its apparent purpose, making it more difficult to subvert.
#include <stdio.h>
#include <stdlib.h>
#include <mem.h>
#include <string.h>
#include <lsapi.h>
#define MY_PUBLISHER_NAME "ACME(tm) SoftWare Vendor, Inc."
#define MY_PRODUCT_NAME "ACME Number Cruncher"
#define MY_VERSION "Vrsn 0.0a"
#define LOG_COMMENT "ACME requested a license"
LS_ULONG secrets[4] = {0x1234, 0x4321, 0xABCD, 0xDCBA}
#if defined(LITTLE_ENDIAN)
void swap(LS_UCHAR *byte1, LS_UCHAR *byte2)
{
LS_UCHAR tmp;
tmp = *byte2;
*byte2 = *byte1;
*byte1 = tmp;
}
#endif // LITTLE_ENDIAN
//
// PlaceInt32() will place a 32 bit integer (signed/unsigned) into the
// provided buffer in the required format for challenge processing. It
// will return the amount of bytes consumed in the buffer.
//
int PlaceInt32(char *buffer, LS_ULONG value)
{
// if we are on a little endian machine, we need to re-order the bytes
#if defined(LITTLE_ENDIAN)
swap(&(((char *) &value)[0]), &(((char *) &value)[3]));
swap(&(((char *) &value)[1]), &(((char *) &value)[2]));
#endif // LITTLE_ENDIAN
// place the value into the buffer
*(LS_ULONG *) buffer = value;
return((int) sizeof(LS_ULONG));
}
//
// PlaceString() will place an LSAPI string into the supplied buffer, the
// number of bytes occupied will be returned. The NULL terminator will not
// be copied.
//
int PlaceString(char *buffer, LS_UCHAR *str)
{
memcpy(buffer, str, strlen(str));
return((int) strlen(str));
}
//
// PlaceSecrets() will put the random number, secret number and the secret
// into the supplied buffer in the proper format. The number of bytes that
// these values occupy in the buffer will be returned.
//
int PlaceSecrets(char *buffer, LS_CHALLENGE *challengeStruct)
{
char *bptr;
int buffLen, bytes;
// initialize the pointers
bptr = buffer;
buffLen = 0;
// place the random number
bytes = PlaceInt32(bptr, challengeStruct->ChallengeData.Random);
bptr+= bytes;
buffLen+= bytes;
// place the secret index
bytes = PlaceInt32(bptr, challengeStruct->ChallengeData.SecretIndex);
bptr+= bytes;
buffLen+= bytes;
// place the secret itself
bytes = PlaceInt32(bptr, secrets[challengeStruct->ChallengeData.Secret[Index]);
buffLen+= bytes;
return(buffLen);
}
//
// PlaceRequestInParameters() will place all of the input parameters for
// an LSRequest call. It will return the number of bytes it has placed
// into the provided buffer. The buffer is assumed to be large enough to
// hold all of the arguments
//
int PlaceRequestInParameters(char *argBuff, LS_UCHAR *LicenseSystem, LS_UCHAR *PublisherName, LS_UCHAR *ProductName, LS_UCHAR *Version, LS_ULONG TotUnitsReserved, LS_UCHAR *LogComment)
{
char *bptr;
int bytes, buffLen;
bytes = 0;
buffLen = 0;
bptr = argBuff;
// place the function name
bytes = PlaceString(bptr, "LSRequest");
bptr+= bytes;
buffLen+= bytes;
// place the arguments
bytes = PlaceString(bptr, LicenseSystem);
bptr+= bytes;
buffLen+= bytes;
bytes = PlaceString(bptr, PublisherName);
bptr+= bytes;
buffLen+= bytes;
bytes = PlaceString(bptr, ProductName);
bptr+= bytes;
buffLen+= bytes;
bytes = PlaceString(bptr, Version);
bptr+= bytes;
buffLen+= bytes;
bytes = PlaceInt32(bptr, TotUnitsReserved);
bptr+= bytes
buffLen+= bytes;
bytes = PlaceString(bptr, LogComment);
bptr+= bytes;
buffLen+= bytes;
return(buffLen);
}
//
// PlaceUpdateInParameters() will place the input parameters for an LSUpdate
// call into the specified buffer. The number of bytes occupied by this info
// will be returned. The buffer is assumed to be large enough to hold all of
// the arguments
int PlaceUpdateInParameters(char *argBuff, LS_HANDLE LicenseHandle, LS_ULONG TotUnitsConsumed, LS_ULONG TotUnitsReserved, LS_UCHAR *LogComment)
{
int bufferLen;
char *bPtr;
bufferLen = 0;
bPtr = argBuff;
// place the function name
bytes = PlaceString(bPtr, "LSUpdate");
// place the arguments
bytes = PlaceInt32(bPtr, LicenseHandle);
bPtr+= bytes;
bufferLen+= bytes;
bytes = PlaceInt32(bPtr, TotUnitsConsumed);
bPtr+= bytes
bufferLen+= bytes;
bytes = PlaceInt32(bPtr, TotUnitsReserved);
bPtr+= bytes;
bufferLen+= bytes;
bytes = PlaceString(bPtr, LogComment);
bPtr+= bytes;
bufferLen+= bytes;
return(bufferLen);
}
//
// void CreateChallengeQuery() will create the input challenge based on
// the supplied parameters. It will return a complete challenge structure,
// utilizing the basic challenge protocol, which is the ready to pass to any
// LSAPI function which requires a challenge.
//
void CreateRequestChallengeQuery(LS_UCHAR *LicenseSystem, LS_UCHAR *PublisherName,LS_UCHAR *ProductName, LS_UCHAR *Version, LS_ULONG TotUnitsReserved,LS_UCHAR *LogComment, LS_CHALLENGE *challengeStruct)
{
char *argbuff, *bptr;
int buffLen, bytes;
// Indicate that we are using the basic protocol
challengeStruct->Protocol = LS_BASIC_PROTOCOL;
// Indicate the size of the challenge data structure to the provider
challengeStruct->Size = sizeof(LS_CHALLDATA);
// Pick a random number, from 0 to maximum value
challengeStruct->ChallengeData.Random = random(0xFFFFFFFF);
// Pick a random secret value from 1 to 4
challengeStruct->ChallengeData.SecretIndex = (challengeData->ulRandom % 4) + 1;
// Create the buffer for the message digest
argbuff = (char *) malloc(strlen("LSRequest") + strlen(LicenseSystem) + 1 + strlen(PublisherName) + trlen(ProductName) + 1 + strlen(Version) + 1 + sizeof(LS_ULONG) + strlen(LogComment) + 1 + sizeof(LS_ULONG) + sizeof(LS_ULONG) + 4);
bptr = argbuff;
// place the arguments
buffLen = PlaceRequestInParameters(argbuff, LicenseSystem, PublisherName, ProductName, Version, TotUnitsReserved, LogComment);
bptr+= buffLen;
// Place the random number, secret number, secret
bytes = PlaceSecrets(bptr, challengeStruct);
buffLen+= bytes;
// Compute the Message Digest
ComputeMD4(argbuff, buffLen,
challengeStruct->ChallengeData.MsgDigest.MessageDigest);
}
//
// CreateRequestChallengeResponse() will form what the provider should
// respond with for a challenge on an LSRequest call. This value will be
// returned in messageDigest. This value can be compared with the value
// returned by the provider to determine the provider's validity.
//
void CreateRequestChallengeResponse(LS_UCHAR *LicenseSystem, LS_UCHAR *PublisherName, LS_UCHAR *ProductName, LS_UCHAR *Version, LS_ULONG TotUnitsReserved, LS_UCHAR LogComment, LS_ULONG totUnitsGranted, LS_STATUS_CODE Status, LS_CHALLENGE *challengeStruct, LS_UCHAR *messageDigest)
{
char *argbuff, *bptr;
int buffLen, bytes;
// Create the buffer for the message digest
argbuff = (char *) malloc(strlen("LSRequest") + strlen(LicenseSystem) + 1 + strlen(PublisherName) + 1 + strlen(ProductName) + 1 + strlen(Version) + 1 + sizeof(LS_ULONG) + strlen(LogComment) + 1 + sizeof(LS_ULONG) + sizeof(LS_ULONG) + sizeof(LS_ULONG) +sizeof(LS_ULONG) + sizeof(LS_HANDLE) + 4);
bptr = argbuff;
// place the arguments
buffLen = PlaceRequestInParameters(argbuff, LicenseSystem, PublisherName, ProductName, Version, TotUnitsReserved, LogComment);
bptr+= buffLen;
// place the output parameters
bytes = PlaceInt32(bptr, totUnitsGranted);
bptr+= bytes;
buffLen+= bytes;
bytes = PlaceInt32(bptr, Status);
bptr+= bytes;
buffLen+= bytes;
// Place the random number, secret number, secret
bytes = PlaceSecrets(bptr, challengeStruct);
buffLen+= bytes;
// Compute the Message Digest
ComputeMD4(argbuff, buffLen, messageDigest);
}
//
// CreateUpdateChallengeQuery() will create the query portion of the challenge
// to send to the provider. It will return a complete challenge structure
// ready to pass through the API.
//
void CreateUpdateChallengeQuery(LS_HANDLE LicenseHandle, LS_ULONG TotUnitsConsumed, LS_ULONG TotUnitsReserved, LS_UCHAR *LogComment, LS_CHALLENGE *challengeStruct)
{
char *argBuff, bPtr;
int bufferLen, bytes;
// Indicate that we are using the basic protocol
challengeStruct->Protocol = LS_BASIC_PROTOCOL;
// Indicate the size of the challenge data structure to the provider
challengeStruct->Size = sizeof(LS_CHALLDATA);
// Pick a random number, from 0 to maximum value
challengeStruct->ChallengeData.Random = random(0xFFFFFFFF);
// Pick a random secret value from 1 to 4
challengeStruct->ChallengeData.SecretIndex = (challengeData->Random % 4) + 1;
// Create the buffer for the message digest
argBuff = (char *) malloc(strlen("LSUpdate") + sizeof(LS_HANDLE) + sizeof(LS_ULONG) + sizeof(LS_ULONG) + strlen(LogComment) + 1 + sizeof(LS_ULONG) + sizeof(LS_ULONG) + SECRET_SIZE);
bPtr = argBuffer;
bufferLen = PlaceUpdateInParameters(argBuffer, LicenseHandle,TotUnitsConsumed, TotUnitsReserved, LogComment);
bPtr+= bufferLen;
// place the random number, the secret number, and the secret
bytes = PlaceSecrets(bPtr, challengeStruct);
bufferLen+= bytes;
// Compute the Message Digest
ComputeMD4(argBuff, bufferLen, challengeStruct->ChallengeData.MsgDigest.MessageDigest);
}
//
// CreateUpdateChallengeResponse() will form what the provider should
// respond with for a challenge on an LSUpdate call. This value will be
// returned in messageDigest. This value can be compared with the value
// returned by the provider to determine the provider's validity.
//
void CreateUpdateChallengeResponse(LS_HANDLE LicenseHandle, LS_ULONG TotUnitsConsumed, LS_ULONG TotUnitsReserved, LS_UCHAR *LogComment, LS_ULONG totUnitsGranted, LS_ULONG Status, LS_CHALLENGE *challengeStruct, LS_UCHAR *messageDigest)
{
int bytes;
char *argBuff, *bPtr;
argBuff = (char *) malloc(strlen("LSUpdate") + sizeof(LS_HANDLE) + sizeof(LS_ULONG) + sizeof(LS_ULONG) + strlen(LogComment) + 1 + sizeof(LS_ULONG) + sizeof(LS_ULONG) + sizeof(LS_ULONG) + sizeof(LS_ULONG) + SECRET_SIZE);
bPtr = argBuffer;
bufferLen = PlaceUpdateInParameters(argBuffer, LicenseHandle,TotUnitsConsumed, TotUnitsReserved, LogComment);
bPtr+= bufferLen;
// place the output parameters
bytes = PlaceInt32(bPtr, totUnitsGranted);
bPtr+= bytes;
bufferLen+= bytes;
bytes = PlaceInt32(bPtr, Status);
bPtr+= bytes;
bufferLen+= bytes;
// place the random number, the secret number, and the secret
bytes = PlaceSecrets(bPtr, challengeStruct);
bufferLen+= bytes;
// Compute the Message Digest
ComputeMD4(argBuff, bufferLen, messageDigest);
challengeStruct->ChallengeData.MsgDigest.MessageDigest);
}
//
// PrintErrorMessage() will display the error message for the associated
// handle/licensing system.
//
void PrintErrorMessage(LS_HANDLE licHandle, LS_STATUS_CODE status, LS_UCHAR *licSystem)
{
LS_STATUS_CODE localStat;
LS_UCHAR *errorBuff;
LS__ULONG errorBuffSize;
errorBuffSize = DEFAULT_ERROR_BUFFER_SZ;
errorBuff = (LS_UCHAR *) malloc(errorBuffSize);
localStat = LSGetMessage(licHandle, status, errorBuff, errorBuffSize);
while (localStat == LS_BUFFER_TOO_SMALL) {
errorBuffSize*= 2;
errorBuff = (LS_UCHAR *) realloc(errorBuff, errorBuffSize);
localStat = LSGetMessage(licHandle, status, errorBuff, errorBuffSize);
} // end while
if (localStat != LS_SUCCESS)
printf("Licensing system %s reports: An unknown licensing system error occured\n", licSystem);
else
printf("Licensing system %s reports: %s\n", licSystem, errorBuff);
free(errorBuff);
}
//
// LS_HANDLE GetMyLicense() will retrieve the default number of units from
// each licensing system provider. It will return the resulting status in
// stat, and the return value will be a valid licensing handle. Any errors
// encountered with each licensing system will be displayed. stat will reflect
// the last error encountered.
//
LS_HANDLE GetMyLicense(LS_STATUS_CODE *stat)
{
LS_ULONG totUnitsGranted, Index;
LS_HANDLE licHandle;
LS_CHALLENGE challengeStruct;
LS_UCHAR messageDigest[16], providerName[255];
LS_STATUS_CODE localStat;
// visit the first provider
ulIndex = 0;
localStat = LSEnumProviders(Index, providerName);
// if there are no providers installed, return that error code
if (localStat == LS_BAD_INDEX) {
*stat = LS_SYSTEM_UNAVAILABLE;
return(licHandle);
} // end if
// otherwise, loop through the providers until either we run out, or
// successfully retrieve the units necessary to run
while (localStat != LS_BAD_INDEX) {
// fill in the challenge structure
CreateRequestChallengeQuery(providerName, MY_PUBLISHER_NAME, MY_PRODUCT_NAME, MY_VERSION, LS_DEFAULT_UNITS, LOG_COMMENT, &challengeStruct);
// Perform the request with the created challenge
*stat = LSRequest(providerName, (LS_UCHAR *) MY_PUBLISHER_NAME, (LS_UCHAR *) MY_PRODUCT_NAME, (LS_UCHAR *) MY_VERSION, LS_DEFAULT_UNITS, (LS_UCHAR *) LOG_COMMENT, &challengeStruct, &totUnitsGranted, &licHandle);
// if the status was alright, check the challenge
if (*stat == LS_SUCCESS) {
// We have received licenses, compute the challenge response ourselves to test the validity of // the provider's response
CreateRequestChallengeResponse(LS_ANY, (LS_UCHAR *) MY_PUBLISHER_NAME, (LS_UCHAR *) MY_PRODUCT_NAME, (LS_UCHAR *) MY_VERSION, LS_DEFAULT_UNITS, (LS_UCHAR *) LOG_COMMENT, totUnitsGranted, *stat, &challengeStruct, messageDigest);
// compare our response, and the providers response
if (memcmp(messageDigest, challengeStruct.ChallengeData.MsgDigest.MessageDigest, sizeof(messageDigest)) != 0)
// indicate an error has occured
*stat = LS_AUTHORIZATION_UNAVAILABLE;
} // end if
// if stat indicates an error, get the providers message and display
// and try the next provider
if (*stat != LS_SUCCESS)
PrintErrorMessage(licHandle, *stat, providerName);
else {
// we have succesfully gotten our units, return the handle/status
printf("Units successfully retrieved from license provider %s\n", providerName);
return(licHandle);
} // end else
// free the handle, and try again
LSFreeHandle(licHandle);
} // end while loop
// we ran out of license providers, just return the last error code, and a
// meaningless (now freed) handle
return(licHandle);
}
//
// LS_STATUS_CODE PreformUpdate() will preform an update call for the specified license handle,
// and will return the status of that operation
//
LS_STATUS_CODE PerformUpdate(LS_HANDLE licHandle)
{
LS_STATUS_CODE retVal;
LS_CHALLENGE challengeStruct;
LS_ULONG totUnitsGranted;
LS_UCHAR ResponseMessageDigest[16];
// form up the challenge query
CreateUpdateChallengeQuery(licHandle, LS_DEFAULT_UNITS, LS_DEFAULT_UNITS, (LS_UCHAR *) LOG_COMMENT, &challengeStruct);
// Make the actual update call, with the default number of units,
// specifying that we should not acquire any more units
retVal = LSUpdate(licHandle, LS_DEFAULT_UNITS, LS_DEFAULT_UNITS, (LS_UCHAR *) LOG_COMMENT, &challengeStruct, &totUnitsGranted);
// if the status was not OK, return immediately
if (retVal != LS_SUCCESS)
return(retVal);
// create the challenge response
CreateUpdateChallengeResponse(licHandle, LS_DEFAULT_UNITS, LS_DEFAULT_UNITS, (LS_UCHAR *) LOG_COMMENT, &challengeStruct, totUnitsGranted, retVal, ResponseMessageDigest);
// compare the the provider's response and our own
if (memcmp(messageDigest, challengeStruct.ChallengeData.MsgDigest.MessageDigest, sizeof(messageDigest)) != 0)
// return an error
retVal = LS_AUTHORIZATION_UNAVAILABLE;
// return the result
return(retVal);
}
//
// void DoTheWorkOfMyProgram() will preform the actual work of the program.
// Also, it will preform the update based on the recommended default update
// period for the provided handle.
void DoTheWorkOfMyProgram(LS_HANDLE licHandle)
{
LS_STATUS_CODE status;
//
// This area does whatever the program needs to do
//
// This area would be run periodically, in whatever manner is appropriate
// for your particular operating system
status = PerformUpdate(licHandle);
// end the program if status was not OK, first displaying the error message
if (status != LS_SUCCESS) {
// get our current licensing system name
// display the error message that occured
PrintErrorMessage(licHandle, status, licSystem);
// Free our licensing handle
LSFreeHandle(licHandle);
return;
}
//
// continue doing the work of our program, looping/jumping/threading back to
// the above area periodically
//
//
// at the end of our program, free our handle
//
LSFreeHandle(licHandle);
return;
}
void main(int argc, char **argv)
{
LS_HANDLE licHandle;
LS_STATUS_CODE stat;
// obtain license(s) and retrieve a licensing handle
licHandle = GetMyLicense(&stat);
// check the stat
if (stat != LS_SUCCESS)
printf("No licenses retrieved from any licensing system! Exiting...\n");
// otherwise run our program
else
DoTheWorkOfMyProgram(licHandle, argc, argv);
}