PLUSManaged: Adding Basic Copy Protection

For a high-level primer on copy-protection, please see our blog post "Product Activation: Fingerprints, Copy Protection, Disconnected Computers".

A major aspect of software licensing is copy protection. This prevents an end-user from activating a license on one system and copying that application to another system to gain additional unauthorized licenses. A machine fingerprint is generated and saved in each license file. This fingerprint consists of several attributes known as system identifiers, which may consist of the network adapter's MAC address, the computer name, or other uniquely identifiable data. Each time an application runs, it generates the fingerprint for the current system and compares it to that stored in the license file. If enough of the individual system identifiers match, it is reasonable to believe it is the same system and the application is authorized to run. Otherwise, the user must reactivate. The system fingerprint is also included in the activation request and sent to SOLO Server. If the product option is configured to "Allow Reactivations on Same Computer" then SOLO Server will look for a previously activated installation with a matching fingerprint, and if found, will allow the system to reactivate.

While creating your license implementation class, you should have created a Validate method. This is where you can implement your copy protection logic by adding validation of information in the license and the system running your application.

Adding System Identifier Validation

System identification is the primary means of preventing a license from working on a system other than the one on which it was activated. In other words, if you were to activate an application on Computer A, copy Computer A's license file to Computer B, the application would prevent the application from running on Computer B using Computer A's license file.

Note

More detailed information on each system identifier can be found in the PLUSManaged API documentation under the com.softwarekey.Client.Licensing namespace.

Basic System Identifier Validation

 To add basic system identifier validation, we can update the Validate method added above to look like the following:

C#
public bool Validate()

{
SystemIdentifierValidation systemIdValidation = new SystemIdentifierValidation(this.AuthorizedIdentifiers,
this.CurrentIdentifiers,

SystemIdentifierValidation.REQUIRE_EXACT_MATCH);
if (!systemIdValidation.Validate())

{
this.LastError = systemIdValidation.LastError;

return false;
}



return true;
}
VB.NET
Public Function Validate() As Boolean
Dim systemIdValidation As New SystemIdentifierValidation(Me.AuthorizedIdentifiers, _
Me.CurrentIdentifiers, _

SystemIdentifierValidation.REQUIRE_EXACT_MATCH)
If Not systemIdValidation.Validate() Then
Me.LastError = systemIdValidation.LastError

Return False
End If



Return True
End Function

The code above checks to make sure the system's current identifiers are an exact match to the identifiers authorized during activation. If the CurrentIdentifiers do not match the AuthorizedIdentifiers in the license file exactly, then the license is rejected by the example above. If you only use a single type of SystemIdentifierAlgorithm, it is possible for you to replace SystemIdentifierValidation.REQUIRE_EXACT_MATCH to a number representing the minimum number of identifiers that must match instead.  However, if you use several different SystemIdentifierAlgorithm implementations in your application, then you should consider adding your own, customized validation.

Customized Validation and Fuzzy-Matching

It is possible to customize your validation logic to allow for some amount of acceptable change/variation by comparing the hash values of certain types of CurrentIdentifiers against the hash values of the same type of AuthorizedIdentifiers. To implement this in your application, you can add a new method to your license implementation class, which validates the identifiers using custom logic similar to what is shown below.

Important

Do not use the SystemIdentifierValidation with customized identifier validation and matching, as the two will likely conflict with each other.

C#
private bool ValidateIdentifiers()
{
int formatSerials = 0;
int formatSerialMatches = 0;
int nics = 0;
int nicMatches = 0;
int computerNames = 0;
int computerNameMatches = 0;

//calculate the number of authorized identifiers of each type, and calculate how many matches are found
foreach (SystemIdentifier authorizedIdentifier in this.AuthorizedIdentifiers)
{
SystemIdentifier matchingIdentifier = null;

foreach (SystemIdentifier currentIdentifier in this.CurrentIdentifiers)
{
//Use the SystemIdentifier class's == operator overload to compare the value hash and type
if (currentIdentifier == authorizedIdentifier)
{
//we found a match
matchingIdentifier = currentIdentifier;
break;
}
}

//update our counters
switch (authorizedIdentifier.Type)
{
case "NicIdentifier":
nics++;
if (matchingIdentifier != null)
nicMatches++;
break;
case "HardDiskVolumeSerialIdentifier":
formatSerials++;
if (matchingIdentifier != null)
formatSerialMatches++;
break;
case "ComputerNameIdentifier":
computerNames++;
if (matchingIdentifier != null)
computerNameMatches++;
break;
}
}

//Make sure we have at least one match for each type of identifier we have authorized
if ((formatSerialMatches < 1) ||
(nicMatches < 1) ||
(computerNameMatches < 1))
{
LastError = new LicenseError(LicenseError.ERROR_LICENSE_SYSTEM_IDENTIFIERS_DONT_MATCH);
return false;
}

return true;
}
VB.NET
Private Function ValidateIdentifiers() As Boolean
Dim formatSerials As Integer = 0
Dim formatSerialMatches As Integer = 0
Dim nics As Integer = 0
Dim nicMatches As Integer = 0
Dim computerNames As Integer = 0
Dim computerNameMatches As Integer = 0

'calculate the number of authorized identifiers of each type, and calculate how many matches are found
For Each authorizedIdentifier As SystemIdentifier In Me.AuthorizedIdentifiers
Dim matchingIdentifier As SystemIdentifier = Nothing

For Each currentIdentifier As SystemIdentifier In Me.CurrentIdentifiers
'Use the SystemIdentifier class's Equals override method to compare the value hash and type
If currentIdentifier.Equals(authorizedIdentifier) Then
'we found a match
matchingIdentifier = currentIdentifier
Exit For
End If
Next

'update our counters
Select authorizedIdentifier.Type
Case "NicIdentifier"
nics += 1
If Not matchingIdentifier Is Nothing Then nicMatches += 1
Case "HardDiskVolumeSerialIdentifier"
formatSerials += 1
If Not matchingIdentifier Is Nothing Then formatSerialMatches += 1
Case "ComputerNameIdentifier"
computerNames += 1
If Not matchingIdentifier Is Nothing Then computerNameMatches += 1
End Select
Next

'Make sure we have at least one match for each type of identifier we have authorized
If (formatSerialMatches < 1) Or _
(nicMatches < 1) Or _
(computerNameMatches < 1) Then
LastError = New LicenseError(LicenseError.ERROR_LICENSE_SYSTEM_IDENTIFIERS_DONT_MATCH)
Return False
End If

Return True
End Function

The example code above assumes three types of SystemIdentifierAlgorithm are used by the application, and requires at least one match for each. Once your customized validation method is implemented, you can update your Validate method to call to it as shown below.

C#
public bool Validate()
{
if (!this.ValidateIdentifiers())
{
this.LastError = systemIdValidation.LastError;
return false;
}

return true;
}
VB.NET
Public Function Validate() As Boolean
If Not Me.ValidateIdentifiers() Then
Me.LastError = systemIdValidation.LastError
Return False
End If

Return True
End Function