OnlineUnlockStatus not unlocking when applying a valid expiry time

Hey Guys,

I have just started looking into using the onlineUnlockStatus class. It works very nicely for a permanent unlock. However, I am finding that it is not working so well once I add an expiry time. It seems that the isUnlocked() method is never returning true even with a valid expiry time.

I have done my best to look through and understand the JUCE code and I think that there may be a problem in the following.

bool OnlineUnlockStatus::applyKeyFile (String keyFileContent)
{
    KeyFileUtils::KeyFileData data;
    data = KeyFileUtils::getDataFromKeyFile (KeyFileUtils::getXmlFromKeyFile (keyFileContent, getPublicKey()));

    if (data.licensee.isNotEmpty() && data.email.isNotEmpty() && doesProductIDMatch (data.appID))
    {
        setUserEmail (data.email);
        status.setProperty (keyfileDataProp, keyFileContent, nullptr);
        status.removeProperty (data.keyFileExpires ? expiryTimeProp : unlockedProp, nullptr);

        var actualResult (0), dummyResult (1.0);
        var v (machineNumberAllowed (data.machineNumbers, getLocalMachineIDs()));
        actualResult.swapWith (v);
        v = machineNumberAllowed (StringArray ("01"), getLocalMachineIDs());
        dummyResult.swapWith (v);
        jassert (! dummyResult);

        if (data.keyFileExpires)
        {
            if ((! dummyResult) && actualResult)
                status.setProperty (expiryTimeProp, data.expiryTime.toMilliseconds(), nullptr);

            return getExpiryTime().toMilliseconds() > 0;
        }

        if ((! dummyResult) && actualResult)
            status.setProperty (unlockedProp, actualResult, nullptr);

        return isUnlocked();
    }

    return false;
}

the expiry time is stored when data.keyFileExpires is true. However, it seems strange to return true here if the expiry time is greater than zero. Although, technically a valid key file has been received at this point, wouldn’t it be preferable to return true here only when the expiry time is greater than the current time?

Also, the expiry time does not seem to be checked as part of the OnlineUnlockStatus::isUnlocked() function and I can’t see any code that updates unlockedProp based on the expiry time either.

inline var isUnlocked() const { return status[unlockedProp]; }

Hopefully I am not missing something obvious. Let me know what you think!

Cheers,

Chris

Yeah, it’s done like that deliberately so you can disambiguate between a “full” authorisation and an “expiring demo” authorisation.

In Waveform her have something that looks like this:

bool enableAllFeatures()
{
    if (unlocker.isUnlocked())
        return true;

    if (unlocker.hasDemoExpired())
        return false;

    if (unlocker.isInDemoMode())
        return true;

    return false;
}

(N.B. this is a big paraphrase and not what our code is in reality, in reality this logic gives a description to the user of their “authorisation status” which is why we need to know if they have started a demo period but it has since expired).

TL;DR: use isUnlocked in combination with getExpiryTime to determine what functionality to enable and what messages to show the user.

Ah, that makes so much more sense now!

Thanks Dave :sunglasses:

Coming back to this old thread as I’m implementing the ability of adding an expiration date into our copy protection scheme.

I’m currently using isUnlocked() all across the codebase for checking the validity of the license so changing this to an additional call to check the expiration date is rather tedious (at the moment we’re not planning on offering different feature sets based on a trial mode, but just enable or disable the entire product altogether).

What I would like to do instead is the ability of “unlocking” the unlocker by supplying a time that is checked against the expiration date, and if it’s OK, then the internal unlocked property is set to true so that subsequent calls to isUnlocked() will return true.

This factors in the possibility of the user being super smart and changing the system clock to bypass the expiration check by having a Time object that you can verify at some point (my current idea is to ask our server for a RSA encoded time value that is decrypted with the RSA key of the unlocker to verify that the time hasn’t been tampered with).

So I would suggest the following addition to juce::OnlineUnlockStatus:

/** Attempts to unlock a license with an expiration date using a Time object that is verified in some way. 
    The function returns true if the expiry date is later than the supplied time object.
		
    This modifies the value returned by isUnlocked() so if you don't want to distinguish between those scenarios,
    you can use this to unlock the license if it's not expired (by default isUnlocked() will return false if the
    license key file contains a expiration date).

    Note: If you pass in Time::getCurrentTime() here, the user can just change the system clock to bypass
    the activation check so make sure you are using a more reliable way to get the time. 
    If the license key doesn't contain an expiry data, this function will do nothing and just return the 
 current unlocked state.
*/
bool unlockWithTime(Time safeTimeObject);

with the implementation

bool OnlineUnlockStatus::unlockWithTime(Time safeTimeObject)
{
	Time times[2] = { getExpiryTime(), safeTimeObject };

	if (times[0] == Time(0))
		return isUnlocked();

	var actualResult(0), dummyResult(1.0);

	var v(!(times[0] < times[1]));
	actualResult.swapWith(v);
	v = var(times[0] == times[1]);
	dummyResult.swapWith(v);
	jassert(!dummyResult);

	if ((!dummyResult) && actualResult)
		status.setProperty(unlockedProp, actualResult, nullptr);

	return var(actualResult);
}

Note: I tried to follow the gibberish coding style of the other unlock flag methods to irritate hackers (wow, much security…) and I’m sure I’ve messed up a bool condition somewhere - this code isn’t tested yet, I just wanted to throw this in the ring for discussion as I could imagine it might be a valuable addition for other developers too.