56 lines
2.3 KiB
JavaScript
56 lines
2.3 KiB
JavaScript
const crypto = require("node:crypto");
|
|
const fs = require("node:fs");
|
|
|
|
function requiredText(value, fieldName) {
|
|
const text = String(value || "").trim();
|
|
if (!text) throw new Error(`${fieldName}不能为空`);
|
|
return text;
|
|
}
|
|
|
|
function normalizeTimestamp(value, fieldName) {
|
|
const timestamp = requiredText(value, fieldName);
|
|
const match = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})$/.exec(timestamp);
|
|
if (!match) throw new Error(`${fieldName}格式必须为 YYYY-MM-DD HH:MM`);
|
|
const [, year, month, day, hour, minute] = match.map(Number);
|
|
const parsed = new Date(year, month - 1, day, hour, minute);
|
|
if (parsed.getFullYear() !== year || parsed.getMonth() !== month - 1 ||
|
|
parsed.getDate() !== day || parsed.getHours() !== hour || parsed.getMinutes() !== minute) {
|
|
throw new Error(`${fieldName}不是有效日期时间`);
|
|
}
|
|
return timestamp;
|
|
}
|
|
|
|
function signLicense(payload, privateKeyPath) {
|
|
const licenseId = payload.license_id || crypto.randomUUID();
|
|
const deviceId = requiredText(payload.device_id, "device_id");
|
|
const deviceParts = deviceId.split("/");
|
|
if (deviceParts.length !== 2 || deviceParts.some((part) => !/^[a-z0-9][a-z0-9_-]{1,63}$/.test(part))) {
|
|
throw new Error("device_id 必须是 company-code/line-code 格式");
|
|
}
|
|
const issued = normalizeTimestamp(payload.issued, "签发时间");
|
|
const expiry = normalizeTimestamp(payload.expiry, "到期时间");
|
|
if (expiry <= issued) throw new Error("到期时间必须晚于签发时间");
|
|
const normalized = {
|
|
license_id: licenseId,
|
|
customer: requiredText(payload.customer, "customer"),
|
|
company_id: requiredText(payload.company_id, "company_id"),
|
|
production_line_id: requiredText(payload.production_line_id, "production_line_id"),
|
|
device_id: deviceId,
|
|
issued,
|
|
expiry,
|
|
features: requiredText(payload.features || "*", "features")
|
|
};
|
|
const payloadBase64 = Buffer.from(JSON.stringify(normalized)).toString("base64");
|
|
const privateKey = fs.readFileSync(privateKeyPath);
|
|
const signature = crypto.sign("sha256", Buffer.from(payloadBase64), {
|
|
key: privateKey,
|
|
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
saltLength: crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN
|
|
});
|
|
return {
|
|
payload: normalized,
|
|
content: `${payloadBase64}|${signature.toString("base64")}`
|
|
};
|
|
}
|
|
|
|
module.exports = { signLicense }; |