From 213c8c7d114c0c4d979f4a1283d713e0eddb6e41 Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 15 Jan 2024 22:01:35 +0100 Subject: [PATCH] Don't crash on certificates without subject alternative names --- .../usecase/FormatServerCertificateError.kt | 2 +- .../certificate/ui/ServerCertificateView.kt | 12 +++--- .../FormatServerCertificateErrorTest.kt | 41 +++++++++++++++++++ 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/feature/account/server/certificate/src/main/kotlin/app/k9mail/feature/account/server/certificate/domain/usecase/FormatServerCertificateError.kt b/feature/account/server/certificate/src/main/kotlin/app/k9mail/feature/account/server/certificate/domain/usecase/FormatServerCertificateError.kt index 2c6220a46..de89a6b6a 100644 --- a/feature/account/server/certificate/src/main/kotlin/app/k9mail/feature/account/server/certificate/domain/usecase/FormatServerCertificateError.kt +++ b/feature/account/server/certificate/src/main/kotlin/app/k9mail/feature/account/server/certificate/domain/usecase/FormatServerCertificateError.kt @@ -23,7 +23,7 @@ class FormatServerCertificateError( val notValidBeforeInstant = Instant.fromEpochMilliseconds(certificate.notBefore.time) val notValidAfterInstant = Instant.fromEpochMilliseconds(certificate.notAfter.time) - val subjectAlternativeNames = certificate.subjectAlternativeNames.map { it[1].toString() } + val subjectAlternativeNames = certificate.subjectAlternativeNames.orEmpty().map { it[1].toString() } val notValidBefore = dateFormat.format(Date(notValidBeforeInstant.toEpochMilliseconds())) val notValidAfter = dateFormat.format(Date(notValidAfterInstant.toEpochMilliseconds())) diff --git a/feature/account/server/certificate/src/main/kotlin/app/k9mail/feature/account/server/certificate/ui/ServerCertificateView.kt b/feature/account/server/certificate/src/main/kotlin/app/k9mail/feature/account/server/certificate/ui/ServerCertificateView.kt index cd01bc941..116b27ef7 100644 --- a/feature/account/server/certificate/src/main/kotlin/app/k9mail/feature/account/server/certificate/ui/ServerCertificateView.kt +++ b/feature/account/server/certificate/src/main/kotlin/app/k9mail/feature/account/server/certificate/ui/ServerCertificateView.kt @@ -39,12 +39,14 @@ internal fun ServerCertificateView( TextHeadline6(stringResource(R.string.account_server_certificate_section_title)) Spacer(modifier = Modifier.height(MainTheme.spacings.double)) - TextSubtitle2(stringResource(R.string.account_server_certificate_subject_alternative_names)) - for (subjectAlternativeName in serverCertificateProperties.subjectAlternativeNames) { - BulletedListItem(serverNameFormatter.format(subjectAlternativeName)) - } + if (serverCertificateProperties.subjectAlternativeNames.isNotEmpty()) { + TextSubtitle2(stringResource(R.string.account_server_certificate_subject_alternative_names)) + for (subjectAlternativeName in serverCertificateProperties.subjectAlternativeNames) { + BulletedListItem(serverNameFormatter.format(subjectAlternativeName)) + } - Spacer(modifier = Modifier.height(MainTheme.spacings.double)) + Spacer(modifier = Modifier.height(MainTheme.spacings.double)) + } TextSubtitle2(stringResource(R.string.account_server_certificate_not_valid_before)) TextBody1(text = serverCertificateProperties.notValidBefore) diff --git a/feature/account/server/certificate/src/test/kotlin/app/k9mail/feature/account/server/certificate/domain/usecase/FormatServerCertificateErrorTest.kt b/feature/account/server/certificate/src/test/kotlin/app/k9mail/feature/account/server/certificate/domain/usecase/FormatServerCertificateErrorTest.kt index 653b1a2ed..8771ab157 100644 --- a/feature/account/server/certificate/src/test/kotlin/app/k9mail/feature/account/server/certificate/domain/usecase/FormatServerCertificateErrorTest.kt +++ b/feature/account/server/certificate/src/test/kotlin/app/k9mail/feature/account/server/certificate/domain/usecase/FormatServerCertificateErrorTest.kt @@ -3,6 +3,7 @@ package app.k9mail.feature.account.server.certificate.domain.usecase import app.k9mail.feature.account.server.certificate.domain.entity.ServerCertificateError import app.k9mail.feature.account.server.certificate.domain.entity.ServerCertificateProperties import assertk.assertThat +import assertk.assertions.isEmpty import assertk.assertions.isEqualTo import java.security.cert.CertificateFactory import java.security.cert.X509Certificate @@ -51,6 +52,22 @@ class FormatServerCertificateErrorTest { ) } + @Test + fun `format certificate without subject alternative names`() { + val formatCertificateError = FormatServerCertificateError( + dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT, Locale.ROOT), + ) + val serverCertificateError = ServerCertificateError( + hostname = "10.0.0.1", + port = 993, + certificateChain = listOf(readCertificate(CERTIFICATE_WITHOUT_SAN)), + ) + + val result = formatCertificateError(serverCertificateError) + + assertThat(result.serverCertificateProperties.subjectAlternativeNames).isEmpty() + } + private fun readCertificate(asciiArmoredCertificate: String): X509Certificate { val inputStream = asciiArmoredCertificate.byteInputStream() @@ -92,5 +109,29 @@ class FormatServerCertificateErrorTest { RwxPuzZEaFZcVlmtqoq8 -----END CERTIFICATE----- """.trimIndent() + + val CERTIFICATE_WITHOUT_SAN = """ + -----BEGIN CERTIFICATE----- + MIIDfDCCAmSgAwIBAgIJAJB2iRjpM5OgMA0GCSqGSIb3DQEBCwUAME4xMTAvBgNV + BAsMKE5vIFNOSSBwcm92aWRlZDsgcGxlYXNlIGZpeCB5b3VyIGNsaWVudC4xGTAX + BgNVBAMTEGludmFsaWQyLmludmFsaWQwHhcNMTUwMTAxMDAwMDAwWhcNMzAwMTAx + MDAwMDAwWjBOMTEwLwYDVQQLDChObyBTTkkgcHJvdmlkZWQ7IHBsZWFzZSBmaXgg + eW91ciBjbGllbnQuMRkwFwYDVQQDExBpbnZhbGlkMi5pbnZhbGlkMIIBIjANBgkq + hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzWJP5cMThJgMBeTvRKKl7N6ZcZAbKDVA + tNBNnRhIgSitXxCzKtt9rp2RHkLn76oZjdNO25EPp+QgMiWU/rkkB00Y18Oahw5f + i8s+K9dRv6i+gSOiv2jlIeW/S0hOswUUDH0JXFkEPKILzpl5ML7wdp5kt93vHxa7 + HswOtAxEz2WtxMdezm/3CgO3sls20wl3W03iI+kCt7HyvhGy2aRPLhJfeABpQr0U + ku3q6mtomy2cgFawekN/X/aH8KknX799MPcuWutM2q88mtUEBsuZmy2nsjK9J7/y + hhCRDzOV/yY8c5+l/u/rWuwwkZ2lgzGp4xBBfhXdr6+m9kmwWCUm9QIDAQABo10w + WzAOBgNVHQ8BAf8EBAMCAqQwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC + MA8GA1UdEwEB/wQFMAMBAf8wGQYDVR0OBBIEELsPOJZvPr5PK0bQQWrUrLUwDQYJ + KoZIhvcNAQELBQADggEBALnZ4lRc9WHtafO4Y+0DWp4qgSdaGygzS/wtcRP+S2V+ + HFOCeYDmeZ9qs0WpNlrtyeBKzBH8hOt9y8aUbZBw2M1F2Mi23Q+dhAEUfQCOKbIT + tunBuVfDTTbAHUuNl/eyr78v8Egi133z7zVgydVG1KA0AOSCB+B65glbpx+xMCpg + ZLux9THydwg3tPo/LfYbRCof+Mb8I3ZCY9O6FfZGjuxJn+0ux3SDora3NX/FmJ+i + kTCTsMtIFWhH3hoyYAamOOuITpPZHD7yP0lfbuncGDEqAQu2YWbYxRixfq2VSxgv + gWbFcmkgBLYpE8iDWT3Kdluo1+6PHaDaLg2SacOY6Go= + -----END CERTIFICATE----- + """.trimIndent() } }