From 2932af463ced07e47b5f0aaccdaf17c47e9631f3 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 14 Sep 2021 17:37:37 +0200 Subject: [PATCH] Check version of backup files against expected version from metadata and throw security exception if it does not match --- .../seedvault/header/HeaderReader.kt | 10 +++- .../transport/restore/FullRestore.kt | 12 ++-- .../seedvault/transport/restore/KVRestore.kt | 18 ++++-- .../transport/restore/RestoreCoordinator.kt | 5 +- .../seedvault/header/HeaderReaderTest.kt | 19 +++++-- .../transport/restore/FullRestoreTest.kt | 50 +++++++++++------ .../transport/restore/KVRestoreTest.kt | 56 ++++++++++++------- .../restore/RestoreCoordinatorTest.kt | 9 +-- .../restore/RestoreV0IntegrationTest.kt | 2 +- 9 files changed, 118 insertions(+), 63 deletions(-) diff --git a/app/src/main/java/com/stevesoltys/seedvault/header/HeaderReader.kt b/app/src/main/java/com/stevesoltys/seedvault/header/HeaderReader.kt index 9c023d57..192aa825 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/header/HeaderReader.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/header/HeaderReader.kt @@ -5,10 +5,11 @@ import java.io.EOFException import java.io.IOException import java.io.InputStream import java.nio.ByteBuffer +import java.security.GeneralSecurityException internal interface HeaderReader { @Throws(IOException::class, UnsupportedVersionException::class) - fun readVersion(inputStream: InputStream): Byte + fun readVersion(inputStream: InputStream, expectedVersion: Byte): Byte @Deprecated("") @Throws(SecurityException::class) @@ -21,11 +22,14 @@ internal interface HeaderReader { internal class HeaderReaderImpl : HeaderReader { - @Throws(IOException::class, UnsupportedVersionException::class) - override fun readVersion(inputStream: InputStream): Byte { + @Throws(IOException::class, UnsupportedVersionException::class, GeneralSecurityException::class) + override fun readVersion(inputStream: InputStream, expectedVersion: Byte): Byte { val version = inputStream.read().toByte() if (version < 0) throw IOException() if (version > VERSION) throw UnsupportedVersionException(version) + if (expectedVersion != version) throw GeneralSecurityException( + "Expected version ${expectedVersion.toInt()}, but got ${version.toInt()}" + ) return version } diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt index 4cf3bb2f..67690366 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt @@ -20,10 +20,10 @@ import java.io.OutputStream import java.security.GeneralSecurityException private class FullRestoreState( + val version: Byte, val token: Long, val packageInfo: PackageInfo ) { - var version: Byte? = null var inputStream: InputStream? = null } @@ -55,8 +55,8 @@ internal class FullRestore( * It is possible that the system decides to not restore the package. * Then a new state will be initialized right away without calling other methods. */ - fun initializeState(token: Long, packageInfo: PackageInfo) { - state = FullRestoreState(token, packageInfo) + fun initializeState(version: Byte, token: Long, packageInfo: PackageInfo) { + state = FullRestoreState(version, token, packageInfo) } /** @@ -94,8 +94,7 @@ internal class FullRestore( Log.i(TAG, "First Chunk, initializing package input stream.") try { val inputStream = plugin.getInputStreamForPackage(state.token, state.packageInfo) - val version = headerReader.readVersion(inputStream) - state.version = version + val version = headerReader.readVersion(inputStream, state.version) if (version == 0.toByte()) { crypto.decryptHeader(inputStream, version, packageName) state.inputStream = inputStream @@ -132,9 +131,8 @@ internal class FullRestore( private fun copyInputStream(outputStream: OutputStream): Int { val state = this.state ?: throw IllegalStateException("no state") val inputStream = state.inputStream ?: throw IllegalStateException("no stream") - val version = state.version ?: throw IllegalStateException("no version") - if (version == 0.toByte()) { + if (state.version == 0.toByte()) { // read segment from input stream and decrypt it val decrypted = try { crypto.decryptSegment(inputStream) diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt index 9ec0f306..5ee74ed8 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt @@ -17,10 +17,12 @@ import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.getADForKV import libcore.io.IoUtils.closeQuietly import java.io.IOException +import java.security.GeneralSecurityException import java.util.ArrayList import javax.crypto.AEADBadTagException private class KVRestoreState( + val version: Byte, val token: Long, val packageInfo: PackageInfo, /** @@ -57,8 +59,13 @@ internal class KVRestore( * * @param pmPackageInfo single optional [PackageInfo] to optimize restore of @pm@ */ - fun initializeState(token: Long, packageInfo: PackageInfo, pmPackageInfo: PackageInfo? = null) { - state = KVRestoreState(token, packageInfo, pmPackageInfo) + fun initializeState( + version: Byte, + token: Long, + packageInfo: PackageInfo, + pmPackageInfo: PackageInfo? = null + ) { + state = KVRestoreState(version, token, packageInfo, pmPackageInfo) } /** @@ -98,6 +105,9 @@ internal class KVRestore( } catch (e: SecurityException) { Log.e(TAG, "Security exception while reading backup records", e) TRANSPORT_ERROR + } catch (e: GeneralSecurityException) { + Log.e(TAG, "General security exception while reading backup records", e) + TRANSPORT_ERROR } catch (e: UnsupportedVersionException) { Log.e(TAG, "Unsupported version in backup: ${e.version}", e) TRANSPORT_ERROR @@ -140,14 +150,14 @@ internal class KVRestore( /** * Read the encrypted value for the given key and write it to the given [BackupDataOutput]. */ - @Throws(IOException::class, UnsupportedVersionException::class, SecurityException::class) + @Throws(IOException::class, UnsupportedVersionException::class, GeneralSecurityException::class) private suspend fun readAndWriteValue( state: KVRestoreState, dKey: DecodedKey, out: BackupDataOutput ) = plugin.getInputStreamForRecord(state.token, state.packageInfo, dKey.base64Key) .use { inputStream -> - val version = headerReader.readVersion(inputStream) + val version = headerReader.readVersion(inputStream, state.version) val packageName = state.packageInfo.packageName val value = if (version == 0.toByte()) { crypto.decryptHeader(inputStream, version, packageName, dKey.key) diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt index 4c77e92a..500be24e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt @@ -200,6 +200,7 @@ internal class RestoreCoordinator( val state = this.state ?: throw IllegalStateException("no state") if (!state.packages.hasNext()) return NO_MORE_PACKAGES + val version = state.backupMetadata.version val packageInfo = state.packages.next() val packageName = packageInfo.packageName @@ -208,13 +209,13 @@ internal class RestoreCoordinator( // check key/value data first and if available, don't even check for full data kv.hasDataForPackage(state.token, packageInfo) -> { Log.i(TAG, "Found K/V data for $packageName.") - kv.initializeState(state.token, packageInfo, state.pmPackageInfo) + kv.initializeState(version, state.token, packageInfo, state.pmPackageInfo) state.currentPackage = packageName TYPE_KEY_VALUE } full.hasDataForPackage(state.token, packageInfo) -> { Log.i(TAG, "Found full backup data for $packageName.") - full.initializeState(state.token, packageInfo) + full.initializeState(version, state.token, packageInfo) state.currentPackage = packageName TYPE_FULL_STREAM } diff --git a/app/src/test/java/com/stevesoltys/seedvault/header/HeaderReaderTest.kt b/app/src/test/java/com/stevesoltys/seedvault/header/HeaderReaderTest.kt index 7a16769c..7765d1ee 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/header/HeaderReaderTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/header/HeaderReaderTest.kt @@ -26,7 +26,7 @@ internal class HeaderReaderTest { val input = byteArrayOf(VERSION) val inputStream = ByteArrayInputStream(input) - assertEquals(VERSION, reader.readVersion(inputStream)) + assertEquals(VERSION, reader.readVersion(inputStream, VERSION)) } @Test @@ -34,7 +34,7 @@ internal class HeaderReaderTest { val input = ByteArray(0) val inputStream = ByteArrayInputStream(input) assertThrows(IOException::class.javaObjectType) { - reader.readVersion(inputStream) + reader.readVersion(inputStream, VERSION) } } @@ -43,7 +43,7 @@ internal class HeaderReaderTest { val input = byteArrayOf((VERSION + 1).toByte()) val inputStream = ByteArrayInputStream(input) assertThrows(UnsupportedVersionException::class.javaObjectType) { - reader.readVersion(inputStream) + reader.readVersion(inputStream, VERSION) } } @@ -52,7 +52,7 @@ internal class HeaderReaderTest { val input = byteArrayOf((-1).toByte()) val inputStream = ByteArrayInputStream(input) assertThrows(IOException::class.javaObjectType) { - reader.readVersion(inputStream) + reader.readVersion(inputStream, VERSION) } } @@ -61,7 +61,16 @@ internal class HeaderReaderTest { val input = byteArrayOf(Byte.MAX_VALUE) val inputStream = ByteArrayInputStream(input) assertThrows(UnsupportedVersionException::class.javaObjectType) { - reader.readVersion(inputStream) + reader.readVersion(inputStream, Byte.MAX_VALUE) + } + } + + @Test + fun `unexpected version throws exception`() { + val input = byteArrayOf(VERSION + 1) + val inputStream = ByteArrayInputStream(input) + assertThrows(UnsupportedVersionException::class.javaObjectType) { + reader.readVersion(inputStream, VERSION) } } diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/FullRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/FullRestoreTest.kt index 668cea66..9764377c 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/FullRestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/FullRestoreTest.kt @@ -54,7 +54,7 @@ internal class FullRestoreTest : RestoreTest() { @Test fun `initializing state leaves a state`() { assertFalse(restore.hasState()) - restore.initializeState(token, packageInfo) + restore.initializeState(VERSION, token, packageInfo) assertTrue(restore.hasState()) } @@ -68,7 +68,7 @@ internal class FullRestoreTest : RestoreTest() { @Test fun `getting InputStream for package when getting first chunk throws`() = runBlocking { - restore.initializeState(token, packageInfo) + restore.initializeState(VERSION, token, packageInfo) coEvery { plugin.getInputStreamForPackage(token, packageInfo) } throws IOException() every { fileDescriptor.close() } just Runs @@ -81,10 +81,10 @@ internal class FullRestoreTest : RestoreTest() { @Test fun `reading version header when getting first chunk throws`() = runBlocking { - restore.initializeState(token, packageInfo) + restore.initializeState(VERSION, token, packageInfo) coEvery { plugin.getInputStreamForPackage(token, packageInfo) } returns inputStream - every { headerReader.readVersion(inputStream) } throws IOException() + every { headerReader.readVersion(inputStream, VERSION) } throws IOException() every { fileDescriptor.close() } just Runs assertEquals( @@ -95,11 +95,11 @@ internal class FullRestoreTest : RestoreTest() { @Test fun `reading unsupported version when getting first chunk`() = runBlocking { - restore.initializeState(token, packageInfo) + restore.initializeState(VERSION, token, packageInfo) coEvery { plugin.getInputStreamForPackage(token, packageInfo) } returns inputStream every { - headerReader.readVersion(inputStream) + headerReader.readVersion(inputStream, VERSION) } throws UnsupportedVersionException(unsupportedVersion) every { fileDescriptor.close() } just Runs @@ -111,10 +111,10 @@ internal class FullRestoreTest : RestoreTest() { @Test fun `getting decrypted stream when getting first chunk throws`() = runBlocking { - restore.initializeState(token, packageInfo) + restore.initializeState(VERSION, token, packageInfo) coEvery { plugin.getInputStreamForPackage(token, packageInfo) } returns inputStream - every { headerReader.readVersion(inputStream) } returns VERSION + every { headerReader.readVersion(inputStream, VERSION) } returns VERSION every { crypto.newDecryptingStream(inputStream, ad) } throws IOException() every { fileDescriptor.close() } just Runs @@ -127,10 +127,10 @@ internal class FullRestoreTest : RestoreTest() { @Test fun `getting decrypted stream when getting first chunk throws general security exception`() = runBlocking { - restore.initializeState(token, packageInfo) + restore.initializeState(VERSION, token, packageInfo) coEvery { plugin.getInputStreamForPackage(token, packageInfo) } returns inputStream - every { headerReader.readVersion(inputStream) } returns VERSION + every { headerReader.readVersion(inputStream, VERSION) } returns VERSION every { crypto.newDecryptingStream(inputStream, ad) } throws GeneralSecurityException() every { fileDescriptor.close() } just Runs @@ -139,7 +139,7 @@ internal class FullRestoreTest : RestoreTest() { @Test fun `full chunk gets decrypted`() = runBlocking { - restore.initializeState(token, packageInfo) + restore.initializeState(VERSION, token, packageInfo) initInputStream() readAndEncryptInputStream(encrypted) @@ -153,10 +153,10 @@ internal class FullRestoreTest : RestoreTest() { @Test fun `full chunk gets decrypted from version 0`() = runBlocking { - restore.initializeState(token, packageInfo) + restore.initializeState(0.toByte(), token, packageInfo) coEvery { plugin.getInputStreamForPackage(token, packageInfo) } returns inputStream - every { headerReader.readVersion(inputStream) } returns 0.toByte() + every { headerReader.readVersion(inputStream, 0.toByte()) } returns 0.toByte() every { crypto.decryptHeader(inputStream, 0.toByte(), packageInfo.packageName) } returns VersionHeader(0.toByte(), packageInfo.packageName) @@ -172,14 +172,30 @@ internal class FullRestoreTest : RestoreTest() { assertFalse(restore.hasState()) } + @Test + fun `unexpected version aborts with error`() = runBlocking { + restore.initializeState(Byte.MAX_VALUE, token, packageInfo) + + coEvery { plugin.getInputStreamForPackage(token, packageInfo) } returns inputStream + every { + headerReader.readVersion(inputStream, Byte.MAX_VALUE) + } throws GeneralSecurityException() + every { inputStream.close() } just Runs + every { fileDescriptor.close() } just Runs + + assertEquals(TRANSPORT_ERROR, restore.getNextFullRestoreDataChunk(fileDescriptor)) + restore.abortFullRestore() + assertFalse(restore.hasState()) + } + @Test fun `three full chunk get decrypted and then return no more data`() = runBlocking { val encryptedBytes = Random.nextBytes(MAX_SEGMENT_LENGTH * 2 + 1) val decryptedInputStream = ByteArrayInputStream(encryptedBytes) - restore.initializeState(token, packageInfo) + restore.initializeState(VERSION, token, packageInfo) coEvery { plugin.getInputStreamForPackage(token, packageInfo) } returns inputStream - every { headerReader.readVersion(inputStream) } returns VERSION + every { headerReader.readVersion(inputStream, VERSION) } returns VERSION every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream every { outputFactory.getOutputStream(fileDescriptor) } returns outputStream every { fileDescriptor.close() } just Runs @@ -197,7 +213,7 @@ internal class FullRestoreTest : RestoreTest() { @Test fun `aborting full restore closes stream, resets state`() = runBlocking { - restore.initializeState(token, packageInfo) + restore.initializeState(VERSION, token, packageInfo) initInputStream() readAndEncryptInputStream(encrypted) @@ -212,7 +228,7 @@ internal class FullRestoreTest : RestoreTest() { private fun initInputStream() { coEvery { plugin.getInputStreamForPackage(token, packageInfo) } returns inputStream - every { headerReader.readVersion(inputStream) } returns VERSION + every { headerReader.readVersion(inputStream, VERSION) } returns VERSION every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream } diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/KVRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/KVRestoreTest.kt index 975977a6..05a8337d 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/KVRestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/KVRestoreTest.kt @@ -22,6 +22,7 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import java.io.IOException import java.io.InputStream +import java.security.GeneralSecurityException import kotlin.random.Random @Suppress("BlockingMethodInNonBlockingContext") @@ -60,7 +61,7 @@ internal class KVRestoreTest : RestoreTest() { @Test fun `listing records throws`() = runBlocking { - restore.initializeState(token, packageInfo) + restore.initializeState(VERSION, token, packageInfo) coEvery { plugin.listRecords(token, packageInfo) } throws IOException() @@ -69,12 +70,12 @@ internal class KVRestoreTest : RestoreTest() { @Test fun `reading VersionHeader with unsupported version throws`() = runBlocking { - restore.initializeState(token, packageInfo) + restore.initializeState(VERSION, token, packageInfo) getRecordsAndOutput() coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream every { - headerReader.readVersion(inputStream) + headerReader.readVersion(inputStream, VERSION) } throws UnsupportedVersionException(unsupportedVersion) streamsGetClosed() @@ -84,11 +85,11 @@ internal class KVRestoreTest : RestoreTest() { @Test fun `error reading VersionHeader throws`() = runBlocking { - restore.initializeState(token, packageInfo) + restore.initializeState(VERSION, token, packageInfo) getRecordsAndOutput() coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream - every { headerReader.readVersion(inputStream) } throws IOException() + every { headerReader.readVersion(inputStream, VERSION) } throws IOException() streamsGetClosed() assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor)) @@ -97,11 +98,11 @@ internal class KVRestoreTest : RestoreTest() { @Test fun `decrypting stream throws`() = runBlocking { - restore.initializeState(token, packageInfo) + restore.initializeState(VERSION, token, packageInfo) getRecordsAndOutput() coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream - every { headerReader.readVersion(inputStream) } returns VERSION + every { headerReader.readVersion(inputStream, VERSION) } returns VERSION every { crypto.newDecryptingStream(inputStream, ad) } throws IOException() streamsGetClosed() @@ -111,11 +112,11 @@ internal class KVRestoreTest : RestoreTest() { @Test fun `decrypting stream throws security exception`() = runBlocking { - restore.initializeState(token, packageInfo) + restore.initializeState(VERSION, token, packageInfo) getRecordsAndOutput() coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream - every { headerReader.readVersion(inputStream) } returns VERSION + every { headerReader.readVersion(inputStream, VERSION) } returns VERSION every { crypto.newDecryptingStream(inputStream, ad) } throws SecurityException() streamsGetClosed() @@ -125,11 +126,11 @@ internal class KVRestoreTest : RestoreTest() { @Test fun `writing header throws`() = runBlocking { - restore.initializeState(token, packageInfo) + restore.initializeState(VERSION, token, packageInfo) getRecordsAndOutput() coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream - every { headerReader.readVersion(inputStream) } returns VERSION + every { headerReader.readVersion(inputStream, VERSION) } returns VERSION every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream every { decryptedInputStream.readBytes() } returns data every { output.writeEntityHeader(key, data.size) } throws IOException() @@ -141,11 +142,11 @@ internal class KVRestoreTest : RestoreTest() { @Test fun `writing value throws`() = runBlocking { - restore.initializeState(token, packageInfo) + restore.initializeState(VERSION, token, packageInfo) getRecordsAndOutput() coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream - every { headerReader.readVersion(inputStream) } returns VERSION + every { headerReader.readVersion(inputStream, VERSION) } returns VERSION every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream every { decryptedInputStream.readBytes() } returns data every { output.writeEntityHeader(key, data.size) } returns 42 @@ -158,11 +159,11 @@ internal class KVRestoreTest : RestoreTest() { @Test fun `writing value succeeds`() = runBlocking { - restore.initializeState(token, packageInfo) + restore.initializeState(VERSION, token, packageInfo) getRecordsAndOutput() coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream - every { headerReader.readVersion(inputStream) } returns VERSION + every { headerReader.readVersion(inputStream, VERSION) } returns VERSION every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream every { decryptedInputStream.readBytes() } returns data every { output.writeEntityHeader(key, data.size) } returns 42 @@ -175,11 +176,11 @@ internal class KVRestoreTest : RestoreTest() { @Test fun `writing value uses old v0 code`() = runBlocking { - restore.initializeState(token, packageInfo) + restore.initializeState(0.toByte(), token, packageInfo) getRecordsAndOutput() coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream - every { headerReader.readVersion(inputStream) } returns 0.toByte() + every { headerReader.readVersion(inputStream, 0.toByte()) } returns 0.toByte() every { crypto.decryptHeader(inputStream, 0.toByte(), packageInfo.packageName, key) } returns VersionHeader(VERSION, packageInfo.packageName, key) @@ -192,24 +193,39 @@ internal class KVRestoreTest : RestoreTest() { verifyStreamWasClosed() } + @Test + fun `unexpected version aborts with error`() = runBlocking { + restore.initializeState(Byte.MAX_VALUE, token, packageInfo) + + getRecordsAndOutput() + coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream + every { + headerReader.readVersion(inputStream, Byte.MAX_VALUE) + } throws GeneralSecurityException() + streamsGetClosed() + + assertEquals(TRANSPORT_ERROR, restore.getRestoreData(fileDescriptor)) + verifyStreamWasClosed() + } + @Test fun `writing two values succeeds`() = runBlocking { val data2 = getRandomByteArray() val inputStream2 = mockk() val decryptedInputStream2 = mockk() - restore.initializeState(token, packageInfo) + restore.initializeState(VERSION, token, packageInfo) getRecordsAndOutput(listOf(key64, key264)) // first key/value coEvery { plugin.getInputStreamForRecord(token, packageInfo, key64) } returns inputStream - every { headerReader.readVersion(inputStream) } returns VERSION + every { headerReader.readVersion(inputStream, VERSION) } returns VERSION every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream every { decryptedInputStream.readBytes() } returns data every { output.writeEntityHeader(key, data.size) } returns 42 every { output.writeEntityData(data, data.size) } returns data.size // second key/value coEvery { plugin.getInputStreamForRecord(token, packageInfo, key264) } returns inputStream2 - every { headerReader.readVersion(inputStream2) } returns VERSION + every { headerReader.readVersion(inputStream2, VERSION) } returns VERSION every { crypto.newDecryptingStream(inputStream2, ad) } returns decryptedInputStream2 every { decryptedInputStream2.readBytes() } returns data2 every { output.writeEntityHeader(key2, data2.size) } returns 42 diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinatorTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinatorTest.kt index bafef625..7bf53145 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinatorTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinatorTest.kt @@ -10,6 +10,7 @@ import android.content.pm.PackageInfo import android.os.ParcelFileDescriptor import com.stevesoltys.seedvault.coAssertThrows import com.stevesoltys.seedvault.getRandomString +import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.metadata.EncryptedBackupMetadata import com.stevesoltys.seedvault.metadata.MetadataReader import com.stevesoltys.seedvault.metadata.PackageMetadata @@ -213,7 +214,7 @@ internal class RestoreCoordinatorTest : TransportTest() { restore.startRestore(token, packageInfoArray) coEvery { kv.hasDataForPackage(token, packageInfo) } returns true - every { kv.initializeState(token, packageInfo) } just Runs + every { kv.initializeState(VERSION, token, packageInfo) } just Runs val expected = RestoreDescription(packageInfo.packageName, TYPE_KEY_VALUE) assertEquals(expected, restore.nextRestorePackage()) @@ -226,7 +227,7 @@ internal class RestoreCoordinatorTest : TransportTest() { coEvery { kv.hasDataForPackage(token, packageInfo) } returns false coEvery { full.hasDataForPackage(token, packageInfo) } returns true - every { full.initializeState(token, packageInfo) } just Runs + every { full.initializeState(VERSION, token, packageInfo) } just Runs val expected = RestoreDescription(packageInfo.packageName, TYPE_FULL_STREAM) assertEquals(expected, restore.nextRestorePackage()) @@ -249,14 +250,14 @@ internal class RestoreCoordinatorTest : TransportTest() { restore.startRestore(token, packageInfoArray2) coEvery { kv.hasDataForPackage(token, packageInfo) } returns true - every { kv.initializeState(token, packageInfo) } just Runs + every { kv.initializeState(VERSION, token, packageInfo) } just Runs val expected = RestoreDescription(packageInfo.packageName, TYPE_KEY_VALUE) assertEquals(expected, restore.nextRestorePackage()) coEvery { kv.hasDataForPackage(token, packageInfo2) } returns false coEvery { full.hasDataForPackage(token, packageInfo2) } returns true - every { full.initializeState(token, packageInfo2) } just Runs + every { full.initializeState(VERSION, token, packageInfo2) } just Runs val expected2 = RestoreDescription(packageInfo2.packageName, TYPE_FULL_STREAM) assertEquals(expected2, restore.nextRestorePackage()) diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt index b0ee9835..43469e22 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt @@ -61,7 +61,7 @@ internal class RestoreV0IntegrationTest : TransportTest() { kvRestore, fullRestore, metadataReader - ).apply { beforeStartRestore(metadata) } + ).apply { beforeStartRestore(metadata.copy(version = 0x00)) } private val fileDescriptor = mockk(relaxed = true) private val appData = ("562AB665C3543120FC794D7CDA3AC18E5959235A4D" +