smart_ims_license/server/licenseManager.js
2026-01-23 20:48:40 +09:00

110 lines
3.8 KiB
JavaScript

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 };