TrueLicense-based project certificate validation

Posted by 1josh13 on Sun, 24 Nov 2019 02:41:21 +0100

1. Brief description

Developed software products tend to have a trial period of time when they are delivered for use, during which we do not want our code to be duplicated by our customers. At this time, the license comes in handy. The functions of the license include setting expiration date, binding ip, binding mac, etc.The Licensor directly generates a license for use by the user, and if the trial period needs to be extended, it only needs to regenerate a license without manually modifying the source code.

TrueLicense is an open source certificate management engine, described in detail in https://truelicense.java.net/

First, the following principles of the license authorization mechanism are introduced:

  1. Generate a key pair that contains the private and public keys.
  2. Authorizers retain private keys and use them to generate license signature certificates for authorization information such as use deadline, mac address, and so on.
  3. The public key is given to the consumer and used in the code to verify that the license signing certificate meets the criteria for use.

2. Generating key pairs

The following commands are executed in the window cmd command window, noting the current execution directory where the last key pair is generated:
1. First use the KeyTool tool to generate a private key library: (-alias alias-validity 3650 means 10 years valid)

keytool -genkey -alias privatekey -keysize 1024 -keystore privateKeys.store -validity 3650

2. Then export the certificate in the private key library to a file

keytool -export -alias privatekey -file certfile.cer -keystore privateKeys.store

3. Then import this certificate file into the public key library

keytool -import -alias publiccert -file certfile.cer -keystore publicCerts.store

The resulting files, privateKeys.store (private key), and publicCerts.store (public key), are copied out for use.

3. Preparations

First, we need to introduce truelicense's jar package for our certificate management.

<dependency>
      <groupId>de.schlichtherle.truelicense</groupId>
      <artifactId>truelicense-core</artifactId>
      <version>1.33</version>
</dependency>

Then, we set up a certificate manager in singleton mode.

public class LicenseManagerHolder {

    private static volatile LicenseManager licenseManager = null;

    private LicenseManagerHolder() {
    }

    public static LicenseManager getLicenseManager(LicenseParam param) {
        if (licenseManager == null) {
            synchronized (LicenseManagerHolder.class) {
                if (licenseManager == null) {
                    licenseManager = new LicenseManager(param);
                }
            }
        }
        return licenseManager;
    }
}

4. Generating certificates using private keys

To generate a certificate using a private key, we need two parts, one is the configuration information of the private key (the configuration information of the private key is obtained during the generation of the private key library) and the other is the custom project certificate information.The following shows:

########## Configuration information for private keys ###########
# Alias for private key
private.key.alias=privatekey
# privateKeyPwd (This is the password used to generate the key pair - it needs to be carefully stored and not known to the user)
private.key.pwd=123456
# keyStorePwd (the password is the password to access the keystore - set when key pairs are generated using keytool, and the user knows the password)
key.store.pwd=123456
# Item Unique ID
subject=demo
# The address of the keystore (in the resource directory)
priPath=/privateKeys.store

########## license content ###########
# Release date
issuedTime=2019-09-12
# Effective Start Date
notBefore=2019-09-12
# Effective deadline
notAfter=2019-12-30
# ip address
ipAddress=192.168.31.25
# mac address
macAddress=5C-C5-D4-3E-CA-A6
# User type, user, computer, else
consumerType=user
# Number of consumers allowed for certificates
consumerAmount=1
# Certificate description
info=power by xiamen yungu


#Address to generate certificate
licPath=D:\\license.lic

Next, there's the implementation part of how to generate a certificate

@Slf4j
public class CreateLicense {

    /**
     * X500Princal Is the inherent format of a certificate file, as detailed in the API
     */
    private final static X500Principal DEFAULT_HOLDERAND_ISSUER = new X500Principal("CN=Duke, OU=JavaSoft, O=Sun Microsystems, C=US");

    private String priAlias;
    private String privateKeyPwd;
    private String keyStorePwd;
    private String subject;
    private String priPath;

    private String issued;
    private String notBefore;
    private String notAfter;
    private String ipAddress;
    private String macAddress;
    private String consumerType;
    private int consumerAmount;
    private String info;

    private String licPath;


    /**
     * Constructor, parameter initialization
     *
     * @param confPath Parameter Profile Path
     */
    public CreateLicense(String confPath) {
        // Get parameters
        Properties prop = new Properties();
        try (InputStream in = getClass().getResourceAsStream(confPath)) {
            prop.load(in);
        } catch (IOException e) {
            log.error("CreateLicense Properties load inputStream error.", e);
        }
        //common param
        priAlias = prop.getProperty("private.key.alias");
        privateKeyPwd = prop.getProperty("private.key.pwd");
        keyStorePwd = prop.getProperty("key.store.pwd");
        subject = prop.getProperty("subject");
        priPath = prop.getProperty("priPath");
        // license content
        issued = prop.getProperty("issuedTime");
        notBefore = prop.getProperty("notBefore");
        notAfter = prop.getProperty("notAfter");
        ipAddress = prop.getProperty("ipAddress");
        macAddress = prop.getProperty("macAddress");
        consumerType = prop.getProperty("consumerType");
        consumerAmount = Integer.valueOf(prop.getProperty("consumerAmount"));
        info = prop.getProperty("info");

        licPath = prop.getProperty("licPath");
    }


    /**
     * Generate a certificate and execute on the certificate publisher side
     *
     * @throws Exception
     */
    public void create() throws Exception {
        LicenseManager licenseManager = LicenseManagerHolder.getLicenseManager(initLicenseParams());
        licenseManager.store(buildLicenseContent(), new File(licPath));
        log.info("------ Certificate Published Successfully ------");
    }

    /**
     * Initialize Certificate Related Parameters
     *
     * @return
     */
    private LicenseParam initLicenseParams() {
        Class<CreateLicense> clazz = CreateLicense.class;
        Preferences preferences = Preferences.userNodeForPackage(clazz);
        // Set symmetric password to encrypt certificate contents
        CipherParam cipherParam = new DefaultCipherParam(keyStorePwd);
        // Parameter 1,2 from which Class.getResource() gets the keystore;
        // Alias of parameter 3 keystore;
        // Parameter 4 key store password;
        // Parameter 5 Key Library Password
        KeyStoreParam privateStoreParam = new DefaultKeyStoreParam(clazz, priPath, priAlias, keyStorePwd, privateKeyPwd);
        // Returns the parameters required to generate the certificate
        return new DefaultLicenseParam(subject, preferences, privateStoreParam, cipherParam);
    }

    /**
     * Building information about certificates from external profiles
     *
     * @return
     * @throws ParseException
     */
    public LicenseContent buildLicenseContent() throws ParseException {
        LicenseContent content = new LicenseContent();
        SimpleDateFormat formate = new SimpleDateFormat("yyyy-MM-dd");
        content.setConsumerAmount(consumerAmount);
        content.setConsumerType(consumerType);
        content.setHolder(DEFAULT_HOLDERAND_ISSUER);
        content.setIssuer(DEFAULT_HOLDERAND_ISSUER);
        content.setIssued(formate.parse(issued));
        content.setNotBefore(formate.parse(notBefore));
        content.setNotAfter(formate.parse(notAfter));
        content.setInfo(info);
        // Extended Field
        Map<String, String> map = new HashMap<>(4);
        map.put("ip", ipAddress);
        map.put("mac", macAddress);
        content.setExtra(map);
        return content;
    }
}

Finally, try to generate a certificate!

    public static void main(String[] args) throws Exception {
        CreateLicense clicense = new CreateLicense("/licenseCreateParam.properties");
        clicense.create();
    }

4. Verify Certificate by Public Key

To generate a certificate using a public key, we need information such as a public key library, a license certificate, and so on.

########## Public Key Configuration Information ###########
# Public Key Alias
public.alias=publiccert
# The password is the password to access the keystore - set when the keytool is used to generate the key pair, and the user knows the password
key.store.pwd=123456
# Unique ID of the project - consistent with the subject of the private key
subject = yungu
# Certificate path (I configure this under the linux root path, i.e. /license.lic)
license.dir=/license.lic
# Public library path (in resource directory)
public.store.path=/publicCerts.store

The next step is how to validate the license certificate with the public key, how to validate the ip, mac address, etc. ~

@Slf4j
public class VerifyLicense {

    private String pubAlias;
    private String keyStorePwd;
    private String subject;
    private String licDir;
    private String pubPath;

    public VerifyLicense() {
        // Take Default Configuration
        setConf("/licenseVerifyParam.properties");
    }

    public VerifyLicense(String confPath) {
        setConf(confPath);
    }

    /**
     * Get configuration information from an external profile
     *
     * @param confPath Profile Path
     */
    private void setConf(String confPath) {
        // Get parameters
        Properties prop = new Properties();
        InputStream in = getClass().getResourceAsStream(confPath);
        try {
            prop.load(in);
        } catch (IOException e) {
            log.error("VerifyLicense Properties load inputStream error.", e);
        }
        this.subject = prop.getProperty("subject");
        this.pubAlias = prop.getProperty("public.alias");
        this.keyStorePwd = prop.getProperty("key.store.pwd");
        this.licDir = prop.getProperty("license.dir");
        this.pubPath = prop.getProperty("public.store.path");
    }

    /**
     * Install Certificate Certificate
     */
    public void install() {
        try {
            LicenseManager licenseManager = getLicenseManager();
            licenseManager.install(new File(licDir));
            log.info("Installation Certificate Successful!");
        } catch (Exception e) {
            log.error("Installation Certificate Failed!", e);
            Runtime.getRuntime().halt(1);
        }

    }

    private LicenseManager getLicenseManager() {
        return LicenseManagerHolder.getLicenseManager(initLicenseParams());
    }

    /**
     * Initialize Certificate Related Parameters
     */
    private LicenseParam initLicenseParams() {
        Class<VerifyLicense> clazz = VerifyLicense.class;
        Preferences pre = Preferences.userNodeForPackage(clazz);
        CipherParam cipherParam = new DefaultCipherParam(keyStorePwd);
        KeyStoreParam pubStoreParam = new DefaultKeyStoreParam(clazz, pubPath, pubAlias, keyStorePwd, null);
        return new DefaultLicenseParam(subject, pre, pubStoreParam, cipherParam);
    }

    /**
     * Verify the validity of the certificate
     */
    public boolean vertify() {
        try {
            LicenseManager licenseManager = getLicenseManager();
            LicenseContent verify = licenseManager.verify();
            log.info("Verify Certificate Success!");
            Map<String, String> extra = (Map) verify.getExtra();
            String ip = extra.get("ip");
            InetAddress inetAddress = InetAddress.getLocalHost();
            String localIp = inetAddress.toString().split("/")[1];
            if (!Objects.equals(ip, localIp)) {
                log.error("IP Address verification failed");
                return false;
            }
            String mac = extra.get("mac");
            String localMac = getLocalMac(inetAddress);
            if (!Objects.equals(mac, localMac)) {
                log.error("MAC Address verification failed");
                return false;
            }
            log.info("IP,MAC Address validation passed");
            return true;
        } catch (LicenseContentException ex) {
            log.error("Certificate has expired!", ex);
            return false;
        } catch (Exception e) {
            log.error("Certificate validation failed!", e);
            return false;
        }
    }

    /**
     * Get local mac address
     *
     * @param inetAddress
     * @throws SocketException
     */
    private String getLocalMac(InetAddress inetAddress) throws SocketException {
        //Get network card, get address
        byte[] mac = NetworkInterface.getByInetAddress(inetAddress).getHardwareAddress();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < mac.length; i++) {
            if (i != 0) {
                sb.append("-");
            }
            //Byte to integer
            int temp = mac[i] & 0xff;
            String str = Integer.toHexString(temp);
            if (str.length() == 1) {
                sb.append("0" + str);
            } else {
                sb.append(str);
            }
        }
        return sb.toString().toUpperCase();
    }
}

Now we have the public key verification process, wait!It's not over yet!We need to install licnese certificates and verify ip, mac, and so on when the project starts.If the check fails, it prevents the project from starting!

@Component
public class LicenseCheck {

    @PostConstruct
    public void init() {
        VerifyLicense vlicense = new VerifyLicense();
        vlicense.install();
        if (!vlicense.vertify()) {
            Runtime.getRuntime().halt(1);
        }
    }
}

Topics: Java Mac Linux network