diff --git a/app/src/androidTest/kotlin/ee/ria/DigiDoc/domain/preferences/DataStoreTest.kt b/app/src/androidTest/kotlin/ee/ria/DigiDoc/domain/preferences/DataStoreTest.kt
index e3278e27..c0463347 100644
--- a/app/src/androidTest/kotlin/ee/ria/DigiDoc/domain/preferences/DataStoreTest.kt
+++ b/app/src/androidTest/kotlin/ee/ria/DigiDoc/domain/preferences/DataStoreTest.kt
@@ -40,6 +40,7 @@ import ee.ria.DigiDoc.network.siva.SivaSetting
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.BeforeClass
@@ -614,4 +615,107 @@ class DataStoreTest {
assertFalse(result)
}
+
+ @Test
+ fun dataStore_getWebEidBrowserPackage_defaultNull() {
+ val result = dataStore.getWebEidBrowserPackage()
+
+ assertNull(result)
+ }
+
+ @Test
+ fun dataStore_setWebEidBrowserPackage_success() {
+ dataStore.setWebEidBrowserPackage("com.android.chrome")
+
+ val result = dataStore.getWebEidBrowserPackage()
+
+ assertEquals("com.android.chrome", result)
+ }
+
+ @Test
+ fun dataStore_setWebEidBrowserPackage_nullClearsValue() {
+ dataStore.setWebEidBrowserPackage("com.android.chrome")
+ dataStore.setWebEidBrowserPackage(null)
+
+ val result = dataStore.getWebEidBrowserPackage()
+
+ assertNull(result)
+ }
+
+ @Test
+ fun dataStore_getTemporaryCanNumber_defaultEmpty() {
+ val result = dataStore.getTemporaryCanNumber()
+
+ assertEquals("", result)
+ }
+
+ @Test
+ fun dataStore_setTemporaryCanNumber_success() {
+ dataStore.setTemporaryCanNumber("123456")
+
+ val result = dataStore.getTemporaryCanNumber()
+
+ assertEquals("123456", result)
+ }
+
+ @Test
+ fun dataStore_clearTemporaryCanNumber_success() {
+ dataStore.setTemporaryCanNumber("123456")
+ dataStore.clearTemporaryCanNumber()
+
+ val result = dataStore.getTemporaryCanNumber()
+
+ assertEquals("", result)
+ }
+
+ @Test
+ fun dataStore_getWebEidRememberMe_defaultTrue() {
+ val result = dataStore.getWebEidRememberMe()
+
+ assertTrue(result)
+ }
+
+ @Test
+ fun dataStore_setWebEidRememberMe_successWithFalse() {
+ dataStore.setWebEidRememberMe(false)
+
+ val result = dataStore.getWebEidRememberMe()
+
+ assertFalse(result)
+ }
+
+ @Test
+ fun dataStore_setWebEidRememberMe_successWithTrue() {
+ dataStore.setWebEidRememberMe(true)
+
+ val result = dataStore.getWebEidRememberMe()
+
+ assertTrue(result)
+ }
+
+ @Test
+ fun dataStore_isWebEidSessionActive_defaultFalse() {
+ val result = dataStore.isWebEidSessionActive()
+
+ assertFalse(result)
+ }
+
+ @Test
+ fun dataStore_setWebEidSessionActive_successWithTrue() {
+ dataStore.setWebEidSessionActive(true)
+
+ val result = dataStore.isWebEidSessionActive()
+
+ assertTrue(result)
+ }
+
+ @Test
+ fun dataStore_setWebEidSessionActive_successWithFalse() {
+ dataStore.setWebEidSessionActive(true)
+ dataStore.setWebEidSessionActive(false)
+
+ val result = dataStore.isWebEidSessionActive()
+
+ assertFalse(result)
+ }
}
diff --git a/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/WebEidViewModelTest.kt b/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/WebEidViewModelTest.kt
index d6bc8a89..952609e0 100644
--- a/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/WebEidViewModelTest.kt
+++ b/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/WebEidViewModelTest.kt
@@ -25,7 +25,9 @@ import android.net.Uri
import android.util.Base64.URL_SAFE
import android.util.Base64.decode
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.test.platform.app.InstrumentationRegistry
import ee.ria.DigiDoc.R
+import ee.ria.DigiDoc.domain.preferences.DataStore
import ee.ria.DigiDoc.webEid.WebEidAuthService
import ee.ria.DigiDoc.webEid.WebEidSignService
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -58,6 +60,8 @@ class WebEidViewModelTest {
@Mock
private lateinit var signService: WebEidSignService
+ private lateinit var dataStore: DataStore
+
private lateinit var viewModel: WebEidViewModel
private val signingCertBase64Raw =
@@ -82,7 +86,9 @@ class WebEidViewModelTest {
@Before
fun setup() {
MockitoAnnotations.openMocks(this)
- viewModel = WebEidViewModel(authService, signService)
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+ dataStore = DataStore(context)
+ viewModel = WebEidViewModel(authService, signService, dataStore)
}
@Test
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6bc844f5..03ca0154 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -156,6 +156,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/MainActivity.kt b/app/src/main/kotlin/ee/ria/DigiDoc/MainActivity.kt
index a83126e5..95b6cba8 100644
--- a/app/src/main/kotlin/ee/ria/DigiDoc/MainActivity.kt
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/MainActivity.kt
@@ -121,7 +121,15 @@ class MainActivity :
val componentClassName = this.javaClass.name
val locale = dataStore.getLocale() ?: getLocale("en")
- val webEidUri = intent?.data?.takeIf { it.scheme == "web-eid-mobile" }
+ val webEidUri = intent.data?.takeIf { it.scheme == "web-eid-mobile" }
+
+ if (webEidUri != null) {
+ val browserPackage =
+ intent
+ .getStringExtra("com.android.browser.application_id")
+ ?.takeIf { it.isNotEmpty() }
+ dataStore.setWebEidBrowserPackage(browserPackage)
+ }
val externalFileUris =
if (webEidUri != null) {
diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/domain/preferences/DataStore.kt b/app/src/main/kotlin/ee/ria/DigiDoc/domain/preferences/DataStore.kt
index f2fc84c2..c9146e52 100644
--- a/app/src/main/kotlin/ee/ria/DigiDoc/domain/preferences/DataStore.kt
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/domain/preferences/DataStore.kt
@@ -148,6 +148,60 @@ class DataStore
if (cert.isNotEmpty()) editor.putString(key, cert).commit()
}
+ fun getTemporaryCanNumber(): String {
+ val encryptedPrefs = getEncryptedPreferences(context)
+ return encryptedPrefs?.getString(
+ resources.getString(R.string.main_settings_temporary_can_key),
+ "",
+ ) ?: ""
+ }
+
+ fun setTemporaryCanNumber(can: String) {
+ val encryptedPrefs = getEncryptedPreferences(context)
+ encryptedPrefs?.edit {
+ putString(resources.getString(R.string.main_settings_temporary_can_key), can)
+ }
+ }
+
+ fun clearTemporaryCanNumber() {
+ val encryptedPrefs = getEncryptedPreferences(context)
+ encryptedPrefs?.edit {
+ remove(resources.getString(R.string.main_settings_temporary_can_key))
+ }
+ }
+
+ fun setWebEidRememberMe(value: Boolean) {
+ preferences.edit {
+ putBoolean("web_eid_remember_me", value)
+ }
+ }
+
+ fun getWebEidRememberMe(): Boolean = preferences.getBoolean("web_eid_remember_me", true)
+
+ fun setWebEidBrowserPackage(packageName: String?) {
+ preferences.edit {
+ if (packageName.isNullOrEmpty()) {
+ remove("web_eid_browser_package")
+ } else {
+ putString("web_eid_browser_package", packageName)
+ }
+ }
+ }
+
+ fun getWebEidBrowserPackage(): String? = preferences.getString("web_eid_browser_package", null)
+
+ fun isWebEidSessionActive(): Boolean {
+ val prefs = getEncryptedPreferences(context)
+ return prefs?.getBoolean("web_eid_session_active", false) ?: false
+ }
+
+ fun setWebEidSessionActive(active: Boolean) {
+ val prefs = getEncryptedPreferences(context)
+ prefs?.edit {
+ putBoolean("web_eid_session_active", active)
+ }
+ }
+
fun getPhoneNo(): String =
preferences.getString(
resources.getString(R.string.main_settings_phone_no_key),
diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/WebEidFragment.kt b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/WebEidFragment.kt
index eb5dcf0f..0f5a8454 100644
--- a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/WebEidFragment.kt
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/WebEidFragment.kt
@@ -63,9 +63,13 @@ fun WebEidFragment(
LaunchedEffect(viewModel) {
viewModel.relyingPartyResponseEvents.collect { responseUri ->
+ val browserPackage = viewModel.getWebEidBrowserPackage()
val browserIntent =
Intent(Intent.ACTION_VIEW, responseUri).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ if (!browserPackage.isNullOrEmpty()) {
+ setPackage(browserPackage)
+ }
}
activity.startActivity(browserIntent)
activity.finishAndRemoveTask()
diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/WebEidScreen.kt b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/WebEidScreen.kt
index b0af9832..384622e1 100644
--- a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/WebEidScreen.kt
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/WebEidScreen.kt
@@ -22,6 +22,7 @@
package ee.ria.DigiDoc.fragment.screen
import android.app.Activity
+import android.content.Intent
import android.content.res.Configuration
import androidx.activity.compose.LocalActivity
import androidx.compose.foundation.background
@@ -125,8 +126,12 @@ fun WebEidScreen(
val snackBarScope = rememberCoroutineScope()
val messages by SnackBarManager.messages.collectAsState(emptyList())
val dialogError by viewModel.dialogError.collectAsState()
- var rememberMe by rememberSaveable { mutableStateOf(true) }
- val hasStoredCanNumber = sharedSettingsViewModel.dataStore.getCanNumber().isNotEmpty()
+ var rememberMe by rememberSaveable {
+ mutableStateOf(sharedSettingsViewModel.dataStore.getWebEidRememberMe())
+ }
+ val hasStoredCanNumber =
+ sharedSettingsViewModel.dataStore.getCanNumber().isNotEmpty() ||
+ sharedSettingsViewModel.dataStore.getTemporaryCanNumber().isNotEmpty()
LaunchedEffect(messages) {
messages.forEach { message ->
@@ -137,6 +142,15 @@ fun WebEidScreen(
}
}
+ LaunchedEffect(authRequest, certificateRequest) {
+ if (authRequest != null || certificateRequest != null) {
+ if (!sharedSettingsViewModel.dataStore.isWebEidSessionActive()) {
+ sharedSettingsViewModel.dataStore.clearTemporaryCanNumber()
+ }
+ sharedSettingsViewModel.dataStore.setWebEidSessionActive(true)
+ }
+ }
+
Scaffold(
snackbarHost = {
SnackbarHost(
@@ -297,7 +311,10 @@ fun WebEidScreen(
if (!isWebEidAuthenticating) {
WebEidRememberMe(
rememberMe = rememberMe,
- onRememberMeChange = { rememberMe = it },
+ onRememberMeChange = {
+ rememberMe = it
+ sharedSettingsViewModel.dataStore.setWebEidRememberMe(it)
+ },
)
}
} else if (isCertificateFlow || signRequest != null) {
@@ -308,9 +325,14 @@ fun WebEidScreen(
signRequest != null -> signRequest.origin
else -> ""
}
+ val signingPersonInfo =
+ signRequest?.personalData?.let {
+ "${it.givenNames} ${it.surname}, ${it.personalCode}"
+ }
WebEidSignOrCertificateInfo(
origin = origin,
isCertificateFlow = isCertificateFlow,
+ signingPersonInfo = signingPersonInfo,
)
}
@@ -347,7 +369,10 @@ fun WebEidScreen(
if (!isWebEidAuthenticating) {
WebEidRememberMe(
rememberMe = rememberMe,
- onRememberMeChange = { rememberMe = it },
+ onRememberMeChange = {
+ rememberMe = it
+ sharedSettingsViewModel.dataStore.setWebEidRememberMe(it)
+ },
)
}
} else {
@@ -364,6 +389,8 @@ fun WebEidScreen(
cancelWebEidSignAction()
},
onSuccess = {
+ sharedSettingsViewModel.dataStore.clearTemporaryCanNumber()
+ sharedSettingsViewModel.dataStore.setWebEidSessionActive(false)
isWebEidAuthenticating = false
navController.navigateUp()
},
@@ -429,6 +456,15 @@ fun WebEidScreen(
OutlinedButton(
onClick = {
isWebEidAuthenticating = false
+ val browserPackage = viewModel.getWebEidBrowserPackage()
+ if (!browserPackage.isNullOrEmpty()) {
+ val launchIntent =
+ activity.packageManager.getLaunchIntentForPackage(browserPackage)
+ if (launchIntent != null) {
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ activity.startActivity(launchIntent)
+ }
+ }
activity.finishAndRemoveTask()
},
modifier = Modifier.fillMaxWidth(),
@@ -500,6 +536,7 @@ private fun WebEidAuthInfo(authRequest: WebEidAuthRequest) {
private fun WebEidSignOrCertificateInfo(
origin: String,
isCertificateFlow: Boolean,
+ signingPersonInfo: String? = null,
) {
Column(
modifier = Modifier.fillMaxWidth(),
@@ -529,7 +566,12 @@ private fun WebEidSignOrCertificateInfo(
Spacer(modifier = Modifier.height(16.dp))
Text(
- text = stringResource(R.string.web_eid_details_forwarded),
+ text =
+ if (isCertificateFlow) {
+ stringResource(R.string.web_eid_details_forwarded)
+ } else {
+ stringResource(R.string.web_eid_details)
+ },
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Left,
)
@@ -537,7 +579,12 @@ private fun WebEidSignOrCertificateInfo(
Spacer(modifier = Modifier.height(2.dp))
Text(
- text = stringResource(R.string.web_eid_name_personal_identification_code),
+ text =
+ if (!isCertificateFlow && !signingPersonInfo.isNullOrBlank()) {
+ signingPersonInfo
+ } else {
+ stringResource(R.string.web_eid_name_personal_identification_code)
+ },
style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.Left,
)
@@ -545,7 +592,12 @@ private fun WebEidSignOrCertificateInfo(
Spacer(modifier = Modifier.height(16.dp))
Text(
- text = stringResource(R.string.web_eid_certificate_consent_text),
+ text =
+ if (isCertificateFlow) {
+ stringResource(R.string.web_eid_certificate_consent_text)
+ } else {
+ stringResource(R.string.web_eid_signature_consent_text)
+ },
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onBackground,
textAlign = TextAlign.Left,
diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/NFCView.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/NFCView.kt
index e5c96ed7..a84a8ad4 100644
--- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/NFCView.kt
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/NFCView.kt
@@ -54,6 +54,7 @@ import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -187,10 +188,24 @@ fun NFCView(
var shouldRememberMe by rememberSaveable { mutableStateOf(rememberMe) }
var canNumber by rememberSaveable(stateSaver = textFieldValueSaver) {
+ val storedCan = sharedSettingsViewModel.dataStore.getCanNumber()
+ val tempCan = sharedSettingsViewModel.dataStore.getTemporaryCanNumber()
+
+ val initialCan =
+ when {
+ identityAction == IdentityAction.CERTIFICATE && storedCan.isNotEmpty() -> storedCan
+ identityAction == IdentityAction.CERTIFICATE -> ""
+ identityAction == IdentityAction.SIGN && tempCan.isNotEmpty() -> tempCan
+
+ storedCan.isNotEmpty() -> storedCan
+ tempCan.isNotEmpty() && isWebEidAuthenticating -> tempCan
+ else -> ""
+ }
+
mutableStateOf(
TextFieldValue(
- text = sharedSettingsViewModel.dataStore.getCanNumber(),
- selection = TextRange(sharedSettingsViewModel.dataStore.getCanNumber().length),
+ text = initialCan,
+ selection = TextRange(initialCan.length),
),
)
}
@@ -201,20 +216,27 @@ fun NFCView(
val showErrorDialog = rememberSaveable { mutableStateOf(false) }
val focusManager = LocalFocusManager.current
val saveFormParams = {
- val previousCanNumber = sharedSettingsViewModel.dataStore.getCanNumber()
- val currentCanNumber = canNumber.text
+ val currentCan = canNumber.text
- if (shouldRememberMe) {
- if (previousCanNumber != currentCanNumber) {
- signingCert = ""
- sharedSettingsViewModel.dataStore.setSigningCertificate("")
+ when {
+ (
+ identityAction == IdentityAction.AUTH ||
+ identityAction == IdentityAction.CERTIFICATE
+ ) &&
+ shouldRememberMe -> {
+ sharedSettingsViewModel.dataStore.setCanNumber(currentCan)
+ sharedSettingsViewModel.dataStore.setSigningCertificate(signingCert)
+ sharedSettingsViewModel.dataStore.clearTemporaryCanNumber()
}
- sharedSettingsViewModel.dataStore.setCanNumber(currentCanNumber)
- sharedSettingsViewModel.dataStore.setSigningCertificate(signingCert)
- } else {
- sharedSettingsViewModel.dataStore.setCanNumber("")
- sharedSettingsViewModel.dataStore.setSigningCertificate("")
+ (
+ identityAction == IdentityAction.AUTH ||
+ identityAction == IdentityAction.CERTIFICATE
+ ) &&
+ !shouldRememberMe -> {
+ sharedSettingsViewModel.dataStore.setCanNumber("")
+ sharedSettingsViewModel.dataStore.setTemporaryCanNumber(currentCan)
+ }
}
}
@@ -261,6 +283,8 @@ fun NFCView(
BackHandler {
nfcViewModel.handleBackButton()
+ sharedSettingsViewModel.dataStore.clearTemporaryCanNumber()
+ sharedSettingsViewModel.dataStore.setWebEidSessionActive(false)
if (isSigning || isDecrypting || isAuthenticating) {
onError()
} else {
@@ -268,6 +292,16 @@ fun NFCView(
}
}
+ DisposableEffect(Unit) {
+ onDispose {
+ val webEidActive = sharedSettingsViewModel.dataStore.isWebEidSessionActive()
+
+ if (!shouldRememberMe && !webEidActive) {
+ sharedSettingsViewModel.dataStore.clearTemporaryCanNumber()
+ }
+ }
+ }
+
LaunchedEffect(nfcViewModel.shouldResetPIN) {
nfcViewModel.shouldResetPIN.asFlow().collect { bool ->
bool.let {
@@ -350,6 +384,7 @@ fun NFCView(
LaunchedEffect(nfcViewModel.webEidAuthResult) {
nfcViewModel.webEidAuthResult.asFlow().collect { result ->
result?.let { (authCert, signingCert, signature) ->
+ nfcViewModel.setExpectedWebEidSigningCertificate(signingCert)
val encodedCert = Base64.getEncoder().encodeToString(signingCert)
sharedSettingsViewModel.dataStore.setSigningCertificate(encodedCert)
webEidViewModel?.handleWebEidAuthResult(authCert, signingCert, signature)
@@ -364,6 +399,7 @@ fun NFCView(
result?.let { signCert ->
sharedSettingsViewModel.dataStore.setSigningCertificate(signCert)
val certBytes = Base64.getDecoder().decode(signCert)
+ nfcViewModel.setExpectedWebEidSigningCertificate(certBytes)
webEidViewModel?.handleWebEidCertificateResult(certBytes)
nfcViewModel.resetWebEidCertificateResult()
onSuccess()
@@ -667,9 +703,10 @@ fun NFCView(
)
}
} else {
- if (sharedSettingsViewModel.dataStore.getCanNumber().isNotEmpty()) {
- saveFormParams()
- }
+ saveFormParams()
+ val expectedSigningCertBase64 =
+ sharedSettingsViewModel.dataStore
+ .getSigningCertificate()
nfcViewModel.performNFCWebEidSignWorkRequest(
activity = activity,
context = context,
@@ -677,6 +714,7 @@ fun NFCView(
pin2Code = pinCode.value,
responseUri = responseUriString,
hash = hashString,
+ expectedSigningCertBase64 = expectedSigningCertBase64,
)
}
}
diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/NFCViewModel.kt b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/NFCViewModel.kt
index e4ea3028..a94ead41 100644
--- a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/NFCViewModel.kt
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/NFCViewModel.kt
@@ -106,6 +106,7 @@ class NFCViewModel
val webEidSignResult: LiveData?> = _webEidSignResult
private val _webEidCertificateResult = MutableLiveData()
val webEidCertificateResult: LiveData = _webEidCertificateResult
+ private var expectedWebEidSigningCert: ByteArray? = null
private val dialogMessages: ImmutableMap =
ImmutableMap
@@ -176,6 +177,10 @@ class NFCViewModel
fun getNFCStatus(activity: Activity): NfcStatus = NfcStatus.NFC_ACTIVE
+ fun setExpectedWebEidSigningCertificate(cert: ByteArray) {
+ expectedWebEidSigningCert = cert
+ }
+
private fun resetValues() {
_errorState.postValue(null)
_message.postValue(null)
@@ -605,6 +610,7 @@ class NFCViewModel
pin2Code: ByteArray?,
responseUri: String,
hash: String,
+ expectedSigningCertBase64: String?,
) {
val pinType = context.getString(R.string.signature_id_card_pin2)
activity.requestedOrientation = activity.resources.configuration.orientation
@@ -623,6 +629,15 @@ class NFCViewModel
val card = TokenWithPace.create(nfcReader)
card.tunnel(canNumber)
val signerCert = card.certificate(CertificateType.SIGNING)
+ expectedSigningCertBase64
+ ?.takeIf { it.isNotEmpty() }
+ ?.let {
+ val expectedCert = Base64.getDecoder().decode(it)
+ if (!expectedCert.contentEquals(signerCert)) {
+ throw IllegalStateException("Web eID signing certificate mismatch")
+ }
+ }
+
val signerCertB64 = Base64.getEncoder().encodeToString(signerCert)
val hashBytes = Base64.getDecoder().decode(hash)
val (_, signatureArray) = idCardService.sign(card, pin2Code, hashBytes)
@@ -654,6 +669,7 @@ class NFCViewModel
showTechnicalError(ex)
}
} finally {
+ expectedWebEidSigningCert = null
pin2Code.clearSensitive()
nfcSmartCardReaderManager.disableNfcReaderMode()
activity.requestedOrientation =
@@ -665,6 +681,7 @@ class NFCViewModel
}
fun handleBackButton() {
+ expectedWebEidSigningCert = null
_shouldResetPIN.postValue(true)
resetValues()
}
@@ -726,6 +743,15 @@ class NFCViewModel
errorLog(logTag, "Unable to sign with NFC - Certificate status: unknown", e)
}
+ private fun showWebEidSigningCertificateMismatchError(e: Exception) {
+ _errorState.postValue(Triple(R.string.signature_update_nfc_wrong_certificate, null, null))
+ errorLog(
+ logTag,
+ "Web eID signing failed - signing certificate does not match previously used certificate",
+ e,
+ )
+ }
+
private fun showTechnicalError(e: Exception) {
_errorState.postValue(Triple(R.string.signature_update_nfc_technical_error, null, null))
errorLog(logTag, "Unable to perform with NFC: ${e.message}", e)
@@ -811,6 +837,11 @@ class NFCViewModel
true
}
+ message.contains("Web eID signing certificate mismatch") -> {
+ showWebEidSigningCertificateMismatchError(ex)
+ true
+ }
+
else -> false
}.also {
errorLog(logTag, "Exception: ${ex.message}", ex)
@@ -819,6 +850,7 @@ class NFCViewModel
override fun onCleared() {
super.onCleared()
+ expectedWebEidSigningCert = null
nfcSmartCardReaderManager.disableNfcReaderMode()
}
}
diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/WebEidViewModel.kt b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/WebEidViewModel.kt
index b501f681..2d4218da 100644
--- a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/WebEidViewModel.kt
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/WebEidViewModel.kt
@@ -25,6 +25,7 @@ import android.net.Uri
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import ee.ria.DigiDoc.R
+import ee.ria.DigiDoc.domain.preferences.DataStore
import ee.ria.DigiDoc.utilsLib.logging.LoggingUtil.Companion.errorLog
import ee.ria.DigiDoc.webEid.WebEidAuthService
import ee.ria.DigiDoc.webEid.WebEidSignService
@@ -50,8 +51,12 @@ class WebEidViewModel
constructor(
private val authService: WebEidAuthService,
private val signService: WebEidSignService,
+ private val dataStore: DataStore,
) : ViewModel() {
private val logTag = javaClass.simpleName
+
+ fun getWebEidBrowserPackage(): String? = dataStore.getWebEidBrowserPackage()
+
private val _authRequest = MutableStateFlow(null)
val authRequest: StateFlow = _authRequest.asStateFlow()
private val _certificateRequest = MutableStateFlow(null)
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
index 33ef2756..72765ed5 100644
--- a/app/src/main/res/values-et/strings.xml
+++ b/app/src/main/res/values-et/strings.xml
@@ -289,6 +289,7 @@
https://www.id.ee/artikkel/id-kaardi-pin-ja-puk-koodide-muutmine/
Sertifikaat on kehtetu
Sertifikaadi staatus on teadmata
+ Valitud ID-kaart ei vasta varem kasutatud sertifikaadile. Palun kasuta sama ID-kaarti, millega autentisid.
ID-kaardi
Mobiil-ID
Smart-ID
@@ -666,11 +667,13 @@
Autentides nõustun oma nime ja isikukoodi edastamisega teenusepakkujale.
Autentimispäring:
Edastatavad andmed:
+ Andmed:
NIMI, ISIKUKOOD
Järgmisel kasutamisel on andmeväljad eeltäidetud.
Kinnita
Vali sertifikaat
Sertifikaati valides nõustun oma nime ja isikukoodi edastamisega teenusepakkujale.
+ PIN2 koodi sisestamisega annad omakäelise digiallkirja.
Allkirjastamine
Allkirjasta ID-kaardiga
Sertifikaadipäring:
diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml
index 8e254fdf..c8a34638 100644
--- a/app/src/main/res/values/donottranslate.xml
+++ b/app/src/main/res/values/donottranslate.xml
@@ -67,6 +67,7 @@
mainSettingsUUID
can
signingCert
+ temporaryCanNumber
mainSettingsMobileNr
mainSettingsPersonalCode
mainSettingsSmartIdPersonalCode
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d5fd3b09..591d2cec 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -289,6 +289,7 @@
https://www.id.ee/en/article/changing-id-card-pin-codes-and-puk-code/
Certificate status revoked
Certificate status unknown
+ The selected ID card does not match the previously used certificate. Please use the same ID card you authenticated with.
ID-card\'s
Mobile-ID
Smart-ID
@@ -666,11 +667,13 @@
By authenticating, I agree to the transfer of my name and personal identification code to the service provider.
Authentication request from:
Details forwarded:
+ Details:
NAME, PERSONAL IDENTIFICATION CODE
The entered data will be filled the next time you authenticate.
Confirm
Select a certificate
By choosing the certificate, I agree to the transfer of my name and personal identification code to the service provider.
+ By entering your PIN2, you give a handwritten-equivalent digital signature.
Sign
Sign with ID-card
Certificate request from:
diff --git a/web-eid-lib/src/main/java/ee/ria/DigiDoc/webEid/domain/model/WebEidPersonalData.kt b/web-eid-lib/src/main/java/ee/ria/DigiDoc/webEid/domain/model/WebEidPersonalData.kt
new file mode 100644
index 00000000..187f75bb
--- /dev/null
+++ b/web-eid-lib/src/main/java/ee/ria/DigiDoc/webEid/domain/model/WebEidPersonalData.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2017 - 2026 Riigi Infosüsteemi Amet
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+@file:Suppress("PackageName")
+
+package ee.ria.DigiDoc.webEid.domain.model
+
+data class WebEidPersonalData(
+ val givenNames: String,
+ val surname: String,
+ val personalCode: String,
+)
diff --git a/web-eid-lib/src/main/java/ee/ria/DigiDoc/webEid/domain/model/WebEidSignRequest.kt b/web-eid-lib/src/main/java/ee/ria/DigiDoc/webEid/domain/model/WebEidSignRequest.kt
index 46b4aea9..5a4bab82 100644
--- a/web-eid-lib/src/main/java/ee/ria/DigiDoc/webEid/domain/model/WebEidSignRequest.kt
+++ b/web-eid-lib/src/main/java/ee/ria/DigiDoc/webEid/domain/model/WebEidSignRequest.kt
@@ -29,4 +29,5 @@ data class WebEidSignRequest(
val signingCertificate: X509Certificate,
val hash: String?,
val hashFunction: String?,
+ val personalData: WebEidPersonalData?,
)
diff --git a/web-eid-lib/src/main/java/ee/ria/DigiDoc/webEid/utils/WebEidRequestParser.kt b/web-eid-lib/src/main/java/ee/ria/DigiDoc/webEid/utils/WebEidRequestParser.kt
index c576cbd4..7c748a9a 100644
--- a/web-eid-lib/src/main/java/ee/ria/DigiDoc/webEid/utils/WebEidRequestParser.kt
+++ b/web-eid-lib/src/main/java/ee/ria/DigiDoc/webEid/utils/WebEidRequestParser.kt
@@ -25,12 +25,17 @@ import android.net.Uri
import ee.ria.DigiDoc.utilsLib.signing.CertificateUtil
import ee.ria.DigiDoc.webEid.domain.model.WebEidAuthRequest
import ee.ria.DigiDoc.webEid.domain.model.WebEidCertificateRequest
+import ee.ria.DigiDoc.webEid.domain.model.WebEidPersonalData
import ee.ria.DigiDoc.webEid.domain.model.WebEidSignRequest
import ee.ria.DigiDoc.webEid.exception.WebEidErrorCode.ERR_WEBEID_MOBILE_INVALID_REQUEST
import ee.ria.DigiDoc.webEid.exception.WebEidException
+import org.bouncycastle.asn1.x500.X500Name
+import org.bouncycastle.asn1.x500.style.BCStyle
+import org.bouncycastle.asn1.x500.style.IETFUtils
import org.json.JSONObject
import java.net.URI
import java.net.URISyntaxException
+import java.security.cert.X509Certificate
import java.util.Base64
object WebEidRequestParser {
@@ -109,6 +114,7 @@ object WebEidRequestParser {
signingCertificate,
hash = hash,
hashFunction = hashFunction,
+ personalData = extractPersonalData(signingCertificate),
)
}
@@ -208,4 +214,30 @@ object WebEidRequestParser {
return hashBytes
}
+
+ private fun extractPersonalData(cert: X509Certificate): WebEidPersonalData {
+ val x500Name = X500Name.getInstance(cert.subjectX500Principal.encoded)
+ val cnRDNs = x500Name.getRDNs(BCStyle.CN)
+
+ require(cnRDNs.isNotEmpty()) {
+ "Signing certificate CN missing"
+ }
+
+ val cn =
+ IETFUtils
+ .valueToString(cnRDNs.first().first.value)
+ .replace("\\,", ",")
+ .replace("\\ ", " ")
+ val parts = cn.split(",").map { it.trim() }
+
+ require(parts.size >= 3) {
+ "Unexpected signing certificate CN format: $cn"
+ }
+
+ return WebEidPersonalData(
+ surname = parts[0],
+ givenNames = parts[1],
+ personalCode = parts[2],
+ )
+ }
}