diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/MyEidScreen.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/MyEidScreen.kt
index 532a8fb8a..1f9eefadb 100644
--- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/MyEidScreen.kt
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/MyEidScreen.kt
@@ -28,6 +28,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
@@ -52,7 +53,9 @@ import ee.ria.DigiDoc.idcard.CardType
import ee.ria.DigiDoc.idcard.CodeType
import ee.ria.DigiDoc.idcard.DateOfBirthUtil
import ee.ria.DigiDoc.ui.component.menu.SettingsMenuBottomSheet
+import ee.ria.DigiDoc.ui.component.myeid.mydata.MyEidDocumentStatus
import ee.ria.DigiDoc.ui.component.myeid.mydata.MyEidMyDataView
+import ee.ria.DigiDoc.ui.component.myeid.mydata.certificateStatus
import ee.ria.DigiDoc.ui.component.myeid.pinandcertificate.MyEidPinAndCertificateView
import ee.ria.DigiDoc.ui.component.shared.HrefDynamicText
import ee.ria.DigiDoc.ui.component.shared.StatusSnackbarHost
@@ -90,6 +93,11 @@ fun MyEidScreen(
val isPin2Activated = idCardData?.pin2CodeChanged == true
val isCourierCard = idCardData?.personalData != null && idCardData?.pin1CodeChanged == false
+ val authCertificate = remember(idCardData) { idCardData?.authCertificate?.data?.x509Certificate() }
+ val signCertificate = remember(idCardData) { idCardData?.signCertificate?.data?.x509Certificate() }
+ val authCertificateStatus = idCardData?.let { certificateStatus(authCertificate) }
+ val signCertificateStatus = idCardData?.let { certificateStatus(signCertificate) }
+
val alphaForPin1BlockedState = getAlphaForBlockedState((isPin1Blocked && isPukBlocked) || isCourierCard)
val alphaForPin2BlockedState = getAlphaForBlockedState((isPin2Blocked && isPukBlocked) || isCourierCard)
val alphaForPukBlockedState = getAlphaForBlockedState(isPukBlocked)
@@ -310,15 +318,14 @@ fun MyEidScreen(
.alpha(alphaForPin1BlockedState),
title = stringResource(R.string.myeid_authentication_certificate_title),
subtitle =
- stringResource(
- R.string.myeid_certificate_valid_to,
- sharedMyEidViewModel.getNotAfter(
- idCardData?.authCertificate?.data?.x509Certificate(),
- ),
+ certificateSubtitle(
+ authCertificateStatus,
+ sharedMyEidViewModel.getNotAfter(authCertificate),
),
isPinBlocked = isPin1Blocked,
isPukBlocked = isPukBlocked,
isNotActivated = isCourierCard,
+ certificateStatus = authCertificateStatus,
forgotPinText =
if (isPin1Blocked) {
stringResource(
@@ -344,6 +351,14 @@ fun MyEidScreen(
},
)
+ if (authCertificateStatus == MyEidDocumentStatus.EXPIRED) {
+ ExpiredCertificateText(
+ modifier = modifier,
+ testTag = "myEidExpiredPin1DescriptionText",
+ codeType = CodeType.PIN1,
+ )
+ }
+
if (isPin1Blocked && !isPukBlocked) {
Text(
modifier =
@@ -398,15 +413,14 @@ fun MyEidScreen(
.alpha(alphaForPin2BlockedState),
title = stringResource(R.string.myeid_signing_certificate_title),
subtitle =
- stringResource(
- R.string.myeid_certificate_valid_to,
- sharedMyEidViewModel.getNotAfter(
- idCardData?.signCertificate?.data?.x509Certificate(),
- ),
+ certificateSubtitle(
+ signCertificateStatus,
+ sharedMyEidViewModel.getNotAfter(signCertificate),
),
isPinBlocked = isPin2Blocked,
isPukBlocked = isPukBlocked,
isNotActivated = isCourierCard,
+ certificateStatus = signCertificateStatus,
forgotPinText =
if (isPin2Blocked) {
stringResource(
@@ -432,12 +446,22 @@ fun MyEidScreen(
},
)
+ if (signCertificateStatus == MyEidDocumentStatus.EXPIRED) {
+ ExpiredCertificateText(
+ modifier = modifier,
+ testTag = "myEidExpiredPin2DescriptionText",
+ codeType = CodeType.PIN2,
+ )
+ }
+
if (isCourierCard) {
CourierCardWarningText(
modifier = modifier,
testTag = "myEidCourierCardPin2DescriptionText",
)
- } else if (!isPin2Activated) {
+ } else if (!isPin2Activated &&
+ signCertificateStatus != MyEidDocumentStatus.EXPIRED
+ ) {
Text(
modifier =
modifier
@@ -623,6 +647,36 @@ fun MyEidScreen(
)
}
+@Composable
+private fun certificateSubtitle(
+ status: MyEidDocumentStatus?,
+ validTo: String,
+): String =
+ when (status) {
+ MyEidDocumentStatus.EXPIRED ->
+ stringResource(R.string.signature_update_signature_error_message_certificate_expired)
+ MyEidDocumentStatus.UNKNOWN -> stringResource(R.string.myeid_status_unknown)
+ else -> stringResource(R.string.myeid_certificate_valid_to, validTo)
+ }
+
+@Composable
+private fun ExpiredCertificateText(
+ modifier: Modifier,
+ testTag: String,
+ codeType: CodeType,
+) {
+ Text(
+ modifier =
+ modifier
+ .fillMaxWidth()
+ .focusable(true)
+ .testTag(testTag),
+ text = stringResource(R.string.myeid_cert_expired, codeType),
+ color = MaterialTheme.colorScheme.error,
+ style = MaterialTheme.typography.bodySmall,
+ )
+}
+
@Composable
private fun CourierCardWarningText(
modifier: Modifier,
diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/mydata/MyEidDocumentStatus.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/mydata/MyEidDocumentStatus.kt
index f6406313b..463d0c926 100644
--- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/mydata/MyEidDocumentStatus.kt
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/mydata/MyEidDocumentStatus.kt
@@ -7,6 +7,9 @@ package ee.ria.DigiDoc.ui.component.myeid.mydata
import android.content.Context
import ee.ria.DigiDoc.R
+import java.security.cert.CertificateExpiredException
+import java.security.cert.CertificateNotYetValidException
+import java.security.cert.X509Certificate
enum class MyEidDocumentStatus {
VALID,
@@ -20,3 +23,17 @@ fun MyEidDocumentStatus.getLocalized(context: Context): String =
MyEidDocumentStatus.EXPIRED -> context.getString(R.string.myeid_status_expired)
MyEidDocumentStatus.UNKNOWN -> context.getString(R.string.myeid_status_unknown)
}
+
+fun certificateStatus(x509Certificate: X509Certificate?): MyEidDocumentStatus {
+ if (x509Certificate == null) {
+ return MyEidDocumentStatus.UNKNOWN
+ }
+ return try {
+ x509Certificate.checkValidity()
+ MyEidDocumentStatus.VALID
+ } catch (cee: CertificateExpiredException) {
+ MyEidDocumentStatus.EXPIRED
+ } catch (cnyve: CertificateNotYetValidException) {
+ MyEidDocumentStatus.UNKNOWN
+ }
+}
diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/pinandcertificate/MyEidPinAndCertificateView.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/pinandcertificate/MyEidPinAndCertificateView.kt
index 05c2514e6..8dd341ae5 100644
--- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/pinandcertificate/MyEidPinAndCertificateView.kt
+++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/pinandcertificate/MyEidPinAndCertificateView.kt
@@ -42,6 +42,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import ee.ria.DigiDoc.R
+import ee.ria.DigiDoc.ui.component.myeid.mydata.MyEidDocumentStatus
import ee.ria.DigiDoc.ui.component.shared.HrefDynamicText
import ee.ria.DigiDoc.ui.theme.Dimensions.LINE_HEIGHT
import ee.ria.DigiDoc.ui.theme.Dimensions.SPadding
@@ -64,6 +65,7 @@ fun MyEidPinAndCertificateView(
isPinBlocked: Boolean = false,
isPukBlocked: Boolean = false,
isNotActivated: Boolean = false,
+ certificateStatus: MyEidDocumentStatus? = null,
showForgotPin: Boolean = true,
forgotPinText: String = "",
onForgotPinClick: (() -> Unit)? = null,
@@ -72,6 +74,15 @@ fun MyEidPinAndCertificateView(
) {
val spokenTitleAndSubtitle = spokenDates("$title. $subtitle").lowercase()
+ val isCertificateUsable = certificateStatus == null || certificateStatus == MyEidDocumentStatus.VALID
+ val showErrorIcon = !isCertificateUsable || isPinBlocked
+ val subtitleColor =
+ if (isCertificateUsable) {
+ MaterialTheme.colorScheme.onSurface
+ } else {
+ MaterialTheme.colorScheme.error
+ }
+
Column(
modifier =
modifier
@@ -101,9 +112,21 @@ fun MyEidPinAndCertificateView(
horizontalArrangement = Arrangement.Start,
) {
Icon(
- imageVector = ImageVector.vectorResource(R.drawable.ic_m3_check_circle_48dp_wght400),
+ imageVector =
+ ImageVector.vectorResource(
+ if (showErrorIcon) {
+ R.drawable.ic_m3_error_48dp_wght400
+ } else {
+ R.drawable.ic_m3_check_circle_48dp_wght400
+ },
+ ),
contentDescription = null,
- tint = MaterialTheme.colorScheme.primary,
+ tint =
+ if (showErrorIcon) {
+ MaterialTheme.colorScheme.error
+ } else {
+ MaterialTheme.colorScheme.primary
+ },
modifier =
modifier
.size(iconSizeM)
@@ -141,7 +164,7 @@ fun MyEidPinAndCertificateView(
newLineBeforeLink = true,
textStyle =
TextStyle(
- color = MaterialTheme.colorScheme.onSurface,
+ color = subtitleColor,
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
textAlign = TextAlign.Start,
lineHeight = TextUnit(LINE_HEIGHT, TextUnitType.Sp),
@@ -152,7 +175,7 @@ fun MyEidPinAndCertificateView(
modifier = modifier.notAccessible(),
text = subtitle,
style = MaterialTheme.typography.bodyMedium,
- color = MaterialTheme.colorScheme.onSurface,
+ color = subtitleColor,
)
}
}
@@ -241,6 +264,15 @@ fun MyEidPinAndCertificateViewPreview() {
MyEidPinAndCertificateView(
title = "Identity certificate",
subtitle = "Certificate is valid until ${LocalDate.now()}",
+ certificateStatus = MyEidDocumentStatus.VALID,
+ forgotPinText = "Forgot PIN?",
+ onForgotPinClick = {},
+ changePinText = "Change PIN",
+ )
+ MyEidPinAndCertificateView(
+ title = "Signing certificate",
+ subtitle = "Certificate has expired",
+ certificateStatus = MyEidDocumentStatus.EXPIRED,
forgotPinText = "Forgot PIN?",
onForgotPinClick = {},
changePinText = "Change PIN",
@@ -248,7 +280,9 @@ fun MyEidPinAndCertificateViewPreview() {
MyEidPinAndCertificateView(
title = "Identity certificate",
subtitle = "Certificate is valid until ${LocalDate.now()}",
- forgotPinText = "Forgot PIN?",
+ isPinBlocked = true,
+ certificateStatus = MyEidDocumentStatus.VALID,
+ forgotPinText = "Unblock PIN",
onForgotPinClick = {},
changePinText = "Change PIN",
)
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
index de87b963f..48482de5e 100644
--- a/app/src/main/res/values-et/strings.xml
+++ b/app/src/main/res/values-et/strings.xml
@@ -706,7 +706,7 @@
https://www.id.ee/artikkel/id-kaardi-pin-ja-puk-koodide-muutmine/
PUK on blokeeritud, kuna PUK-koodi on sisestatud 3 korda valesti. PUK-koodi ei saa ise lahti blokeerida.\n\nKuigi PUK-kood on blokeeritud, saab kõiki eID võimalusi kasutada, välja arvatud PUK-koodi vajavaid.\n\nUue PUK-koodi saamiseks tuleb taotleda uus dokument.
https://www.id.ee/artikkel/id-kaardi-pin-ja-puk-koodide-muutmine/
- Sertifikaat on <c>aegunud</c>! %1$s ei saa kasutada, kuna sertifikaat on aegunud.
+ %1$s ei saa kasutada, kuna sertifikaat on aegunud.
Uus %1$s-kood ei saa olla osa sinu isikukoodist
Uus %1$s-kood ei saa olla sinu sünnikuupäev
Uus %1$s-kood ei saa olla nii lihtne
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index edaed2d5b..0fba25de5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -706,7 +706,7 @@
https://www.id.ee/en/article/changing-id-card-pin-codes-and-puk-code/
PUK has been blocked because PUK code has been entered incorrectly 3 times. You can not unblock the PUK code yourself.\n\nAs long as the PUK code is blocked, all eID options can be used, except transactions that need PUK code.\n\nA new document must be requested to receive the new PUK code.
https://www.id.ee/en/article/changing-id-card-pin-codes-and-puk-code/
- Certificate has <c>expired</c>! %1$s cannot be used because the certificate has expired.
+ %1$s cannot be used because the certificate has expired.
New %1$s code can\'t be part of your personal code
New %1$s code can\'t be your date of birth
New %1$s code can\'t be so easy
diff --git a/app/src/test/kotlin/ee/ria/DigiDoc/ui/component/myeid/mydata/MyEidDocumentStatusTest.kt b/app/src/test/kotlin/ee/ria/DigiDoc/ui/component/myeid/mydata/MyEidDocumentStatusTest.kt
new file mode 100644
index 000000000..5cd9c53d9
--- /dev/null
+++ b/app/src/test/kotlin/ee/ria/DigiDoc/ui/component/myeid/mydata/MyEidDocumentStatusTest.kt
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: Estonian Information System Authority
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+@file:Suppress("PackageName")
+
+package ee.ria.DigiDoc.ui.component.myeid.mydata
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import java.security.cert.CertificateExpiredException
+import java.security.cert.CertificateNotYetValidException
+import java.security.cert.X509Certificate
+
+class MyEidDocumentStatusTest {
+ @Test
+ fun myEidDocumentStatus_certificateStatus_returnsUnknownWhenCertificateIsNull() {
+ assertEquals(MyEidDocumentStatus.UNKNOWN, certificateStatus(null))
+ }
+
+ @Test
+ fun myEidDocumentStatus_certificateStatus_returnsExpiredWhenCertificateHasExpired() {
+ val certificate = mock()
+ whenever(certificate.checkValidity()).thenThrow(CertificateExpiredException())
+
+ assertEquals(MyEidDocumentStatus.EXPIRED, certificateStatus(certificate))
+ }
+
+ @Test
+ fun myEidDocumentStatus_certificateStatus_returnsUnknownWhenCertificateIsNotYetValid() {
+ val certificate = mock()
+ whenever(certificate.checkValidity()).thenThrow(CertificateNotYetValidException())
+
+ assertEquals(MyEidDocumentStatus.UNKNOWN, certificateStatus(certificate))
+ }
+
+ @Test
+ fun myEidDocumentStatus_certificateStatus_returnsValidWhenCertificateIsWithinItsValidityPeriod() {
+ val certificate = mock()
+
+ assertEquals(MyEidDocumentStatus.VALID, certificateStatus(certificate))
+ }
+}