Files
fullstack-starter/docs/工具箱端-授权对接指南/utils/ZipVerifierUtil.kt

135 lines
4.7 KiB
Kotlin

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<String>) {
// 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<String, String>
?: 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()
}
}