diff --git a/identity-appsupport/src/commonMain/composeResources/values/strings.xml b/identity-appsupport/src/commonMain/composeResources/values/strings.xml
index f023c540b..1116060cc 100644
--- a/identity-appsupport/src/commonMain/composeResources/values/strings.xml
+++ b/identity-appsupport/src/commonMain/composeResources/values/strings.xml
@@ -36,10 +36,14 @@
Issued
Expired
Subject
+ Issuer
Country Name
Common Name
- Organization
- Issuer
+ Organization
+ Organizational Unit
+ Locality
+ State or Province
+ Other Name
Public Key Information
Algorithm
Named Curve
diff --git a/identity-appsupport/src/commonMain/kotlin/com/android/identity/appsupport/ui/certificateviewer/CertificateViewData.kt b/identity-appsupport/src/commonMain/kotlin/com/android/identity/appsupport/ui/certificateviewer/CertificateViewData.kt
index f70d7367e..75bee5ea5 100644
--- a/identity-appsupport/src/commonMain/kotlin/com/android/identity/appsupport/ui/certificateviewer/CertificateViewData.kt
+++ b/identity-appsupport/src/commonMain/kotlin/com/android/identity/appsupport/ui/certificateviewer/CertificateViewData.kt
@@ -1,5 +1,6 @@
package com.android.identity.appsupport.ui.certificateviewer
+import com.android.identity.asn1.ASN1String
import com.android.identity.asn1.OID
import com.android.identity.crypto.X509Cert
import com.android.identity.util.unsignedBigIntToString
@@ -9,6 +10,8 @@ import kotlinx.datetime.TimeZone
import kotlinx.datetime.format
import kotlinx.datetime.format.DateTimeComponents
import kotlinx.datetime.offsetAt
+import kotlin.collections.mapValues
+import kotlin.text.toIntOrNull
/**
* View Model immutable data.
@@ -21,12 +24,8 @@ internal data class CertificateViewData(
val version: String,
val issued: String,
val expired: String,
- val subjectCountry: String,
- val subjectCommonName: String,
- val subjectOrg: String,
- val issuerCountry: String,
- val issuerCommonName: String,
- val issuerOrg: String,
+ val subject: Map = emptyMap(),
+ val issuer: Map = emptyMap(),
val pkAlgorithm: String,
val pkNamedCurve: String,
val pkValue: String,
@@ -36,8 +35,9 @@ internal data class CertificateViewData(
companion object {
/** Map Certificate data to View fields. */
- fun from(cert: X509Cert) : CertificateViewData {
- val notAvail = "" // TODO: Can't populate from data marker. Process properly in Composables.
+ fun from(cert: X509Cert): CertificateViewData {
+ val notAvail =
+ "" // TODO: Can't populate from data marker. Process properly in Composables.
val type = "X.509"
val serialNumber: String = with(cert.serialNumber) {
@@ -54,17 +54,9 @@ internal data class CertificateViewData(
val expired = cert.validityNotAfter.formatWithIsoFormat()
- val subjectCountry = cert.subject.components[OID.COUNTRY_NAME.oid]?.value ?: notAvail
+ val subject = refineKeyNames(cert.subject.components)
- val subjectCommonName = cert.subject.components[OID.COMMON_NAME.oid]?.value ?: notAvail
-
- val subjectOrg = cert.subject.components[OID.ORGANIZATION_NAME.oid]?.value ?: notAvail
-
- val issuerCountry = cert.issuer.components[OID.COUNTRY_NAME.oid]?.value ?: notAvail
-
- val issuerCommonName = cert.issuer.components[OID.COMMON_NAME.oid]?.value ?: notAvail
-
- val issuerOrg = cert.issuer.components[OID.ORGANIZATION_NAME.oid]?.value ?: notAvail
+ val issuer = refineKeyNames(cert.issuer.components)
val pkAlgorithm: String = runCatching { cert.signatureAlgorithm.name }
.getOrElse { e -> e.message ?: notAvail }
@@ -88,12 +80,8 @@ internal data class CertificateViewData(
version,
issued,
expired,
- subjectCountry,
- subjectCommonName,
- subjectOrg,
- issuerCountry,
- issuerCommonName,
- issuerOrg,
+ subject,
+ issuer,
pkAlgorithm,
pkNamedCurve,
pkValue,
@@ -101,6 +89,15 @@ internal data class CertificateViewData(
)
}
+ private fun refineKeyNames(components: Map): Map =
+ components
+ .mapKeys { (key, _) ->
+ key.substringBefore("=", key)
+ }
+ .mapValues { (_, value) ->
+ value.value
+ }
+
/**
* The Date Time format can be customized by creating specific DateTimeComponents<> structure
* as needed for KMP compatibility.
diff --git a/identity-appsupport/src/commonMain/kotlin/com/android/identity/appsupport/ui/certificateviewer/CertificateViewer.kt b/identity-appsupport/src/commonMain/kotlin/com/android/identity/appsupport/ui/certificateviewer/CertificateViewer.kt
index b1736eadb..7be299563 100644
--- a/identity-appsupport/src/commonMain/kotlin/com/android/identity/appsupport/ui/certificateviewer/CertificateViewer.kt
+++ b/identity-appsupport/src/commonMain/kotlin/com/android/identity/appsupport/ui/certificateviewer/CertificateViewer.kt
@@ -32,6 +32,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.android.identity.appsupport.ui.certificateviewer.CertificateViewData.Companion.from
import com.android.identity.asn1.ASN1Integer
+import com.android.identity.asn1.OID
import com.android.identity.crypto.Crypto
import com.android.identity.crypto.EcCurve
import com.android.identity.crypto.X500Name
@@ -44,11 +45,15 @@ import identitycredential.identity_appsupport.generated.resources.k_common_name
import identitycredential.identity_appsupport.generated.resources.k_country_name
import identitycredential.identity_appsupport.generated.resources.k_expired
import identitycredential.identity_appsupport.generated.resources.k_issued
-import identitycredential.identity_appsupport.generated.resources.k_organization
+import identitycredential.identity_appsupport.generated.resources.k_locality_name
+import identitycredential.identity_appsupport.generated.resources.k_org_name
+import identitycredential.identity_appsupport.generated.resources.k_org_unit_name
+import identitycredential.identity_appsupport.generated.resources.k_other_name
import identitycredential.identity_appsupport.generated.resources.k_pk_algorithm
import identitycredential.identity_appsupport.generated.resources.k_pk_named_curve
import identitycredential.identity_appsupport.generated.resources.k_pk_value
import identitycredential.identity_appsupport.generated.resources.k_serial_number
+import identitycredential.identity_appsupport.generated.resources.k_state_name
import identitycredential.identity_appsupport.generated.resources.k_type
import identitycredential.identity_appsupport.generated.resources.k_version
import identitycredential.identity_appsupport.generated.resources.no_certificates_in_chain
@@ -59,6 +64,7 @@ import identitycredential.identity_appsupport.generated.resources.sub_public_key
import identitycredential.identity_appsupport.generated.resources.sub_subject
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
+import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.ui.tooling.preview.Preview
import kotlin.time.Duration.Companion.hours
@@ -200,15 +206,19 @@ private fun CertificateView(
KeyValuePairLine(stringResource(Res.string.k_issued), data.issued)
KeyValuePairLine(stringResource(Res.string.k_expired), data.expired)
- Subtitle(stringResource(Res.string.sub_subject))
- KeyValuePairLine(stringResource(Res.string.k_country_name), data.subjectCountry)
- KeyValuePairLine(stringResource(Res.string.k_common_name), data.subjectCommonName)
- KeyValuePairLine(stringResource(Res.string.k_organization), data.subjectOrg)
+ if (data.subject.isNotEmpty()) {
+ Subtitle(stringResource(Res.string.sub_subject))
+ data.subject.forEach { (key, value) ->
+ KeyValuePairLine(stringResource(resFromKey(key)), value)
+ }
+ }
- Subtitle(stringResource(Res.string.sub_issuer))
- KeyValuePairLine(stringResource(Res.string.k_country_name), data.issuerCountry)
- KeyValuePairLine(stringResource(Res.string.k_common_name), data.issuerCommonName)
- KeyValuePairLine(stringResource(Res.string.k_organization), data.issuerOrg)
+ if (data.issuer.isNotEmpty()) {
+ Subtitle(stringResource(Res.string.sub_issuer))
+ data.subject.forEach { (key, value) ->
+ KeyValuePairLine(stringResource(resFromKey(key)), value)
+ }
+ }
Subtitle(stringResource(Res.string.sub_public_key_info))
KeyValuePairLine(stringResource(Res.string.k_pk_algorithm), data.pkAlgorithm)
@@ -382,3 +392,15 @@ private fun validateParameters(
}
}
+private fun resFromKey(it: String): StringResource {
+ val nMap = mapOf(
+ OID.COMMON_NAME.oid to Res.string.k_common_name,
+ OID.COUNTRY_NAME.oid to Res.string.k_country_name,
+ OID.LOCALITY_NAME.oid to Res.string.k_locality_name,
+ OID.STATE_OR_PROVINCE_NAME.oid to Res.string.k_state_name,
+ OID.ORGANIZATION_NAME.oid to Res.string.k_org_name,
+ OID.ORGANIZATIONAL_UNIT_NAME.oid to Res.string.k_org_unit_name,
+ )
+ return nMap[it] ?: Res.string.k_other_name
+}
+