refactor(server): signAndPackReport 对齐 Kotlin 参考实现的摘要与签名结构
This commit is contained in:
@@ -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')
|
||||||
|
|||||||
Reference in New Issue
Block a user