├── .gitignore ├── .editorconfig ├── bin └── redeem ├── package.json └── lib └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Sublime Text files 2 | *.sublime* 3 | *.*~*.TMP 4 | 5 | # OS temp files 6 | .DS_Store 7 | Thumbs.db 8 | Desktop.ini 9 | 10 | # Tool temp files 11 | npm-debug.log 12 | *.sw* 13 | *~ 14 | \#*# 15 | 16 | # project ignores 17 | !.gitkeep 18 | node_modules 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # More information at http://EditorConfig.org 2 | 3 | root = true 4 | 5 | # General Options 6 | [*] 7 | end_of_line = lf 8 | indent_size = 4 9 | indent_style = space 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | # Markdown 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | 17 | # Special Files 18 | [{package.json,.travis.yml}] 19 | indent_size = 2 20 | -------------------------------------------------------------------------------- /bin/redeem: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var crypto = require('crypto'); 4 | var ecc = require('eccrypto'); 5 | 6 | var license = require('../lib'); 7 | 8 | var privateKey = crypto.randomBytes(32); 9 | var publicKey = ecc.getPublic(privateKey); 10 | 11 | var originalLicData = { 12 | Name: 'Chad Engler', 13 | Expires: (new Date()).toISOString() 14 | }; 15 | 16 | license.generate(originalLicData, privateKey) 17 | .then(function (licenseString) { 18 | console.log(licenseString); 19 | return license.verify(licenseString, publicKey); 20 | }) 21 | .then(function (licenseData) { 22 | console.log('VALID LICENSE:', licenseData); 23 | }) 24 | .catch(function (err) { 25 | console.log('LICENSE INVALID!', err.stack); 26 | }); 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redeem", 3 | "version": "1.0.0", 4 | "description": "A license key generator and verifier", 5 | "author": "Chad Engler ", 6 | "license": "ISC", 7 | "main": "index.js", 8 | "scripts": { 9 | "genKey": "openssl ecparam -name secp256k1 -genkey -noout -out secp256k1-key.pem" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:englercj/redeem.git" 14 | }, 15 | "keywords": [ 16 | "license", 17 | "keys", 18 | "ecdsa", 19 | "crypto", 20 | "redemption" 21 | ], 22 | "bugs": { 23 | "url": "https://github.com/englercj/redeem/issues" 24 | }, 25 | "homepage": "https://github.com/englercj/redeem", 26 | "dependencies": { 27 | "eccrypto": "^0.9.8" 28 | }, 29 | "engines": { 30 | "node": ">=0.12.x" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'); 2 | var ecc = require('eccrypto'); 3 | var Buffer = require('buffer').Buffer; 4 | 5 | module.exports = { 6 | templateHeader: '-----BEGIN LICENSE KEY-----\n', 7 | templateFooter: '\n-----END LICENSE KEY-----', 8 | 9 | /** 10 | * @param data {object} The data to be signed and used in the license. 11 | * @param privateKey {Buffer} The private key to use for signing. 12 | * @return {Promise} A promise to return the license string. 13 | */ 14 | generate: function (data, privateKey) { 15 | if (!data) { 16 | throw new Error('Data is required to generate a license.'); 17 | } 18 | 19 | if (!privateKey) { 20 | throw new Error('Private key is required to generate a license.'); 21 | } 22 | 23 | if (!Buffer.isBuffer(privateKey)) { 24 | throw new Error('Private key must be a Buffer to generate a license.'); 25 | } 26 | 27 | var self = this, 28 | msg = crypto.createHash('sha256').update(JSON.stringify(data)).digest(); 29 | 30 | return ecc.sign(privateKey, msg).then(function (sig) { 31 | return new Promise(function (resolve, reject) { 32 | resolve(self.templateHeader + obj2kv(data) + '\n' + formatSignature(sig) + self.templateFooter); 33 | }); 34 | }); 35 | }, 36 | 37 | /** 38 | * Verifies a license by validating the information provided against the signature, and 39 | * parsing back out the data object that was used to generate the license. 40 | * 41 | * @param data {object} The data to be signed and used in the license. 42 | * @param publicKey {Buffer} The private key to use for signing. 43 | * @return {Promise} A promise to verify the license stringand return the data object. 44 | */ 45 | verify: function (license, publicKey) { 46 | if (!license) { 47 | throw new Error('License string is required to verify a license.'); 48 | } 49 | 50 | if (!publicKey) { 51 | throw new Error('Public key is required to verify a license.'); 52 | } 53 | 54 | if (!Buffer.isBuffer(publicKey)) { 55 | throw new Error('Public key must be a Buffer to verify a license.'); 56 | } 57 | 58 | var data = license.replace(this.templateHeader, '').replace(this.templateFooter, '').split('\n\n'), 59 | dataObject = kv2obj(data[0]), 60 | msg = crypto.createHash('sha256').update(JSON.stringify(dataObject)).digest(), 61 | sig = parseSignature(data[1]); 62 | 63 | return ecc.verify(publicKey, msg, sig).then(function () { 64 | return new Promise(function (resolve, reject) { 65 | resolve(dataObject); 66 | }); 67 | }); 68 | } 69 | }; 70 | 71 | /** 72 | * Converts an object to string KV output. 73 | * 74 | * @private 75 | * @param data {object} The data to convert. 76 | * @return {string} 77 | */ 78 | function obj2kv(data) { 79 | var str = ''; 80 | 81 | for (var key in data) { 82 | str += key + ': ' + data[key] + '\n'; 83 | } 84 | 85 | return str; 86 | } 87 | 88 | /** 89 | * Converts a string in KV format to an object. 90 | * 91 | * @private 92 | * @param data {string} The data to convert. 93 | * @return {object} 94 | */ 95 | function kv2obj(data) { 96 | var lines = data.split('\n'), 97 | obj = {}; 98 | 99 | for (var i = 0; i < lines.length; ++i) { 100 | var line = lines[i].split(': '); 101 | obj[line[0]] = line[1]; 102 | } 103 | 104 | return obj; 105 | } 106 | 107 | /** 108 | * Formats the signature for output. 109 | * 110 | * @private 111 | * @param signature {Buffer} The signature buffer. 112 | * @return {string} The signature string. 113 | */ 114 | function formatSignature(sig) { 115 | var str = sig.toString('hex').toUpperCase(), 116 | idx = 0, 117 | step = 32, 118 | sub = null; 119 | 120 | // insert a newline every `step` characters 121 | while(sub = str.substr(idx, step)) { 122 | str = str.substr(0, idx) + sub + '\n' + str.substr(idx + step); 123 | idx += step + 1; 124 | } 125 | 126 | return str.trim(); 127 | } 128 | 129 | /** 130 | * Parses the signature for verification. 131 | * 132 | * @private 133 | * @param signature {string} The signature string. 134 | * @return {Buffer} The signature buffer. 135 | */ 136 | function parseSignature(sig) { 137 | return new Buffer(sig.replace(/\n/g, ''), 'hex'); 138 | } --------------------------------------------------------------------------------