A Java True License example - Part 1, the Software License Server

Summary: I'm sharing my Java True License client and server source code. This code is taken from a Java/Swing software application which I successfully licensed and sold using the True License software library.

Because of my current schedule, it's going to take me a few days to get all of this out here, but my plan is to share all the Java software licensing code I created with the True License software library. In short, when using True License, or any other Java license manager library, you're going to need to create two components:

  1. A software license server, which generates a new software license whenever a customer purchases a copy of your software, and 
  2. A software license client, which is code you embed into your Java/Swing client application to install and verify the license.

In this article I'm sharing the code for my Java Software License Server. This Java license server has been used successfully on my Hyde software application, which is a Java/Swing application I created to run on Mac OS X systems. Hyde is an application that lets you hide your desktop and desktop icons.

A True License example - My Java software license server

The Hyde Java license server is fairly simple, just a one-class Java application. If you're tried to use True License, you'll know, however, that creating a one-class Java application wasn't the hard part; the hard part was deciphering the True License API and documentation.

My Java software license server works something like this:

  1. The customer buys a license for Hyde through PayPal.
  2. The PayPal checkout process calls my PHP PayPal IPN script.
  3. My PHP PayPal IPN script calls a Unix shell script, and that shell script runs this Java software license server application. My PayPal IPN script tells this script where the properties file for this customer can be found.
  4. My Java software license server code (shown below) reads the properties file for the customer, and generates a software license file for them.
  5. The PayPal IPN script waits for this license file to be generated, and when it is, it emails it to the customer.

Given that background ... I'm not going to spend much time writing about this code myself today, though I will come back and update this True License example later this week. In short, here is the Java source code for my True License based Java software license server application:

package com.devdaily.dslicenseserver;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.Properties;
import java.util.prefs.Preferences;
import javax.security.auth.x500.X500Principal;
import de.schlichtherle.license.CipherParam;
import de.schlichtherle.license.KeyStoreParam;
import de.schlichtherle.license.LicenseContent;
import de.schlichtherle.license.LicenseManager;
import de.schlichtherle.license.LicenseParam;

/**
 * Copyright 2010, Alvin J. Alexander, http://devdaily.com.
 * 
 * This software is released under the terms of the
 * GNU LGPL license. See http://www.gnu.org/licenses/lgpl.html
 * for more information.
 * 
 * Usage:   java LicenseServer.jar [baseFilename]
 * 
 *          This results in reading in a file named "baseFilename.dat",
 *          and writing a filename named "baseFilename.lic".
 *          
 *          baseFilename will be in a format like "firstName-lastName-mmddhhmmss"
 * 
 */
public class LicenseServer
{
  // TODO move this to the properties file
  private static final String APP_VERSION = "1.1";
  private static final String PROPERTIES_FILENAME = "DSLicenseServer.properties";

  // get these from properties file
  private String appName;
  private String dataFileExtension;
  private String licenseFileExtension;

  // keystore information (from properties file)
  private static String keystoreFilename;     // this app needs the "private" keystore
  private static String keystorePassword;
  private static String keyPassword;
  private static String alias;
  private static String cipherParamPassword;  // 6+ chars, and both letters and numbers

  // built by our app
  private final KeyStoreParam privateKeyStoreParam;
  private final CipherParam cipherParam;

  // exit status codes
  private static int EXIT_STATUS_ALL_GOOD                     = 0;
  private static int EXIT_STATUS_ERR_WRONG_NUM_ARGS           = 1;
  private static int EXIT_STATUS_ERR_EXCEPTION_THROWN         = 2;
  private static int EXIT_STATUS_ERR_CANT_READ_DATA_FILE      = 3;
  private static int EXIT_STATUS_ERR_CANT_OUR_PROPERTIES_FILE = 4;

  // properties we get from the data file, and write to the license file
  private String firstName;
  private String lastName;
  private String city;
  private String state;
  private String country;

  public static void main(String[] args)
  {
    // should have one arg, and it should be the basename of the file(s)
    if (args.length != 2)
    {
      System.err.println("Need two args: [directory] [baseFilename]");
      System.exit(EXIT_STATUS_ERR_WRONG_NUM_ARGS);
    }

    // args ok, run program
    new LicenseServer(args[0], args[1]);
  }
  
  public LicenseServer(final String directory, final String fileBasename)
  {
    // load all the properties we need to run
    loadOurPropertiesFile();
    
    // read all the attributes from the data file for this customer
    loadInfoFromCustomerDataFile(directory, fileBasename);
    
    // set up an implementation of the KeyStoreParam interface that returns 
    // the information required to work with the keystore containing the private key:
    privateKeyStoreParam = new KeyStoreParam() 
    {
      public InputStream getStream() throws IOException 
      {
        final String resourceName = keystoreFilename;
        final InputStream in = getClass().getResourceAsStream(resourceName);
        if (in == null)
        {
          System.err.println("Could not load file: " + resourceName);
          throw new FileNotFoundException(resourceName);
        }
        return in;
      }
      public String getAlias() 
      {
        return alias;
      }
      public String getStorePwd() 
      {
        return keystorePassword;
      }
      public String getKeyPwd() 
      {
        return keyPassword;
      }
    };

    // Set up an implementation of the CipherParam interface to return the password to be
    // used when performing the PKCS-5 encryption.
    cipherParam = new CipherParam() 
    {
      public String getKeyPwd() 
      {
        return cipherParamPassword;
      }
    };
     
    // Set up an implementation of the LicenseParam interface.
    // Note that the subject string returned by getSubject() must match the subject property
    // of any LicenseContent instance to be used with this LicenseParam instance.
    LicenseParam licenseParam = new LicenseParam() 
    {
      public String getSubject() 
      {
        return appName;
      }
      public Preferences getPreferences() 
      {
        // TODO why is this needed for the app that creates the license?
        //return Preferences.userNodeForPackage(LicenseServer.class);
        return null;
      }
      public KeyStoreParam getKeyStoreParam() 
      {
        return privateKeyStoreParam;
      }
      public CipherParam getCipherParam() 
      {
        return cipherParam;
      }
    };

    // create the license file
    LicenseManager lm = new LicenseManager(licenseParam);
    try
    {
      // write the file to the same directory we read it in from
      String filename = directory + "/" + fileBasename + licenseFileExtension; 
      lm.store(createLicenseContent(licenseParam), new File(filename));
      System.exit(EXIT_STATUS_ALL_GOOD);
    }
    catch (Exception e)
    {
      e.printStackTrace();
      System.exit(EXIT_STATUS_ERR_EXCEPTION_THROWN);
    }
  }

  /**
   * Load the general properties this application needs in order to run.
   */
  private void loadOurPropertiesFile()
  {
    Properties properties = new Properties();
    FileInputStream in;
    try
    {
      in = new FileInputStream(PROPERTIES_FILENAME);
      properties.load(in);
      in.close();
      appName = properties.getProperty("app_name");
      dataFileExtension = properties.getProperty("data_file_extension");
      licenseFileExtension = properties.getProperty("license_file_extension");
      keystoreFilename = properties.getProperty("keystore_filename");
      keystorePassword = properties.getProperty("keystore_password");  
      alias =  properties.getProperty("alias");
      keyPassword =  properties.getProperty("key_password");
      cipherParamPassword = properties.getProperty("cipher_param_password");
    }
    catch (IOException e)
    {
      e.printStackTrace();
      System.exit(EXIT_STATUS_ERR_CANT_OUR_PROPERTIES_FILE);
    }
  }
  
  /**
   * Read the data file that has information about the current customer.
   * @param directory The directory where the properties file is located.
   * @param fileBasename The base portion of the filename.
   */
  private void loadInfoFromCustomerDataFile(final String directory, final String fileBasename)
  {
    Properties properties = new Properties();
    FileInputStream in;
    try
    {
      in = new FileInputStream(directory + "/" + fileBasename + dataFileExtension);
      properties.load(in);
      in.close();
      firstName = properties.getProperty("first_name");
      lastName = properties.getProperty("last_name");
      city = properties.getProperty("city");
      state = properties.getProperty("state");
      country = properties.getProperty("country");
    }
    catch (Exception e)
    {
      e.printStackTrace();
      System.exit(EXIT_STATUS_ERR_CANT_READ_DATA_FILE);
    }
  }

  // Set up the LicenseContent instance. This is the information that will be in the
  // generated license file.
 
  private LicenseContent createLicenseContent(LicenseParam licenseParam)
  {
    LicenseContent result = new LicenseContent();
    X500Principal holder = new X500Principal("CN=" + firstName + " " + lastName + ", "
        + "L=" + city +", " 
        + "ST=" + state +", "
        + "C=" + country);
    result.setHolder(holder);
    X500Principal issuer = new X500Principal(
      "CN=devdaily.com, L=Simpsonville, ST=KY, "
      + " OU=Software Development,"
      + " O=DevDaily Interactive,"
      + " C=United States,"
      + " DC=US");
    result.setIssuer(issuer);
    // i'm selling one license
    result.setConsumerAmount(1);
    // i think this needs to be "user"
    result.setConsumerType("User");
    result.setInfo("License key for the " + appName + " application.");
    Date now = new Date();
    result.setIssued(now);
    now.setYear(now.getYear() + 50);
    result.setNotAfter(now);
    result.setSubject(licenseParam.getSubject());
    return result;
  }

}

My Java software license server properties file

As mentioned, that my Java software license server uses a properties file. This file contains the properties that my Java software license server needs, as well as passwords that True License needs. (Since this properties file is kept in a non-public area of my web server, I assume it is safe to store these passwords there.)

#
# license server properties
#
app_name=Hyde
data_file_extension=.dat
license_file_extension=.lic

keystore_filename=dcPrivateKey.store
alias=dcPrivateKey
keystore_password=TH3 K3YS 0F THR33S
key_password=M0NST3R M4SH H4WK

# truelicense wants this to be 6+ chars, and both letters and numbers
cipher_param_password=d0s4ny1kn0wth1s

My Java software license manager server shell script

As mentioned above, my PHP PayPal IPN script calls a shell script to run my Java software license server application. Here's the source code for that shell script:

#!/bin/sh

# needs two params: [i/o directory] [baseFilename]

cd /var/app-licensing/hyde
java -jar DSLicenseServer.jar $1 $2 > /tmp/DSLicensing.out 2>&1

As you can see, there's not much required there.

A sample data file

The "I/O Directory" in that shell script can be any working directory you set up on your server. For instance my directory was:

/var/app-licensing/hyde

The baseFilename in that example was unique to my approach. Here's how everything worked up to the point where this shell script was run:

 

john-doe-anchorage-ak", which is the base filename for the file "

 

john-doe-anchorage-ak.dat".

     

    john-doe-anchorage-ak.lic".

    • The IPN script, which was waiting until this license file was generated, then emailed this license file to the customer.

    As the final piece of the puzzle, the ".dat" file generated by my IPN script looks like this:

    first_name = John
    last_name = Doe
    email = john.doe@acme.com
    city = Anchorage
    state = AK
    country = US
    

    As you can see, this file just contains the basic information about a customer. It doesn't include any credit card information or anything like that.

    I probably should have included an order number and a date of the order in this file as well, but I didn't. You might want that information to handle potential problems with orders. I should have added something unique to the filename, such as an order number, but I knew sales would be pretty slow, and I'd probably be able to get away with this for a while.

    A True License example - My Java software license server

    As Part 2 of this tutorial, my TrueLicense Java software license manager client is now online. Enjoy!