UpdateMetadataSignatureTest.kt

3.3 KB · raw

1package com.peachpasswords.updates23import java.util.Base644import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters5import org.bouncycastle.crypto.signers.Ed25519Signer6import org.junit.Assert.assertEquals7import org.junit.Assert.assertThrows8import org.junit.Test9import com.peachpasswords.sync.verifyEd2551910import com.peachpasswords.sync.hexToBytes1112class UpdateMetadataSignatureTest {13    private val seed = ByteArray(32) { 7 }14    private val privateKey = Ed25519PrivateKeyParameters(seed, 0)15    private val publicKeyHex = privateKey.generatePublicKey().encoded.joinToString("") { "%02x".format(it) }16    private val version = "2.14.97"17    private val platform = ANDROID_PLATFORM_KEY18    private val url = "https://kepr.uk/peach-android/releases/$version/peach-android-$platform"19    private val checksum = "a".repeat(64)2021    private fun signedBody(22        signatureOverride: String? = null,23        urlOverride: String = url,24        signingUrl: String = urlOverride,25    ): String {26        val payload = canonicalReleaseMetadataPayload(version, platform, signingUrl, checksum, "koh-offer")27        val signer = Ed25519Signer().apply {28            init(true, privateKey)29            update(payload, 0, payload.size)30        }31        val signature = signatureOverride ?: Base64.getEncoder().encodeToString(signer.generateSignature())32        return """33            {"version":"$version","platforms":{"$platform":{34              "url":"$urlOverride","blake3":"$checksum",35              "provenance":{"version":"$version","platform":"$platform","checksum":"blake3:$checksum","source":"koh-offer"},36              "signature":"$signature"}}}37        """.trimIndent()38    }3940    @Test41    fun `matching signature is required before manifest is accepted`() {42        val payload = canonicalReleaseMetadataPayload(version, platform, url, checksum, "koh-offer")43        val rawSignature = Ed25519Signer().apply {44            init(true, privateKey)45            update(payload, 0, payload.size)46        }.generateSignature()47        assertEquals(true, verifyEd25519(privateKey.generatePublicKey().encoded, payload, rawSignature))48        assertEquals(64, publicKeyHex.length)49        val encoded = Base64.getEncoder().encodeToString(rawSignature)50        assertEquals(true, verifyEd25519(hexToBytes(publicKeyHex), payload, Base64.getDecoder().decode(encoded)))51        val parsed = parseSignedReleaseManifest(signedBody(signatureOverride = encoded), publicKeyHex = publicKeyHex)52        assertEquals(url, parsed.url)53        assertEquals("koh-offer", parsed.provenanceSource)54    }5556    @Test57    fun `unsigned metadata is rejected`() {58        assertThrows(ManifestFormatException::class.java) {59            parseSignedReleaseManifest(signedBody(signatureOverride = ""), publicKeyHex = publicKeyHex)60        }61    }6263    @Test64    fun `alternate URL under a valid checksum is rejected`() {65        val body = signedBody(urlOverride = "https://evil.invalid/release.apk", signingUrl = url)66        assertThrows(ManifestFormatException::class.java) {67            parseSignedReleaseManifest(body, publicKeyHex = publicKeyHex)68        }69    }7071    @Test72    fun `unknown key is rejected`() {73        assertThrows(ManifestFormatException::class.java) {74            parseSignedReleaseManifest(signedBody(), publicKeyHex = "01".repeat(32))75        }76    }77}