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 +} +