From 5d7bd5b5c7eb21ca66a7bcda260f0938aed548fe Mon Sep 17 00:00:00 2001 From: Graham Date: Sun, 7 Feb 2021 00:36:55 +0000 Subject: [PATCH] Add support for signed master indexes This commit also changes the way the master index format detection works, as the previous scheme could not distinguish VERSIONED from WHIRLPOOL. Signed-off-by: Graham --- .../openrs2/archive/cache/CacheImporter.kt | 7 +- .../openrs2/archive/cache/ImportCommand.kt | 5 +- .../org/openrs2/cache/Js5MasterIndex.kt | 188 +++++++++++-- .../org/openrs2/cache/MasterIndexFormat.kt | 3 +- .../org/openrs2/cache/Js5MasterIndexTest.kt | 259 ++++++++++++++++-- .../master-index/{ => original}/255/0.dat | Bin .../openrs2/cache/master-index/private.key | 28 ++ .../org/openrs2/cache/master-index/public.key | 9 + .../cache/master-index/versioned/255/0.dat | Bin 0 -> 9 bytes .../master-index/{ => versioned}/255/1.dat | Bin .../master-index/{ => versioned}/255/3.dat | Bin .../master-index/{ => versioned}/255/6.dat | Bin .../cache/master-index/whirlpool/255/0.dat | Bin 0 -> 9 bytes .../cache/master-index/whirlpool/255/1.dat | Bin 0 -> 13 bytes 14 files changed, 445 insertions(+), 54 deletions(-) rename cache/src/test/resources/org/openrs2/cache/master-index/{ => original}/255/0.dat (100%) create mode 100644 cache/src/test/resources/org/openrs2/cache/master-index/private.key create mode 100644 cache/src/test/resources/org/openrs2/cache/master-index/public.key create mode 100644 cache/src/test/resources/org/openrs2/cache/master-index/versioned/255/0.dat rename cache/src/test/resources/org/openrs2/cache/master-index/{ => versioned}/255/1.dat (100%) rename cache/src/test/resources/org/openrs2/cache/master-index/{ => versioned}/255/3.dat (100%) rename cache/src/test/resources/org/openrs2/cache/master-index/{ => versioned}/255/6.dat (100%) create mode 100644 cache/src/test/resources/org/openrs2/cache/master-index/whirlpool/255/0.dat create mode 100644 cache/src/test/resources/org/openrs2/cache/master-index/whirlpool/255/1.dat diff --git a/archive/src/main/kotlin/org/openrs2/archive/cache/CacheImporter.kt b/archive/src/main/kotlin/org/openrs2/archive/cache/CacheImporter.kt index 49f6913f..c7dd892d 100644 --- a/archive/src/main/kotlin/org/openrs2/archive/cache/CacheImporter.kt +++ b/archive/src/main/kotlin/org/openrs2/archive/cache/CacheImporter.kt @@ -64,7 +64,6 @@ public class CacheImporter @Inject constructor( public suspend fun import( store: Store, - masterIndexFormat: MasterIndexFormat?, game: String, build: Int?, timestamp: Instant?, @@ -77,7 +76,7 @@ public class CacheImporter @Inject constructor( val gameId = getGameId(connection, game) // import master index - val masterIndex = createMasterIndex(store, masterIndexFormat) + val masterIndex = createMasterIndex(store) try { addMasterIndex(connection, masterIndex, gameId, build, timestamp, name, description, false) } finally { @@ -316,11 +315,11 @@ public class CacheImporter @Inject constructor( } } - private fun createMasterIndex(store: Store, format: MasterIndexFormat?): MasterIndex { + private fun createMasterIndex(store: Store): MasterIndex { val index = Js5MasterIndex.create(store) alloc.buffer().use { uncompressed -> - index.write(uncompressed, format ?: index.minimumFormat) + index.write(uncompressed) Js5Compression.compress(uncompressed, Js5CompressionType.UNCOMPRESSED).use { buf -> return MasterIndex(index, buf.retain()) diff --git a/archive/src/main/kotlin/org/openrs2/archive/cache/ImportCommand.kt b/archive/src/main/kotlin/org/openrs2/archive/cache/ImportCommand.kt index 1787215d..dacd90b2 100644 --- a/archive/src/main/kotlin/org/openrs2/archive/cache/ImportCommand.kt +++ b/archive/src/main/kotlin/org/openrs2/archive/cache/ImportCommand.kt @@ -3,18 +3,15 @@ package org.openrs2.archive.cache import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.options.option -import com.github.ajalt.clikt.parameters.types.enum import com.github.ajalt.clikt.parameters.types.int import com.github.ajalt.clikt.parameters.types.path import com.google.inject.Guice import kotlinx.coroutines.runBlocking import org.openrs2.archive.ArchiveModule -import org.openrs2.cache.MasterIndexFormat import org.openrs2.cache.Store import org.openrs2.cli.instant public class ImportCommand : CliktCommand(name = "import") { - private val masterIndexFormat by option().enum() private val build by option().int() private val timestamp by option().instant() private val name by option() @@ -32,7 +29,7 @@ public class ImportCommand : CliktCommand(name = "import") { val importer = injector.getInstance(CacheImporter::class.java) Store.open(input).use { store -> - importer.import(store, masterIndexFormat, game, build, timestamp, name, description) + importer.import(store, game, build, timestamp, name, description) } } } diff --git a/cache/src/main/kotlin/org/openrs2/cache/Js5MasterIndex.kt b/cache/src/main/kotlin/org/openrs2/cache/Js5MasterIndex.kt index 7df74255..fad6f308 100644 --- a/cache/src/main/kotlin/org/openrs2/cache/Js5MasterIndex.kt +++ b/cache/src/main/kotlin/org/openrs2/cache/Js5MasterIndex.kt @@ -1,36 +1,113 @@ package org.openrs2.cache import io.netty.buffer.ByteBuf +import io.netty.buffer.ByteBufUtil +import org.bouncycastle.crypto.params.RSAKeyParameters import org.openrs2.buffer.crc32 import org.openrs2.buffer.use +import org.openrs2.crypto.Rsa +import org.openrs2.crypto.Whirlpool +import org.openrs2.crypto.rsaDecrypt +import org.openrs2.crypto.rsaEncrypt +import org.openrs2.crypto.whirlpool -public inline class Js5MasterIndex(public val entries: MutableList = mutableListOf()) { - public data class Entry(public var version: Int, public var checksum: Int) +public data class Js5MasterIndex( + public var format: MasterIndexFormat, + public val entries: MutableList = mutableListOf() +) { + public class Entry( + public var version: Int, + public var checksum: Int, + digest: ByteArray? + ) { + public var digest: ByteArray? = digest + set(value) { + require(value == null || value.size == Whirlpool.DIGESTBYTES) + field = value + } - public val minimumFormat: MasterIndexFormat - get() { - for (entry in entries) { - if (entry.version != 0) { - return MasterIndexFormat.VERSIONED - } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Entry + + if (version != other.version) return false + if (checksum != other.checksum) return false + if (digest != null) { + if (other.digest == null) return false + if (!digest.contentEquals(other.digest)) return false + } else if (other.digest != null) return false + + return true + } + + override fun hashCode(): Int { + var result = version + result = 31 * result + checksum + result = 31 * result + (digest?.contentHashCode() ?: 0) + return result + } + + override fun toString(): String { + val digest = digest + val hex = if (digest != null) { + ByteBufUtil.hexDump(digest) + } else { + "null" } + return "Entry(version=$version, checksum=$checksum, digest=$hex)" + } + } + + public fun write(buf: ByteBuf, key: RSAKeyParameters? = null) { + val start = buf.writerIndex() - return MasterIndexFormat.ORIGINAL + if (format >= MasterIndexFormat.WHIRLPOOL) { + buf.writeByte(entries.size) } - public fun write(buf: ByteBuf, format: MasterIndexFormat) { for (entry in entries) { buf.writeInt(entry.checksum) if (format >= MasterIndexFormat.VERSIONED) { buf.writeInt(entry.version) } + + if (format >= MasterIndexFormat.WHIRLPOOL) { + val digest = entry.digest + if (digest != null) { + buf.writeBytes(digest) + } else { + buf.writeZero(Whirlpool.DIGESTBYTES) + } + } + } + + if (format >= MasterIndexFormat.WHIRLPOOL) { + val digest = buf.whirlpool(start, buf.writerIndex() - start) + + if (key != null) { + buf.alloc().buffer(SIGNATURE_LENGTH, SIGNATURE_LENGTH).use { plaintext -> + plaintext.writeByte(Rsa.MAGIC) + plaintext.writeBytes(digest) + + plaintext.rsaEncrypt(key).use { ciphertext -> + buf.writeBytes(ciphertext) + } + } + } else { + buf.writeByte(Rsa.MAGIC) + buf.writeBytes(digest) + } } } public companion object { + private const val SIGNATURE_LENGTH = Whirlpool.DIGESTBYTES + 1 + public fun create(store: Store): Js5MasterIndex { - val index = Js5MasterIndex() + val masterIndex = Js5MasterIndex(MasterIndexFormat.ORIGINAL) var nextArchive = 0 for (archive in store.list(Js5Archive.ARCHIVESET)) { @@ -40,46 +117,111 @@ public inline class Js5MasterIndex(public val entries: MutableList = muta * entries with a zero CRC are probably invalid. */ for (i in nextArchive until archive) { - index.entries += Entry(0, 0) + masterIndex.entries += Entry(0, 0, null) } val entry = store.read(Js5Archive.ARCHIVESET, archive).use { buf -> val checksum = buf.crc32() + val digest = buf.whirlpool() + val version = Js5Compression.uncompress(buf).use { uncompressed -> - Js5Index.read(uncompressed).version + val index = Js5Index.read(uncompressed) + + if (index.hasDigests) { + masterIndex.format = maxOf(masterIndex.format, MasterIndexFormat.WHIRLPOOL) + } else if (index.protocol >= Js5Protocol.VERSIONED) { + masterIndex.format = maxOf(masterIndex.format, MasterIndexFormat.VERSIONED) + } + + index.version } + // TODO(gpe): should we throw an exception if there are trailing bytes here or in the block above? - Entry(version, checksum) + Entry(version, checksum, digest) } - index.entries += entry + masterIndex.entries += entry nextArchive = archive + 1 } - return index + return masterIndex } - public fun read(buf: ByteBuf, format: MasterIndexFormat): Js5MasterIndex { - when (format) { + public fun read(buf: ByteBuf, format: MasterIndexFormat, key: RSAKeyParameters? = null): Js5MasterIndex { + val index = Js5MasterIndex(format) + + val start = buf.readerIndex() + val len = buf.readableBytes() + + val archives = when (format) { MasterIndexFormat.ORIGINAL -> { - require(buf.readableBytes() % 4 == 0) + require(len % 4 == 0) { + "Length is not a multiple of 4 bytes" + } + len / 4 } MasterIndexFormat.VERSIONED -> { - require(buf.readableBytes() % 8 == 0) + require(len % 8 == 0) { + "Length is not a multiple of 8 bytes" + } + len / 8 + } + MasterIndexFormat.WHIRLPOOL -> { + buf.readUnsignedByte().toInt() } } - val index = Js5MasterIndex() - while (buf.isReadable) { + for (i in 0 until archives) { val checksum = buf.readInt() + val version = if (format >= MasterIndexFormat.VERSIONED) { buf.readInt() } else { 0 } - index.entries += Entry(version, checksum) + + val digest = if (format >= MasterIndexFormat.WHIRLPOOL) { + val bytes = ByteArray(Whirlpool.DIGESTBYTES) + buf.readBytes(bytes) + bytes + } else { + null + } + + index.entries += Entry(version, checksum, digest) + } + + val end = buf.readerIndex() + + if (format >= MasterIndexFormat.WHIRLPOOL) { + val ciphertext = buf.readSlice(buf.readableBytes()) + decrypt(ciphertext, key).use { plaintext -> + require(plaintext.readableBytes() == SIGNATURE_LENGTH) { + "Invalid signature length" + } + + // the client doesn't verify what I presume is the RSA magic byte + plaintext.skipBytes(1) + + val expected = ByteArray(Whirlpool.DIGESTBYTES) + plaintext.readBytes(expected) + + val actual = buf.whirlpool(start, end - start) + require(expected.contentEquals(actual)) { + "Invalid signature" + } + } } + return index } + + private fun decrypt(buf: ByteBuf, key: RSAKeyParameters?): ByteBuf { + return if (key != null) { + buf.rsaDecrypt(key) + } else { + buf.retain() + } + } } } diff --git a/cache/src/main/kotlin/org/openrs2/cache/MasterIndexFormat.kt b/cache/src/main/kotlin/org/openrs2/cache/MasterIndexFormat.kt index 7a11e0a4..2bef1a4d 100644 --- a/cache/src/main/kotlin/org/openrs2/cache/MasterIndexFormat.kt +++ b/cache/src/main/kotlin/org/openrs2/cache/MasterIndexFormat.kt @@ -2,5 +2,6 @@ package org.openrs2.cache public enum class MasterIndexFormat { ORIGINAL, - VERSIONED + VERSIONED, + WHIRLPOOL } diff --git a/cache/src/test/kotlin/org/openrs2/cache/Js5MasterIndexTest.kt b/cache/src/test/kotlin/org/openrs2/cache/Js5MasterIndexTest.kt index 6ae4916c..ac90e2d4 100644 --- a/cache/src/test/kotlin/org/openrs2/cache/Js5MasterIndexTest.kt +++ b/cache/src/test/kotlin/org/openrs2/cache/Js5MasterIndexTest.kt @@ -1,8 +1,10 @@ package org.openrs2.cache import io.netty.buffer.ByteBufAllocator +import io.netty.buffer.ByteBufUtil import io.netty.buffer.Unpooled import org.openrs2.buffer.use +import org.openrs2.crypto.Rsa import java.nio.file.Path import kotlin.test.Test import kotlin.test.assertEquals @@ -10,47 +12,169 @@ import kotlin.test.assertFailsWith object Js5MasterIndexTest { private val ROOT = Path.of(FlatFileStoreTest::class.java.getResource("master-index").toURI()) + private val PRIVATE_KEY = Rsa.readPrivateKey(ROOT.resolve("private.key")) + private val PUBLIC_KEY = Rsa.readPublicKey(ROOT.resolve("public.key")) - private val encodedOriginal = byteArrayOf(0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 5) + private val encodedOriginal = ByteBufUtil.decodeHexDump("000000010000000300000005") private val decodedOriginal = Js5MasterIndex( + MasterIndexFormat.ORIGINAL, mutableListOf( - Js5MasterIndex.Entry(0, 1), - Js5MasterIndex.Entry(0, 3), - Js5MasterIndex.Entry(0, 5) + Js5MasterIndex.Entry(0, 1, null), + Js5MasterIndex.Entry(0, 3, null), + Js5MasterIndex.Entry(0, 5, null) ) ) - private val encodedVersioned = byteArrayOf(0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 4) + private val encodedVersioned = ByteBufUtil.decodeHexDump("000000010000000000000003000000020000000500000004") private val decodedVersioned = Js5MasterIndex( + MasterIndexFormat.VERSIONED, mutableListOf( - Js5MasterIndex.Entry(0, 1), - Js5MasterIndex.Entry(2, 3), - Js5MasterIndex.Entry(4, 5) + Js5MasterIndex.Entry(0, 1, null), + Js5MasterIndex.Entry(2, 3, null), + Js5MasterIndex.Entry(4, 5, null) ) ) + private val encodedWhirlpool = ByteBufUtil.decodeHexDump( + "01" + + "89abcdef" + + "01234567" + + "0e1a2b93c80a41c7ad2a985dff707a6a8ff82e229cbc468f04191198920955a1" + + "4b3d7eab77a17faf99208dee5b44afb789962ad79f230b3b59106a0af892219c" + + "0a" + + "ee8f66a2ce0b07de4d2b792eed26ae7a6c307b763891d085c63ea55b4c003bc0" + + "b3ecb77cc1a8f9ccd53c405b3264e598820b4940f630ff079a9feb950f639671" + ) + private val decodedWhirlpool = Js5MasterIndex( + MasterIndexFormat.WHIRLPOOL, + mutableListOf( + Js5MasterIndex.Entry( + 0x01234567, 0x89ABCDEF.toInt(), ByteBufUtil.decodeHexDump( + "0e1a2b93c80a41c7ad2a985dff707a6a8ff82e229cbc468f04191198920955a1" + + "4b3d7eab77a17faf99208dee5b44afb789962ad79f230b3b59106a0af892219c" + ) + ) + ) + ) + + private val encodedWhirlpoolNullDigest = ByteBufUtil.decodeHexDump( + "01" + + "89abcdef" + + "01234567" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0a" + + "4a0e22540fb0a9bc06fe84bfb35f9281ba9fbd30288c3375c508ad741c4d4491" + + "8a65765bc2dce9d67029be79bd544f96055a41d725c080bc5b85a48b5aae6e4d" + ) + private val decodedWhirlpoolNullDigest = Js5MasterIndex( + MasterIndexFormat.WHIRLPOOL, + mutableListOf( + Js5MasterIndex.Entry(0x01234567, 0x89ABCDEF.toInt(), null) + ) + ) + + private val encodedSigned = ByteBufUtil.decodeHexDump( + "01" + + "89abcdef" + + "01234567" + + "0e1a2b93c80a41c7ad2a985dff707a6a8ff82e229cbc468f04191198920955a1" + + "4b3d7eab77a17faf99208dee5b44afb789962ad79f230b3b59106a0af892219c" + + "2134b1e637d4c9f3b7bdd446ad40cedb6d824cfb48f937ae0d6e2ba3977881ea" + + "ed02adae179ed89cea56e98772186bb569bb24a4951e441716df0d5d7199c088" + + "28974d43c3644e74bf29ec1435e425f6cb05aca14a84163c5b46b6e6a9362f22" + + "4f69f4a5888b3fe7aec0141da25b17c7f65069eed59f3be134fa1ade4e191b41" + + "d561447446cd1cc4d11e6499c49e00066173908491d8d2ff282aefa86e6c6b15" + + "dceb437d0436b6195ef60d4128e1e0184bf6929b73abd1a8aa2a047e3cb90d03" + + "57707ce3f4f5a7af8471eda5c0c0748454a9cbb48c25ebe4e7fd94e3881b6461" + + "d06e2bce128dc96decb537b8e9611591d445d7dfd3701d25ac05f8d091581aef" + ) + + @Test + fun testCreateOriginal() { + val index = Store.open(ROOT.resolve("original")).use { store -> + Js5MasterIndex.create(store) + } + + assertEquals( + Js5MasterIndex( + MasterIndexFormat.ORIGINAL, + mutableListOf( + Js5MasterIndex.Entry( + 0, 609698396, ByteBufUtil.decodeHexDump( + "0e1a2b93c80a41c7ad2a985dff707a6a8ff82e229cbc468f04191198920955a1" + + "4b3d7eab77a17faf99208dee5b44afb789962ad79f230b3b59106a0af892219c" + ) + ), + ) + ), index + ) + } + @Test - fun testMinimumFormat() { - assertEquals(MasterIndexFormat.ORIGINAL, decodedOriginal.minimumFormat) - assertEquals(MasterIndexFormat.VERSIONED, decodedVersioned.minimumFormat) + fun testCreateVersioned() { + val index = Store.open(ROOT.resolve("versioned")).use { store -> + Js5MasterIndex.create(store) + } + + assertEquals( + Js5MasterIndex( + MasterIndexFormat.VERSIONED, + mutableListOf( + Js5MasterIndex.Entry( + 0, 609698396, ByteBufUtil.decodeHexDump( + "0e1a2b93c80a41c7ad2a985dff707a6a8ff82e229cbc468f04191198920955a1" + + "4b3d7eab77a17faf99208dee5b44afb789962ad79f230b3b59106a0af892219c" + ) + ), + Js5MasterIndex.Entry( + 0x12345678, 78747481, ByteBufUtil.decodeHexDump( + "180ff4ad371f56d4a90d81e0b69b23836cd9b101b828f18b7e6d232c4d302539" + + "638eb2e9259957645aae294f09b2d669c93dbbfc0d8359f1b232ae468f678ca1" + ) + ), + Js5MasterIndex.Entry(0, 0, null), + Js5MasterIndex.Entry( + 0x9ABCDEF0.toInt(), -456081154, ByteBufUtil.decodeHexDump( + "972003261b7628525346e0052567662e5695147ad710f877b63b9ab53b3f6650" + + "ca003035fde4398b2ef73a60e4b13798aa597a30c1bf0a13c0cd412394af5f96" + ) + ), + Js5MasterIndex.Entry(0, 0, null), + Js5MasterIndex.Entry(0, 0, null), + Js5MasterIndex.Entry( + 0xAA55AA55.toInt(), 186613982, ByteBufUtil.decodeHexDump( + "d50a6e9abd3b5269606304dc2769cbc8618e1ae6ff705291c0dfcc374e450dd2" + + "5f1be5f1d5459651d22d3e87ef0a1c69be7807f661cd001be24a6609f6d57916" + ) + ) + ) + ), index + ) } @Test - fun testCreate() { - val index = Store.open(ROOT).use { store -> + fun testCreateWhirlpool() { + val index = Store.open(ROOT.resolve("whirlpool")).use { store -> Js5MasterIndex.create(store) } assertEquals( Js5MasterIndex( + MasterIndexFormat.WHIRLPOOL, mutableListOf( - Js5MasterIndex.Entry(0, 609698396), - Js5MasterIndex.Entry(0x12345678, 78747481), - Js5MasterIndex.Entry(0, 0), - Js5MasterIndex.Entry(0x9ABCDEF0.toInt(), -456081154), - Js5MasterIndex.Entry(0, 0), - Js5MasterIndex.Entry(0, 0), - Js5MasterIndex.Entry(0xAA55AA55.toInt(), 186613982) + Js5MasterIndex.Entry( + 0, 668177970, ByteBufUtil.decodeHexDump( + "2faa83116e1d1719d5db15f128eb57f62afbf0207c47bced3f558ec17645d138" + + "72f4fb9b0e36a5f6f5d30e1295b3fa49556dfd0819cb5137f3b69f64155f3fb7" + ) + ), + Js5MasterIndex.Entry( + 0, 1925442845, ByteBufUtil.decodeHexDump( + "fcc45b0ab6d0067889e44de0004bcbb6cc538aff8f80edf1b49b583cedd73fea" + + "937ae6990235257fe8aa35c44d35450c13e670711337ee5116957cd98cc27985" + ) + ) ) ), index ) @@ -73,7 +197,7 @@ object Js5MasterIndexTest { @Test fun testWriteOriginal() { ByteBufAllocator.DEFAULT.buffer().use { actual -> - decodedOriginal.write(actual, MasterIndexFormat.ORIGINAL) + decodedOriginal.write(actual) Unpooled.wrappedBuffer(encodedOriginal).use { expected -> assertEquals(expected, actual) @@ -98,11 +222,102 @@ object Js5MasterIndexTest { @Test fun testWriteVersioned() { ByteBufAllocator.DEFAULT.buffer().use { actual -> - decodedVersioned.write(actual, MasterIndexFormat.VERSIONED) + decodedVersioned.write(actual) Unpooled.wrappedBuffer(encodedVersioned).use { expected -> assertEquals(expected, actual) } } } + + @Test + fun testReadWhirlpool() { + Unpooled.wrappedBuffer(encodedWhirlpool).use { buf -> + val index = Js5MasterIndex.read(buf, MasterIndexFormat.WHIRLPOOL) + assertEquals(decodedWhirlpool, index) + } + } + + @Test + fun testReadWhirlpoolInvalidSignature() { + Unpooled.copiedBuffer(encodedWhirlpool).use { buf -> + val lastIndex = buf.writerIndex() - 1 + buf.setByte(lastIndex, buf.getByte(lastIndex).toInt().inv()) + + assertFailsWith { + Js5MasterIndex.read(buf, MasterIndexFormat.WHIRLPOOL) + } + } + } + + @Test + fun testReadWhirlpoolInvalidSignatureLength() { + Unpooled.wrappedBuffer(encodedWhirlpool, 0, encodedWhirlpool.size - 1).use { buf -> + assertFailsWith { + Js5MasterIndex.read(buf, MasterIndexFormat.WHIRLPOOL) + } + } + } + + @Test + fun testWriteWhirlpool() { + ByteBufAllocator.DEFAULT.buffer().use { actual -> + decodedWhirlpool.write(actual) + + Unpooled.wrappedBuffer(encodedWhirlpool).use { expected -> + assertEquals(expected, actual) + } + } + } + + @Test + fun testWriteWhirlpoolNullDigest() { + ByteBufAllocator.DEFAULT.buffer().use { actual -> + decodedWhirlpoolNullDigest.write(actual) + + Unpooled.wrappedBuffer(encodedWhirlpoolNullDigest).use { expected -> + assertEquals(expected, actual) + } + } + } + + @Test + fun testReadSigned() { + Unpooled.wrappedBuffer(encodedSigned).use { buf -> + val index = Js5MasterIndex.read(buf, MasterIndexFormat.WHIRLPOOL, PUBLIC_KEY) + assertEquals(decodedWhirlpool, index) + } + } + + @Test + fun testReadSignedInvalidSignature() { + Unpooled.copiedBuffer(encodedSigned).use { buf -> + val lastIndex = buf.writerIndex() - 1 + buf.setByte(lastIndex, buf.getByte(lastIndex).toInt().inv()) + + assertFailsWith { + Js5MasterIndex.read(buf, MasterIndexFormat.WHIRLPOOL, PUBLIC_KEY) + } + } + } + + @Test + fun testReadSignedInvalidSignatureLength() { + Unpooled.wrappedBuffer(encodedSigned, 0, encodedSigned.size - 1).use { buf -> + assertFailsWith { + Js5MasterIndex.read(buf, MasterIndexFormat.WHIRLPOOL, PUBLIC_KEY) + } + } + } + + @Test + fun testWriteSigned() { + ByteBufAllocator.DEFAULT.buffer().use { actual -> + decodedWhirlpool.write(actual, PRIVATE_KEY) + + Unpooled.wrappedBuffer(encodedSigned).use { expected -> + assertEquals(expected, actual) + } + } + } } diff --git a/cache/src/test/resources/org/openrs2/cache/master-index/255/0.dat b/cache/src/test/resources/org/openrs2/cache/master-index/original/255/0.dat similarity index 100% rename from cache/src/test/resources/org/openrs2/cache/master-index/255/0.dat rename to cache/src/test/resources/org/openrs2/cache/master-index/original/255/0.dat diff --git a/cache/src/test/resources/org/openrs2/cache/master-index/private.key b/cache/src/test/resources/org/openrs2/cache/master-index/private.key new file mode 100644 index 00000000..03fb8917 --- /dev/null +++ b/cache/src/test/resources/org/openrs2/cache/master-index/private.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDErqLXr8aHZeAz +5ajLpsICaDGue4fOWntCF+52Rws8vHyxkuPGtMXAdXCDFr0LCYPGykcqsHMqF2/+ +KNtPindRpQ6spJEDbGhLROSj7Pybc8A2livS3GG5LbZjTbkzIqpUYcR7p4VrbxEO +IN5Y7B9vzMpqHhcMCfkzn9k6RypzwMYCqNZUNJVJotJTALSg4gG2GEmZeqyUy7oJ +cFew+L6xnrEsZ41S0ouWoE0wCWuxikJeMD6eFZRNjRMt8mV4NB28y2iNUuJbI+Yh +Kg6kI5bflnF14sUY/yr996coXWEc6hzyCjHU30ItmtD6f9W48pXm7q3jhKhBvdsw +T8ofLS9JAgMBAAECggEAN/nr5q7kNczMznhiXfOL69tzqFEICba+tcTR3V/C1vpe +ErvXj8oLLgc+ilCCZQ3EL2OKdZ/aZvRcr105bZ3e76GmV4ROsUa9OA7Xc8AS9Lsw +zVhYCJ8oQOe9rO2F9AO9rl5U6Ux4MGdf10GLhotNNyh1w1XlAUwlXIf17xwp/N80 +H2vowW3U1cb1AMaJsNGtpSolGEIb6jA7S0CjNLf2eNXeNjswpDyGgFRp5bFr6N6s +151kGOZSt+qpei9I91lh8zMPYVhSrz42wjH8HgisHKH8VaWLiQTdaqhqNU5nJZ1W +UGejOCPcMeeOTrnmM24tIs9Xeh/bWnYRwDodM5RAcQKBgQD35/B1ehlbHxMNKYxK +uaucqfjQ8aDJU2tB8rr/VfjGWzpB2mcOj7/gHCEuSXQTkdQ2SXlGU92w5jLkav3g +vSERk3Vnh/t/Dq9HysqeuzAp8SXavnDvYGLTCUpMyFECXrPIKrMs+U2F4pTOOXbF +UBQQ1f8WRwes0bd0ahIdyJW9awKBgQDLGo4aSWibBAZC/z4ja4fjR2sJuWAPxoIM +N3wa9VYCibgrvJlaXvlEsYWDcvkFaaBWjJYWrFjHX7PCwtW7J9rBw3tpGzIcNXCj +YfJRjMMxkPiCMF07JEPBiRo1WUmF1O8ubzK8LDr9DRlxSrjzrbWAYtcvY/M3k5Sz +OA0RHKbfGwKBgHYxoRWBh0FIiX7HBlpCN63T5AtKiIw0N3kTz1AZnyiDKj1ncach +piulfbRh1PPXnUPct/Nt3M6QkkcRM8XIplGI6nrX/HJRgARMjVosiQQWMyQdlB6s +57ESRthg0S6+FB0lLpQMsIdaxfOkthnQ2iBExv/KEcC1pC/eupB0p9/NAoGBAJum +mTq6AWmzVt0nYUah1P0wMW69W0obtnSIXRsH48eEJdmW6uugF2Y2qfyIMyGbxl4t +1aRAprT8ufXLfSK2M2cFWeG+DtQhfFYp7RvkRX8J+/lB+WEmtKpwWN6Ds93VxwuN ++pLNTtO5o0L4oe9Vs+BVX1YZQj7YYkBK93CixZv7AoGBAL45w+WCiN4lSLfivfcO +fk82aWISwAMxDFCRbNQ2/T8xSlP+9Y8x5ixseK0tBRyKvqzay4jinpDjtslKQCyb +XJ/pvrxlTQDOD7uK+IpfEpSrZPHpGRnosx2QUsdDbDr0KBupQvx5i2BGVfatNDM1 +K3Z5TSOop7aSYoEjkH8O+mdl +-----END PRIVATE KEY----- diff --git a/cache/src/test/resources/org/openrs2/cache/master-index/public.key b/cache/src/test/resources/org/openrs2/cache/master-index/public.key new file mode 100644 index 00000000..286dfeaf --- /dev/null +++ b/cache/src/test/resources/org/openrs2/cache/master-index/public.key @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxK6i16/Gh2XgM+Woy6bC +AmgxrnuHzlp7QhfudkcLPLx8sZLjxrTFwHVwgxa9CwmDxspHKrBzKhdv/ijbT4p3 +UaUOrKSRA2xoS0Tko+z8m3PANpYr0txhuS22Y025MyKqVGHEe6eFa28RDiDeWOwf +b8zKah4XDAn5M5/ZOkcqc8DGAqjWVDSVSaLSUwC0oOIBthhJmXqslMu6CXBXsPi+ +sZ6xLGeNUtKLlqBNMAlrsYpCXjA+nhWUTY0TLfJleDQdvMtojVLiWyPmISoOpCOW +35ZxdeLFGP8q/fenKF1hHOoc8gox1N9CLZrQ+n/VuPKV5u6t44SoQb3bME/KHy0v +SQIDAQAB +-----END PUBLIC KEY----- diff --git a/cache/src/test/resources/org/openrs2/cache/master-index/versioned/255/0.dat b/cache/src/test/resources/org/openrs2/cache/master-index/versioned/255/0.dat new file mode 100644 index 0000000000000000000000000000000000000000..8b4eb14584cf6a6e018f4118842e08d5b35d1f8f GIT binary patch literal 9 OcmZQzU|?Wj1rh)NF#rkx literal 0 HcmV?d00001 diff --git a/cache/src/test/resources/org/openrs2/cache/master-index/255/1.dat b/cache/src/test/resources/org/openrs2/cache/master-index/versioned/255/1.dat similarity index 100% rename from cache/src/test/resources/org/openrs2/cache/master-index/255/1.dat rename to cache/src/test/resources/org/openrs2/cache/master-index/versioned/255/1.dat diff --git a/cache/src/test/resources/org/openrs2/cache/master-index/255/3.dat b/cache/src/test/resources/org/openrs2/cache/master-index/versioned/255/3.dat similarity index 100% rename from cache/src/test/resources/org/openrs2/cache/master-index/255/3.dat rename to cache/src/test/resources/org/openrs2/cache/master-index/versioned/255/3.dat diff --git a/cache/src/test/resources/org/openrs2/cache/master-index/255/6.dat b/cache/src/test/resources/org/openrs2/cache/master-index/versioned/255/6.dat similarity index 100% rename from cache/src/test/resources/org/openrs2/cache/master-index/255/6.dat rename to cache/src/test/resources/org/openrs2/cache/master-index/versioned/255/6.dat diff --git a/cache/src/test/resources/org/openrs2/cache/master-index/whirlpool/255/0.dat b/cache/src/test/resources/org/openrs2/cache/master-index/whirlpool/255/0.dat new file mode 100644 index 0000000000000000000000000000000000000000..4c90c3b832e0e45a75288429cc4f12d343601916 GIT binary patch literal 9 QcmZQzU|?WjWny3e001`t3;+NC literal 0 HcmV?d00001 diff --git a/cache/src/test/resources/org/openrs2/cache/master-index/whirlpool/255/1.dat b/cache/src/test/resources/org/openrs2/cache/master-index/whirlpool/255/1.dat new file mode 100644 index 0000000000000000000000000000000000000000..5d11cfc9e6ae0cee257bd97ba581e618efdcbc9b GIT binary patch literal 13 QcmZQzU|`^20}>zr004ym4*&oF literal 0 HcmV?d00001