135 lines
4.7 KiB
Kotlin
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()
|
|
}
|
|
|
|
}
|