package top.tangyh.lamp.filing.utils import io.github.oshai.kotlinlogging.KotlinLogging import java.nio.charset.StandardCharsets import java.security.MessageDigest import java.util.* import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec private val logger = KotlinLogging.logger {} /** * 设备签名工具类 * 用于生成和验证设备报告签名 * * 签名算法:HMAC-SHA256 * 签名数据(严格顺序): * sign_payload = taskId + inspectionId + * SHA256(assets.json) + * SHA256(vulnerabilities.json) + * SHA256(weakPasswords.json) + * SHA256(漏洞评估报告.html) * * 安全设计说明: * - 使用 HMAC-SHA256 提供消息认证,防止伪造和篡改 * - 签名包含 taskId 和 inspectionId,确保签名与特定任务绑定 * - 包含多个报告文件的 SHA256,确保报告内容完整性 * - 只有拥有正确 licence + fingerprint 的设备才能生成有效签名 */ object DeviceSignatureUtil { private const val HMAC_ALGORITHM = "HmacSHA256" /** * 签名数据文件列表(严格顺序) */ data class SignatureFileHashes( val assetsJsonSha256: String, val vulnerabilitiesJsonSha256: String, val weakPasswordsJsonSha256: String, val reportHtmlSha256: String ) /** * 生成设备签名 * * @param key 派生密钥(32字节) * @param taskId 任务ID * @param inspectionId 检查ID * @param fileHashes 各个文件的 SHA256 哈希值(hex字符串) * @return Base64 编码的签名 */ fun generateSignature( key: ByteArray, taskId: String, inspectionId: Long, fileHashes: SignatureFileHashes ): String { return try { // 组装签名数据(严格顺序): // taskId + inspectionId + SHA256(assets.json) + SHA256(vulnerabilities.json) + // SHA256(weakPasswords.json) + SHA256(漏洞评估报告.html) val signatureData = buildString { append(taskId) append(inspectionId) append(fileHashes.assetsJsonSha256) append(fileHashes.vulnerabilitiesJsonSha256) append(fileHashes.weakPasswordsJsonSha256) append(fileHashes.reportHtmlSha256) } val dataBytes = signatureData.toByteArray(StandardCharsets.UTF_8) // 使用 HMAC-SHA256 计算签名 val mac = Mac.getInstance(HMAC_ALGORITHM) val secretKey = SecretKeySpec(key, HMAC_ALGORITHM) mac.init(secretKey) val signatureBytes = mac.doFinal(dataBytes) // Base64 编码返回 Base64.getEncoder().encodeToString(signatureBytes) } catch (e: Exception) { logger.error(e) { "生成设备签名失败: taskId=$taskId, inspectionId=$inspectionId" } throw RuntimeException("生成设备签名失败: ${e.message}", e) } } /** * 验证设备签名 * * @param key 派生密钥(32字节) * @param taskId 任务ID * @param inspectionId 检查ID * @param fileHashes 各个文件的 SHA256 哈希值(hex字符串) * @param expectedSignature Base64 编码的期望签名 * @return true 如果签名匹配,false 否则 */ fun verifySignature( key: ByteArray, taskId: String, inspectionId: Long, fileHashes: SignatureFileHashes, expectedSignature: String ): Boolean { return try { val calculatedSignature = generateSignature(key, taskId, inspectionId, fileHashes) // 使用时间安全的比较,防止时序攻击 MessageDigest.isEqual( Base64.getDecoder().decode(expectedSignature), Base64.getDecoder().decode(calculatedSignature) ) } catch (e: Exception) { logger.error(e) { "验证设备签名失败: taskId=$taskId, inspectionId=$inspectionId" } false } } /** * 计算文件的 SHA256 哈希值(hex字符串) * * @param fileContent 文件内容 * @return SHA256 哈希值的 hex 字符串 */ fun calculateSha256(fileContent: ByteArray): String { val digest = MessageDigest.getInstance("SHA-256") val hashBytes = digest.digest(fileContent) return hashBytes.joinToString("") { "%02x".format(it) } } }