Useful functions for server and license validation

The following examples use Javascript. Use them as an implementation reference for your programming language of choice.

To validate a message signature in Javascript, you need the jsrsasign library (download).

With this library you can write a function such as:

function isSignatureValid(message, signature, publicKey) {
    var publicKeyHex = b64tohex(publicKey);
    var signatureHex = b64tohex(signature);

    var validator = new KJUR.crypto.Signature({"alg": "SHA1withRSA"});
    validator.init(KEYUTIL.getKey(publicKeyHex, null, "pkcs8pub"));

    validator.updateString(message);

    var valid = validator.verify(signatureHex);
    return valid;
}

To join values separated by ^, create a function such as this:

function joinValues(){
    var out = "";
    for(var i = 0; i < arguments.length; i++){
        if(arguments[i] !== null && arguments[i] !== undefined) {
            if(out.length > 0){
                out += '^';
            }
            out += arguments[i];
        }
    }
    return out;
}

Server response validation

To validate a server response, use:

function isServerResponseValid(publicKey, serverResponseJson) {
    var response = JSON.parse(serverResponseJson);

    var message = "";
    if(response.error){
        message = joinValues(response.result, response.error);
        
    } else if (response.license){
    
        var license = response.license;
        var product = license.product;
        
        message = joinValues(response.result,
            product.name, product.variant, product.version, product.releaseDate,
            product.shop, product.id, product.variantId, product.shopId,
            license.serialKey, license.email, license.firstName, license.lastName,
            license.hostSignature, license.expiration, license.supportEnd, license.signedLicenseKey);
    } else {
        return false;
    }

    return isSignatureValid(message, response.signature, publicKey);
}

Testing

Here are some message examples you can use to test the isServerResponseValid function:

Error response:

var publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCCcHCpt1Qtm+Q63WHF8vT174RoPP+go3hJKkAxZBhCBg4dZ5R/IE9hfXJSF5Kg724jrbT0ft8kIhAOyai47J81g+2tbhlTuVbMhNrhBDoNUDqak5dyRHHc+yrEEV1q2Ed3tX8mTvXHLJ4YoI/TBl5Ht7Zd/s6msTIOwVC8KkYvtwIDAQAB";
var serverResponse = '{"result":"SUPPORT_ENDED","signature":"eK4O/z17UUH+IJd8oVxx326+1Nddv3rKcKQvrGqCPFSEm5CaU3S+NnHxw+dIeoZmKKAPilrxt6x0Et0f36C4qdxTqEqiWksQUUyxHJQs2bzx0+Q58zMy5PaKXH3Bgx641x8bT0CTIhZBfgD18S8fnDqtS/LGqBHzDtO0t0wZpv0=","error":"Your license has no upgrade support for version 4.0.0. The support period ended on 2020-12-31"}';
var valid = isServerResponseValid(publicKey, serverResponse); // --> true

License assignment & validation response:

var publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCCcHCpt1Qtm+Q63WHF8vT174RoPP+go3hJKkAxZBhCBg4dZ5R/IE9hfXJSF5Kg724jrbT0ft8kIhAOyai47J81g+2tbhlTuVbMhNrhBDoNUDqak5dyRHHc+yrEEV1q2Ed3tX8mTvXHLJ4YoI/TBl5Ht7Zd/s6msTIOwVC8KkYvtwIDAQAB";
var serverResponse = '{"result":"VALID","signature":"SyrPUZxqgF4V9MS0C+yGKkMt51ie6V32ro8Qrb0ENKHiemHKql1/J0DYGzzS93CEPe2ClsLzMPU09Pbwvama+A8/Qedfg7HdaXnBk7sI6HSutirgKJwGpN+GmxKzMNNiUb0r1LptLDt/mzX+RqCb1CVLfMGImb/S6fgTZC557vE=","license":{"product":{"name":"license for A","variant":"enterprise","version":"4.0.0","releaseDate":"2023-06-01","shop":"shop1","id":1,"variantId":1,"shopId":1},"serialKey":"Q1KR-L6A5-NIXH-JUZ7","email":"me@email.com","firstName":"me","lastName":"mario","hostSignature":"_SIG_","supportEnd":"2023-06-02","signedLicenseKey":"TkcBt6VwQjBJ+IUIHl/OlHrMga1asmtkhPkdzO92Q/mg+yUsGIxzhL6h0u8O61oT3B1IbyQ8Q+OE2Z0VgWqUw4S83q0fR6zOk81TZ1CYdhqDhoAnSa5XEN4lorbFLOcS5KtsjscsqahEKJE/TOmr0qj562t5VknGP7iSF+QcBUo="}}';
var valid = isServerResponseValid(publicKey, serverResponse); // --> true

Trial license assignment & validation response:

var publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClN9AqdKc1m0SKNPif9zwfDr0A8rHtreuq46eM2NeN8JbC9TeOuo799rgp42Dnt+LCkHnaupzvNTnk59v56Q1sESHSXjklcazCIqOob1AtY8g9M0lR5ktF45Np8UmOrbf27U0BOwykf6GEyzKCp5Z7ayf6A4opiZzqFdGXIOWc1wIDAQAB";
var serverResponse = '{"result":"VALID","signature":"kN8pNVmCwidw8wpmK+b2qmpRGekD2bMPG7KtELve0OU57VwWM7brRRtldnLeGC24zQhMJMP5OovAZKvbnS0gFDB/3U6Hnc61FC1I9Nqu/zJQ/Su898sd3TknlBmEUkf4MEPUnt2yAZYWT9GvyRaxblyNYJlnv/JrQUn77bZI4s0=","license":{"product":{"name":"license for A","variant":"","version":"1.0.0","releaseDate":"2015-12-12","shop":"shop2","id":2,"variantId":0,"shopId":2},"email":"me@email.com","firstName":"me","lastName":"mario","hostSignature":"_SIG_","expiration":"2016-01-15","supportEnd":"2016-01-15","signedLicenseKey":"OTKJm4s+hn04n6npGtbHfFZcd188aI1K2QBICA+NT80Xxum/Qz2ML4UGpYG1AIIB9mzDO+bnDRS7v+jLWzgf0AU1VmBTinoaUwF4v5nZDIFPB1D0oE+hNhtAdJUoPPCVSAyGSeDeUyBx6gVWbS2n3Qo1VxjqEEQiYu+GdE4x07A="}}';
var valid = isServerResponseValid(publicKey, serverResponse); // --> true

Validating local license integrity

Use this to make sure no one has tampered with the license information your program stores locally:

function isLocalLicenseValid(publicKey, localLicenseJson) {
    var license = JSON.parse(localLicenseJson);
    var product = license.product;
    var message = joinValues(
        product.name, product.variant, product.version, product.releaseDate,
        product.shop, product.id, product.variantId, product.shopId,
        license.serialKey, license.email, license.firstName, license.lastName,
        license.hostSignature, license.expiration, license.supportEnd);

    return isSignatureValid(message, license.signedLicenseKey, publicKey);
}

Testing

Here are some message examples you can use to test the isLocalLicenseValid function:

Full license details:

var publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCCcHCpt1Qtm+Q63WHF8vT174RoPP+go3hJKkAxZBhCBg4dZ5R/IE9hfXJSF5Kg724jrbT0ft8kIhAOyai47J81g+2tbhlTuVbMhNrhBDoNUDqak5dyRHHc+yrEEV1q2Ed3tX8mTvXHLJ4YoI/TBl5Ht7Zd/s6msTIOwVC8KkYvtwIDAQAB";
var localLicenseJson = '{"product":{"name":"license for A","variant":"enterprise","version":"4.0.0","releaseDate":"2023-06-01","shop":"shop1","id":1,"variantId":1,"shopId":1},"serialKey":"Q1KR-L6A5-NIXH-JUZ7","email":"me@email.com","firstName":"me","lastName":"mario","hostSignature":"_SIG_","supportEnd":"2023-06-02","signedLicenseKey":"TkcBt6VwQjBJ+IUIHl/OlHrMga1asmtkhPkdzO92Q/mg+yUsGIxzhL6h0u8O61oT3B1IbyQ8Q+OE2Z0VgWqUw4S83q0fR6zOk81TZ1CYdhqDhoAnSa5XEN4lorbFLOcS5KtsjscsqahEKJE/TOmr0qj562t5VknGP7iSF+QcBUo="}';
var valid = isLocalLicenseValid(publicKey, localLicenseJson); --> true

Trial license details:

var publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClN9AqdKc1m0SKNPif9zwfDr0A8rHtreuq46eM2NeN8JbC9TeOuo799rgp42Dnt+LCkHnaupzvNTnk59v56Q1sESHSXjklcazCIqOob1AtY8g9M0lR5ktF45Np8UmOrbf27U0BOwykf6GEyzKCp5Z7ayf6A4opiZzqFdGXIOWc1wIDAQAB";
var localLicenseJson = '{"product":{"name":"license for A","variant":"","version":"1.0.0","releaseDate":"2015-12-12","shop":"shop2","id":2,"variantId":0,"shopId":2},"email":"me@email.com","firstName":"me","lastName":"mario","hostSignature":"_SIG_","expiration":"2016-01-15","supportEnd":"2016-01-15","signedLicenseKey":"OTKJm4s+hn04n6npGtbHfFZcd188aI1K2QBICA+NT80Xxum/Qz2ML4UGpYG1AIIB9mzDO+bnDRS7v+jLWzgf0AU1VmBTinoaUwF4v5nZDIFPB1D0oE+hNhtAdJUoPPCVSAyGSeDeUyBx6gVWbS2n3Qo1VxjqEEQiYu+GdE4x07A="}';
var valid = isLocalLicenseValid(publicKey, localLicenseJson); // --> true

Validating local license details

Finally, this function checks the license information against the product currently in use, the current host that is running your software, then validates the expiration and support end dates:

function validateLocalLicense(myProductDetailsJson, localLicenseJson, currentHostSignature) {
    var myProduct = JSON.parse(myProductDetailsJson).product;

    //first check if license contents have not been tampered with.
    if (!isLocalLicenseValid(myProduct.publicKey, localLicenseJson)) {
        return 'INVALID';
    }

    var license = JSON.parse(localLicenseJson);

    if (myProduct.id !== license.product.id) {
        return 'INVALID';
    }

    if (myProduct.shopId !== license.product.shopId) {
        return 'INVALID';
    }

    if (myProduct.variantId > 0) { //product expects a license for a specific variant. The variant in the license must match
        if (myProduct.variantId !== license.product.variantId) {
            return 'INVALID';
        }
    } // else the variant is determined by the license purchased by the customer

    // validate that the computer being used is the one in the license
    if (currentHostSignature !== license.hostSignature) { // this is a basic string comparison. Use something more sophisticated here if you need.
        return 'UNKNOWN_HOST';
    }

    var currentDate = new Date().getTime();
    var productReleaseDate = new Date(myProduct.releaseDate).getTime();

    //check if license expired
    if (license.expiration !== null && license.expiration !== undefined) {
        var licenseExpiration = new Date(license.expiration).getTime();

        if (currentDate > licenseExpiration || productReleaseDate > licenseExpiration) {
            if (license.serialKey === null || license.serialKey === undefined) { // no serial key? This is a trial license.
                return 'TRIAL_EXPIRED';
            } else {
                return 'EXPIRED';
            }
        }
    }

    //check if license can be used with current product version (i.e. it's been released before the support period ends)
    if (license.supportEnd !== null && license.supportEnd !== undefined) {
        var licenseSupportEnd = new Date(license.supportEnd).getTime();

        if (productReleaseDate > licenseSupportEnd) {
            return 'SUPPORT_ENDED';
        }
    }

    return 'VALID';
}

Testing

Here are some message examples you can use to test the validateLocalLicense function:

Full license details:

var localLicenseJson = '{"product":{"name":"license for A","variant":"enterprise","version":"4.0.0","releaseDate":"2023-06-01","shop":"shop1","id":1,"variantId":1,"shopId":1},"serialKey":"Q1KR-L6A5-NIXH-JUZ7","email":"me@email.com","firstName":"me","lastName":"mario","hostSignature":"_SIG_","supportEnd":"2023-06-02","signedLicenseKey":"TkcBt6VwQjBJ+IUIHl/OlHrMga1asmtkhPkdzO92Q/mg+yUsGIxzhL6h0u8O61oT3B1IbyQ8Q+OE2Z0VgWqUw4S83q0fR6zOk81TZ1CYdhqDhoAnSa5XEN4lorbFLOcS5KtsjscsqahEKJE/TOmr0qj562t5VknGP7iSF+QcBUo="}';
var productJson = '{"product":{"name":"license for A","variant":"enterprise","version":"4.0.0","releaseDate":"2023-06-01","shop":"shop1","id":1,"variantId":1,"shopId":1,"publicKey":"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCCcHCpt1Qtm+Q63WHF8vT174RoPP+go3hJKkAxZBhCBg4dZ5R/IE9hfXJSF5Kg724jrbT0ft8kIhAOyai47J81g+2tbhlTuVbMhNrhBDoNUDqak5dyRHHc+yrEEV1q2Ed3tX8mTvXHLJ4YoI/TBl5Ht7Zd/s6msTIOwVC8KkYvtwIDAQAB"}}';
var currentHostSignature = '_SIG_';
var validationResult = validateLocalLicense(productJson, localLicenseJson, currentHostSignature);
// --> VALID    

License copied to another device:

var localLicenseJson = '{"product":{"name":"license for A","variant":"enterprise","version":"4.0.0","releaseDate":"2023-06-01","shop":"shop1","id":1,"variantId":1,"shopId":1},"serialKey":"Q1KR-L6A5-NIXH-JUZ7","email":"me@email.com","firstName":"me","lastName":"mario","hostSignature":"_SIG_","supportEnd":"2023-06-02","signedLicenseKey":"TkcBt6VwQjBJ+IUIHl/OlHrMga1asmtkhPkdzO92Q/mg+yUsGIxzhL6h0u8O61oT3B1IbyQ8Q+OE2Z0VgWqUw4S83q0fR6zOk81TZ1CYdhqDhoAnSa5XEN4lorbFLOcS5KtsjscsqahEKJE/TOmr0qj562t5VknGP7iSF+QcBUo="}';
var productJson = '{"product":{"name":"license for A","variant":"enterprise","version":"4.0.0","releaseDate":"2023-06-01","shop":"shop1","id":1,"variantId":1,"shopId":1,"publicKey":"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCCcHCpt1Qtm+Q63WHF8vT174RoPP+go3hJKkAxZBhCBg4dZ5R/IE9hfXJSF5Kg724jrbT0ft8kIhAOyai47J81g+2tbhlTuVbMhNrhBDoNUDqak5dyRHHc+yrEEV1q2Ed3tX8mTvXHLJ4YoI/TBl5Ht7Zd/s6msTIOwVC8KkYvtwIDAQAB"}}';
var currentHostSignature = '_Some Other Computer Hardware_';
var validationResult = validateLocalLicense(productJson, localLicenseJson, currentHostSignature);
// --> UNKNOWN_HOST    

Product updated to version 4.5.3, released on 2023-09-04, after support period:

var localLicenseJson = '{"product":{"name":"license for A","variant":"enterprise","version":"4.0.0","releaseDate":"2023-06-01","shop":"shop1","id":1,"variantId":1,"shopId":1},"serialKey":"Q1KR-L6A5-NIXH-JUZ7","email":"me@email.com","firstName":"me","lastName":"mario","hostSignature":"_SIG_","supportEnd":"2023-06-02","signedLicenseKey":"TkcBt6VwQjBJ+IUIHl/OlHrMga1asmtkhPkdzO92Q/mg+yUsGIxzhL6h0u8O61oT3B1IbyQ8Q+OE2Z0VgWqUw4S83q0fR6zOk81TZ1CYdhqDhoAnSa5XEN4lorbFLOcS5KtsjscsqahEKJE/TOmr0qj562t5VknGP7iSF+QcBUo="}';
var productJson = '{"product":{"name":"license for A","variant":"enterprise","version":"4.0.2","releaseDate":"2023-09-04","shop":"shop1","id":1,"variantId":1,"shopId":1,"publicKey":"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCCcHCpt1Qtm+Q63WHF8vT174RoPP+go3hJKkAxZBhCBg4dZ5R/IE9hfXJSF5Kg724jrbT0ft8kIhAOyai47J81g+2tbhlTuVbMhNrhBDoNUDqak5dyRHHc+yrEEV1q2Ed3tX8mTvXHLJ4YoI/TBl5Ht7Zd/s6msTIOwVC8KkYvtwIDAQAB"}}';
var currentHostSignature = '_SIG_';
var validationResult = validateLocalLicense(productJson, localLicenseJson, currentHostSignature);
// --> SUPPORT_ENDED    

Expired trial license:

var localLicenseJson = '{"product":{"name":"license for A","variant":"","version":"1.0.0","releaseDate":"2015-12-12","shop":"shop2","id":2,"variantId":0,"shopId":2},"email":"me@email.com","firstName":"me","lastName":"mario","hostSignature":"_SIG_","expiration":"2016-01-15","supportEnd":"2016-01-15","signedLicenseKey":"OTKJm4s+hn04n6npGtbHfFZcd188aI1K2QBICA+NT80Xxum/Qz2ML4UGpYG1AIIB9mzDO+bnDRS7v+jLWzgf0AU1VmBTinoaUwF4v5nZDIFPB1D0oE+hNhtAdJUoPPCVSAyGSeDeUyBx6gVWbS2n3Qo1VxjqEEQiYu+GdE4x07A="}';
var productJson = '{"product":{"name":"license for A","variant":"","version":"1.0.0","releaseDate":"2015-12-12","shop":"shop2","id":2,"variantId":0,"shopId":2,"publicKey":"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClN9AqdKc1m0SKNPif9zwfDr0A8rHtreuq46eM2NeN8JbC9TeOuo799rgp42Dnt+LCkHnaupzvNTnk59v56Q1sESHSXjklcazCIqOob1AtY8g9M0lR5ktF45Np8UmOrbf27U0BOwykf6GEyzKCp5Z7ayf6A4opiZzqFdGXIOWc1wIDAQAB"}}'
var currentHostSignature = '_SIG_';
var validationResult = validateLocalLicense(productJson, localLicenseJson, currentHostSignature);
// --> TRIAL_EXPIRED

Further Reading

If you find a bug

We deal with errors very seriously and stop the world to fix bugs in less than 24 hours whenever possible. It’s rare to have known issues dangling around for longer than that. We provide a test environment for you to test whether our updates work for you as soon as the adjustments are made.

If you have suggestions or find a bug don’t hesitate to open an issue here or send us send us an e-mail.

We are happy to help if you have any questions in regards to how to use our app for your specific use case. Just send us an e-mail with your query and we’ll reply as soon as humanely possible.

We can work for you

If you don’t have the resources or don’t really want to waste time coding we can build a custom solution for you using our products. We deliver quickly as we know the ins and outs of everything we are dealing with. Send us an e-mail to sales@univocity.com with your requirements and we’ll be happy to assist.

The univocity team.

www.univocity.com