diff --git a/apps/server/src/server/api/routers/crypto.router.ts b/apps/server/src/server/api/routers/crypto.router.ts index 259bc97..fdd7982 100644 --- a/apps/server/src/server/api/routers/crypto.router.ts +++ b/apps/server/src/server/api/routers/crypto.router.ts @@ -19,8 +19,11 @@ import { os } from '../server' const summaryPayloadSchema = z .object({ taskId: z.string().min(1, 'summary.json must contain a non-empty taskId'), - checkId: z.string().optional(), - inspectionId: z.string().optional(), + checkId: z.union([z.string(), z.number()]).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() @@ -106,43 +109,52 @@ export const signAndPackReport = os.crypto.signAndPackReport.use(db).handler(asy } const summaryPayload = parsed.data - const checkId = summaryPayload.checkId ?? summaryPayload.inspectionId ?? '' - const signingContext = `${summaryPayload.taskId}${checkId}` + const checkId = String(summaryPayload.checkId ?? summaryPayload.inspectionId ?? '') + 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 + // signPayload = taskId + inspectionId + assetsSha256 + vulnerabilitiesSha256 + weakPasswordsSha256 + reportHtmlSha256 + // (plain concatenation, no separators, fixed order — matching Kotlin reference) const ikm = config.licence + config.fingerprint const signingKey = hkdfSha256(ikm, 'AUTH_V3_SALT', 'device_report_signature') - const fileHashEntries = zipFiles - .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 signPayload = `${summaryPayload.taskId}${checkId}${assetsSha256}${vulnerabilitiesSha256}${weakPasswordsSha256}${reportHtmlSha256}` 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 = { - deviceSignature, - signingContext, + orgId, + checkId, + taskId: summaryPayload.taskId, licence: config.licence, fingerprint: config.fingerprint, - payload: summaryPayload, - timestamp: Date.now(), + deviceSignature, + summary: summaryPayload.summary ?? '', } const summaryBytes = Buffer.from(JSON.stringify(finalSummary), 'utf-8') - // Build manifest.json + // Build manifest.json (fixed file list, matching Kotlin reference) const manifestFiles: Record = { 'summary.json': sha256Hex(summaryBytes), - } - for (const item of fileHashEntries) { - if (item.name !== 'summary.json') { - manifestFiles[item.name] = item.hash - } + 'assets.json': assetsSha256, + 'vulnerabilities.json': vulnerabilitiesSha256, + 'weakPasswords.json': weakPasswordsSha256, + '漏洞评估报告.html': reportHtmlSha256, } const manifestBytes = Buffer.from(JSON.stringify({ files: manifestFiles }, null, 2), 'utf-8')