refactor(server): signAndPackReport 对齐 Kotlin 参考实现的摘要与签名结构

This commit is contained in:
2026-03-10 15:08:12 +08:00
parent 4a5dd437fa
commit da82403f7f

View File

@@ -19,8 +19,11 @@ import { os } from '../server'
const summaryPayloadSchema = z const summaryPayloadSchema = z
.object({ .object({
taskId: z.string().min(1, 'summary.json must contain a non-empty taskId'), taskId: z.string().min(1, 'summary.json must contain a non-empty taskId'),
checkId: z.string().optional(), checkId: z.union([z.string(), z.number()]).optional(),
inspectionId: z.string().optional(), inspectionId: z.union([z.string(), z.number()]).optional(),
orgId: z.union([z.string(), z.number()]).optional(),
enterpriseId: z.union([z.string(), z.number()]).optional(),
summary: z.string().optional(),
}) })
.loose() .loose()
@@ -106,43 +109,52 @@ export const signAndPackReport = os.crypto.signAndPackReport.use(db).handler(asy
} }
const summaryPayload = parsed.data const summaryPayload = parsed.data
const checkId = summaryPayload.checkId ?? summaryPayload.inspectionId ?? '' const checkId = String(summaryPayload.checkId ?? summaryPayload.inspectionId ?? '')
const signingContext = `${summaryPayload.taskId}${checkId}` const orgId = summaryPayload.orgId ?? summaryPayload.enterpriseId ?? ''
// Helper: find file in ZIP and compute its SHA256 hash
const requireFileHash = (name: string): string => {
const file = zipFiles.find((f) => f.name === name)
if (!file) {
throw new ORPCError('BAD_REQUEST', { message: `rawZip must contain ${name}` })
}
return sha256Hex(Buffer.from(file.bytes))
}
// Compute SHA256 of each content file (fixed order, matching Kotlin reference)
const assetsSha256 = requireFileHash('assets.json')
const vulnerabilitiesSha256 = requireFileHash('vulnerabilities.json')
const weakPasswordsSha256 = requireFileHash('weakPasswords.json')
const reportHtmlSha256 = requireFileHash('漏洞评估报告.html')
// Compute device signature // Compute device signature
// signPayload = taskId + inspectionId + assetsSha256 + vulnerabilitiesSha256 + weakPasswordsSha256 + reportHtmlSha256
// (plain concatenation, no separators, fixed order — matching Kotlin reference)
const ikm = config.licence + config.fingerprint const ikm = config.licence + config.fingerprint
const signingKey = hkdfSha256(ikm, 'AUTH_V3_SALT', 'device_report_signature') const signingKey = hkdfSha256(ikm, 'AUTH_V3_SALT', 'device_report_signature')
const fileHashEntries = zipFiles const signPayload = `${summaryPayload.taskId}${checkId}${assetsSha256}${vulnerabilitiesSha256}${weakPasswordsSha256}${reportHtmlSha256}`
.map((item) => ({
name: item.name,
hash: sha256Hex(Buffer.from(item.bytes)),
}))
.sort((a, b) => a.name.localeCompare(b.name, 'en'))
const hashPayload = fileHashEntries.map((item) => `${item.name}:${item.hash}`).join('|')
const signPayload = `${signingContext}|${hashPayload}`
const deviceSignature = hmacSha256Base64(signingKey, signPayload) const deviceSignature = hmacSha256Base64(signingKey, signPayload)
// Build final summary.json with device signature and identity // Build final summary.json with flat structure (matching Kotlin reference)
const finalSummary = { const finalSummary = {
deviceSignature, orgId,
signingContext, checkId,
taskId: summaryPayload.taskId,
licence: config.licence, licence: config.licence,
fingerprint: config.fingerprint, fingerprint: config.fingerprint,
payload: summaryPayload, deviceSignature,
timestamp: Date.now(), summary: summaryPayload.summary ?? '',
} }
const summaryBytes = Buffer.from(JSON.stringify(finalSummary), 'utf-8') const summaryBytes = Buffer.from(JSON.stringify(finalSummary), 'utf-8')
// Build manifest.json // Build manifest.json (fixed file list, matching Kotlin reference)
const manifestFiles: Record<string, string> = { const manifestFiles: Record<string, string> = {
'summary.json': sha256Hex(summaryBytes), 'summary.json': sha256Hex(summaryBytes),
} 'assets.json': assetsSha256,
for (const item of fileHashEntries) { 'vulnerabilities.json': vulnerabilitiesSha256,
if (item.name !== 'summary.json') { 'weakPasswords.json': weakPasswordsSha256,
manifestFiles[item.name] = item.hash '漏洞评估报告.html': reportHtmlSha256,
}
} }
const manifestBytes = Buffer.from(JSON.stringify({ files: manifestFiles }, null, 2), 'utf-8') const manifestBytes = Buffer.from(JSON.stringify({ files: manifestFiles }, null, 2), 'utf-8')