Changing keyfile format of OnlineUnlockStatus


#1

Is there a way to change the format of the keyfile used by the OnlineUnlockStatus class, without re-writing the whole class?


#2

Upon closer inspection, it looks like the format of the keyfile is determined by struct KeyFileUtils within the juce_OnlineUnlockStatus.cpp file.

And, therefore the answer to my initial question seems to be “no”.

Now, the next question in my mind is, should I just rewrite the .cpp file and attempt to leave the juce_OnlineUnlockStatus.h untouched?

Obviously anything’s possible, but I’m wondering more in terms of forking JUCE and being able to merge future changes back in to my copy. If I leave the header file intact, then at least the class’s interface would remain compatible with future updates, right? Just looking for advice to minimize headaches…


#3

We could add hooks to save it in some other format, but the KeyFileUtils class doesn’t actually write anything to disk, it just gives you the content, so you could easily just convert the file into your own format before you save/load it, without touching the juce code.

Why would you need to change it anyway? It’s just a lump of text, and the classes are going to need all the items it contains, so how would it help to store it differently? In its current form you could just store it inside your own XML, JSON or binary wrapper format with no hassle.


#4

Sorry if I wasn’t being precise enough in my question. I didn’t actually mean the file format (txt, xml, whatever) of the keyfile as stored to disk. I meant the contents of the keyfile, as determined by KeyFileUtils::createKeyFileContent.

Currently the keyfile contents are created from the arguments for: appName, userEmail, userName, and machineNumbers, plus a timestamp added in the createKeyFileContent method.

My point was, what if I wanted to store additional information in the keyfile? For example, the license ID number that was used to authorize the software install. (Otherwise, for customers who have purchased multiple copies of a license, there’s no association between a specific license ID and the “unlocked” status of the software on a particular system.)


#5

Ah right - yes, that’d be a more meaningful use-case! I guess for that sort of thing the best approach would be to create some overridable method for generating custom XML instead of what’s currently there.

I guess you could either just hack the code yourself and keep it in a fork, or send us a suggested change which would let you hook in what you need, which we could review and maybe add to the class.


#6

OK, cool, I’ll see what I come up with and if it seems worth sharing.

Besides storing additional fields in the keyfile, another change I was looking into was expanding OnlineUnlockStatus to optionally use RSA signature verification, rather than only offering the RSA decryption approach that it currently uses.

My approach to license keyfiles has been to publish them (i.e. to the user) as XML files with all the fields easily human readable. Then an additional XML element gets added to the file, which is a signature to verify the rest of the contents. Hence my interest in using RSA signatures instead of RSA encryption.

Benefits to using a readable XML file with an RSA signature include:

  1. In cases where you cannot tie a keyfile to a particular system (via the Machine ID), a keyfile can then be easily passed around and shared by any number of users. However, including the purchaser’s email address as a readable element in the XML file discourages this sharing, by making the source of the original keyfile known. This deterrent would not happen however with an encrypted keyfile that just looks like a string of gibberish.

  2. If you are including an expiration time in a license, then the user can easily read what that date is if they need to know.

I’m definitely not an expert in the field, but that’s just my take from my experience.

That said, unless you trying to hide information from the user, I don’t see any benefit to encrypting keyfiles rather than signing them.


#7

Also on that note - any chance of the RSAKey class being expanded to handle RSA signing and verification?


#8

I don’t think that’d offer anything that the current format doesn’t… IIRC we put both the plain text content and the encrypted version in the same file, so it can be read.

And sure, adding RSA signing is a good FR but probably would be pretty low on our priority list, I’m afraid


#9

What it offers is plain text that cannot be altered without invalidating the keyfile.

The first benefit I listed above (for signing rather than encrypting) was discouraging a user from sharing a keyfile by having their email address readable in it. If you simply append a plaintext “comment” with their email address to the encrypted key, they could just delete the plaintext part before passing around their keyfile, and it would still work.

Some may view that as a “fringe case” sort of benefit. In my view, in the absence of using Machine IDs to tie an authorization to a particular system, then psychological pressure is all you have to encourage users to do the right thing. Being publicly accountable, with their identity exposed if they share their keyfile, is one way to do that.


#10

Like I said, that has the same outcome as our format. Changing the text in our keyfiles also invalidates it, because the reader will choke on it if the plain text doesn’t match the encrypted version.

The reason our format is encrypted version + unencrypted version rather than plain text + hash is that it makes it harder to crack. If security is based on checking a hash, then a cracker only has to remove the branch instruction that fails if a check fails, and then it’ll accept any key file. However with our version, the actual unlock credentials come from the encrpyted block, and a hacker doesn’t have the private key to create it. So cracking it would involve either replacing the entire decoding function, or at least replacing the obfuscated RSA key, which are both much harder.


#11

With all due respect, that is not what I have observed while learning to use OnlineUnlockStatus.

The saveState method gets passed a single Base64 encoded string. That’s what I then save to disk in a keyfile. When I quit and re-load the plug-in host, it fires getState and reads that string back in, authorizing the plug-in again.

In other words, the keyfile is solely the encrypted string, no plaintext – and the OnlineUnlockStatus class has no problem unlocking the plug-in with just that.

Am I missing something essential with how that is supposed to work?


#12

Also, I want to be clear that I’m not advocating a plain text + hash function validation. I’m talking about plain text with an RSA signature, requiring a public key to verify.

Again, sorry if I’m missing your point.


#13

I could be mis-remembering but I’m pretty sure we distribute e.g. tracktion keyfiles which contain the plaint text as well. Obviously like you said, it’s very hard to see what’s in a file otherwise. Maybe that’s something we do in tracktion in addition to the basic unlocker classes just generating the encrypted bit.

Yes, you are!

When I said “plain text + hash” then I was talking about an RSA hash signature. My point (which I thought I explained pretty clearly!) was that if you load the user details from the plain-text, then to crack it is easy, regardless of the hash function used, because all a hacker has to do is change the branch instruction in your app which decides whether the hash was correct, and then any keyfile will work, with any content. My point was that by loading all the details from the encrypted block, then it’s much harder to crack.


#14

I can’t speak to how Tracktion keyfiles are distributed, but a keyfile produced from the OnlineUnlockStatus::saveState method does not contain human-readable plaintext.

I had said “sorry if I’m missing your point” to be polite, and to acknowledge the possibility that perhaps we were talking apples and oranges when saying “RSA signature” and “hash”. But ok.

I will have to leave the finer points of cracking plug-ins to the pros, as that is not my area of expertise. However, if you’re talking about a cracker going in and manipulating branch instructions (rather than, say, producing their own keygen), then of course anything’s possible, whichever cryptographic method was used to secure the license file.

With your encryption approach, once you decrypt the license details, then as you say you have your “actual unlock credentials”… but those are then checked with branching instructions. You say that “by loading all the details from the encrypted block, then it’s much harder to crack” - why? By virtue of the fact that there are multiple branching instructions, to check those multiple credentials? (e.g. one each to check for matching product ID and an allowed Machine ID)

If instead you were using an RSA signature scheme, there’s no reason why the check of a valid signature couldn’t be repeated multiple times, or be obscured somewhat with dummyResult shenanigans like is done in OnlineUnlockStatus::applyKeyFile.

Jules, I hear that you have a preference for using encrypted license files, and I’m not trying to change your mind about that. For the reasons I’ve mentioned previously here (chiefly, user accountability in the absence of using Machine IDs in license files), I have a preference for using signed license files with readable plaintext XML. And so I do wish that the JUCE OnlineUnlockStatus and RSAKey classes could perform RSA signing and verification duties, because then I wouldn’t have to bring in an outside library for that. It would be less work for me and, most likely, a tighter and more secure implementation than I would produce working solo.