Improve error message when no backups could be found for restore

This commit is contained in:
Torsten Grote 2019-09-13 15:23:48 -03:00
parent 54ad762eb1
commit 10ad6d6b2d
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
7 changed files with 20 additions and 22 deletions

View file

@ -17,7 +17,7 @@ internal const val JSON_TOKEN = "token"
internal const val JSON_ANDROID_VERSION = "androidVersion" internal const val JSON_ANDROID_VERSION = "androidVersion"
internal const val JSON_DEVICE_NAME = "deviceName" internal const val JSON_DEVICE_NAME = "deviceName"
class FormatException(cause: Throwable) : Exception(cause) class DecryptionFailedException(cause: Throwable) : Exception(cause)
class EncryptedBackupMetadata private constructor(val token: Long, val inputStream: InputStream?, val error: Boolean) { class EncryptedBackupMetadata private constructor(val token: Long, val inputStream: InputStream?, val error: Boolean) {
constructor(token: Long, inputStream: InputStream) : this(token, inputStream, false) constructor(token: Long, inputStream: InputStream) : this(token, inputStream, false)

View file

@ -13,14 +13,14 @@ import javax.crypto.AEADBadTagException
interface MetadataReader { interface MetadataReader {
@Throws(FormatException::class, SecurityException::class, UnsupportedVersionException::class, IOException::class) @Throws(SecurityException::class, DecryptionFailedException::class, UnsupportedVersionException::class, IOException::class)
fun readMetadata(inputStream: InputStream, expectedToken: Long): BackupMetadata fun readMetadata(inputStream: InputStream, expectedToken: Long): BackupMetadata
} }
class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader { class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader {
@Throws(FormatException::class, SecurityException::class, UnsupportedVersionException::class, IOException::class) @Throws(SecurityException::class, DecryptionFailedException::class, UnsupportedVersionException::class, IOException::class)
override fun readMetadata(inputStream: InputStream, expectedToken: Long): BackupMetadata { override fun readMetadata(inputStream: InputStream, expectedToken: Long): BackupMetadata {
val version = inputStream.read().toByte() val version = inputStream.read().toByte()
if (version < 0) throw IOException() if (version < 0) throw IOException()
@ -28,14 +28,13 @@ class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader {
val metadataBytes = try { val metadataBytes = try {
crypto.decryptSegment(inputStream) crypto.decryptSegment(inputStream)
} catch (e: AEADBadTagException) { } catch (e: AEADBadTagException) {
// TODO use yet another exception? throw DecryptionFailedException(e)
throw SecurityException(e)
} }
return decode(metadataBytes, version, expectedToken) return decode(metadataBytes, version, expectedToken)
} }
@VisibleForTesting @VisibleForTesting
@Throws(FormatException::class, SecurityException::class) @Throws(SecurityException::class)
internal fun decode(bytes: ByteArray, expectedVersion: Byte, expectedToken: Long): BackupMetadata { internal fun decode(bytes: ByteArray, expectedVersion: Byte, expectedToken: Long): BackupMetadata {
// NOTE: We don't do extensive validation of the parsed input here, // NOTE: We don't do extensive validation of the parsed input here,
// because it was encrypted with authentication, so we should be able to trust it. // because it was encrypted with authentication, so we should be able to trust it.
@ -59,7 +58,7 @@ class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader {
deviceName = json.getString(JSON_DEVICE_NAME) deviceName = json.getString(JSON_DEVICE_NAME)
) )
} catch (e: JSONException) { } catch (e: JSONException) {
throw FormatException(e) throw SecurityException(e)
} }
} }

View file

@ -10,7 +10,7 @@ import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.util.Log import android.util.Log
import com.stevesoltys.backup.header.UnsupportedVersionException import com.stevesoltys.backup.header.UnsupportedVersionException
import com.stevesoltys.backup.metadata.FormatException import com.stevesoltys.backup.metadata.DecryptionFailedException
import com.stevesoltys.backup.metadata.MetadataReader import com.stevesoltys.backup.metadata.MetadataReader
import com.stevesoltys.backup.settings.getBackupToken import com.stevesoltys.backup.settings.getBackupToken
import libcore.io.IoUtils.closeQuietly import libcore.io.IoUtils.closeQuietly
@ -50,12 +50,12 @@ internal class RestoreCoordinator(
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, "Error while getting restore sets", e) Log.e(TAG, "Error while getting restore sets", e)
return null return null
} catch (e: FormatException) {
Log.e(TAG, "Error while getting restore sets", e)
return null
} catch (e: SecurityException) { } catch (e: SecurityException) {
Log.e(TAG, "Error while getting restore sets", e) Log.e(TAG, "Error while getting restore sets", e)
return null return null
} catch (e: DecryptionFailedException) {
Log.e(TAG, "Error while decrypting restore set", e)
continue
} catch (e: UnsupportedVersionException) { } catch (e: UnsupportedVersionException) {
Log.w(TAG, "Backup with unsupported version read", e) Log.w(TAG, "Backup with unsupported version read", e)
continue continue

View file

@ -47,9 +47,6 @@ class RecoveryCodeViewModel(application: Application) : AndroidViewModel(applica
} catch (e: InvalidWordCountException) { } catch (e: InvalidWordCountException) {
throw AssertionError(e) throw AssertionError(e)
} }
// TODO if (isRestore) check if we can decrypt a backup
val mnemonic = input.joinToString(" ") val mnemonic = input.joinToString(" ")
val seed = SeedCalculator(JavaxPBKDF2WithHmacSHA512.INSTANCE).calculateSeed(mnemonic, "") val seed = SeedCalculator(JavaxPBKDF2WithHmacSHA512.INSTANCE).calculateSeed(mnemonic, "")
Backup.keyManager.storeBackupKey(seed) Backup.keyManager.storeBackupKey(seed)

View file

@ -54,7 +54,8 @@
<TextView <TextView
android:id="@+id/errorView" android:id="@+id/errorView"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_margin="16dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@android:color/holo_red_dark" android:textColor="@android:color/holo_red_dark"
android:textSize="18sp" android:textSize="18sp"
@ -63,7 +64,8 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/titleView" app:layout_constraintTop_toBottomOf="@+id/titleView"
tools:text="There was an error retrieving your backups." /> tools:text="@string/restore_set_empty_result"
tools:visibility="visible" />
<TextView <TextView
android:id="@+id/backView" android:id="@+id/backView"

View file

@ -90,8 +90,8 @@
<string name="restore_back">Don\'t restore</string> <string name="restore_back">Don\'t restore</string>
<string name="restore_invalid_location_title">No backups found</string> <string name="restore_invalid_location_title">No backups found</string>
<string name="restore_invalid_location_message">We could not find any backups at this location.\n\nPlease choose another location that contains a %s folder.</string> <string name="restore_invalid_location_message">We could not find any backups at this location.\n\nPlease choose another location that contains a %s folder.</string>
<string name="restore_set_error">An error occurred loading the backups.</string> <string name="restore_set_error">An error occurred while loading the backups.</string>
<string name="restore_set_empty_result">No backups found at given location.</string> <string name="restore_set_empty_result">No suitable backups found at given location.\n\nThis is most likely due to a wrong recovery code or a storage error.</string>
<string name="restore_restoring">Restoring Backup</string> <string name="restore_restoring">Restoring Backup</string>
<string name="restore_current_package">Restoring %s…</string> <string name="restore_current_package">Restoring %s…</string>
<string name="restore_finished_success">Restore complete.</string> <string name="restore_finished_success">Restore complete.</string>

View file

@ -47,21 +47,21 @@ class MetadataReaderTest {
} }
@Test @Test
fun `malformed JSON throws FormatException`() { fun `malformed JSON throws SecurityException`() {
assertThrows(FormatException::class.java) { assertThrows(SecurityException::class.java) {
decoder.decode("{".toByteArray(Utf8), metadata.version, metadata.token) decoder.decode("{".toByteArray(Utf8), metadata.version, metadata.token)
} }
} }
@Test @Test
fun `missing fields throws FormatException`() { fun `missing fields throws SecurityException`() {
val json = JSONObject() val json = JSONObject()
json.put(JSON_VERSION, metadata.version.toInt()) json.put(JSON_VERSION, metadata.version.toInt())
json.put(JSON_TOKEN, metadata.token) json.put(JSON_TOKEN, metadata.token)
json.put(JSON_ANDROID_VERSION, metadata.androidVersion) json.put(JSON_ANDROID_VERSION, metadata.androidVersion)
val jsonBytes = json.toString().toByteArray(Utf8) val jsonBytes = json.toString().toByteArray(Utf8)
assertThrows(FormatException::class.java) { assertThrows(SecurityException::class.java) {
decoder.decode(jsonBytes, metadata.version, metadata.token) decoder.decode(jsonBytes, metadata.version, metadata.token)
} }
} }