How does the keygen-sh offline-license-check validate the expiration of the license?

Viewed 933

I have written some software that I wish to sell to end-users and I'm investigating license servers. Keygen-sh mentions on their website (https://keygen.sh/docs/validating-licenses/) what to do when the user is offline while trying to validate their license with keygen's server. I'm interested in option three:

Validation request failed e.g. timeout

The failure could be for a number of reasons, but the most likely culprit is that your user doesn't have an active internet connection. You could handle this in a variety of ways, but here are a few ideas:

  1. Begin a grace period (e.g. 30 days or 5 additional product usages), where you disallow access to the product if a license validation has not been successful after that timeframe. You should clearly tell your users that they should connect to the internet when possible and be upfront about the grace period.

  2. Immediately disallow access to the product. This should be used with care, and is not recommended unless your product relies on an active internet connection.

  3. Fallback to offline license key validation if you're utilizing cryptographically signed or encrypted license keys (see the API reference for more information).

In case of option 1, how am I supposed to check that 30 days have past since their last active internet connection? If I store the last date in a .txt file, they could simply delete that file.

That brings me to option 3. But how does the cryptographic check validate for expiration? The code (https://github.com/keygen-sh/example-python-cryptographic-verification) doesn't seem to use the system time.

I'm aware that I can't prevent my software from being cracked. But it shouldn't be as easy as deleting a file or cutting the internet connection. Let's assume they don't tamper with their system time and they won't decompile my code.

2 Answers

I'm the founder of Keygen — let me try to clarify the options here. The options are actually not mutually exclusive, so you could do both option 1, 2 and 3, depending on a few variables.

Your thinking is more or less correct here. You could store a datetime in a txt file, or into some other means of storage, but like you said, it poses the risk of tampering. To prevent this, what I usually recommend doing is called license validation caching. This is where would cache successful license validation requests for later use, alongside the request's cryptographic signature.

There's an example of doing this is Node here: https://github.com/keygen-sh/example-validation-caching (I know you're using Python, so bear with me here.)

Essentially, when you perform a license validation request, you'd would want to cache the response body into the file system, e.g. a file called cache.json, as well as cache the response body's cryptographic signature, available in the X-Signature header of the response, and store that into the file system also, e.g. perhaps a file called cache.sig.

When there's no active internet connection available, you would want to try and read from that cache, and if data exists, you would cryptographically verify the cached data and signature using RSA PKCS v1.5 padding (similar to the example you linked) to ensure that it hasn't been tampered with (i.e. the problem you mentioned with storing the raw datetime in a txt file). If the cached data is present, and after its authenticity has been verified, you would then decode the cached JSON data and check the expiration.

Now, if there's no active internet connection and the cache doesn't exist, then you would likely want to move to option 2, i.e. disallow use of the program, since there's no proof that a valid license key has ever been provided. Or, we could utilize option 3 and prompt for a cryptographically signed license key and verify that in the offline environment.

If you do go with option 3, since you seem to want to check for an expiration offline as well, you would need to embed an expiration timestamp inside of the license's key itself. since the API doesn't automatically embed any data inside of cryptographically signed (or encrypted) license keys. In a fully offline environment without any caching, you won't have access to the full license object from the API, meaning you will only have the data that is embedded within the key itself.

I went ahead and created an example for you of doing this in Python: https://github.com/keygen-sh/example-python-offline-validation-caching

I've molded @ezekg's comments into a Python package, keygen_licensing_tools. Secure cached requests is as easy as

from datetime import datetime, timedelta
from keygen_licensing_tools import validate_license_key_cached

out = validate_license_key_cached(
    account_id="your accound id",
    key="the license key",
    keygen_verify_key="your Ed25519 128-bit Verify Key",
    cache_path="/tmp/license-cache.json",
    refresh_cache_period=timedelta(days=3),
)

# out.is_valid
# out.timestamp
# ...
Related