Skip to content

Syndika-Corp/drm-utils

Repository files navigation

DRM Utils

npm package Build Status Downloads Issues Code Coverage Commitizen Friendly Semantic Release

DRM Utils is a utility library made for Typescript indended to help licensing your NodeJS software (e.g. Electron).

Install

npm install drm-utils

In case you indend to use terminal command- kindly use npm install drm-utils -g instead, to install and register the binary globally.

Library Usage

  • Working with License:
import { License } from 'drm-utils';

const PRIVATE_KEY = Secp256k1.utils.randomPrivateKey();
const PUBLIC_KEY = Secp256k1.getPublicKey(PRIVATE_KEY);

const license = await License.create('alex');
await license.sign(PRIVATE_KEY);

console.log("Your valid license key:", license.signedLicense); // 616c6578-6c3835327162-7dba8218000-3044022058abfe388905b2d3572b67bf6f0d10f3ef5787877ddbb80350733cb5c218ef26022045ba16a22a737cafe84552e54ccdccd545c6a70b486beabcfbab0f250967429f
console.log("Is license key valid?", license.validate(PUBLIC_KEY) ? 'YES' : 'NO'); // YES
  • Working with DRM abstraction:
import { DRM } from 'drm-utils';

const ISSUER_ID = 'cucer';
const PUBLIC_KEY = '047c237e5dd2a2cb6d1d3176997e50240fcdaf40704b898eb46d22882c6048d8d77deafe1169c2dc6010241e3f7654a5a07a1cf22be38dd8907deafafa321e16ad'; // Secp256k1.getPublicKey(PRIVATE_KEY)
const ISSUERS_MAPPING = new Map().set(ISSUER_ID, PUBLIC_KEY); // add any issuers you'd like

// This creates a DRM abstraction with Env and File storage strategies
// and offline validation mechanism using the ISSUERS_MAPPING (e.g. you may implement loading from remote API)
// Using the defaults the license will attempt to be loaded from:
//  1. Environment variable "SYN_DRM_LICENSE"
//  2. The "syn_drm_license.lic" file in CWD (working directory) 
const drm = await DRM.create(ISSUERS_MAPPING).loadLicense();
console.log('Has the license key been found?', drm.hasLicense ? 'YES' : 'NO'); // NO

// Set the license, e.g. input by user manually from UI or fetched from a server (the license.signedLicense)
// The store method will persist given license to both env and file storages as described above
const theLicenseStringFromInput = '6375636572-6c3835327162-68b4b7d0-3044022032de288bdfa6bd9975eb3fe119bdca2d1086ee00ef297ebb0fe193e95a04cb1602206588e0db8424c66a915c93458e4812d3048e37bac088f3ef433a8584148e58fa';
await drm.storeLicense(theLicenseStringFromInput);
// ...or if not willing to persist (beware- calling storeLicense() will persist it)
await drm.setLicense(theLicenseStringFromInput);

// To validate a license you can call
console.log('Is the license key valid?', await drm.validateLicense(/* optionally set license string or instance here */) ? 'YES' : 'NO'); // YES

Extending the Library

Note that storage and validation strategies can be set in DRM, thus custom logic such as loading license from DB and check it online via some API can be easily implemented (by implementing custom ILicenseStorage and ILicenseValidator).

  • Interfaces for custom implementations:
export interface ILicenseStorage {
  /**
   * Load license key
   * @param args Any parameters accepted
   * @throws MisingLicenseError
   */
  load(...args: any[]): License | Promise<License>;

  /**
   * Stores license. Optional implementation.
   * @param license
   */
  store(license: License): boolean | Promise<boolean>;
}

export interface ILicenseValidator {
  /**
   * Validates license
   * @param license
   */
  validate(license: License): boolean | Promise<boolean>;
}
  • Using custom implementation:
const drm = new DRM(customStorage, customValidator).loadLicense();

Terminal (CLI) Usage

  • Generate Operator Key
Usage: drm-utils pk [options]

Options:
  -h, --help  display help for command
  • Generate a license key
Usage: drm-utils generate [options] <machineID>

Arguments:
  machineID                 A MachineID e.g. l852qb

Options:
  -i, --issuer-id <issuer>  Issuer ID (name) to be used
  -v, --valid-until <date>  Valid until date unless lifelong (e.g. "Sep 2025")
  -k, --private-key <key>   Operator PRIVATE key for signing the license (never stored)
  -h, --help                display help for command
  • Sign a generated raw license
Usage: drm-utils sign [options] <license>

Arguments:
  license                  NON signed license key, e.g. 616c6578-6c3835327162-7dba8218000

Options:
  -k, --private-key <key>  Operator PRIVATE key (never stored)
  -h, --help               display help for command
  • Validate a license key (works with both signed and non-signed)
Usage: drm-utils validate [options] <license>

Arguments:
  license                 Validate a license key (either signed or NON signed), e.g. 616c6578-6c3835327162-7dba8218000 OR
                          616c6578-6c3835327162-7dba8218000-3045022100c915f5f698f840f2cae76c97cd319e8db522593a1dbd273305d332037f8960030220165e9c67349353d4fcc061b4cc0f7b5ab912d7bb92e7eba7db78c622c9039d25

Options:
  -p, --public-key <key>  Operator PUBLIC key (for SIGNED licese only)
  -s, --skip-datecheck    Skip validating time validity (signature only)
  -h, --help              display help for command

Documentation

Multi-Strategy License Key consists of libraries and utils to create and operate offline and online license keys on machines (think Electron).

License Key Structure

License Key Standard: {hex(key-issuer-id)}-{hex(murmur2(machine-id))}-{hex(valid-until)}-{hex-sig}

  • key-issuer-id- License Key Issuer ID, further mapped to the secp256k1 public key to verify signature license key segment. It can be either offline (hardcoded onto config/env) or online (fetched from a license server).

  • murmur2()- MurmurHash2 is a non-cryptographic hash function suitable for general hash-based lookup/validation. It is fast and small in size thus suitable for it's purpose of the local machine ID validation against the dedicated license key segment.

  • machine-id- Local Machine ID generated crossplatform (for Win uses MachineGuid from the registry HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography, OXS- IOPlatformUUID and Linux- /var/lib/dbus/machine-id).

  • valid-until- Timestamp the license key is valid until. If you want the key to be untlimited in time, simply use some Number.MAX_SAFE_INTEGER (e.g. max value of the signed UINT32).

  • hex-sig- Signature is the main component used to validate the key. Details described below.

hex-sig Signature component

The signature is the hex representation of a secp256k1 signature following BIP0340 derived from license segments parameters (key-issuer-id, machine-id, machine-id), hashed using sha256 standard as following:

deriv = sha256({hex(key-issuer-id)}-{hex(murmur2(machine-id))}-{hex(valid-until)})
sig = secp256k1.sign(deriv, privateKey) // privateKey- secp256k1 private key
hexSig = toHex(sig) // the generated hex-sig

The signature verifiable as following:

sig = fromHex(hexSig)
isValid = secp256k1.verify(sig, deriv, pubKey) // isValid - boolean, pubKey- public key derived from secp256k1 private key

Note that the hex representation of signature might be normalized/humanized e.g. by adding dashes every 32 bytes to visually separate/enhance the signature look.