Digitaler-Impfnachweis / covpass-android

The official CovPass(-Check) Android apps and SDK.
https://digitaler-impfnachweis-app.de
Apache License 2.0
184 stars 59 forks source link

Allow Backup/Restore #101

Closed Jakeler closed 10 months ago

Jakeler commented 2 years ago

Avoid duplicates

Current Implementation

The App apparently utilizes android keystore APIs, which often use secure hardware to store the secrets, this makes it very hard to extract data. While this is good under normal operation, this also makes it practically impossible to create working backups.

Usual root backup tools like OAndBackupX or TitaniumBackup can only restore normal app data. The restored app crashes immediately with:

Expand crash log ``` 11-11 18:46:21.294 614 614 E KeyMasterHalDevice: Finish send cmd failed 11-11 18:46:21.294 614 614 E KeyMasterHalDevice: ret: 0 11-11 18:46:21.294 614 614 E KeyMasterHalDevice: resp->status: -30 11-11 18:46:21.294 29851 29851 W b : encountered a potentially transient KeyStore error, will wait and retry 11-11 18:46:21.294 29851 29851 W b : javax.crypto.AEADBadTagException 11-11 18:46:21.294 29851 29851 W b : at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:517) 11-11 18:46:21.294 29851 29851 W b : at javax.crypto.Cipher.doFinal(Cipher.java:2113) 11-11 18:46:21.294 29851 29851 W b : at j3.b.c(Unknown Source:32) 11-11 18:46:21.294 29851 29851 W b : at j3.b.b(Unknown Source:0) 11-11 18:46:21.294 29851 29851 W b : at f3.i.c(Unknown Source:11) 11-11 18:46:21.294 29851 29851 W b : at f3.i.j(Unknown Source:9) 11-11 18:46:21.294 29851 29851 W b : at j3.a$b.e(Unknown Source:6) 11-11 18:46:21.294 29851 29851 W b : at j3.a$b.f(Unknown Source:0) 11-11 18:46:21.294 29851 29851 W b : at j3.a$b.d(Unknown Source:11) 11-11 18:46:21.294 29851 29851 W b : at a1.a.b(Unknown Source:50) 11-11 18:46:21.294 29851 29851 W b : at a1.a.a(Unknown Source:4) 11-11 18:46:21.294 29851 29851 W b : at n6.f.a(Unknown Source:36) 11-11 18:46:21.294 29851 29851 W b : at n6.f.b(Unknown Source:13) 11-11 18:46:21.294 29851 29851 W b : at n6.a.(Unknown Source:12) 11-11 18:46:21.294 29851 29851 W b : at i6.d$k.a(Unknown Source:12) 11-11 18:46:21.294 29851 29851 W b : at i6.d$k.invoke(Unknown Source:0) 11-11 18:46:21.294 29851 29851 W b : at x7.r.getValue(Unknown Source:20) 11-11 18:46:21.294 29851 29851 W b : at i6.d.p(Unknown Source:2) 11-11 18:46:21.294 29851 29851 W b : at i6.d$f.a(Unknown Source:10) 11-11 18:46:21.294 29851 29851 W b : at i6.d$f.invoke(Unknown Source:0) 11-11 18:46:21.294 29851 29851 W b : at x7.r.getValue(Unknown Source:20) 11-11 18:46:21.294 29851 29851 W b : at i6.d.k(Unknown Source:2) 11-11 18:46:21.294 29851 29851 W b : at i6.d.a(Unknown Source:0) 11-11 18:46:21.294 29851 29851 W b : at i6.d$o.a(Unknown Source:2) 11-11 18:46:21.294 29851 29851 W b : at i6.d$o.invoke(Unknown Source:0) 11-11 18:46:21.294 29851 29851 W b : at x7.r.getValue(Unknown Source:20) 11-11 18:46:21.294 29851 29851 W b : at i6.d.t(Unknown Source:2) 11-11 18:46:21.294 29851 29851 W b : at r5.f$d.f(Unknown Source:53) 11-11 18:46:21.294 29851 29851 W b : at c8.a.g(Unknown Source:9) 11-11 18:46:21.294 29851 29851 W b : at kotlinx.coroutines.f1.run(Unknown Source:129) 11-11 18:46:21.294 29851 29851 W b : at kotlinx.coroutines.n1.c1(Unknown Source:75) 11-11 18:46:21.294 29851 29851 W b : at kotlinx.coroutines.h.c1(Unknown Source:30) 11-11 18:46:21.294 29851 29851 W b : at kotlinx.coroutines.k.a(Unknown Source:73) 11-11 18:46:21.294 29851 29851 W b : at kotlinx.coroutines.j.e(Unknown Source:0) 11-11 18:46:21.294 29851 29851 W b : at kotlinx.coroutines.k.b(Unknown Source:6) 11-11 18:46:21.294 29851 29851 W b : at kotlinx.coroutines.j.f(Unknown Source:0) 11-11 18:46:21.294 29851 29851 W b : at r5.f.b(Unknown Source:7) 11-11 18:46:21.294 29851 29851 W b : at r5.f.onCreate(Unknown Source:56) 11-11 18:46:21.294 29851 29851 W b : at de.rki.covpass.app.App.onCreate(Unknown Source:0) 11-11 18:46:21.294 29851 29851 W b : at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1198) 11-11 18:46:21.294 29851 29851 W b : at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6728) 11-11 18:46:21.294 29851 29851 W b : at android.app.ActivityThread.access$1300(ActivityThread.java:238) 11-11 18:46:21.294 29851 29851 W b : at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1914) 11-11 18:46:21.294 29851 29851 W b : at android.os.Handler.dispatchMessage(Handler.java:106) 11-11 18:46:21.294 29851 29851 W b : at android.os.Looper.loop(Looper.java:223) 11-11 18:46:21.294 29851 29851 W b : at android.app.ActivityThread.main(ActivityThread.java:7668) 11-11 18:46:21.294 29851 29851 W b : at java.lang.reflect.Method.invoke(Native Method) 11-11 18:46:21.294 29851 29851 W b : at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:594) 11-11 18:46:21.294 29851 29851 W b : at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 11-11 18:46:21.294 29851 29851 W b : Caused by: android.security.KeyStoreException: Signature/MAC verification failed 11-11 18:46:21.294 29851 29851 W b : at android.security.KeyStore.getKeyStoreException(KeyStore.java:1310) 11-11 18:46:21.294 29851 29851 W b : at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:176) 11-11 18:46:21.294 29851 29851 W b : at android.security.keystore.AndroidKeyStoreAuthenticatedAESCipherSpi$BufferAllOutputUntilDoFinalStreamer.doFinal(AndroidKeyStoreAuthenticatedAESCipherSpi.java:373) 11-11 18:46:21.294 29851 29851 W b : at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:506) 11-11 18:46:21.294 29851 29851 W b : ... 48 more 11-11 18:46:21.358 614 614 E KeyMasterHalDevice: Finish send cmd failed 11-11 18:46:21.358 614 614 E KeyMasterHalDevice: ret: 0 11-11 18:46:21.358 614 614 E KeyMasterHalDevice: resp->status: -30 11-11 18:46:21.359 29851 29851 W a : cannot decrypt keyset: 11-11 18:46:21.359 29851 29851 W a : javax.crypto.AEADBadTagException 11-11 18:46:21.359 29851 29851 W a : at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:517) 11-11 18:46:21.359 29851 29851 W a : at javax.crypto.Cipher.doFinal(Cipher.java:2113) 11-11 18:46:21.359 29851 29851 W a : at j3.b.c(Unknown Source:32) 11-11 18:46:21.359 29851 29851 W a : at j3.b.b(Unknown Source:18) 11-11 18:46:21.359 29851 29851 W a : at f3.i.c(Unknown Source:11) 11-11 18:46:21.359 29851 29851 W a : at f3.i.j(Unknown Source:9) 11-11 18:46:21.359 29851 29851 W a : at j3.a$b.e(Unknown Source:6) 11-11 18:46:21.359 29851 29851 W a : at j3.a$b.f(Unknown Source:0) 11-11 18:46:21.359 29851 29851 W a : at j3.a$b.d(Unknown Source:11) 11-11 18:46:21.359 29851 29851 W a : at a1.a.b(Unknown Source:50) 11-11 18:46:21.359 29851 29851 W a : at a1.a.a(Unknown Source:4) 11-11 18:46:21.359 29851 29851 W a : at n6.f.a(Unknown Source:36) 11-11 18:46:21.359 29851 29851 W a : at n6.f.b(Unknown Source:13) 11-11 18:46:21.359 29851 29851 W a : at n6.a.(Unknown Source:12) 11-11 18:46:21.359 29851 29851 W a : at i6.d$k.a(Unknown Source:12) 11-11 18:46:21.359 29851 29851 W a : at i6.d$k.invoke(Unknown Source:0) 11-11 18:46:21.359 29851 29851 W a : at x7.r.getValue(Unknown Source:20) 11-11 18:46:21.359 29851 29851 W a : at i6.d.p(Unknown Source:2) 11-11 18:46:21.359 29851 29851 W a : at i6.d$f.a(Unknown Source:10) 11-11 18:46:21.359 29851 29851 W a : at i6.d$f.invoke(Unknown Source:0) 11-11 18:46:21.359 29851 29851 W a : at x7.r.getValue(Unknown Source:20) 11-11 18:46:21.359 29851 29851 W a : at i6.d.k(Unknown Source:2) 11-11 18:46:21.359 29851 29851 W a : at i6.d.a(Unknown Source:0) 11-11 18:46:21.359 29851 29851 W a : at i6.d$o.a(Unknown Source:2) 11-11 18:46:21.359 29851 29851 W a : at i6.d$o.invoke(Unknown Source:0) 11-11 18:46:21.359 29851 29851 W a : at x7.r.getValue(Unknown Source:20) 11-11 18:46:21.359 29851 29851 W a : at i6.d.t(Unknown Source:2) 11-11 18:46:21.359 29851 29851 W a : at r5.f$d.f(Unknown Source:53) 11-11 18:46:21.359 29851 29851 W a : at c8.a.g(Unknown Source:9) 11-11 18:46:21.359 29851 29851 W a : at kotlinx.coroutines.f1.run(Unknown Source:129) 11-11 18:46:21.359 29851 29851 W a : at kotlinx.coroutines.n1.c1(Unknown Source:75) 11-11 18:46:21.359 29851 29851 W a : at kotlinx.coroutines.h.c1(Unknown Source:30) 11-11 18:46:21.359 29851 29851 W a : at kotlinx.coroutines.k.a(Unknown Source:73) 11-11 18:46:21.359 29851 29851 W a : at kotlinx.coroutines.j.e(Unknown Source:0) 11-11 18:46:21.359 29851 29851 W a : at kotlinx.coroutines.k.b(Unknown Source:6) 11-11 18:46:21.359 29851 29851 W a : at kotlinx.coroutines.j.f(Unknown Source:0) 11-11 18:46:21.359 29851 29851 W a : at r5.f.b(Unknown Source:7) 11-11 18:46:21.359 29851 29851 W a : at r5.f.onCreate(Unknown Source:56) 11-11 18:46:21.359 29851 29851 W a : at de.rki.covpass.app.App.onCreate(Unknown Source:0) 11-11 18:46:21.359 29851 29851 W a : at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1198) 11-11 18:46:21.359 29851 29851 W a : at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6728) 11-11 18:46:21.359 29851 29851 W a : at android.app.ActivityThread.access$1300(ActivityThread.java:238) 11-11 18:46:21.359 29851 29851 W a : at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1914) 11-11 18:46:21.359 29851 29851 W a : at android.os.Handler.dispatchMessage(Handler.java:106) 11-11 18:46:21.359 29851 29851 W a : at android.os.Looper.loop(Looper.java:223) 11-11 18:46:21.359 29851 29851 W a : at android.app.ActivityThread.main(ActivityThread.java:7668) 11-11 18:46:21.359 29851 29851 W a : at java.lang.reflect.Method.invoke(Native Method) 11-11 18:46:21.359 29851 29851 W a : at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:594) 11-11 18:46:21.359 29851 29851 W a : at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 11-11 18:46:21.359 29851 29851 W a : Caused by: android.security.KeyStoreException: Signature/MAC verification failed 11-11 18:46:21.359 29851 29851 W a : at android.security.KeyStore.getKeyStoreException(KeyStore.java:1310) 11-11 18:46:21.359 29851 29851 W a : at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:176) 11-11 18:46:21.359 29851 29851 W a : at android.security.keystore.AndroidKeyStoreAuthenticatedAESCipherSpi$BufferAllOutputUntilDoFinalStreamer.doFinal(AndroidKeyStoreAuthenticatedAESCipherSpi.java:373) 11-11 18:46:21.359 29851 29851 W a : at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:506) 11-11 18:46:21.359 29851 29851 W a : ... 48 more 11-11 18:46:21.360 29851 29851 D AndroidRuntime: Shutting down VM 11-11 18:46:21.360 29851 29851 E AndroidRuntime: FATAL EXCEPTION: main 11-11 18:46:21.360 29851 29851 E AndroidRuntime: Process: de.rki.covpass.app, PID: 29851 11-11 18:46:21.360 29851 29851 E AndroidRuntime: java.lang.RuntimeException: Unable to create application de.rki.covpass.app.App: com.google.crypto.tink.shaded.protobuf.c0: Protocol message contained an invalid tag (zero). 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6733) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at android.app.ActivityThread.access$1300(ActivityThread.java:238) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1914) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at android.os.Looper.loop(Looper.java:223) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:7668) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:594) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: Caused by: com.google.crypto.tink.shaded.protobuf.c0: Protocol message contained an invalid tag (zero). 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at com.google.crypto.tink.shaded.protobuf.z.K(Unknown Source:75) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at com.google.crypto.tink.shaded.protobuf.z.H(Unknown Source:2) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at m3.c0.X(Unknown Source:2) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at j3.d.read(Unknown Source:8) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at f3.b.a(Unknown Source:0) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at j3.a$b.e(Unknown Source:29) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at j3.a$b.f(Unknown Source:0) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at j3.a$b.d(Unknown Source:11) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at a1.a.b(Unknown Source:50) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at a1.a.a(Unknown Source:4) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at n6.f.a(Unknown Source:36) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at n6.f.b(Unknown Source:13) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at n6.a.(Unknown Source:12) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at i6.d$k.a(Unknown Source:12) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at i6.d$k.invoke(Unknown Source:0) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at x7.r.getValue(Unknown Source:20) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at i6.d.p(Unknown Source:2) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at i6.d$f.a(Unknown Source:10) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at i6.d$f.invoke(Unknown Source:0) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at x7.r.getValue(Unknown Source:20) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at i6.d.k(Unknown Source:2) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at i6.d.a(Unknown Source:0) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at i6.d$o.a(Unknown Source:2) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at i6.d$o.invoke(Unknown Source:0) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at x7.r.getValue(Unknown Source:20) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at i6.d.t(Unknown Source:2) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at r5.f$d.f(Unknown Source:53) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at c8.a.g(Unknown Source:9) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at kotlinx.coroutines.f1.run(Unknown Source:129) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at kotlinx.coroutines.n1.c1(Unknown Source:75) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at kotlinx.coroutines.h.c1(Unknown Source:30) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at kotlinx.coroutines.k.a(Unknown Source:73) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at kotlinx.coroutines.j.e(Unknown Source:0) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at kotlinx.coroutines.k.b(Unknown Source:6) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at kotlinx.coroutines.j.f(Unknown Source:0) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at r5.f.b(Unknown Source:7) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at r5.f.onCreate(Unknown Source:56) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at de.rki.covpass.app.App.onCreate(Unknown Source:0) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1198) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6728) 11-11 18:46:21.360 29851 29851 E AndroidRuntime: ... 8 more 11-11 18:46:21.363 1593 3063 W ActivityTaskManager: Force finishing activity de.rki.covpass.app/.main.MainActivity ```

Suggested Enhancement

There are multiple possible solutions. Most convenient option would be to have a switch that disables Android keystore encryption and just saves the certificates in normal app data. Android app data already has a quite high security level (Linux permissions and SELinux). Corona-Warn-App does it like this, going even further seems overkill to me. Another idea would be to include a export/import feature in the app. If we consider the "save as EU pdf" as export then it is already half implemented. Only part missing is a import from pdf, like suggested in #20.

Expected Benefits

One part of security is availability and backups are essential to achieve it. Also having a backup option would make it more convenient to always have the certificates when often switching devices.

oliver-steinbrecher commented 2 years ago

We've planed to implement a gallery import (image/pdf) but this has been postponed in favor of other topics. The other approaches are not planed or taken into further considerations.

ingowerren commented 10 months ago

In 2021, the European Union introduced the digital COVID certificate as an EU-wide recognised proof of vaccination, recovery or negative testing for COVID-19. This regulation expired on 30 June 2023. On a transitional basis, the EU will continue to operate the technical systems to enable cross-border certificate checks until 31 December 2023. The transitional operation will end on that date. In Germany, the possibility of issuing digital COVID certificates such as vaccination and recovery certificates will therefore be discontinued on 31 December 2023. The CovPass app and the CovPassCheck app will therefore also be discontinued. The CovPass app will be switched to wallet mode, where the stored certificates will be retained.

What does this mean for you? All functionalities will continue to beavailable until 31 December 2023.

From 1 January 2024:

Support for the app will no longer be offered.