Tag Archives: Licensing

Invision Power Revokes Perpetual/Lifetime Licensing – My Thoughts

Written by William Roush on December 28, 2014 at 1:56 am

A story about a board administrator’s experiences with Invision Power’s move to drop Perpetual/Lifetime licensing, and the responses from the community.

What Is Perpetual/Lifetime Licensing?

Perpetual/Lifetime licensing allowed those of us on the older Invision Power’s IP.Board software (prior to 2007) to have lifetime upgrades for as long as the product was around and only pay for support if we wanted to open support tickets. For someone that wanted to run the boards for an extended period of time and didn’t need to rely on support for anything could break even after quite a few years of operation (usually around 4 years).

The Decision To Purchase

Back in 2006 I was running a small board with a handful of users, mostly friends. I had some plans to expand the board that never really panned out, during this time Invision Power was closing out their lifetime licensing and I decided that dropping the extra cash for the software would be a worthwhile investment if I ever decided to start up a board again.

This is important, because I honestly haven’t really been using the software for the past 8 years, which sets the tone for why I feel burned. Recently I decided to investigate the use of IP.Board for a new project…

Invision Power’s Move To IPS4

So Invision Power is working on a new version of their forum software, called IPS4, it’ll consist of a “core” module and their forum software.

“With IPS4, we’ve changed directions somewhat in that IP.Board as we all know it will become a forums app within the suite. Members, profiles, search engine, ad management, spam mitigation and other key items are now part of the suite core. This allows for cleaner, more streamlined integration across the whole suite with the forums app no longer carrying the weight of the load which led to clunkier integration in the 3.x line.” – Lindy, IPS Management

No different than it is now...

No different than it is now…

Whoops, that’s how our software is technically licensed in 3.x, oh well. Sure more responsibilities are in the core, but the point still stands.

“After careful consideration, we determined the most appropriate thing to do is offer the ability for legacy customers to switch to the newest structure, free of charge — this would allow them access to anything current customers have access to. Legacy customers would receive six months free renewals, so there are no out of pocket expenses. After that period, renewals would then be $25 every six months, which would include all of the latest services such as chat and spam mitigation.” – Lindy, IPS Management

“After careful consideration, we decided to invalidate lifetime licensing and ask for more money.” Bold strategy cotton, lets see if it pays off.

“I would like to stress that these legacy licenses were only offered for 2 years – there really aren’t that many of them and even less exist today as most have converted to the new structure over the years to leverage hosted services such as spam mitigation. All told, these changes will only have a net impact on less than 4% of our license holders – of those 4% we’re not sure how many still even use their license. From IPS’ perspective, it makes sense to clean up our backend systems and rid them of 10-12yrs of accumulated coding provisions to handle different license types under different scenarios. Further, it allows us to more cleanly reintroduce our software as a true suite of community applications with seamless integration across the offerings.” – Lindy, IPS Management

If those of us with lifetime licensing don’t make up a substantial part of your customers, mind refunding that licensing then? Also, customers shouldn’t have licensed benefits revoked because you didn’t consider it in your billing software setup.

A forum member brought this up:

“The license was for IP.Board, not IP.Core + IP.Board. If IPS wanted to, they could call the forums “IP.Forums” or “IP.Discussions” starting with v4 and legacy license holders wouldn’t have any claim to it at all. Might be off of IP.Board, but since it’s a rewrite of the code and such, it could be given a different name and would be perfectly legit.” – Wolfie, Forum Contributor

However that wasn’t the case when Invision Power 2.x (around 2004) or 3.x (2009), when under both of these releases they could have just renamed the system (and if I’m not mistaken 3.x was actually rebranded to “IP.Board” from “Invision Power Board”).

Discussion link 1, discussion link 2

Further Iffy Behavior – Charging For Domain Changes

Another fee they’ve tacked on is some “administration” fee for changing your domain more than once every 6 months. In the early days of testing I hosted the site on a handful of different domains (all variances of the original domain name), it was a pain but I got the license changes done during the process. Now you pay $15.

“It’s to stop dishonest clients from “domain hopping” a licence in order to be able to get support on them all, instead of legitimately paying for support on them all.” – The Heff, Invision Power Client

That doesn’t make sense, you only get support for whatever the currently licensed and active board is. Auditing can easily track hopping, and paying $15 doesn’t prevent this (and is still much cheaper than buying multiple licenses), it barely impacts those that are stealing $200 pieces of software, and harms those that are experimenting or host multiple domains (and aren’t just reverse proxying their way to victory).

Also, this system is completely broken for lifetime license holders. Those “hacks” that make the system work are working well.

This sounds like a cash grab and less like they’re resolving an issue.

Leaving Early Adopters Out In The Cold

Mind you, the solution we’re being given is to give us a $100 credit towards a platform that we must pay $25 every 6 months for updates (something we got for free prior)! This is their “generous” offer to us (and they’re using this word so many times it’s raising my blood pressure. We get to decide if it’s generous, it’s pretty crass for you to be repeating it yourselves so often hopping we’ll parrot it back to you).

I think one of the biggest telling pieces is this bit from Lindy:

“Naturally, we consulted with our legal counsel just as a matter of due diligence and the option to simply discontinue legacy licenses without any further intervention was technically within our right, but we did not achieve our position as a respected leader in the industry by being that company. After feedback discussion from customers and other parties, we devised and fine-tuned this solution, which we think is very fair.” – Lindy, IPS Management

Pretty much telling me “yeah, we were so unsure about this we consulted a lawyer” is already pretty telling, you knew this wouldn’t go over so well you had to seek legal council.

“The goal behind this change is to allow the software and internal systems to move forward without provisions and hacks from purchases made a decade ago. When these licenses were offered, we had limited product offerings, no spam mitigation, chat, etc. Since introducing those, we’ve incorporated hack upon hack to accommodate older licenses because we genuinely do appreciate those early adopters and their purchases. Further, we had IP.Board – which was everything – the core and otherwise. In IPS4, we have a forums app, which is an independent component of the suite, much like Gallery, Blog, Nexus, Content, etc. Search engines, file/storage handling, members/profiles and much more is all part of the community suite core that all applications share. With that, the time has come to press forward for the future and we would very much like to take our early supporters with us – hence the, in my opinion, appropriately generous offer which in some cases, more or very close to the original purchase price; again, even after a decade of usage.” – Lindy, IPS Management

You’re telling me that because of your team’s inability to plan on how to support various configurations that you’re just dropping a chunk of your customers, a good chunk that is the reason you guys are here today? Your earliest adopters?

Let alone WordPress can monetize on a hybrid system of free/paid services, are you telling me Invision Power’s engineers can’t figure out how to write a system that’ll handle it?

Literally telling me they must “hack” the system to make it compatible is telling me they don’t know how to properly implement a system that’ll handle it.

Mind you these are my options if I stay with this product:

  1. Don’t upgrade, stop receiving updates when Invision Power stops updating 3.x, get my IP.Board install hacked.
  2. Upgrade, get two years free usage, then pay $50/yr after that.

At the end of the day, I’ll just find another board, but I’m sure going to recommend a long list of boards before this one now, and I’m going to feel really burned that I dropped money on the future of a product (literally), and got that taken away by the very company I gave money to.

 

And to leave with this tidbit:

“Please think about what the Internet has done in the past decade, how it has evolved and how we, as a company, have evolved. Ultimately, we need to move forward and we feel we’ve found a fair and just way for you to join us. We do recognize, however, that not everyone will feel that way and apologize for those ill feelings.” – Lindy, IPS Management

Literally “People are moving things to recurring payment services now, and we want a piece of that”, thanks Invision Power.

Tamper-proof Licensing Using Cryptographic Signatures

Written by William Roush on November 4, 2013 at 9:15 pm

I’m going to dive right into writing a basic licensing system using strong established cryptographical signing processes to prevent tampering or the creation of a key generator. I’m going to digitally sign the plain-text license using OpenPGP’s private key, this will generate a human readable license file, but modifying the payload will cause an exception to be thrown and tampering detected.

Solutions

I’m going to go over the basic options available. There are many others and there are a lot of hybrids of these various types, but for a quick introduction to licensing options, this should get you up to speed.

No Licensing

Of course the option for no licensing is always available. This provides no protection against a user copying your application and giving it to another user. Sometimes the overhead of writing and maintaining licensing code may not be worth the effort.

Plaintext Keys

Plaintext keys store user information, dates, times and keys in various locations, such as files on the hard disk, registry keys or resource files. These are fairly trivial to change and circumvention information can be passed around by word of mouth. You can attempt to store keys in hidden places like some other software, but these can and likely will be tracked down, and depending on if your method is ethically unsound, can land you unfavorable opinion with your would-be customers.

These are very easy, and can be a preferred choice if you’re not concerned with someone attempting to crack your licensing code, but may want some features granted by having licensing support in your software.

Symmetrical Cryptography / Hash Verification

Both of these methods fall under the same pros/cons for the most part. Symmetrical keys will encrypt the entire license file and should use some sort of secure algorithm (ex: AES256) with a secret key. Hash verification may store the data encrypted or unencrypted, but will hash the key information either with a secret method or a secret salt and store that with the license file, where you can validate that it hasn’t been tampered with, tampered keys fail validation.

Both of these suffer from the same issue: the code to decrypt/hash and verify your license files provide all the information needed to encrypt/hash new license files, allowing someone to create a license generator. In other languages these is a higher barrier of entry to be able to reverse engineer a native library, but in .NET your code can be fairly easily decompiled and methods like this reverse engineered (generally even when obfuscated).

Asymmetrical Cryptography

Asymmetrical cryptography, also known as Public-key cryptography is a method of encrypting data using the public key, and only being able to decrypt it with the private key (this can also be used for verification purposes by reversing the usage of the keys). In our example, we’ll use the example of digital signatures: where we use the private key for signing the license, and the public key to verify it hasn’t been tampered with.

Without the private key, a user cannot issue new licenses, a key generator will be impossible to make without one. The best bet resides in the malicious person to take one of two paths: to modify the software to remove the license requirement entirely, or modify the software to use a different public key, so they can use their own private key to generate licenses.

We’re on a better path now: a malicious user that has to patch our binaries to circumvent licensing will incur the overhead of having to patch every binary we release, while we can’t ever stop circumvention, we can make it annoying!

Online Verification

Online verification is one of the hardest methods, if not the hardest to circumvent. However a lot has to be assumed about your customers. You need to assume your users will be online at regular intervals, additional the use of software they’ve purchased will be entirely dependent on you keeping your licensing servers up and running.

Circumvention requires you to rewrite binaries to no longer check online, or spoof authentication servers.

PGP and You

I’m going to use OpenPGP, an open encryption standard that includes public-key cryptography. This library is used a lot for signing/encrypting e-mails and code among various programming groups, and I’ll continue to use this later for various examples such as signing code in a later blog.

Generating Your Key Pair

I recommend using GPG4Win, an easy to use application for creating and managing GPG and X509 certificates.

Select "File" > "New Certificate"

Select “File” > “New Certificate”

We want to create an OpenPGP key pair.

We want to create an OpenPGP key pair.

Enter the certificate information.

Enter the certificate information.

Your private key is the key to your kingdom, use a strong passphrase.

You want to save the private key (*.gpg) somewhere where your application can access it.

You want to save the private key (*.gpg) somewhere where your application can access it.

You'll want to export the public key too.

You’ll want to export the public key too.

Dealing with PGP On .NET

I’m going to use a very commonly used cryptography library for .NET BouncyCastle, mainly because it’s extremely powerful and licensed under MIT a very open license friendly to both open source projects and closed source.

An Example License File

We’re going to go ahead with the most basic setup: we’re not going to encrypt the payload so we can understand what is going on, for further obscurity you can encrypt the payload or even the entire license, but it wont add much additional security (just make it harder to understand what is going on till they decompile your .NET code).

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

William Roush
1/1/2018 12:00:00 AM
1/1/2014 12:00:00 AM
-----BEGIN PGP SIGNATURE-----
Version: BCPG C# v1.7.4114.6375

iQFYBAABAgBCBQJSdv5OOxxSb3VzaFRlY2ggRXhhbXBsZSBDb2RlIFNpZ25pbmcg
S2V5IDxleGFtcGxlQHJvdXNodGVjaC5uZXQ+AAoJEGyAwS0mpw1Ki5MH/jQAKiur
FF06Q7D0BeaUE6eBk40I2LocZwSE2Osx94Tdux08L8vpaOUG4xBx6UsNKusvrbaV
hSxeU1e1LkO3QMu8+//FAFsJizTYjYZRoRe167WFRlQbteZdgeB1S+HMdDhsBs+Z
3+FGAulhs7EBmRkgSoYEX0NdJc1uEfW5kRr4KfL4v+m9UU6Z796KWFzOi8xqTcRG
eO9cNV7QtAzBOpI2CVuq6KuGj5VjNtQsxUrUNl6U/giV61Y5TYOfRnL47kX4U6sf
Uzp/iRZqemi+n7iBjMsO37+baANySaBRSKUG0EXtQM9sVxHZ7aGoXpjsU+oeWtBY
iWPAPX1ePwQ6LcM=
=57ao
-----END PGP SIGNATURE-----

Writing Your License File Management Code

First I’m going to outline some basics, we’re going to store a name and two dates, a software expiration date and a support expiration date. Additionally I’ll override ToString() for testing purposes, and write a method that returns the license data as a byte array that’ll go into our signed license file.

using System;
using System.IO;
using System.Linq;
using System.Text;

using Org.BouncyCastle.Bcpg.OpenPgp;
using Org.BouncyCastle.Bcpg;
using Org.BouncyCastle.Asn1.Ocsp;
using Org.BouncyCastle.Security;

public class License
{
    public string Name { get; set; }
    public DateTime SoftwareExpires { get; set; }
    public DateTime SupportExpires { get; set; }

    protected PgpPrivateKey PrivateKey { get; set; }
    public PgpPublicKey PublicKey { get; set; }

    public override string ToString()
    {
        return string.Format("Name: {1}{0}Software Expires: {2}{0}Support Expires: {3}",
            Environment.NewLine,
            this.Name,
            this.SoftwareExpires,
            this.SupportExpires
        );
    }

    private byte[] PrepareLicenseForStorage()
    {
        var licenseData = string.Format(
            "{0}\n{1}\n{2}",
            this.Name,
            this.SoftwareExpires.ToString(),
            this.SupportExpires.ToString()
        );

        return Encoding.UTF8.GetBytes(licenseData);
    }

Next we’re going to define how to read a public key out of a file.

public void ReadPublicKey(string path)
{
    using (var keyFileStream = File.OpenRead(path))
    using (var pgpDecoderStream = PgpUtilities.GetDecoderStream(keyFileStream))
    {
        var keyRingBundle = new PgpPublicKeyRingBundle(pgpDecoderStream);
        var keyRings = keyRingBundle.GetKeyRings();
        foreach (PgpPublicKeyRing keyRing in keyRings)
        {
            this.PublicKey = keyRing.GetPublicKey();
            break;
        }
    }
}

Then we’re going to define how to read a private key out of a file.

public void ReadPrivateKey(string path)
{
    using (var keyFileStream = File.OpenRead(path))
    using (var pgpDecoderStream = PgpUtilities.GetDecoderStream(keyFileStream))
    {
        // Get the key ring from the private key file.
        var keyRingBundle = new PgpSecretKeyRingBundle(pgpDecoderStream);
        var keyRings = keyRingBundle.GetKeyRings();

        // Get the first key from the key ring (it's an enumerable, so it's annoying).
        PgpSecretKey secretKey = null;
        foreach(PgpSecretKeyRing keyRing in keyRings)
        {
            secretKey = (PgpSecretKey)keyRing.GetSecretKey();
            break;
        }

        // Get the private key, we don't have a pass phrase on this (you should though!). 
        // Do NOT hard code this into the library, take it as a parameter from your key generation
        // software.
        this.PrivateKey = secretKey.ExtractPrivateKey("".ToCharArray());
    }
}

Next we’re going to define the code for creating a license file.

public void CreateLicenseFile(string fileName)
{
    var hashAlgorithm = HashAlgorithmTag.Sha1;
    using (var fileWriter = new FileStream(fileName, FileMode.Create))
    using (var outputStream = new ArmoredOutputStream(fileWriter))
    {
        // Continue our signature.
        var signatureGenerator = new PgpSignatureGenerator(this.PublicKey.Algorithm, hashAlgorithm);
        signatureGenerator.InitSign(PgpSignature.BinaryDocument, this.PrivateKey);
        foreach (string userId in this.PublicKey.GetUserIds())
        {
            var pgpSigatureSubpacketGenerator = new PgpSignatureSubpacketGenerator();
            pgpSigatureSubpacketGenerator.SetSignerUserId(false, userId);
            signatureGenerator.SetHashedSubpackets(pgpSigatureSubpacketGenerator.Generate());
            break;
        }

        var licenseDataBuffer = this.PrepareLicenseForStorage();

        // We're going to write out the cleartext portion.
        outputStream.BeginClearText(hashAlgorithm);
        outputStream.Write(licenseDataBuffer, 0, licenseDataBuffer.Length);

        // This updates our signature with the data in our license.
        signatureGenerator.Update(licenseDataBuffer, 0, licenseDataBuffer.Length);
        outputStream.EndClearText();

        // Add newline after text so we start the PGP signature header on the next line.
        byte[] newLineBytes = Encoding.UTF8.GetBytes(Environment.NewLine);
        fileWriter.Write(newLineBytes, 0, newLineBytes.Length);

        // Outputs the signature into the file.
        using (var bcpgStream = new BcpgOutputStream(outputStream))
        {
            signatureGenerator.Generate().Encode(bcpgStream);
        }
    }
}

Finally, we need to be able to create a license object from a signed license file.

public static License LoadLicenseFromFile(string fileName, PgpPublicKey publicKey)
{
    using (var inputStream = new FileStream(fileName, FileMode.Open))
    using (var armoredStream = new ArmoredInputStream(inputStream))
    using (var memoryStream = new MemoryStream())
    {
        // We're going to read the payload of the signed text in.
        while (armoredStream.IsClearText())
        {
            memoryStream.WriteByte((byte)armoredStream.ReadByte());
        }
        
        // Load the signature data from the armored file and initalize it with the public key.
        PgpObjectFactory pgpFact = new PgpObjectFactory(armoredStream);
        PgpSignatureList p3 = (PgpSignatureList)pgpFact.NextPgpObject();
        PgpSignature sig = p3[0];
        sig.InitVerify(publicKey);

        // We're going to seek to the beginning, and strip off the final "/r/n-" from the message.
        memoryStream.Seek(0, SeekOrigin.Begin);
        for (int i = 0; i < memoryStream.Length - 3; i++)
        {
            sig.Update((byte)memoryStream.ReadByte());
        }
        
        if (!sig.Verify())
        {
            throw new Exception("Verification Failed")
        }

        // Parse our stored license data.
        string[] licenseData = Encoding.UTF8.GetString(memoryStream.ToArray()).Split('\n');
        return new License
        {
            Name = licenseData[0],
            SoftwareExpires = DateTime.Parse(licenseData[1]),
            SupportExpires = DateTime.Parse(licenseData[2])
        };   
    }
}

From here we have the underlying infrastructure needed to write a license file that cannot be tampered with and that our application can verify is correct.

Changes To Production Code

First in production you’ll never want to ship your licensing software with your private key or with your secret phrase, that stays with you for generating new keys. Secondly we’ll want to read the private key from some internal resource so a malicious hacker can’t just replace your public key file without modifying binaries.