const crypto = require('crypto'); /** * Generate a signed license key (Base64 Payload + Base64 Signature) * @param {string} moduleCode - 'asset', 'production', 'monitoring' * @param {string} type - 'dev', 'sub', 'demo' * @param {string} subscriberId - Subscriber ID * @param {number} [activationDays] - Optional * @param {string} [customExpiryDate] - Optional YYYY-MM-DD * @param {string|Buffer} privateKey - PEM formatted private key * @returns {string} Signed License Token */ function generateLicense(moduleCode, type, subscriberId, activationDays = null, customExpiryDate = null, privateKey) { if (!privateKey) { throw new Error('Private Key is required to generate license.'); } const payloadData = { m: moduleCode, // Module t: type, // Type s: subscriberId, // Subscriber ID d: Date.now() // Issue Date }; if (customExpiryDate) { const date = new Date(customExpiryDate + 'T23:59:59'); if (!isNaN(date.getTime())) payloadData.e = date.getTime(); } else { if (type === 'demo') payloadData.e = Date.now() + (30 * 24 * 60 * 60 * 1000); else if (type === 'sub') payloadData.e = Date.now() + (365 * 24 * 60 * 60 * 1000); } if (activationDays) { payloadData.ad = Date.now() + (activationDays * 24 * 60 * 60 * 1000); } const payloadStr = JSON.stringify(payloadData); const sign = crypto.createSign('SHA256'); sign.update(payloadStr); sign.end(); const signature = sign.sign(privateKey, 'base64'); const payloadB64 = Buffer.from(payloadStr).toString('base64'); return `${payloadB64}.${signature}`; } /** * Verify a signed license key * @param {string} licenseToken - The token string (Payload.Signature) * @param {string|Buffer} publicKey - PEM formatted public key * @returns {object} { isValid, reason, ...payload } */ function verifyLicense(licenseToken, publicKey) { if (!publicKey) { return { isValid: false, reason: 'Server Configuration Error: Public Key missing' }; } if (!licenseToken || !licenseToken.includes('.')) { return { isValid: false, reason: 'Invalid Key Format' }; } const [payloadB64, signature] = licenseToken.split('.'); try { const payloadStr = Buffer.from(payloadB64, 'base64').toString('utf8'); // Verify Signature const verify = crypto.createVerify('SHA256'); verify.update(payloadStr); verify.end(); const isValidSignature = verify.verify(publicKey, signature, 'base64'); if (!isValidSignature) { return { isValid: false, reason: 'Invalid Signature (Key Modified or Wrong Public Key)' }; } // Logic Checks (Expiry, Activation) const payload = JSON.parse(payloadStr); const { m, t, d, s, ad, e } = payload; const subscriberId = s || null; if (ad && Date.now() > ad) { return { isValid: false, reason: `Activation Period Expired (${new Date(ad).toLocaleDateString()})`, module: m, type: t, subscriberId }; } let expiryDate = null; if (e) { expiryDate = new Date(e); if (Date.now() > e) { return { isValid: false, reason: 'Expired', expiryDate, subscriberId }; } } else { // Fallback for old keys? No, this is a breaking change. Old keys fail signature anyway. // Just handle 'dev' type (no expiry) if (t !== 'dev') { // If not dev and no e, treat as permanent or error? // Logic above always sets 'e' for sub/demo. } } return { isValid: true, module: m, type: t, subscriberId, expiryDate }; } catch (e) { return { isValid: false, reason: 'Corrupted Key Payload' }; } } module.exports = { generateLicense, verifyLicense };