package top.tangyh.lamp.filing.utils import com.fasterxml.jackson.databind.ObjectMapper import io.github.oshai.kotlinlogging.KotlinLogging import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider import java.io.ByteArrayInputStream import java.io.InputStream import java.security.MessageDigest import java.security.Security import java.util.zip.ZipFile object ZipVerifierUtil { private val logger = KotlinLogging.logger {} // @JvmStatic // fun main(args: Array) { // verifyZip("signed.zip", "public.key") // } /** * 验证 ZIP 文件 */ @Throws(Exception::class) fun verifyZip(zipPath: String, pubkeyContent: String):Boolean { println(Security.getProviders().joinToString { it.name }) val publicKey = readPublicKey( ByteArrayInputStream(pubkeyContent.toByteArray()) ) val zip = ZipFile(zipPath) // 1. 读取 manifest.json val manifestEntry = zip.getEntry("META-INF/manifest.json") ?: throw RuntimeException("manifest.json is missing!") val manifestJson = zip.getInputStream(manifestEntry).readAllBytes().toString(Charsets.UTF_8) // 2. 读取 signature.asc val sigEntry = zip.getEntry("META-INF/signature.asc") ?: throw RuntimeException("signature.asc is missing!") val signature = zip.getInputStream(sigEntry).readAllBytes() // 3. 使用 OpenPGP 验证签名 val ok = verifyDetachedSignature(publicKey, manifestJson.toByteArray(), signature) if (!ok) throw RuntimeException("PGP signature invalid!") // 4. 校验 manifest 里每个文件的 SHA-256 val mapper = ObjectMapper() val manifest = mapper.readValue(manifestJson, Map::class.java) val files = manifest["files"] as? Map ?: throw RuntimeException("Invalid manifest.json: missing 'files'") for ((name, expectedHash) in files) { val entry = zip.getEntry(name) ?: throw RuntimeException("文件不存在: $name") val data = zip.getInputStream(entry).readAllBytes() val hash = sha256Hex(data) if (!hash.equals(expectedHash, ignoreCase = true)) { throw RuntimeException("Hash mismatch: $name") } } return true } @Throws(Exception::class) private fun sha256Hex(data: ByteArray): String { val md = MessageDigest.getInstance("SHA-256") return bytesToHex(md.digest(data)) } private fun bytesToHex(bytes: ByteArray): String { return bytes.joinToString("") { "%02x".format(it) } } @Throws(Exception::class) private fun readPublicKey(keyIn: InputStream): PGPPublicKey { val keyRings = PGPPublicKeyRingCollection( PGPUtil.getDecoderStream(keyIn), JcaKeyFingerprintCalculator() ) for (keyRing in keyRings) { for (key in keyRing) { if (key.isEncryptionKey || key.isMasterKey) { return key } } } throw IllegalArgumentException("Can't find public key") } @Throws(Exception::class) private fun verifyDetachedSignature( key: PGPPublicKey, data: ByteArray, sigBytes: ByteArray ): Boolean { val decoder = PGPUtil.getDecoderStream(ByteArrayInputStream(sigBytes)) val factory = PGPObjectFactory(decoder, JcaKeyFingerprintCalculator()) val message = factory.nextObject() ?: throw IllegalArgumentException("Invalid signature file") val sigList = when (message) { is PGPSignatureList -> message is PGPCompressedData -> { val compressedFactory = PGPObjectFactory( message.dataStream, JcaKeyFingerprintCalculator() ) val compressedObj = compressedFactory.nextObject() compressedObj as? PGPSignatureList ?: throw IllegalArgumentException("Invalid PGP signature (not signature list)") } else -> throw IllegalArgumentException("Unsupported PGP signature format: ${message::class.java}") } val sig = sigList[0] sig.init(JcaPGPContentVerifierBuilderProvider().setProvider("BC"), key) sig.update(data) return sig.verify() } }