Skip to content

Commit

Permalink
Add X.509 Certificate chains viewer to the testapp using reusable Com…
Browse files Browse the repository at this point in the history
…pose components.

Tested manually with multiple certificates chain (previously defined in tests), as well as with local tests.

Signed-off-by: koukarine <[email protected]>
  • Loading branch information
koukarine committed Jan 24, 2025
1 parent 8bd5402 commit facf3b1
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,14 @@
<string name="k_issued">Issued</string>
<string name="k_expired">Expired</string>
<string name="sub_subject">Subject</string>
<string name="sub_issuer">Issuer</string>
<string name="k_country_name">Country Name</string>
<string name="k_common_name">Common Name</string>
<string name="k_organization">Organization</string>
<string name="sub_issuer">Issuer</string>
<string name="k_org_name">Organization</string>
<string name="k_org_unit_name">Organizational Unit</string>
<string name="k_locality_name">Locality</string>
<string name="k_state_name">State or Province</string>
<string name="k_other_name">Other Name</string>
<string name="sub_public_key_info">Public Key Information</string>
<string name="k_pk_algorithm">Algorithm</string>
<string name="k_pk_named_curve">Named Curve</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
Expand All @@ -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<String, String> = emptyMap(),
val issuer: Map<String, String> = emptyMap(),
val pkAlgorithm: String,
val pkNamedCurve: String,
val pkValue: String,
Expand All @@ -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) {
Expand All @@ -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 }
Expand All @@ -88,19 +80,24 @@ internal data class CertificateViewData(
version,
issued,
expired,
subjectCountry,
subjectCommonName,
subjectOrg,
issuerCountry,
issuerCommonName,
issuerOrg,
subject,
issuer,
pkAlgorithm,
pkNamedCurve,
pkValue,
extensionsMap
)
}

private fun refineKeyNames(components: Map<String, ASN1String>): Map<String, String> =
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}

0 comments on commit facf3b1

Please sign in to comment.