refactor(server): crypto 流程改用验证后的 licenceId
This commit is contained in:
@@ -17,7 +17,7 @@ const handler = new OpenAPIHandler(router, {
|
||||
title: name,
|
||||
version,
|
||||
description:
|
||||
'UX 授权服务 OpenAPI 文档。该服务用于工具箱侧本地身份初始化与密码学能力调用,覆盖设备授权密文生成、任务二维码解密、摘要信息加密、报告签名打包等流程。\n\n推荐调用顺序:\n1) 写入 licence 与 OpenPGP 私钥;\n2) 读取本机身份状态进行前置校验;\n3) 执行加密/解密与签名接口。\n\n说明:除文件下载接口外,返回体均为 JSON;字段示例已提供,便于联调和 Mock。',
|
||||
'UX 授权服务 OpenAPI 文档。该服务用于工具箱侧本地身份初始化与密码学能力调用,覆盖设备授权密文生成、任务二维码解密、摘要信息加密、报告签名打包等流程。\n\n推荐调用顺序:\n1) 写入平台公钥;\n2) 写入已签名 licence JSON;\n3) 写入 OpenPGP 私钥;\n4) 读取本机身份状态进行前置校验;\n5) 执行加密/解密与签名接口。\n\n说明:除文件下载接口外,返回体均为 JSON;字段示例已提供,便于联调和 Mock。',
|
||||
},
|
||||
},
|
||||
docsPath: '/docs',
|
||||
|
||||
@@ -8,7 +8,7 @@ export const encryptDeviceInfo = oc
|
||||
operationId: 'encryptDeviceInfo',
|
||||
summary: '生成设备授权二维码密文',
|
||||
description:
|
||||
'生成设备授权流程所需的二维码密文。\n\n处理流程:\n- 读取本机 licence、fingerprint 与本地持久化的平台公钥;\n- 组装为授权载荷 JSON;\n- 使用平台公钥执行 RSA-OAEP(SHA-256) 加密;\n- 返回 Base64 密文供前端生成二维码。\n\n适用场景:设备授权申请、重新授权。\n\n前置条件:需先调用 config.setPlatformPublicKey 写入平台公钥。',
|
||||
'生成设备授权流程所需的二维码密文。\n\n处理流程:\n- 读取本机已验证的 licenceId、fingerprint 与本地持久化的平台公钥;\n- 组装为授权载荷 JSON;\n- 使用平台公钥执行 RSA-OAEP(SHA-256) 加密;\n- 返回 Base64 密文供前端生成二维码。\n\n适用场景:设备授权申请、重新授权。\n\n前置条件:需先调用 config.setPlatformPublicKey 写入平台公钥,并通过 config.setLicence 安装已签名 licence。',
|
||||
tags: ['Crypto'],
|
||||
})
|
||||
.input(z.object({}).describe('空请求体。平台公钥由本地配置自动读取'))
|
||||
@@ -34,7 +34,7 @@ export const decryptTask = oc
|
||||
operationId: 'decryptTask',
|
||||
summary: '解密任务二维码数据',
|
||||
description:
|
||||
'解密 App 下发的任务二维码密文。\n\n处理流程:\n- 基于本机 licence + fingerprint 派生 AES-256-GCM 密钥;\n- 对二维码中的 Base64 密文进行解密;\n- 返回任务明文 JSON 字符串。\n\n适用场景:扫码接收任务后解析任务详情。',
|
||||
'解密 App 下发的任务二维码密文。\n\n处理流程:\n- 基于本机已验证的 licenceId + fingerprint 派生 AES-256-GCM 密钥;\n- 对二维码中的 Base64 密文进行解密;\n- 返回任务明文 JSON 字符串。\n\n适用场景:扫码接收任务后解析任务详情。',
|
||||
tags: ['Crypto'],
|
||||
})
|
||||
.input(
|
||||
@@ -74,7 +74,7 @@ export const encryptSummary = oc
|
||||
operationId: 'encryptSummary',
|
||||
summary: '加密摘要信息',
|
||||
description:
|
||||
'加密检查摘要信息并产出二维码密文。\n\n处理流程:\n- 使用 licence + fingerprint 结合 taskId(salt) 通过 HKDF-SHA256 派生密钥;\n- 使用 AES-256-GCM 加密摘要明文;\n- 返回 Base64 密文用于摘要二维码生成。\n\n适用场景:任务执行后提交摘要信息。',
|
||||
'加密检查摘要信息并产出二维码密文。\n\n处理流程:\n- 使用已验证的 licenceId + fingerprint 结合 taskId(salt) 通过 HKDF-SHA256 派生密钥;\n- 使用 AES-256-GCM 加密摘要明文;\n- 返回 Base64 密文用于摘要二维码生成。\n\n适用场景:任务执行后提交摘要信息。',
|
||||
tags: ['Crypto'],
|
||||
})
|
||||
.input(
|
||||
@@ -116,7 +116,7 @@ export const signAndPackReport = oc
|
||||
operationId: 'signAndPackReport',
|
||||
summary: '签名并打包检查报告',
|
||||
description:
|
||||
'对原始报告执行设备签名与 OpenPGP 签名并重新打包。\n\n处理流程:\n- 解析上传 ZIP 并提取 summary.json;\n- 用 licence/fingerprint 计算 deviceSignature(HKDF + HMAC-SHA256) 并回写 summary.json;\n- 生成 META-INF/manifest.json;\n- 使用本地 OpenPGP 私钥生成 detached signature(`META-INF/signature.asc`);\n- 返回签名后 ZIP。\n\n适用场景:检查结果归档、可追溯签名分发。',
|
||||
'对原始报告执行设备签名与 OpenPGP 签名并重新打包。\n\n处理流程:\n- 解析上传 ZIP 并提取 summary.json;\n- 用已验证的 licenceId/fingerprint 计算 deviceSignature(HKDF + HMAC-SHA256) 并回写 summary.json;\n- 生成 META-INF/manifest.json;\n- 使用本地 OpenPGP 私钥生成 detached signature(`META-INF/signature.asc`);\n- 返回签名后 ZIP。\n\n适用场景:检查结果归档、可追溯签名分发。',
|
||||
tags: ['Report'],
|
||||
spec: (current) => {
|
||||
const multipartContent =
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
stringify as losslessStringify,
|
||||
} from 'lossless-json'
|
||||
import { z } from 'zod'
|
||||
import { isLicenceExpired } from '@/server/licence'
|
||||
import { extractSafeZipFiles, ZipValidationError } from '@/server/safe-zip'
|
||||
import { getUxConfig } from '@/server/ux-config'
|
||||
import { db } from '../middlewares'
|
||||
@@ -45,12 +46,19 @@ const summaryPayloadSchema = z
|
||||
|
||||
const requireIdentity = async (dbInstance: Parameters<typeof getUxConfig>[0]) => {
|
||||
const config = await getUxConfig(dbInstance)
|
||||
if (!config || !config.licence) {
|
||||
if (!config || !config.licenceId || !config.licenceExpireTime) {
|
||||
throw new ORPCError('PRECONDITION_FAILED', {
|
||||
message: 'Local identity is not initialized. Call config.get and then config.setLicence first.',
|
||||
})
|
||||
}
|
||||
return config as typeof config & { licence: string }
|
||||
|
||||
if (isLicenceExpired(config.licenceExpireTime)) {
|
||||
throw new ORPCError('PRECONDITION_FAILED', {
|
||||
message: 'Local licence has expired. Install a new signed licence before calling crypto APIs.',
|
||||
})
|
||||
}
|
||||
|
||||
return config as typeof config & { licenceId: string; licenceExpireTime: string }
|
||||
}
|
||||
|
||||
export const encryptDeviceInfo = os.crypto.encryptDeviceInfo.use(db).handler(async ({ context }) => {
|
||||
@@ -63,7 +71,7 @@ export const encryptDeviceInfo = os.crypto.encryptDeviceInfo.use(db).handler(asy
|
||||
}
|
||||
|
||||
const deviceInfoJson = JSON.stringify({
|
||||
licence: config.licence,
|
||||
licence: config.licenceId,
|
||||
fingerprint: config.fingerprint,
|
||||
})
|
||||
|
||||
@@ -74,7 +82,7 @@ export const encryptDeviceInfo = os.crypto.encryptDeviceInfo.use(db).handler(asy
|
||||
export const decryptTask = os.crypto.decryptTask.use(db).handler(async ({ context, input }) => {
|
||||
const config = await requireIdentity(context.db)
|
||||
|
||||
const key = sha256(config.licence + config.fingerprint)
|
||||
const key = sha256(config.licenceId + config.fingerprint)
|
||||
const decrypted = aesGcmDecrypt(input.encryptedData, key)
|
||||
return { decrypted }
|
||||
})
|
||||
@@ -82,7 +90,7 @@ export const decryptTask = os.crypto.decryptTask.use(db).handler(async ({ contex
|
||||
export const encryptSummary = os.crypto.encryptSummary.use(db).handler(async ({ context, input }) => {
|
||||
const config = await requireIdentity(context.db)
|
||||
|
||||
const ikm = config.licence + config.fingerprint
|
||||
const ikm = config.licenceId + config.fingerprint
|
||||
const aesKey = hkdfSha256(ikm, input.salt, 'inspection_report_encryption')
|
||||
const encrypted = aesGcmEncrypt(input.plaintext, aesKey)
|
||||
return { encrypted }
|
||||
@@ -152,7 +160,7 @@ export const signAndPackReport = os.crypto.signAndPackReport.use(db).handler(asy
|
||||
// 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.licenceId + config.fingerprint
|
||||
const signingKey = hkdfSha256(ikm, 'AUTH_V3_SALT', 'device_report_signature')
|
||||
|
||||
const signPayload = `${summaryPayload.taskId}${checkId}${assetsSha256}${vulnerabilitiesSha256}${weakPasswordsSha256}${reportHtmlSha256}`
|
||||
@@ -163,7 +171,7 @@ export const signAndPackReport = os.crypto.signAndPackReport.use(db).handler(asy
|
||||
orgId: toLosslessNumber(String(orgId)),
|
||||
checkId: toLosslessNumber(checkId),
|
||||
taskId: summaryPayload.taskId,
|
||||
licence: config.licence,
|
||||
licence: config.licenceId,
|
||||
fingerprint: config.fingerprint,
|
||||
deviceSignature,
|
||||
summary: summaryPayload.summary ?? '',
|
||||
|
||||
Reference in New Issue
Block a user