package com.peachpasswords.updates import java.util.Base64 import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters import org.bouncycastle.crypto.signers.Ed25519Signer import org.junit.Assert.assertEquals import org.junit.Assert.assertThrows import org.junit.Test import com.peachpasswords.sync.verifyEd25519 import com.peachpasswords.sync.hexToBytes class UpdateMetadataSignatureTest { private val seed = ByteArray(32) { 7 } private val privateKey = Ed25519PrivateKeyParameters(seed, 0) private val publicKeyHex = privateKey.generatePublicKey().encoded.joinToString("") { "%02x".format(it) } private val version = "2.14.97" private val platform = ANDROID_PLATFORM_KEY private val url = "https://kepr.uk/peach-android/releases/$version/peach-android-$platform" private val checksum = "a".repeat(64) private fun signedBody( signatureOverride: String? = null, urlOverride: String = url, signingUrl: String = urlOverride, ): String { val payload = canonicalReleaseMetadataPayload(version, platform, signingUrl, checksum, "koh-offer") val signer = Ed25519Signer().apply { init(true, privateKey) update(payload, 0, payload.size) } val signature = signatureOverride ?: Base64.getEncoder().encodeToString(signer.generateSignature()) return """ {"version":"$version","platforms":{"$platform":{ "url":"$urlOverride","blake3":"$checksum", "provenance":{"version":"$version","platform":"$platform","checksum":"blake3:$checksum","source":"koh-offer"}, "signature":"$signature"}}} """.trimIndent() } @Test fun `matching signature is required before manifest is accepted`() { val payload = canonicalReleaseMetadataPayload(version, platform, url, checksum, "koh-offer") val rawSignature = Ed25519Signer().apply { init(true, privateKey) update(payload, 0, payload.size) }.generateSignature() assertEquals(true, verifyEd25519(privateKey.generatePublicKey().encoded, payload, rawSignature)) assertEquals(64, publicKeyHex.length) val encoded = Base64.getEncoder().encodeToString(rawSignature) assertEquals(true, verifyEd25519(hexToBytes(publicKeyHex), payload, Base64.getDecoder().decode(encoded))) val parsed = parseSignedReleaseManifest(signedBody(signatureOverride = encoded), publicKeyHex = publicKeyHex) assertEquals(url, parsed.url) assertEquals("koh-offer", parsed.provenanceSource) } @Test fun `unsigned metadata is rejected`() { assertThrows(ManifestFormatException::class.java) { parseSignedReleaseManifest(signedBody(signatureOverride = ""), publicKeyHex = publicKeyHex) } } @Test fun `alternate URL under a valid checksum is rejected`() { val body = signedBody(urlOverride = "https://evil.invalid/release.apk", signingUrl = url) assertThrows(ManifestFormatException::class.java) { parseSignedReleaseManifest(body, publicKeyHex = publicKeyHex) } } @Test fun `unknown key is rejected`() { assertThrows(ManifestFormatException::class.java) { parseSignedReleaseManifest(signedBody(), publicKeyHex = "01".repeat(32)) } } }