Skip to content

Commit 128587e

Browse files
committed
Add support for password protected crypto containers
1 parent 307d944 commit 128587e

23 files changed

Lines changed: 1430 additions & 570 deletions

File tree

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ dependencies {
188188
testImplementation(libs.junit)
189189
testImplementation(libs.mockito.kotlin)
190190
testImplementation(libs.kotlinx.coroutines.test)
191+
testImplementation(libs.androidx.arch.core.testing)
191192
androidTestImplementation(libs.androidx.junit)
192193
androidTestImplementation(libs.androidx.espresso.core)
193194
androidTestImplementation(platform(libs.androidx.compose.bom))
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
@file:Suppress("PackageName")
2+
3+
package ee.ria.DigiDoc.viewmodel
4+
5+
import android.content.Context
6+
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
7+
import androidx.lifecycle.Observer
8+
import androidx.test.platform.app.InstrumentationRegistry
9+
import ee.ria.DigiDoc.R
10+
import ee.ria.DigiDoc.configuration.repository.ConfigurationRepository
11+
import ee.ria.DigiDoc.cryptolib.Addressee
12+
import ee.ria.DigiDoc.cryptolib.CDOC2Settings
13+
import ee.ria.DigiDoc.cryptolib.CertType
14+
import ee.ria.DigiDoc.cryptolib.CryptoContainer
15+
import ee.ria.DigiDoc.cryptolib.exception.CryptoException
16+
import ee.ria.DigiDoc.cryptolib.repository.RecipientRepository
17+
import ee.ria.DigiDoc.utilsLib.mimetype.MimeTypeResolver
18+
import ee.ria.DigiDoc.viewmodel.shared.SharedContainerViewModel
19+
import kotlinx.coroutines.test.runTest
20+
import org.junit.Assert.assertEquals
21+
import org.junit.Assert.assertNull
22+
import org.junit.Assert.fail
23+
import org.junit.Before
24+
import org.junit.Rule
25+
import org.junit.Test
26+
import org.junit.runner.RunWith
27+
import org.mockito.Mock
28+
import org.mockito.MockitoAnnotations
29+
import org.mockito.junit.MockitoJUnitRunner
30+
import java.io.File
31+
32+
@RunWith(MockitoJUnitRunner::class)
33+
class EncryptRecipientViewModelTest {
34+
@get:Rule
35+
val instantTaskExecutorRule = InstantTaskExecutorRule()
36+
37+
@Mock
38+
private lateinit var mimeTypeResolver: MimeTypeResolver
39+
40+
@Mock
41+
private lateinit var recipientRepository: RecipientRepository
42+
43+
@Mock
44+
private lateinit var configurationRepository: ConfigurationRepository
45+
46+
@Mock
47+
private lateinit var errorStateObserver: Observer<Int?>
48+
49+
private lateinit var context: Context
50+
private lateinit var sharedContainerViewModel: SharedContainerViewModel
51+
private lateinit var viewModel: EncryptRecipientViewModel
52+
53+
@Before
54+
fun setUp() {
55+
MockitoAnnotations.openMocks(this)
56+
context = InstrumentationRegistry.getInstrumentation().targetContext
57+
sharedContainerViewModel = SharedContainerViewModel(context, context.contentResolver)
58+
viewModel =
59+
EncryptRecipientViewModel(
60+
context,
61+
mimeTypeResolver,
62+
recipientRepository,
63+
CDOC2Settings(context, configurationRepository),
64+
configurationRepository,
65+
)
66+
}
67+
68+
@Test
69+
fun encryptRecipientViewModel_encryptContainerWithPassword_returnsEncryptErrorWithNoContainer() =
70+
runTest {
71+
viewModel.errorState.observeForever(errorStateObserver)
72+
73+
viewModel.encryptContainerWithPassword("MyKey", "password123".toByteArray(), sharedContainerViewModel)
74+
75+
assertEquals(R.string.crypto_encrypt_error, viewModel.errorState.value)
76+
}
77+
78+
@Test
79+
fun encryptRecipientViewModel_decryptContainerWithPassword_throwsCryptoExceptionWithNoContainer() =
80+
runTest {
81+
try {
82+
viewModel.decryptContainerWithPassword("password123".toByteArray(), sharedContainerViewModel)
83+
fail("Expected CryptoException to be thrown")
84+
} catch (e: CryptoException) {
85+
assertEquals("No container to decrypt", e.message)
86+
}
87+
}
88+
89+
@Test
90+
fun encryptRecipientViewModel_resetErrorState_clearsErrorState() =
91+
runTest {
92+
viewModel.encryptContainerWithPassword("key", "pass".toByteArray(), sharedContainerViewModel)
93+
viewModel.resetErrorState()
94+
95+
assertNull(viewModel.errorState.value)
96+
}
97+
98+
@Test
99+
fun encryptRecipientViewModel_encryptContainerWithPassword_returnsDataFilesEmptyErrorWithEmptyDataFiles() =
100+
runTest {
101+
sharedContainerViewModel.setCryptoContainer(
102+
CryptoContainer(
103+
context = context,
104+
file = File(context.cacheDir, "test.cdoc"),
105+
dataFiles = ArrayList(),
106+
recipients =
107+
ArrayList<Addressee>().apply {
108+
add(
109+
Addressee(
110+
data = ByteArray(0),
111+
identifier = "key",
112+
serialNumber = null,
113+
givenName = null,
114+
surname = null,
115+
certType = CertType.PasswordType,
116+
validTo = null,
117+
concatKDFAlgorithmURI = null,
118+
),
119+
)
120+
},
121+
decrypted = false,
122+
encrypted = false,
123+
),
124+
)
125+
126+
viewModel.encryptContainerWithPassword("key", "password".toByteArray(), sharedContainerViewModel)
127+
128+
assertEquals(R.string.crypto_encrypt_data_files_empty_error, viewModel.errorState.value)
129+
}
130+
}

app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/EncryptRecipientScreen.kt

Lines changed: 45 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ import ee.ria.DigiDoc.utils.accessibility.AccessibilityUtil.Companion.sendAccess
106106
import ee.ria.DigiDoc.utils.extensions.reachedBottom
107107
import ee.ria.DigiDoc.utils.snackbar.SnackBarManager
108108
import ee.ria.DigiDoc.utils.snackbar.SnackBarManager.showMessage
109+
import ee.ria.DigiDoc.utilsLib.logging.LoggingUtil.Companion.debugLog
109110
import ee.ria.DigiDoc.utilsLib.validator.PersonalCodeValidator
110111
import ee.ria.DigiDoc.viewmodel.EncryptRecipientViewModel
111112
import ee.ria.DigiDoc.viewmodel.EncryptViewModel
@@ -128,6 +129,7 @@ fun EncryptRecipientScreen(
128129
sharedRecipientViewModel: SharedRecipientViewModel,
129130
encryptRecipientViewModel: EncryptRecipientViewModel = hiltViewModel(),
130131
) {
132+
val logTag = "EncryptRecipientScreen"
131133
val context = LocalContext.current
132134

133135
val scope = rememberCoroutineScope()
@@ -345,6 +347,33 @@ fun EncryptRecipientScreen(
345347
isBottomSheetVisible = isSettingsMenuBottomSheetVisible,
346348
)
347349

350+
@Composable
351+
fun RecipientTab(tabModifier: Modifier) {
352+
RecipientTabContent(
353+
modifier = tabModifier,
354+
expanded = expanded,
355+
onExpandedChange = { expanded = it },
356+
searchText = searchText,
357+
onSearchTextChange = encryptRecipientViewModel::onSearchTextChange,
358+
invalidPersonalCodeMessage = invalidPersonalCodeMessage,
359+
onSearch = { encryptRecipientViewModel.onQueryTextChange(it) },
360+
onDismissSearch = dismissSearch,
361+
recipientList = recipientList,
362+
hasSearched = hasSearched,
363+
containerRecipientList = containerRecipientList.value,
364+
onAddRecipientToContainer = { recipient ->
365+
encryptRecipientViewModel.addRecipientToContainer(
366+
recipient,
367+
sharedContainerViewModel,
368+
)
369+
},
370+
onRecipientClick = { recipient ->
371+
clickedRecipient.value = recipient
372+
showRecipientBottomSheet.value = true
373+
},
374+
)
375+
}
376+
348377
if (isCdoc2) {
349378
Column(
350379
modifier = modifier.padding(paddingValues).fillMaxSize(),
@@ -360,29 +389,7 @@ fun EncryptRecipientScreen(
360389
tabItems =
361390
listOf(
362391
Pair(tabRecipientTitle) {
363-
RecipientTabContent(
364-
modifier = Modifier.fillMaxSize(),
365-
expanded = expanded,
366-
onExpandedChange = { expanded = it },
367-
searchText = searchText,
368-
onSearchTextChange = encryptRecipientViewModel::onSearchTextChange,
369-
invalidPersonalCodeMessage = invalidPersonalCodeMessage,
370-
onSearch = { encryptRecipientViewModel.onQueryTextChange(it) },
371-
onDismissSearch = dismissSearch,
372-
recipientList = recipientList,
373-
hasSearched = hasSearched,
374-
containerRecipientList = containerRecipientList.value,
375-
onAddRecipientToContainer = { recipient ->
376-
encryptRecipientViewModel.addRecipientToContainer(
377-
recipient,
378-
sharedContainerViewModel,
379-
)
380-
},
381-
onRecipientClick = { recipient ->
382-
clickedRecipient.value = recipient
383-
showRecipientBottomSheet.value = true
384-
},
385-
)
392+
RecipientTab(Modifier.fillMaxSize())
386393
},
387394
Pair(tabPasswordTitle) {
388395
PasswordTabContent(modifier = Modifier.fillMaxSize())
@@ -391,36 +398,27 @@ fun EncryptRecipientScreen(
391398
)
392399
}
393400
} else {
394-
RecipientTabContent(
395-
modifier = Modifier.padding(paddingValues).fillMaxWidth(),
396-
expanded = expanded,
397-
onExpandedChange = { expanded = it },
398-
searchText = searchText,
399-
onSearchTextChange = encryptRecipientViewModel::onSearchTextChange,
400-
invalidPersonalCodeMessage = invalidPersonalCodeMessage,
401-
onSearch = { encryptRecipientViewModel.onQueryTextChange(it) },
402-
onDismissSearch = dismissSearch,
403-
recipientList = recipientList,
404-
hasSearched = hasSearched,
405-
containerRecipientList = containerRecipientList.value,
406-
onAddRecipientToContainer = { recipient ->
407-
encryptRecipientViewModel.addRecipientToContainer(
408-
recipient,
409-
sharedContainerViewModel,
410-
)
411-
},
412-
onRecipientClick = { recipient ->
413-
clickedRecipient.value = recipient
414-
showRecipientBottomSheet.value = true
415-
},
416-
)
401+
RecipientTab(Modifier.padding(paddingValues).fillMaxWidth())
417402
}
418403

419404
if (showPasswordDialog.value) {
420405
EncryptPasswordDialog(
421406
modifier = modifier,
422407
onDismiss = { showPasswordDialog.value = false },
423-
onEncrypt = { _, _ -> showPasswordDialog.value = false },
408+
onEncrypt = { keyLabel, password ->
409+
debugLog(logTag, "User submitted password encryption dialog")
410+
showPasswordDialog.value = false
411+
encryptionButtonEnabled.value = false
412+
showLoading.value = true
413+
scope.launch(Main) {
414+
encryptRecipientViewModel.encryptContainerWithPassword(
415+
keyLabel = keyLabel,
416+
password = password.toByteArray(Charsets.UTF_8),
417+
sharedContainerViewModel = sharedContainerViewModel,
418+
)
419+
showLoading.value = false
420+
}
421+
},
424422
)
425423
}
426424

0 commit comments

Comments
 (0)