From 5ea62624bca0756fc0f28a4aac5cfc67aefd189f Mon Sep 17 00:00:00 2001 From: Sunny Chung Date: Thu, 14 Nov 2024 17:54:21 +0800 Subject: [PATCH 1/8] refactor PKIX-related code to a new source code file --- .../hellohttp/model/Environment.kt | 139 ---------------- .../multiplatform/hellohttp/util/Pkix.kt | 152 ++++++++++++++++++ .../SubprojectEnvironmentsEditorDialogView.kt | 4 +- .../hellohttp/test/SslConfigTest.kt | 2 +- .../hellohttp/test/SslContextTest.kt | 6 +- 5 files changed, 158 insertions(+), 145 deletions(-) create mode 100644 src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/model/Environment.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/model/Environment.kt index 4de8ab78..ceae5da0 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/model/Environment.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/model/Environment.kt @@ -4,27 +4,9 @@ import com.sunnychung.application.multiplatform.hellohttp.annotation.Persisted import com.sunnychung.application.multiplatform.hellohttp.document.Identifiable import com.sunnychung.application.multiplatform.hellohttp.util.uuidString import com.sunnychung.application.multiplatform.hellohttp.ux.DropDownable -import com.sunnychung.lib.multiplatform.kdatetime.KDateTimeFormat import com.sunnychung.lib.multiplatform.kdatetime.KInstant -import com.sunnychung.lib.multiplatform.kdatetime.KZoneOffset -import com.sunnychung.lib.multiplatform.kdatetime.KZonedInstant import com.sunnychung.lib.multiplatform.kdatetime.extension.seconds import kotlinx.serialization.Serializable -import java.io.ByteArrayInputStream -import java.io.File -import java.io.InputStream -import java.io.InputStreamReader -import java.security.KeyFactory -import java.security.cert.CertificateFactory -import java.security.cert.X509Certificate -import java.security.spec.InvalidKeySpecException -import java.security.spec.PKCS8EncodedKeySpec -import java.util.Base64 -import javax.crypto.Cipher -import javax.crypto.EncryptedPrivateKeyInfo -import javax.crypto.SecretKeyFactory -import javax.crypto.spec.PBEKeySpec -import javax.security.auth.x500.X500Principal @Persisted @Serializable @@ -127,124 +109,3 @@ data class ClientCertificateKeyPair( companion object } - -fun ClientCertificateKeyPair.Companion.importFrom(certFile: File, keyFile: File, keyPassword: String): ClientCertificateKeyPair { - listOf(certFile, keyFile).forEach { file -> - if (!file.canRead()) { - throw IllegalArgumentException("File ${file.name} cannot be read.") - } - } - - val cert: X509Certificate = try { - val certBytes = certFile.readBytes() - parseCaCertificates(certBytes).also { - if (it.size > 1) { - throw RuntimeException("There should be only one certificate but ${it.size} were found.") - } else if (it.isEmpty()) { - throw RuntimeException("No certificate was found.") - } - }.single() - } catch (e: Throwable) { - throw RuntimeException("Error while parsing the certificate file -- ${e.message}", e) - } - - fun decryptAsRsaKeySpec(keyBytes: ByteArray, password: String): PKCS8EncodedKeySpec { - return EncryptedPrivateKeyInfo(keyBytes) - .let { - val secretKey = SecretKeyFactory.getInstance(it.algName).generateSecret(PBEKeySpec(password.toCharArray())) - val cipher = Cipher.getInstance(it.algName) - cipher.init(Cipher.DECRYPT_MODE, secretKey, it.algParameters) - val keySpec = it.getKeySpec(cipher) - - // try if all these work - KeyFactory.getInstance("RSA").generatePrivate(keySpec) - - keySpec - } - } - - val keyBytes = keyFile.readBytes() - val keySpec = try { - if (keyPassword.isEmpty()) { - // try without password. if it's fail, try with empty password - try { - val keySpec = PKCS8EncodedKeySpec(keyBytes) - KeyFactory.getInstance("RSA").generatePrivate(keySpec) - keySpec - } catch (e: InvalidKeySpecException) { - decryptAsRsaKeySpec(keyBytes = keyBytes, keyPassword) - } - } else { - decryptAsRsaKeySpec(keyBytes = keyBytes, keyPassword) - } - } catch (e: Throwable) { - throw RuntimeException("Error while parsing the private key file -- ${e.message}", e) - } - - val now = KInstant.now() - return ClientCertificateKeyPair( - id = uuidString(), - certificate = ImportedFile( - id = uuidString(), - name = cert.subjectX500Principal.getName(X500Principal.RFC1779) + - "\nExpiry: ${KZonedInstant(cert.notAfter.time, KZoneOffset.local()).format(KDateTimeFormat.ISO8601_DATETIME.pattern)}", - originalFilename = certFile.name, - createdWhen = now, - isEnabled = true, - content = cert.encoded, - ), - privateKey = ImportedFile( - id = uuidString(), - name = "Private Key", - originalFilename = keyFile.name, - createdWhen = now, - isEnabled = true, - content = keySpec.encoded, // store decrypted bytes - ), - createdWhen = now, - isEnabled = true, - ) -} - -// ----------- TODO refactor to a separate file ----------- - -fun parseCaCertificates(bytes: ByteArray) : List { - InputStreamReader(ByteArrayInputStream(bytes)).buffered().use { reader -> - val firstLine = reader.readLine() - val certBytes = if (firstLine == "-----BEGIN PKCS7-----") { // p7b - val base64Encoded = buildString { - while (reader.ready()) { - val line = reader.readLine() - if (line != "-----END PKCS7-----") { - append(line) - } else { - break - } - } - } - Base64.getDecoder().decode(base64Encoded) - } else { // der / pem - bytes - } - return CertificateFactory.getInstance("X.509").generateCertificates(ByteArrayInputStream(certBytes)).map { it as X509Certificate } - } -} - -fun importCaCertificates(file: File): List { - val content = file.readBytes() - val certs = parseCaCertificates(content) - - return certs.map { cert -> - ImportedFile( - id = uuidString(), - name = cert.subjectX500Principal.getName(X500Principal.RFC1779) + - "\nExpiry: ${KZonedInstant(cert.notAfter.time, KZoneOffset.local()).format(KDateTimeFormat.ISO8601_DATETIME.pattern)}" + - if (cert.keyUsage?.get(5) != true || cert.basicConstraints < 0) "\n⚠️ Not a CA certificate!" else "" - , - originalFilename = file.name, - createdWhen = KInstant.now(), - isEnabled = true, - content = cert.encoded, - ) - } -} diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt new file mode 100644 index 00000000..8dd4a734 --- /dev/null +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt @@ -0,0 +1,152 @@ +package com.sunnychung.application.multiplatform.hellohttp.util + +import com.sunnychung.application.multiplatform.hellohttp.model.ClientCertificateKeyPair +import com.sunnychung.application.multiplatform.hellohttp.model.ImportedFile +import com.sunnychung.lib.multiplatform.kdatetime.KDateTimeFormat +import com.sunnychung.lib.multiplatform.kdatetime.KInstant +import com.sunnychung.lib.multiplatform.kdatetime.KZoneOffset +import com.sunnychung.lib.multiplatform.kdatetime.KZonedInstant +import java.io.ByteArrayInputStream +import java.io.File +import java.io.InputStreamReader +import java.security.KeyFactory +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate +import java.security.spec.InvalidKeySpecException +import java.security.spec.PKCS8EncodedKeySpec +import java.util.Base64 +import javax.crypto.Cipher +import javax.crypto.EncryptedPrivateKeyInfo +import javax.crypto.SecretKeyFactory +import javax.crypto.spec.PBEKeySpec +import javax.security.auth.x500.X500Principal + +fun ClientCertificateKeyPair.Companion.importFrom(certFile: File, keyFile: File, keyPassword: String): ClientCertificateKeyPair { + listOf(certFile, keyFile).forEach { file -> + if (!file.canRead()) { + throw IllegalArgumentException("File ${file.name} cannot be read.") + } + } + + val cert: X509Certificate = try { + val certBytes = certFile.readBytes() + parseCaCertificates(certBytes).also { + if (it.size > 1) { + throw RuntimeException("There should be only one certificate but ${it.size} were found.") + } else if (it.isEmpty()) { + throw RuntimeException("No certificate was found.") + } + }.single() + } catch (e: Throwable) { + throw RuntimeException("Error while parsing the certificate file -- ${e.message}", e) + } + + fun decryptAsRsaKeySpec(keyBytes: ByteArray, password: String): PKCS8EncodedKeySpec { + return EncryptedPrivateKeyInfo(keyBytes) + .let { + val secretKey = SecretKeyFactory.getInstance(it.algName) + .generateSecret(PBEKeySpec(password.toCharArray())) + val cipher = Cipher.getInstance(it.algName) + cipher.init(Cipher.DECRYPT_MODE, secretKey, it.algParameters) + val keySpec = it.getKeySpec(cipher) + + // try if all these work + KeyFactory.getInstance("RSA").generatePrivate(keySpec) + + keySpec + } + } + + val keyBytes = keyFile.readBytes() + val keySpec = try { + if (keyPassword.isEmpty()) { + // try without password. if it's fail, try with empty password + try { + val keySpec = PKCS8EncodedKeySpec(keyBytes) + KeyFactory.getInstance("RSA").generatePrivate(keySpec) + keySpec + } catch (e: InvalidKeySpecException) { + decryptAsRsaKeySpec(keyBytes = keyBytes, keyPassword) + } + } else { + decryptAsRsaKeySpec(keyBytes = keyBytes, keyPassword) + } + } catch (e: Throwable) { + throw RuntimeException("Error while parsing the private key file -- ${e.message}", e) + } + + val now = KInstant.now() + return ClientCertificateKeyPair( + id = uuidString(), + certificate = ImportedFile( + id = uuidString(), + name = cert.subjectX500Principal.getName(X500Principal.RFC1779) + + "\nExpiry: ${ + KZonedInstant( + cert.notAfter.time, + KZoneOffset.local() + ).format(KDateTimeFormat.ISO8601_DATETIME.pattern) + }", + originalFilename = certFile.name, + createdWhen = now, + isEnabled = true, + content = cert.encoded, + ), + privateKey = ImportedFile( + id = uuidString(), + name = "Private Key", + originalFilename = keyFile.name, + createdWhen = now, + isEnabled = true, + content = keySpec.encoded, // store decrypted bytes + ), + createdWhen = now, + isEnabled = true, + ) +} + +fun parseCaCertificates(bytes: ByteArray) : List { + InputStreamReader(ByteArrayInputStream(bytes)).buffered().use { reader -> + val firstLine = reader.readLine() + val certBytes = if (firstLine == "-----BEGIN PKCS7-----") { // p7b + val base64Encoded = buildString { + while (reader.ready()) { + val line = reader.readLine() + if (line != "-----END PKCS7-----") { + append(line) + } else { + break + } + } + } + Base64.getDecoder().decode(base64Encoded) + } else { // der / pem + bytes + } + return CertificateFactory.getInstance("X.509") + .generateCertificates(ByteArrayInputStream(certBytes)).map { it as X509Certificate } + } +} + +fun importCaCertificates(file: File): List { + val content = file.readBytes() + val certs = parseCaCertificates(content) + + return certs.map { cert -> + ImportedFile( + id = uuidString(), + name = cert.subjectX500Principal.getName(X500Principal.RFC1779) + + "\nExpiry: ${ + KZonedInstant( + cert.notAfter.time, + KZoneOffset.local() + ).format(KDateTimeFormat.ISO8601_DATETIME.pattern) + }" + + if (cert.keyUsage?.get(5) != true || cert.basicConstraints < 0) "\n⚠️ Not a CA certificate!" else "", + originalFilename = file.name, + createdWhen = KInstant.now(), + isEnabled = true, + content = cert.encoded, + ) + } +} \ No newline at end of file diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt index 8912117b..3419c0b5 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt @@ -45,8 +45,8 @@ import com.sunnychung.application.multiplatform.hellohttp.model.HttpConfig import com.sunnychung.application.multiplatform.hellohttp.model.ImportedFile import com.sunnychung.application.multiplatform.hellohttp.model.Subproject import com.sunnychung.application.multiplatform.hellohttp.model.UserKeyValuePair -import com.sunnychung.application.multiplatform.hellohttp.model.importCaCertificates -import com.sunnychung.application.multiplatform.hellohttp.model.importFrom +import com.sunnychung.application.multiplatform.hellohttp.util.importCaCertificates +import com.sunnychung.application.multiplatform.hellohttp.util.importFrom import com.sunnychung.application.multiplatform.hellohttp.util.copyWithChange import com.sunnychung.application.multiplatform.hellohttp.util.copyWithIndexedChange import com.sunnychung.application.multiplatform.hellohttp.util.copyWithRemoval diff --git a/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/SslConfigTest.kt b/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/SslConfigTest.kt index 85edf465..c8c9cc38 100644 --- a/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/SslConfigTest.kt +++ b/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/SslConfigTest.kt @@ -3,7 +3,7 @@ package com.sunnychung.application.multiplatform.hellohttp.test import com.sunnychung.application.multiplatform.hellohttp.manager.NetworkClientManager import com.sunnychung.application.multiplatform.hellohttp.model.ClientCertificateKeyPair import com.sunnychung.application.multiplatform.hellohttp.model.SslConfig -import com.sunnychung.application.multiplatform.hellohttp.model.importFrom +import com.sunnychung.application.multiplatform.hellohttp.util.importFrom import com.sunnychung.application.multiplatform.hellohttp.network.ApacheHttpTransportClient import java.io.File import java.io.IOException diff --git a/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/SslContextTest.kt b/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/SslContextTest.kt index e9f15dfc..a9c078aa 100644 --- a/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/SslContextTest.kt +++ b/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/SslContextTest.kt @@ -3,9 +3,9 @@ package com.sunnychung.application.multiplatform.hellohttp.test import com.sunnychung.application.multiplatform.hellohttp.manager.NetworkClientManager import com.sunnychung.application.multiplatform.hellohttp.model.ClientCertificateKeyPair import com.sunnychung.application.multiplatform.hellohttp.model.SslConfig -import com.sunnychung.application.multiplatform.hellohttp.model.importCaCertificates -import com.sunnychung.application.multiplatform.hellohttp.model.importFrom -import com.sunnychung.application.multiplatform.hellohttp.model.parseCaCertificates +import com.sunnychung.application.multiplatform.hellohttp.util.importCaCertificates +import com.sunnychung.application.multiplatform.hellohttp.util.importFrom +import com.sunnychung.application.multiplatform.hellohttp.util.parseCaCertificates import com.sunnychung.application.multiplatform.hellohttp.network.ApacheHttpTransportClient import com.sunnychung.application.multiplatform.hellohttp.network.util.DenyAllSslCertificateManager import com.sunnychung.application.multiplatform.hellohttp.network.util.MultipleTrustCertificateManager From 16588fe511a202a7397d0193a348870dc1a9e692 Mon Sep 17 00:00:00 2001 From: Sunny Chung Date: Thu, 14 Nov 2024 18:04:48 +0800 Subject: [PATCH 2/8] fix test --- .../multiplatform/hellohttp/test/bigtext/BigTextVerifyImpl.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/bigtext/BigTextVerifyImpl.kt b/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/bigtext/BigTextVerifyImpl.kt index cd907936..fb0cd7f7 100644 --- a/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/bigtext/BigTextVerifyImpl.kt +++ b/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/bigtext/BigTextVerifyImpl.kt @@ -109,6 +109,10 @@ internal class BigTextVerifyImpl(bigTextImpl: BigTextImpl) : BigText { return r } + override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { + return substring(startIndex, endIndex) + } + override fun findLineString(lineIndex: Int): CharSequence { TODO("Not yet implemented") } From fca67ca3e32046f9d0d91a79d358d59f1c4ddc32 Mon Sep 17 00:00:00 2001 From: Sunny Chung Date: Thu, 14 Nov 2024 18:15:43 +0800 Subject: [PATCH 3/8] refactor to extract the logic of parsing private keys as global util functions --- .../multiplatform/hellohttp/util/Pkix.kt | 64 ++++++++++--------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt index 8dd4a734..d052eabd 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt @@ -10,6 +10,7 @@ import java.io.ByteArrayInputStream import java.io.File import java.io.InputStreamReader import java.security.KeyFactory +import java.security.PrivateKey import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.security.spec.InvalidKeySpecException @@ -41,36 +42,9 @@ fun ClientCertificateKeyPair.Companion.importFrom(certFile: File, keyFile: File, throw RuntimeException("Error while parsing the certificate file -- ${e.message}", e) } - fun decryptAsRsaKeySpec(keyBytes: ByteArray, password: String): PKCS8EncodedKeySpec { - return EncryptedPrivateKeyInfo(keyBytes) - .let { - val secretKey = SecretKeyFactory.getInstance(it.algName) - .generateSecret(PBEKeySpec(password.toCharArray())) - val cipher = Cipher.getInstance(it.algName) - cipher.init(Cipher.DECRYPT_MODE, secretKey, it.algParameters) - val keySpec = it.getKeySpec(cipher) - - // try if all these work - KeyFactory.getInstance("RSA").generatePrivate(keySpec) - - keySpec - } - } - val keyBytes = keyFile.readBytes() - val keySpec = try { - if (keyPassword.isEmpty()) { - // try without password. if it's fail, try with empty password - try { - val keySpec = PKCS8EncodedKeySpec(keyBytes) - KeyFactory.getInstance("RSA").generatePrivate(keySpec) - keySpec - } catch (e: InvalidKeySpecException) { - decryptAsRsaKeySpec(keyBytes = keyBytes, keyPassword) - } - } else { - decryptAsRsaKeySpec(keyBytes = keyBytes, keyPassword) - } + val privateKey = try { + parsePrivateKey(keyBytes = keyBytes, keyPassword = keyPassword) } catch (e: Throwable) { throw RuntimeException("Error while parsing the private key file -- ${e.message}", e) } @@ -98,13 +72,43 @@ fun ClientCertificateKeyPair.Companion.importFrom(certFile: File, keyFile: File, originalFilename = keyFile.name, createdWhen = now, isEnabled = true, - content = keySpec.encoded, // store decrypted bytes + content = privateKey.encoded, // store decrypted bytes ), createdWhen = now, isEnabled = true, ) } +private fun parsePrivateKey(keyBytes: ByteArray, keyPassword: String): PrivateKey = if (keyPassword.isEmpty()) { + // try without password. if it's fail, try with empty password + try { + decodeUnencryptedPrivateKey(keyBytes) + } catch (e: InvalidKeySpecException) { + decryptAsPrivateKey(keyBytes = keyBytes, keyPassword) + } +} else { + decryptAsPrivateKey(keyBytes = keyBytes, keyPassword) +} + +fun decodeUnencryptedPrivateKey(keyBytes: ByteArray): PrivateKey { + val keySpec = PKCS8EncodedKeySpec(keyBytes) + return KeyFactory.getInstance("RSA").generatePrivate(keySpec) +} + +fun decryptAsPrivateKey(keyBytes: ByteArray, password: String): PrivateKey { + return EncryptedPrivateKeyInfo(keyBytes) + .let { + val secretKey = SecretKeyFactory.getInstance(it.algName) + .generateSecret(PBEKeySpec(password.toCharArray())) + val cipher = Cipher.getInstance(it.algName) + cipher.init(Cipher.DECRYPT_MODE, secretKey, it.algParameters) + val keySpec = it.getKeySpec(cipher) + + // try if all these work + KeyFactory.getInstance("RSA").generatePrivate(keySpec) + } +} + fun parseCaCertificates(bytes: ByteArray) : List { InputStreamReader(ByteArrayInputStream(bytes)).buffered().use { reader -> val firstLine = reader.readLine() From 6771ea15389b56a29219643289c098fc7b2883db Mon Sep 17 00:00:00 2001 From: Sunny Chung Date: Thu, 14 Nov 2024 23:45:33 +0800 Subject: [PATCH 4/8] add support of PEM and PKCS#1 formats to private key imports, and update supported PKCS#8 private key algorithms not to limit to RSA --- CHANGELOG.md | 3 +- build.gradle.kts | 4 + doc/features/ssl-configuration.md | 16 ++-- .../multiplatform/hellohttp/util/Pkix.kt | 90 +++++++++++++++--- .../SubprojectEnvironmentsEditorDialogView.kt | 2 +- .../hellohttp/test/SslConfigTest.kt | 40 +++++++- src/jvmTest/resources/ssl/clientCert.pem | 32 +++++++ src/jvmTest/resources/ssl/clientKey.pkcs1.der | Bin 0 -> 2348 bytes src/jvmTest/resources/ssl/clientKey.pkcs1.pem | 51 ++++++++++ .../ssl/clientKey.pkcs8.emptyencrypted.pem | 53 +++++++++++ .../ssl/clientKey.pkcs8.encrypted.pem | 53 +++++++++++ src/jvmTest/resources/ssl/clientKey.pkcs8.pem | 52 ++++++++++ .../src/main/resources/tls/clientCert.p7b | 33 +++++++ .../tls/clientKey.pkcs8.emptyencrypted.pem | 53 +++++++++++ .../tls/clientKey.pkcs8.encrypted.pem | 53 +++++++++++ .../main/resources/tls/clientKey.pkcs8.pem | 52 ++++++++++ .../src/main/resources/tls/serverCACert.p7b | 34 +++++++ .../main/resources/tls/serverCertChain.p7b | 68 +++++++++++++ 18 files changed, 661 insertions(+), 28 deletions(-) create mode 100644 src/jvmTest/resources/ssl/clientCert.pem create mode 100644 src/jvmTest/resources/ssl/clientKey.pkcs1.der create mode 100644 src/jvmTest/resources/ssl/clientKey.pkcs1.pem create mode 100644 src/jvmTest/resources/ssl/clientKey.pkcs8.emptyencrypted.pem create mode 100644 src/jvmTest/resources/ssl/clientKey.pkcs8.encrypted.pem create mode 100644 src/jvmTest/resources/ssl/clientKey.pkcs8.pem create mode 100644 test-common/src/main/resources/tls/clientCert.p7b create mode 100644 test-common/src/main/resources/tls/clientKey.pkcs8.emptyencrypted.pem create mode 100644 test-common/src/main/resources/tls/clientKey.pkcs8.encrypted.pem create mode 100644 test-common/src/main/resources/tls/clientKey.pkcs8.pem create mode 100644 test-common/src/main/resources/tls/serverCACert.p7b create mode 100644 test-common/src/main/resources/tls/serverCertChain.p7b diff --git a/CHANGELOG.md b/CHANGELOG.md index cdac7f38..5338a8ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - Mouse hovering variable placeholders in Body Editor to show a tooltip for its value (if exists) - Number badges in Request Parameter Type tabs to indicate the number of active entries declared in the selected example, e.g. the number of active key-value pairs of a multipart request body declared in the selected Request Example. - Certificates in P7B (PKCS#7) format can now be imported +- Private keys in PEM or PKCS#1 formats can now be imported, and does not limit to RSA keys anymore. ### Changed - Importing CA certificates now imports all the certificates from an input file @@ -28,7 +29,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - The copy button overlapped with the search bar in the response body viewer. ### Optimized -- Request body editor, payload body editor and response body viewer are now able to handle bodies with a size of megabytes without noticeable performance issues. +- Request body editor, payload body editor and response body viewer are now able to handle bodies with a size of megabytes without significant performance issues. - Clicking the "Send" button now never freeze for a short while. ## [1.6.0] - 2024-07-22 diff --git a/build.gradle.kts b/build.gradle.kts index a4147911..9f6e1395 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -87,6 +87,9 @@ kotlin { implementation("io.github.tree-sitter:ktreesitter:0.23.0") implementation("io.github.sunny-chung:ktreesitter-json:0.23.0.1") implementation("io.github.sunny-chung:ktreesitter-graphql:1.0.0.0") + + // public/private key decoding + implementation("org.bouncycastle:bcpkix-jdk18on:1.79") } resources.srcDir("$buildDir/resources") @@ -96,6 +99,7 @@ kotlin { implementation(kotlin("test")) implementation("org.junit.jupiter:junit-jupiter-params") implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.15.2") + implementation(project(":test-common")) } } } diff --git a/doc/features/ssl-configuration.md b/doc/features/ssl-configuration.md index b2d95153..2c4784ca 100644 --- a/doc/features/ssl-configuration.md +++ b/doc/features/ssl-configuration.md @@ -20,6 +20,8 @@ Currently, the accepted formats are: - PEM (also known as CER or CRT) - P7B +The formats are detected automatically. + Imported certificates can be disabled by unchecking the corresponding green tick box, or deleted. Changes to the original file would not affect imported ones. @@ -44,16 +46,12 @@ Accepted formats for a client certificate are: Accepted formats for a private key are: - Unencrypted PKCS #8 DER - Password-encrypted PKCS #8 DER +- PKCS #1 DER +- Unencrypted PKCS #8 PEM +- Password-encrypted PKCS #8 PEM +- PKCS #1 PEM -An unencrypted PKCS #8 DER key file can be converted from a PEM file using OpenSSL. -``` -openssl pkcs8 -topk8 -in clientKey.pem -out clientKey.pkcs8.der -outform DER -nocrypt -``` - -A password-encrypted PKCS #8 DER key file can be converted from a PEM file using OpenSSL. -``` -openssl pkcs8 -topk8 -in clientKey.pem -out clientKey.pkcs8.encrypted.der -outform DER -``` +The formats are detected automatically. Files with the `.key` file extension are usually in PEM or DER formats. ## Disable SSL Verification diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt index d052eabd..2599f40b 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt @@ -6,6 +6,13 @@ import com.sunnychung.lib.multiplatform.kdatetime.KDateTimeFormat import com.sunnychung.lib.multiplatform.kdatetime.KInstant import com.sunnychung.lib.multiplatform.kdatetime.KZoneOffset import com.sunnychung.lib.multiplatform.kdatetime.KZonedInstant +import org.bouncycastle.asn1.ASN1Sequence +import org.bouncycastle.asn1.DERNull +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo +import org.bouncycastle.asn1.x509.AlgorithmIdentifier +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter import java.io.ByteArrayInputStream import java.io.File import java.io.InputStreamReader @@ -79,15 +86,62 @@ fun ClientCertificateKeyPair.Companion.importFrom(certFile: File, keyFile: File, ) } -private fun parsePrivateKey(keyBytes: ByteArray, keyPassword: String): PrivateKey = if (keyPassword.isEmpty()) { - // try without password. if it's fail, try with empty password - try { - decodeUnencryptedPrivateKey(keyBytes) - } catch (e: InvalidKeySpecException) { +private fun parsePrivateKey(keyBytes: ByteArray, keyPassword: String): PrivateKey { + val securityProvider = BouncyCastleProvider() + val keyConverter = JcaPEMKeyConverter().setProvider(securityProvider) + + val parsePkcs8AnyUnencryptedPrivateKey = { keyBytes: ByteArray -> + val keyInfo = PrivateKeyInfo.getInstance(keyBytes) + keyConverter.getPrivateKey(keyInfo).also { + // this is an unencrypted key. there should be no password given. + if (keyPassword.isNotEmpty()) { + throw InvalidKeySpecException("Parse fail") + } + } + } + + val parsePkcs8AnyEncryptedPrivateKey = { keyBytes: ByteArray -> decryptAsPrivateKey(keyBytes = keyBytes, keyPassword) } -} else { - decryptAsPrivateKey(keyBytes = keyBytes, keyPassword) + + val parsePkcs1RsaPrivateKey = { keyBytes: ByteArray -> + println("parsePkcs1RsaPrivateKey") + + // this implementation does not throw exception for invalid keys +// val spec: KeySpec = PKCS8EncodedKeySpec(keyBytes) +// KeyFactory.getInstance("RSA", securityProvider).generatePrivate(spec) + + // this does throw + val pkcs8 = pkcs1PrivateKeyToPkcs8Encoded(keyBytes) + parsePkcs8AnyUnencryptedPrivateKey(pkcs8) + } + + // convert PEM to DER if applicable + val keyBytes = keyBytes + .tryToConvertPemToDer("-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----") + .tryToConvertPemToDer("-----BEGIN ENCRYPTED PRIVATE KEY-----", "-----END ENCRYPTED PRIVATE KEY-----") + .tryToConvertPemToDer("-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----") + + val tryInOrder = listOf( + parsePkcs8AnyUnencryptedPrivateKey, + parsePkcs8AnyEncryptedPrivateKey, + parsePkcs1RsaPrivateKey + ) + tryInOrder.forEach { proc -> + try { + return proc(keyBytes) + } catch (_: Throwable) { } + } + + throw InvalidKeySpecException("Cannot parse given private key") +} + +fun pkcs1PrivateKeyToPkcs8Encoded(pkcs1Encoded: ByteArray): ByteArray { + val algId: AlgorithmIdentifier = AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE) + val privateKeyInfo = PrivateKeyInfo(algId, ASN1Sequence.getInstance(pkcs1Encoded)) + + val pkcs8Encoded = privateKeyInfo.encoded + return pkcs8Encoded } fun decodeUnencryptedPrivateKey(keyBytes: ByteArray): PrivateKey { @@ -109,14 +163,18 @@ fun decryptAsPrivateKey(keyBytes: ByteArray, password: String): PrivateKey { } } -fun parseCaCertificates(bytes: ByteArray) : List { - InputStreamReader(ByteArrayInputStream(bytes)).buffered().use { reader -> +private fun ByteArray.tryToConvertPemToDer(startLine: String, endLine: String): ByteArray { + val startBytes = startLine.toByteArray() + if (size <= startBytes.size || !copyOfRange(0, startBytes.size).contentEquals(startBytes)) { + return this + } + InputStreamReader(ByteArrayInputStream(this)).buffered().use { reader -> val firstLine = reader.readLine() - val certBytes = if (firstLine == "-----BEGIN PKCS7-----") { // p7b + return if (firstLine == startLine) { val base64Encoded = buildString { while (reader.ready()) { val line = reader.readLine() - if (line != "-----END PKCS7-----") { + if (line != endLine) { append(line) } else { break @@ -125,13 +183,17 @@ fun parseCaCertificates(bytes: ByteArray) : List { } Base64.getDecoder().decode(base64Encoded) } else { // der / pem - bytes + this } - return CertificateFactory.getInstance("X.509") - .generateCertificates(ByteArrayInputStream(certBytes)).map { it as X509Certificate } } } +fun parseCaCertificates(bytes: ByteArray) : List { + val certBytes = bytes.tryToConvertPemToDer(startLine = "-----BEGIN PKCS7-----", endLine = "-----END PKCS7-----") + return CertificateFactory.getInstance("X.509") + .generateCertificates(ByteArrayInputStream(certBytes)).map { it as X509Certificate } +} + fun importCaCertificates(file: File): List { val content = file.readBytes() val certs = parseCaCertificates(content) diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt index 3419c0b5..750997c2 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt @@ -594,7 +594,7 @@ fun CertificateKeyPairImportForm(modifier: Modifier = Modifier, onAddItem: (Clie Row(verticalAlignment = Alignment.CenterVertically) { AppText(text = "Private Key", modifier = Modifier.width(headerColumnWidth)) AppTextButton( - text = keyFile?.name ?: "Choose a File in PKCS #8 DER format", + text = keyFile?.name ?: "Choose a File in PKCS#1/PKCS#8 DER/PEM format", onClick = { fileChooser = CertificateKeyPairFileChooserType.PrivateKey }, modifier = Modifier.testTag(buildTestTag( TestTagPart.EnvironmentSslClientCertificates, diff --git a/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/SslConfigTest.kt b/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/SslConfigTest.kt index c8c9cc38..357834ce 100644 --- a/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/SslConfigTest.kt +++ b/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/SslConfigTest.kt @@ -3,10 +3,9 @@ package com.sunnychung.application.multiplatform.hellohttp.test import com.sunnychung.application.multiplatform.hellohttp.manager.NetworkClientManager import com.sunnychung.application.multiplatform.hellohttp.model.ClientCertificateKeyPair import com.sunnychung.application.multiplatform.hellohttp.model.SslConfig -import com.sunnychung.application.multiplatform.hellohttp.util.importFrom import com.sunnychung.application.multiplatform.hellohttp.network.ApacheHttpTransportClient +import com.sunnychung.application.multiplatform.hellohttp.util.importFrom import java.io.File -import java.io.IOException import java.security.spec.InvalidKeySpecException import kotlin.test.Test import kotlin.test.assertFailsWith @@ -37,7 +36,7 @@ class SslConfigTest { val exception = assertFailsWith { verifyCertAndKey(certFile = certFile, keyFile = keyFile, password = "asdfh") } - assert(exception.cause is IOException) + assert(exception.cause is InvalidKeySpecException) } @Test @@ -73,4 +72,39 @@ class SslConfigTest { } assert(exception.cause is InvalidKeySpecException) } + + @Test + fun `read client cert and PKCS1 DER unencrypted key`() { + val certFile = File(javaClass.classLoader.getResource("ssl/clientCert.der")!!.file) + val keyFile = File(javaClass.classLoader.getResource("ssl/clientKey.pkcs1.der")!!.file) + verifyCertAndKey(certFile = certFile, keyFile = keyFile, password = "") + } + + @Test + fun `read client cert and PKCS1 PEM unencrypted key`() { + val certFile = File(javaClass.classLoader.getResource("ssl/clientCert.der")!!.file) + val keyFile = File(javaClass.classLoader.getResource("ssl/clientKey.pkcs1.pem")!!.file) + verifyCertAndKey(certFile = certFile, keyFile = keyFile, password = "") + } + + @Test + fun `read PEM client cert and PEM unencrypted key`() { + val certFile = File(javaClass.classLoader.getResource("ssl/clientCert.pem")!!.file) + val keyFile = File(javaClass.classLoader.getResource("ssl/clientKey.pkcs8.pem")!!.file) + verifyCertAndKey(certFile = certFile, keyFile = keyFile, password = "") + } + + @Test + fun `read client cert and PEM key encrypted by empty password`() { + val certFile = File(javaClass.classLoader.getResource("ssl/clientCert.der")!!.file) + val keyFile = File(javaClass.classLoader.getResource("ssl/clientKey.pkcs8.emptyencrypted.pem")!!.file) + verifyCertAndKey(certFile = certFile, keyFile = keyFile, password = "") + } + + @Test + fun `read client cert and PEM key encrypted by password`() { + val certFile = File(javaClass.classLoader.getResource("ssl/clientCert.der")!!.file) + val keyFile = File(javaClass.classLoader.getResource("ssl/clientKey.pkcs8.encrypted.pem")!!.file) + verifyCertAndKey(certFile = certFile, keyFile = keyFile, password = "asdfg") + } } diff --git a/src/jvmTest/resources/ssl/clientCert.pem b/src/jvmTest/resources/ssl/clientCert.pem new file mode 100644 index 00000000..c27dfb40 --- /dev/null +++ b/src/jvmTest/resources/ssl/clientCert.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFdTCCA10CCQCXm/wdDsv+UzANBgkqhkiG9w0BAQsFADB7MQswCQYDVQQGEwJY +WDESMBAGA1UECAwJU3RhdGVOYW1lMREwDwYDVQQHDAhDaXR5TmFtZTEUMBIGA1UE +CgwLQ29tcGFueU5hbWUxGzAZBgNVBAsMEkNvbXBhbnlTZWN0aW9uTmFtZTESMBAG +A1UEAwwJQ2xpZW50IENBMB4XDTIzMTIxNTEwNDA0NloXDTI0MTIxNDEwNDA0Nlow +fjELMAkGA1UEBhMCWFgxEjAQBgNVBAgMCVN0YXRlTmFtZTERMA8GA1UEBwwIQ2l0 +eU5hbWUxFDASBgNVBAoMC0NvbXBhbnlOYW1lMRswGQYDVQQLDBJDb21wYW55U2Vj +dGlvbk5hbWUxFTATBgNVBAMMDENsaWVudCBTdW5ueTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBALGBysJO7JdSQ2igYiJ62JwVDpLPryK+s1FTX8J8H8di +KPI4+fhfm2gHNhpx0oxSibk9EqBmPgDvgJeXZSNSzzZpLyPQ+c5vkjr3Ld+lOhNT +L8ycBQyVew2sCK7Wo5gwpZdOeHlQdcBkVAOjHuqdgdxwDmr8ybz+igde4fIxlHbW +FueEVbohD2iiXlcfO+zej215H25dyccW7G+JM56jFUs6DCRPpCQnCpkvI65jauPy +AG9ZRfl6ScxHI2ZG89erh6sedMe3U3VEQY4QMKAi8cBSAcPvFvwAH8/oK2iHip5L +me4tfkKOJOEFle2Kd3I8Cj8ZJk8XDSUWJzuNWyAMiv5J6oxKk0/PPpKWaV1hu0n6 +hLwUFkW5MNAu/t1qxa7x3b9r3ijFsXy8Gr7K8INZ6L/90KIn8XjwoIhETT2f5V4s +vvowv+UA1/lfLWZOT3EgaHJttS3eYgxXl//unC1+zy6z4OXhCYqDgcoPnBYLzYvc +kfBNUxKB1+sOo1Fse8yQ/773rdnFEgCnfc+Tuwh/aSZM40yVprFCgaX3S54Zfiiq +c4+uA7cIeG8YR+VzziiOhPOuals9F0gUuLvl/Yyx21yzNDRI5co+mh3H+LEvlmL4 +IKbnDSjbyF7R2VXxWLJEhmHnydPqF/n3jagi1rjeF/rpcQTqzY1pHY7Bm6+Kh7iZ +AgMBAAEwDQYJKoZIhvcNAQELBQADggIBAEYPcznepcJ6z0FeG7gEQ790a6/xuMWD +UZzCg4ShhG4E2jb02+z3KLBu/mljkOf95FVZH4bRYcVTy1ZXsIygz1JqiXncNtE3 +Le2rxs3kqzOU/oGivemGu5s4zu6OW2kv/kAbv2pQLQk6kv1fbU8tO2hkUrJtGnYe +SNpNpuRP449Jy+YOJZBjF5CxlTDeeq5UgRDoBTn1XjfFyWYUuU7hx2UcKNqIIPk/ +TF8jStV//nEVe1uxuKTifFLbDKu3ABgBddJXwXQRWWYAfHPpK9h8K7pcxDX5WCOU +ktpbisB3b6NVihmmQXeMueePCPjBHrrY3T29qofnDrNH8M9yLaEnKAy0x3xrDuqV +u6npp7RwurWioZWAHbzq3kWszrb5O5H/Bp4x5aIZArq4pLdU7VQ38eB4psC+z3mf +Br96vBcYTnsC2TQbQKav7Wzor1YUJAX0uRffcFTPPShQ6jZ17hzl0fAO3r+Iw4C/ +DImJRUPYck4Dc+1QQprXy0h6nDTAFiD0LpYB+Ul3thi4eGHkVU+aSndS92CtZz4k +WxqOnfzofdaNmOlAMeJiqmQqdoDfYRYyYyIjDEniERBiqt9v761OCQVvbz+tWEHl +dUZ6GVOqcr3ngFSrZSKMquA75pBSc3s9eslOA2+5ZjVH8vw8c/qoexSWZnkN/akb +7JINJcbIGlC/ +-----END CERTIFICATE----- diff --git a/src/jvmTest/resources/ssl/clientKey.pkcs1.der b/src/jvmTest/resources/ssl/clientKey.pkcs1.der new file mode 100644 index 0000000000000000000000000000000000000000..b6c7c7f8fcd3039af5da624bbc864482bb36d6c2 GIT binary patch literal 2348 zcmV+{3Dfp4f(a-B0RRGm0s#QAfy%;8?3YqQXrN*udf1#54wBEWBEGXxQ(wY-AID-S z@;LeUUz=zLHX3o#j8ciYJrba1J^=54mzQNDQqMMNFC)ojwIm)mFWoU0PtQJ*mT6sKyGiQf-p6_VH4 z%8W7qH@iCClEKk=#5*fht3^FiEnD-C9I8dkk2~g#{>OpMBl5kT;05L`8i9p%?TVV_&)J=)%%4u)^|M=vZdGm#SGLH_n&4k~NR1*`h=yTiY&V}vqGr#fhny$VfZOKPp zfD%_VS{c^8qWP@fM4%%B_bCj0fu)h`!OMe)FC@xSA9Q=+zm$2L^m+O_j zaW0o*84yB3R*+1g#_T8*TArSq#G>c7o=536QRs4wvkz#RR_PD)1MX@w{OSnz7pb-x ztsF&`^Ek^x8ITF@Toz1NPX$Z~PG92fC9^c03;x-%eVea4dy#_4N!HMg?_ZxJ4-X&X zz&~rSSreL>-V$4JV^1Wn{5BoL$zan|V%8tZvp0sL^FWbXng$?h)Mdw&8~K^N72&V8 zPcB}EIpMgf3OfFw0)hbn0NnlHJ!j$8JMdmX#gK;-)6$W176@_I>$QWTcNy=`-o=V! z^N8m{zum@oWJdJvHWt>pXMKiHj?pbT+Im;;&O#)6;GjPC!HKDbC7Al&#{MAsz13^>*Ieh@Gi(|x+QE6^9PzD$6R&n#*P znAjwugaF;ZwV!2*8W{%<-_HzDhq{%$lSuYeNb3xj)HJSUH>CoC0RS7+NU_rFD63(ptp%|jp{vgyi|e6coV!gQXN@uu_6};d z^Wcj^{4VD~z!i9|4Rv@(&@PHADCgrvo7}m5YmmL!lvD&d37+$`3RST$btd2t&@bLx zFH$4&*PLPViux2em{kIT0RRP%=q=v$XC*DDZ9gczo(|BoA;Nz=Xz}DnQ_>GD;dXE-rKI$b7@!&#-_YORBS%v~pNwUH> zw3qxK)Vb$BU=C&#C<=Oo(~e8V_pYTJX`0l@JM##r-BE(#&5l(_dDPomjb4^XaZ*zD z2Z)$TJE!1d%@=1UY}ahB4RStj5h@?~N@v}w@%$e~GOIIpFU-Sb?`ze^2n1aaY3$E* z^w-zB;l*Rm5);C_ub)NP|2!1NEMXo-ZMrt2qiUviF_tIeV zkzmxbjJj|V9O}x+yAafDGQwyRBF*gq-!(KvHT39Ufm$avKU|Y%x%%Bc!lg;qZtZ7k zgr)K1dihreA`hpl;{_*f#+VQst4dnkZ&Z+oX-o#G@fQr$xzA~nYejH&YAkbys=wfQ zdAME;5%DQ#wXQ5t5oua2IjNYM4kFHnRih7q+qw8~ S`M(9WihVydAf6CJ{ISA}cad-a literal 0 HcmV?d00001 diff --git a/src/jvmTest/resources/ssl/clientKey.pkcs1.pem b/src/jvmTest/resources/ssl/clientKey.pkcs1.pem new file mode 100644 index 00000000..7e15d708 --- /dev/null +++ b/src/jvmTest/resources/ssl/clientKey.pkcs1.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/src/jvmTest/resources/ssl/clientKey.pkcs8.emptyencrypted.pem b/src/jvmTest/resources/ssl/clientKey.pkcs8.emptyencrypted.pem new file mode 100644 index 00000000..56f03ee1 --- /dev/null +++ b/src/jvmTest/resources/ssl/clientKey.pkcs8.emptyencrypted.pem @@ -0,0 +1,53 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +KqEd6OHTWN/XB9fyGA== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/src/jvmTest/resources/ssl/clientKey.pkcs8.encrypted.pem b/src/jvmTest/resources/ssl/clientKey.pkcs8.encrypted.pem new file mode 100644 index 00000000..9fa1a739 --- /dev/null +++ b/src/jvmTest/resources/ssl/clientKey.pkcs8.encrypted.pem @@ -0,0 +1,53 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +yihRdeUuE8swjHd9cA== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/src/jvmTest/resources/ssl/clientKey.pkcs8.pem b/src/jvmTest/resources/ssl/clientKey.pkcs8.pem new file mode 100644 index 00000000..74218d87 --- /dev/null +++ b/src/jvmTest/resources/ssl/clientKey.pkcs8.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +ogdargmJCvm/BbaKfT81IJ4QRPyxwg== +-----END PRIVATE KEY----- diff --git a/test-common/src/main/resources/tls/clientCert.p7b b/test-common/src/main/resources/tls/clientCert.p7b new file mode 100644 index 00000000..4984ce67 --- /dev/null +++ b/test-common/src/main/resources/tls/clientCert.p7b @@ -0,0 +1,33 @@ +-----BEGIN PKCS7----- +MIIFpQYJKoZIhvcNAQcCoIIFljCCBZICAQExADALBgkqhkiG9w0BBwGgggV4MIIF +dDCCA1wCCQCI2OjRdxOvTjANBgkqhkiG9w0BAQsFADB7MQswCQYDVQQGEwJYWDES +MBAGA1UECAwJU3RhdGVOYW1lMREwDwYDVQQHDAhDaXR5TmFtZTEUMBIGA1UECgwL +Q29tcGFueU5hbWUxGzAZBgNVBAsMEkNvbXBhbnlTZWN0aW9uTmFtZTESMBAGA1UE +AwwJQ2xpZW50IENBMB4XDTI0MDUyNjA0MzAyNloXDTM0MDUyNDA0MzAyNlowfTEL +MAkGA1UEBhMCWFgxEjAQBgNVBAgMCVN0YXRlTmFtZTERMA8GA1UEBwwIQ2l0eU5h +bWUxFDASBgNVBAoMC0NvbXBhbnlOYW1lMRswGQYDVQQLDBJDb21wYW55U2VjdGlv +bk5hbWUxFDASBgNVBAMMC1Rlc3QgQ2xpZW50MIICIjANBgkqhkiG9w0BAQEFAAOC +Ag8AMIICCgKCAgEAr8SFn/8KSR8o8mytFDrZjDFm1sHMmjCh1HePdESmZDstIgFB +DiOUl8B1FC8x3IZJMKv6t45eOiH/rQur76Tedr09CI6LwpDlarMvQ5Xcbk+cupJG +CiXDEqr8FRHZBkedqDeFmAy34zuV7erM4W/WC6o4OjmPhuMnF+M9clTuNVi0LSVl ++jQvvNKoctDDqyLHDMgMNeQnTJoE1OgWVK6BGA0n1ITnh9hZ4xyYqIwiDqDLEEOr +zVuBuEFC6HezzzkoZ/NByIJ4SxEKDATugAlgqWcdr6TUn7O+jUYmW5olHB0SLOKl +0Q6XNTMv5bqpf98NLFhi4ESEM2xOiadIick0m13nbhTVoEzBI253HtD0iziCtWuv +CxsUxfzbp8IMbbCQdv3yNSET1mLXK0HOWM7f8fbKDGHtm3qF0pdJW3vP86Jba+2a +infqjyEHpLZBDT9DG2EQdWBvgFkeDgFQfRopHjEM5SKLqKp8YwcwJGfyUXUa/K/3 +WhFLGCExqNKrs+b+B9zsbuBM7YWMBh9+pjVdKfgOnHx94JSVyAzls+2w/ONZdBbi +2kQWqMLPasugEAjemrRkUGqOnFcNkSHjkSD9Qh8/PMD6G4UVVMziPvmQjwM2FhsW +qHLygl6eVWmC3ekWvuNiM4QOIrCAncpPYsOqWp5DXaw7+IilHL3CpQDq7WMCAwEA +ATANBgkqhkiG9w0BAQsFAAOCAgEA5j/pQNYDdBGlba7L0NAYuB/Qah5lTrasmxvT +j+FyJD9C6vqICIKF7vKmvesSpF0eO3quJ+ivTYH9OBR+X4i7ZqgHcXL3+F2f7WcB +ox5+DoZO9SKuxD2mxDNboomG9pmrqLHxQZCMT6IOObVY+G923FCZ9FxVvlHBzC4+ +QNvhLsXcfJeC/EhstLXVnBn33pHz14SG+jY+IHi8vgLk4QV9634gY1PM8m4+DqO8 +aaW6v5j4pKCmsLIRPS0GFupjtRFbfZeptlEsqCPsHlRR8sP9NIJaU22dI7oI9Pfk +p1UUDE8MR1Wn81YdrwXxjxn0fMWXXIcpjUGComn/f/9StuFLES7CP/R0GnbIpuc7 +iuYKDxlXMB0tkIEfHWtTa6O21VPhAqi/VsRThGygdqr8ZlAJQ4s3luGq3zmzL9YL +xKrLcumuL+cP0n1SKcS5rla3sElCvb+tj3X9hoUQY7DCg1xRDkQ0UqBegXvsCPSd +aKhrm7j2Xm8bq5q+1knBw55TLgMHnaq4X55pUcPyXf4Bmly4QPajy5l3pysoIIxj +yjwaE3oy1Rt22+EVCxGx90H4WFjqxWEPceNTYkGa00o7lDAKbV1jjMIhjI6IYSCw +uewWnchKEsVwEgoNJqyETvJl/TErSf5J+Fd6xjn750HGtXUtKDE5Qm+fqviWFN1/ +4wAMYbWhADEA +-----END PKCS7----- diff --git a/test-common/src/main/resources/tls/clientKey.pkcs8.emptyencrypted.pem b/test-common/src/main/resources/tls/clientKey.pkcs8.emptyencrypted.pem new file mode 100644 index 00000000..00441e63 --- /dev/null +++ b/test-common/src/main/resources/tls/clientKey.pkcs8.emptyencrypted.pem @@ -0,0 +1,53 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIJaTAbBgkqhkiG9w0BBQMwDgQIWb5LiWSgbjkCAggABIIJSEyYX4pYoFfFB5BW +OshjoPQPOf5bv7QAVv9mDZ2p1RM19PvNFRaSV3GAZq7G5niZdC9qQxYoc4EDRX0B +g+cf+/yXVHPthxS7b2n1Kw51ftBuJUevq4R8btVm13xuTL/D51PFn8BBn8c4PtFP +9r+n0qvGXIzw1lca59CWGv0EfYJi5G8GqK9d5yXbo4RdkEmvTWNxN6iezGVWxlmI +KywPejwvoWpedhjGFEmH68AMsfsKEtDFkQaeW+yoqu8s5+lOW+v3eKuDAAX55WTr +h/6gdJFP4eNxcf6L8PGBHwBlH/fQtUoAguEWXebwue2916gxXs+SA8J2c541ufUy +oHOFHR6l028uCWKU7yTrEEY4vivypRP0XIzaN6mNEPu+3czu4WVGvncszYrELYHu +W6iS3JLdb6F1p5ql5EHLMK7dC+vrvFiOePCWTo8N1bUw7tzWoofi8l3dTy2cvexR +ZCaULVQkHUOmmYj3ggN0Ckn0hu6+zaOtdFAuuuGt6j4h1N2R9LFSNl44jtCt1sxS +34U8sJkI+Cchthvsv7f5qUvF8ZFizNYfqfft4T6iK8jt/rYKOPIzVVGmk4O/KK5N +RkPHKaRyxP+mTyB2vkgwWRqDgj4LUQuV1APmdk2QBbfPoPjhceLwH5apWrCWQ0xX +bIxpGcet1drSPg1Qz/tFdaJLBXZpYQJLcAu8lkvLSSAvOzOovem5Tk39ob2EdEPN +Q53EhQjji/lf3ADWT2h26QHq7nFj6d/iAIqn1ULvS3pgXmwjN01FcE8WxnXxJ7u1 +dDOoQYIwQMrm+miRdo0NsbQD1Rt1RyIR8taBOdk15BCSg5Z1SaO69VDmlrPJi9LE +P+aRo+kaNnG5vs7ZZnowSZ6Oyl0FHrCcMFzVs76+6Oo5HqA7GF+Mz6AfdtqdgGdU +ublwIYAcN1O1hDIOUD5LY0pUHpJvnNujKqloa+engrqjYSaSUpWyZbjGIoIscfYG +OEyKNefdi/3fhEljY1wjV7Pv8QUZIp7k/izIiDYHBahp2FizBZN+xFMPJHSaY72Z +nSQlzResaigfENG6oQJPOSz5q7723nUaych82Qcikw7SvT+IpbKEKFBS2Mr02SJz +ZF66+z+7vZykUiZclNqrplTJXvnWTQX4eIIY6e0QkH+AjXAuu2kRneGiBQuQ15yu +TzV+Duq2S4yFZ+FgljP9rtC7IO6i7sja6GjRLnwnlZubnqsQ1F0Ov+hW7iA5Nz8k +aq94iA8tAq6NqmOGMwwYiiE8czMSxI2fNhzb/etNG/lequXBUqadxiUxnPqJQadN +AEoLTFKyWtevhkrXqJUex7dv26rVcygoYmL0G7CYgLEqPDXDG/6seCiOVo1d+RTr +yZ/1nfNYNhp4lSDnv/p5GaTnQlwCxdMCHuTWPxLQi0v502iPjvJjsfi4URIFScyi +zJ4esSvziJWm3zIcE+t3psuk5ebDnx6OLy/cbinJi/HzR+aZ/uDBOcrj7Gh/Jj/U +ZlIoNcPy8uDoj15Ufu/NeChi60lzFSQiEF6eJlgq058IuKZhsXb8OgqK0gI6TbuU +yygwA8v0sH3N2e8NvRPBwi3L8ZKWa3dV6hFrja6x+JXRmV+zRBCUTlPiBMi2XHF5 +caKPI0z3VTSw3UYRKFB8+EylgDvgiUO5xmqeHV79acr7o6l0xXS3tRoxjRFL747Y +zm51NqK7sXAzh80w5rL1kEhA+OdPZ0Ib72sUagQ63Fq8xRz1ydUUPCeUrdhUWuo0 +WYmpS9pFJEtQWhuRKXah6WEo9pfbfTW2MTwu6S+wMhS/a7rOub8Z75I5STMgofI4 +m1xdPnNnpeDjI3IssnsdS4ptZ/xcVk1ImVGh+YbLUOznpcyTzD6ldAVSXSkT+oRz +zA77sXVKl7edTlUkUUSk/8wp86aeJzFHJ1U6gi1FGAWoO1KGeRDLURF/n1XKc2Bx +vncPcI13uz1MKfaRl3K2MYOTOwgSDwvgimsFdWS1SS1dROIVGMqP+gUK9L+Y3xeS +OODvMa7xy/drNAgvaKK+KrOq9lub9iCTTSIJoP3Jry+vpU6y8SFxsA1qBHeAVEvo +w3wEQxtFrTBMmkeQuCY1hLJKC/xgXOaUrNeahFOy2+xjcmBvJK2mV4HCyRNFmsA4 +czfoP+CrKghcNpduDkcrfsq050PhBMJoPiRurR/BwoGg/HGfpPx4WXOkysMKNeq1 +xoP5KX9wC3zUTLJw9QVK64uDjFh8O1ChdKYXfaeqW9sD7YS8ViV536mIoBBwl1c9 +RIN2CGYw8Z7AXvTmP+adNH9BfJ7+4wpgljiS6iSkVPppRJuP+/jgbTildNtEi1Cg +sVtpuhWmdwL1O8QYbXHDC6dheSjh6G7VIHZSesSdMaNaOuW/3gRNSNt62n8UlTlO +rUYcPFLPWgxGjjZQdhPmpUz5e45XsWjR52W9I6gPOK9CKiQxyMQCkZdIVhOLe2OI +c3QIfVc5gJJpBue56pAm81uuuXQTOoTCtcwnI+q/lAewFJ6lEOcyDqsn6gxgRd5+ +wXp3t18M+fhcpd8j8MgV2wiGJmJ9kfFg2K9c8NcXpys6A5RdG19rA5cXkiqEffCl +mpBlRcgi/93JJjXtt85sWOqYunz++V8KV1BbdXaeZcZ6fDjKCbIclD4x2wKgPbYn +R/AaC35BJOmdN0MUzHPUH0tqN5KeycGWXEqh8OvRSIRjwqG97ab0FZKaHTeSTv1w ++gwA06a/2UlpP1Ns5BSmtmgWFlCceGb1Mbcw8dfvOs2reynDdOuf6rdLieu8Ree8 +6AcKbCsGvWrhvkQkwiy8XBmNPhPw3v9uGau7Bus5TY5aPzJrkDVkGVkFD3+yeOK8 +sF5AZDFa56VOGnHwlveqL9AIdE1oBgjrEh54qw3Mw5i6jN+vET4BpjSzI2U6Xu3o +GEQdhXNWSTpOJ5WgCa7GjL3j0S2c4vFNK5pkd0Fxbj4uG1M/XAP/pHOVG8ob/DP1 +ZBQKzYPy3fI2ZTCweC5YmIo22FHnMVnPRtoG7WCo/Jf7m4Q62tiYVYhH0uwNxBXD +WYGLlYwNJMEkcVva0BVDYlLIiRAr80/Q0LgsGbZJzEvZB8P0fRjLoqDv9EzZg3Ze +ZK9Kfv5cU9Kc3/IJf8/SqrsW8dIkNleen4Q9gqAeCzmfOyQ8wkOsqs4f6HOewOfj +GhdNUkD33g+Fv21lXg== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/test-common/src/main/resources/tls/clientKey.pkcs8.encrypted.pem b/test-common/src/main/resources/tls/clientKey.pkcs8.encrypted.pem new file mode 100644 index 00000000..277a862f --- /dev/null +++ b/test-common/src/main/resources/tls/clientKey.pkcs8.encrypted.pem @@ -0,0 +1,53 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +QzfljHIpb923Y/bc7g== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/test-common/src/main/resources/tls/clientKey.pkcs8.pem b/test-common/src/main/resources/tls/clientKey.pkcs8.pem new file mode 100644 index 00000000..64509153 --- /dev/null +++ b/test-common/src/main/resources/tls/clientKey.pkcs8.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +9vVdjIGGeuiBa6s0Flxrc1Vor1xGhg== +-----END PRIVATE KEY----- diff --git a/test-common/src/main/resources/tls/serverCACert.p7b b/test-common/src/main/resources/tls/serverCACert.p7b new file mode 100644 index 00000000..6e53cea8 --- /dev/null +++ b/test-common/src/main/resources/tls/serverCACert.p7b @@ -0,0 +1,34 @@ +-----BEGIN PKCS7----- +MIIF3AYJKoZIhvcNAQcCoIIFzTCCBckCAQExADALBgkqhkiG9w0BBwGgggWvMIIF +qzCCA5OgAwIBAgIJANSxzx8xm+F9MA0GCSqGSIb3DQEBCwUAMIGAMQswCQYDVQQG +EwJYWDESMBAGA1UECAwJU3RhdGVOYW1lMREwDwYDVQQHDAhDaXR5TmFtZTEUMBIG +A1UECgwLQ29tcGFueU5hbWUxGzAZBgNVBAsMEkNvbXBhbnlTZWN0aW9uTmFtZTEX +MBUGA1UEAwwOVGVzdCBTZXJ2ZXIgQ0EwHhcNMjQwNTI2MDQyOTI0WhcNMzQwNTI0 +MDQyOTI0WjCBgDELMAkGA1UEBhMCWFgxEjAQBgNVBAgMCVN0YXRlTmFtZTERMA8G +A1UEBwwIQ2l0eU5hbWUxFDASBgNVBAoMC0NvbXBhbnlOYW1lMRswGQYDVQQLDBJD +b21wYW55U2VjdGlvbk5hbWUxFzAVBgNVBAMMDlRlc3QgU2VydmVyIENBMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAudGwLk6ZiJ4cR2VJGwX4WoNw9tNP +sKIoQ374I/OGr6P3K5TIiTApssiEGckol0dLZbnLDFG1kPOjwgnlrhm0zDl4Lp4o +3JskRKe1ZBO/Ysb+u5qqA91dXukJy7L5mskV+WhaeWHdqyfd0J0yIugu65Ge7ipL +m17/m6hX3zQ+slV9RYMiwR6dIdIDKHxkLsCpfq875BiG8803Vq3/sVM5L73OWag7 +06IIvSyGiKUgBCQXIE37hkZgF2y6eQ9RG5PfXkkuuKX2Ec0y5efzpJlgC3ZQPa0G +AAVQx5UDy/HXyWbRClGRBmyDax3N06PYIt1QMuftdHTCx+IjZkgmE1LgTVWa+DeD +BYr8Nzq0S5VD9y1EMitPvsQJvPKr9F8Q3QDrKFaOjV6beib2aAA/mTzMyuyCjtO9 +gMmxVBz0bkuoDa/DbwX272fnJqfsymKl/TXzoBrqEMRXnvgvRyf+3z7G3VZlL4Ds +5xU85La10rKNA0WAFaSo5OnmX11WV3xcGMWS1V/d0WzhtfgkZJnhSyp/dg4edlTT +YvRltDidsn/c1GyCqmMu6/svH3O+ii2nUgrPy2tPFbztydb51y+SpnBWivM51mm0 +O1z8xgfdVZOsqLfK9TalImwjfBKYqplBdDiQBY+WuYuQi9Zf1m2rxcEkENEoUfMR +4fJ3dVx1Yw7miEkCAwEAAaMmMCQwEgYDVR0TAQH/BAgwBgEB/wIBATAOBgNVHQ8B +Af8EBAMCAoQwDQYJKoZIhvcNAQELBQADggIBAB/WwuMd3zgflz6vb63YHiltOr6p +Cc93Ii0e2W7x8FpI7tdhrnKmk+xxtbl6prPm/h1WbMI2XLeagLISKAnZ2GaQi/6J +Kjxp5WkFCqIZAZ6C/G8lky7adt5CYdzTYaCGTWpFbYT/VaTKBEZa7bPnthqKiALT +0hUHCJy3XqOvs+7nsi7X0w3KYbcW9Gald/Qo6cBt8NbtPZwSfe6BcesAevtyEtEv ++GBXHchrIiiW/yLKi5LzV3QPPv7e7lb9PQHz0EBErpgM6ato6yd+UuHt5VS2plY5 +UL5FLz2BAGMi68bPr0Jql3DFoTe+VItO63X6p671RhNNt3oCAbPlDi2VTggIAgGl +a7DreRJIc7u/+eHoZJleGCfQBcyyZRCkyk0nCoFkimmooVNB4WnLrkDw0S0ELq6v +qAZwYiOiJu0dZzZWinQC5Da/6gKyzHasLj0oO2vdcopHgBLu27iDeHr+7nc/i+2u +bSsamhG9ZeKW2asSo/FGc4ithr/xzm4cWttkhAtnaLaS/VJJ/cZIAAz4DBXsZLlA +YnXVdH3EI4gukByJrzcFYx2k73ztvxWpZiOKUpm3LOrLBscjXP2qTs8jB+HmVAs6 +wzHxdiZzpDYxmjou9MbVWzX4/2hh5NT7Jaf6FiC289Qg4G75FVB1r8ZyQbtu5XQH +MgcQjelbCZdPSoV5oQAxAA== +-----END PKCS7----- diff --git a/test-common/src/main/resources/tls/serverCertChain.p7b b/test-common/src/main/resources/tls/serverCertChain.p7b new file mode 100644 index 00000000..a72a48c5 --- /dev/null +++ b/test-common/src/main/resources/tls/serverCertChain.p7b @@ -0,0 +1,68 @@ +-----BEGIN PKCS7----- +MIIMVQYJKoZIhvcNAQcCoIIMRjCCDEICAQExADALBgkqhkiG9w0BBwGgggwoMIIG +dTCCBF2gAwIBAgIUJP3xMBP8PodBX42d5mt7jlPlqMkwDQYJKoZIhvcNAQELBQAw +gYAxCzAJBgNVBAYTAlhYMRIwEAYDVQQIDAlTdGF0ZU5hbWUxETAPBgNVBAcMCENp +dHlOYW1lMRQwEgYDVQQKDAtDb21wYW55TmFtZTEbMBkGA1UECwwSQ29tcGFueVNl +Y3Rpb25OYW1lMRcwFQYDVQQDDA5UZXN0IFNlcnZlciBDQTAeFw0yNDA1MjYwNDI5 +NDVaFw0zNDA1MjQwNDI5NDVaMHsxCzAJBgNVBAYTAlhYMRIwEAYDVQQIDAlTdGF0 +ZU5hbWUxETAPBgNVBAcMCENpdHlOYW1lMRQwEgYDVQQKDAtDb21wYW55TmFtZTEb +MBkGA1UECwwSQ29tcGFueVNlY3Rpb25OYW1lMRIwEAYDVQQDDAlsb2NhbGhvc3Qw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC35PA1H0YzhBNnc7XIUyIY +hfAXrhyUWy0ynBevGU3H5alJ6faXTrND/0piTANMAbO1paSvchWKzmRcdiyTQEZF +NdiYg5+gv5AoS8JhxAgQTksbV6unmDyzvDOKX7Qtx3VqUeQg1CqX13luJaGR1ABG +YUNc13DpkmNFsJZ6k0E18R3MUHv6Wfod5futuBffYDv2dkrQqSCMe4W1XIIY47vk +6Y3exGmzc3f/whO1UIWKDuuu6R+1+9xPbMCEywDl1Fu2bkvMBD+RxOi8jAuCc6uK +YhZwarwLLMrbcfn49AKRpBPLsKCQhxZ8WH/Mb6TP3/A28J8fJwH/IuktI7XogBhM +a3Fo9EeAcTS2UVRlo6boaC8/Z8WgvrtSaMly+3lyne68dVKinZ7y6EZYKAoxYF+N +qUDjyrJmVUQFolg+oThuM1nxFZiEVbK3OTl3ss/JyfGWYnTv2pmXd38Dw7zMFesT +C63NqwhCHYMO81wFaAaToIBMm7x3o1xkPR0eYfPcTibczdtVgypMMr4yxrrpAPNH +4f/u/NyJs8q2xhgmYFIrs9Qth+8lqL1zOMvRnmTWPJ+xdy6AdgcrSWnY9/+WqnkO +lyNcJkHypKsk0GmrifGVNUqzoakQbCO1zKpn73RMPoDln/It+FhszXCZRH+9whQe +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 +-----END PKCS7----- From ca7bbefd80e8206af574cef0e9f801768198e55b Mon Sep 17 00:00:00 2001 From: Sunny Chung Date: Fri, 15 Nov 2024 12:20:06 +0800 Subject: [PATCH 5/8] add importing PKCS#12 (known as p12) and PFX bundle files as client certificates --- CHANGELOG.md | 1 + doc/features/ssl-configuration.md | 2 + .../multiplatform/hellohttp/util/Pkix.kt | 66 ++++++++ .../SubprojectEnvironmentsEditorDialogView.kt | 145 +++++++++++++----- .../multiplatform/hellohttp/ux/TestTag.kt | 1 + ...tKeyAndCert.encrypted.password-is-zxcv.p12 | Bin 0 -> 4157 bytes .../main/resources/tls/clientKeyAndCert.p12 | 0 .../tls/clientKeyAndCert.unencryptedkey.p12 | Bin 0 -> 4157 bytes 8 files changed, 180 insertions(+), 35 deletions(-) create mode 100644 test-common/src/main/resources/tls/clientKeyAndCert.encrypted.password-is-zxcv.p12 create mode 100644 test-common/src/main/resources/tls/clientKeyAndCert.p12 create mode 100644 test-common/src/main/resources/tls/clientKeyAndCert.unencryptedkey.p12 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5338a8ed..efb8b753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - Number badges in Request Parameter Type tabs to indicate the number of active entries declared in the selected example, e.g. the number of active key-value pairs of a multipart request body declared in the selected Request Example. - Certificates in P7B (PKCS#7) format can now be imported - Private keys in PEM or PKCS#1 formats can now be imported, and does not limit to RSA keys anymore. +- PKCS#12 (known as p12) and PFX files can now be imported as client certificates ### Changed - Importing CA certificates now imports all the certificates from an input file diff --git a/doc/features/ssl-configuration.md b/doc/features/ssl-configuration.md index 2c4784ca..cfa1f1b5 100644 --- a/doc/features/ssl-configuration.md +++ b/doc/features/ssl-configuration.md @@ -51,6 +51,8 @@ Accepted formats for a private key are: - Password-encrypted PKCS #8 PEM - PKCS #1 PEM +Alternatively, a PKCS#12 or P12 or PFX bundle containing exactly one certificate and one private key can be imported. If multiple entries of the same kind are found, only the first one would be imported. + The formats are detected automatically. Files with the `.key` file extension are usually in PEM or DER formats. diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt index 2599f40b..e9dd37e1 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt @@ -15,8 +15,10 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter import java.io.ByteArrayInputStream import java.io.File +import java.io.FileInputStream import java.io.InputStreamReader import java.security.KeyFactory +import java.security.KeyStore import java.security.PrivateKey import java.security.cert.CertificateFactory import java.security.cert.X509Certificate @@ -188,6 +190,70 @@ private fun ByteArray.tryToConvertPemToDer(startLine: String, endLine: String): } } +fun ClientCertificateKeyPair.Companion.importFrom(bundleFile: File, keyStorePassword: String, keyPassword: String): ClientCertificateKeyPair { + if (!bundleFile.canRead()) { + throw IllegalArgumentException("File ${bundleFile.name} cannot be read.") + } + + val store = KeyStore.getInstance("PKCS12") + store.load(FileInputStream(bundleFile), keyStorePassword.toCharArray()) + + var cert: X509Certificate? = null + var privateKey: PrivateKey? = null + + val e = store.aliases() + while (e.hasMoreElements() && (cert == null || privateKey == null)) { + val alias = e.nextElement() + if (store.isCertificateEntry(alias) && cert == null) { + cert = store.getCertificate(alias) as? X509Certificate + } else if (store.isKeyEntry(alias) && privateKey == null) { + cert = store.getCertificate(alias) as? X509Certificate + privateKey = try { + store.getKey(alias, keyPassword.toCharArray()) as? PrivateKey + } catch (e: Throwable) { + log.w(e) { "The key with alias $alias cannot be retrieved." } + null + } + } + } + + if (cert == null) { + throw RuntimeException("No certificate was found.") + } + if (privateKey == null) { + throw RuntimeException("No key was retrieved.") + } + + val now = KInstant.now() + return ClientCertificateKeyPair( + id = uuidString(), + certificate = ImportedFile( + id = uuidString(), + name = cert.subjectX500Principal.getName(X500Principal.RFC1779) + + "\nExpiry: ${ + KZonedInstant( + cert.notAfter.time, + KZoneOffset.local() + ).format(KDateTimeFormat.ISO8601_DATETIME.pattern) + }", + originalFilename = bundleFile.name, + createdWhen = now, + isEnabled = true, + content = cert.encoded, + ), + privateKey = ImportedFile( + id = uuidString(), + name = "Private Key", + originalFilename = bundleFile.name, + createdWhen = now, + isEnabled = true, + content = privateKey.encoded, // store decrypted bytes + ), + createdWhen = now, + isEnabled = true, + ) +} + fun parseCaCertificates(bytes: ByteArray) : List { val certBytes = bytes.tryToConvertPemToDer(startLine = "-----BEGIN PKCS7-----", endLine = "-----END PKCS7-----") return CertificateFactory.getInstance("X.509") diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt index 750997c2..5ab43c14 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt @@ -45,14 +45,14 @@ import com.sunnychung.application.multiplatform.hellohttp.model.HttpConfig import com.sunnychung.application.multiplatform.hellohttp.model.ImportedFile import com.sunnychung.application.multiplatform.hellohttp.model.Subproject import com.sunnychung.application.multiplatform.hellohttp.model.UserKeyValuePair -import com.sunnychung.application.multiplatform.hellohttp.util.importCaCertificates -import com.sunnychung.application.multiplatform.hellohttp.util.importFrom import com.sunnychung.application.multiplatform.hellohttp.util.copyWithChange import com.sunnychung.application.multiplatform.hellohttp.util.copyWithIndexedChange import com.sunnychung.application.multiplatform.hellohttp.util.copyWithRemoval import com.sunnychung.application.multiplatform.hellohttp.util.copyWithRemovedIndex import com.sunnychung.application.multiplatform.hellohttp.util.copyWithout import com.sunnychung.application.multiplatform.hellohttp.util.formatByteSize +import com.sunnychung.application.multiplatform.hellohttp.util.importCaCertificates +import com.sunnychung.application.multiplatform.hellohttp.util.importFrom import com.sunnychung.application.multiplatform.hellohttp.util.log import com.sunnychung.application.multiplatform.hellohttp.util.uuidString import com.sunnychung.application.multiplatform.hellohttp.ux.local.LocalColor @@ -572,36 +572,93 @@ fun CertificateEditorView( @Composable fun CertificateKeyPairImportForm(modifier: Modifier = Modifier, onAddItem: (ClientCertificateKeyPair) -> Unit) { val headerColumnWidth = 160.dp + val colours = LocalColor.current var certFile by remember { mutableStateOf(null) } var keyFile by remember { mutableStateOf(null) } + var bundleFile by remember { mutableStateOf(null) } + var bundleFilePassword by remember { mutableStateOf("") } var keyFilePassword by remember { mutableStateOf("") } var fileChooser by remember { mutableStateOf(CertificateKeyPairFileChooserType.None) } Column(verticalArrangement = Arrangement.spacedBy(4.dp), modifier = modifier) { - Row(verticalAlignment = Alignment.CenterVertically) { - AppText(text = "Certificate", modifier = Modifier.width(headerColumnWidth)) - AppTextButton( - text = certFile?.name ?: "Choose a File in DER/PEM/P7B/CER/CRT format", - onClick = { fileChooser = CertificateKeyPairFileChooserType.Certificate }, - modifier = Modifier.testTag(buildTestTag( - TestTagPart.EnvironmentSslClientCertificates, - TestTagPart.ClientCertificate, - TestTagPart.FileButton, - )!!) - ) - } - Row(verticalAlignment = Alignment.CenterVertically) { - AppText(text = "Private Key", modifier = Modifier.width(headerColumnWidth)) - AppTextButton( - text = keyFile?.name ?: "Choose a File in PKCS#1/PKCS#8 DER/PEM format", - onClick = { fileChooser = CertificateKeyPairFileChooserType.PrivateKey }, - modifier = Modifier.testTag(buildTestTag( - TestTagPart.EnvironmentSslClientCertificates, - TestTagPart.PrivateKey, - TestTagPart.FileButton, - )!!) - ) + Box(modifier = Modifier.height(IntrinsicSize.Max)) { + val headerColumnWidth = headerColumnWidth - 25.dp + Box( + modifier = Modifier + .padding(12.dp) + .border(width = 1.dp, color = colours.placeholder, RectangleShape) + .fillMaxWidth() + .fillMaxHeight() + ) {} + Column { + Column(verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.padding(start = 25.dp, end = 25.dp, top = 25.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + AppText(text = "Certificate", modifier = Modifier.width(headerColumnWidth)) + AppTextButton( + text = certFile?.name ?: "Choose a File in DER/PEM/P7B/CER/CRT format", + onClick = { fileChooser = CertificateKeyPairFileChooserType.Certificate }, + modifier = Modifier.testTag( + buildTestTag( + TestTagPart.EnvironmentSslClientCertificates, + TestTagPart.ClientCertificate, + TestTagPart.FileButton, + )!! + ) + ) + } + Row(verticalAlignment = Alignment.CenterVertically) { + AppText(text = "Private Key", modifier = Modifier.width(headerColumnWidth)) + AppTextButton( + text = keyFile?.name ?: "Choose a File in PKCS#1/PKCS#8 DER/PEM format", + onClick = { fileChooser = CertificateKeyPairFileChooserType.PrivateKey }, + modifier = Modifier.testTag( + buildTestTag( + TestTagPart.EnvironmentSslClientCertificates, + TestTagPart.PrivateKey, + TestTagPart.FileButton, + )!! + ) + ) + } + } + Box(contentAlignment = Alignment.Center) { + Box( + modifier = Modifier + .padding(horizontal = 12.dp) + .height(1.dp) + .fillMaxWidth() + .background(colours.placeholder) + ) {} + AppText("or", modifier = Modifier.background(colours.background).padding(horizontal = 12.dp, vertical = 4.dp)) + } + Column(verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.padding(start = 25.dp, end = 25.dp, bottom = 25.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + AppText(text = "Bundle", modifier = Modifier.width(headerColumnWidth)) + AppTextButton( + text = bundleFile?.name ?: "Choose a File in PKCS#12/P12/PFX format", + onClick = { fileChooser = CertificateKeyPairFileChooserType.Bundle }, + modifier = Modifier.testTag( + buildTestTag( + TestTagPart.EnvironmentSslClientCertificates, + TestTagPart.Bundle, + TestTagPart.FileButton, + )!! + ) + ) + } + + Row(verticalAlignment = Alignment.CenterVertically) { + AppText(text = "Key Store Password", modifier = Modifier.width(headerColumnWidth)) + AppTextField( + value = bundleFilePassword, + onValueChange = { bundleFilePassword = it }, + visualTransformation = PasswordVisualTransformation(), + modifier = Modifier.defaultMinSize(minWidth = 200.dp) + ) + } + } + } } Row(verticalAlignment = Alignment.CenterVertically) { AppText(text = "Private Key Password", modifier = Modifier.width(headerColumnWidth)) @@ -612,17 +669,24 @@ fun CertificateKeyPairImportForm(modifier: Modifier = Modifier, onAddItem: (Clie modifier = Modifier.defaultMinSize(minWidth = 200.dp) ) } - Row { - Spacer(modifier = Modifier.width(4.dp)) + Row(modifier = Modifier.align(Alignment.End).padding(top = 4.dp, start = 4.dp, end = 4.dp)) { AppTextButton( text = "Import this Certificate-Key Pair", onClick = { val parsed = try { - if (certFile == null) throw IllegalArgumentException("Please select a certificate file.") - if (keyFile == null) throw IllegalArgumentException("Please select a private key file.") + if (bundleFile == null) { + if (certFile == null && keyFile != null) throw IllegalArgumentException("Please select a certificate file.") + if (keyFile == null && certFile != null) throw IllegalArgumentException("Please select a private key file.") + if (keyFile == null && certFile == null) throw IllegalArgumentException("Please select a bundle file or a certificate and private key file.") + ClientCertificateKeyPair.importFrom( + certFile = certFile!!, + keyFile = keyFile!!, + keyPassword = keyFilePassword + ) + } ClientCertificateKeyPair.importFrom( - certFile = certFile!!, - keyFile = keyFile!!, + bundleFile = bundleFile!!, + keyStorePassword = bundleFilePassword, keyPassword = keyFilePassword ) } catch (e: Throwable) { @@ -640,7 +704,7 @@ fun CertificateKeyPairImportForm(modifier: Modifier = Modifier, onAddItem: (Clie } if (fileChooser != CertificateKeyPairFileChooserType.None) { - FileDialog(state = rememberFileDialogState(), title = "Choose a DER file") { + FileDialog(state = rememberFileDialogState(), title = "Choose a file") { val currentFileChooser = fileChooser fileChooser = CertificateKeyPairFileChooserType.None if (!it.isNullOrEmpty()) { @@ -648,8 +712,19 @@ fun CertificateKeyPairImportForm(modifier: Modifier = Modifier, onAddItem: (Clie CertificateKeyPairFileChooserType.None -> { log.w { "currentFileChooser is '$currentFileChooser' for result file ${it.first().absolutePath}" } } - CertificateKeyPairFileChooserType.Certificate -> certFile = it.first() - CertificateKeyPairFileChooserType.PrivateKey -> keyFile = it.first() + CertificateKeyPairFileChooserType.Certificate -> { + certFile = it.first() + bundleFile = null + } + CertificateKeyPairFileChooserType.PrivateKey -> { + keyFile = it.first() + bundleFile = null + } + CertificateKeyPairFileChooserType.Bundle -> { + bundleFile = it.first() + certFile = null + keyFile = null + } } } } @@ -860,7 +935,7 @@ fun ImportUserFileForm(modifier: Modifier = Modifier, onImportFile: (ImportedFil } private enum class CertificateKeyPairFileChooserType { - None, Certificate, PrivateKey + None, Certificate, PrivateKey, Bundle } private enum class EnvironmentEditorTab { diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/TestTag.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/TestTag.kt index 296e6844..8aa99cf0 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/TestTag.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/TestTag.kt @@ -56,6 +56,7 @@ enum class TestTagPart { EnvironmentDisableSystemCaCertificates, ClientCertificate, PrivateKey, + Bundle, CreateButton, ListItemLabel, Inherited, diff --git a/test-common/src/main/resources/tls/clientKeyAndCert.encrypted.password-is-zxcv.p12 b/test-common/src/main/resources/tls/clientKeyAndCert.encrypted.password-is-zxcv.p12 new file mode 100644 index 0000000000000000000000000000000000000000..8bb931516fc5f124a9c2f6ae171292bf4808e7a9 GIT binary patch literal 4157 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 H0s;sCg@DKH literal 0 HcmV?d00001 diff --git a/test-common/src/main/resources/tls/clientKeyAndCert.p12 b/test-common/src/main/resources/tls/clientKeyAndCert.p12 new file mode 100644 index 00000000..e69de29b diff --git a/test-common/src/main/resources/tls/clientKeyAndCert.unencryptedkey.p12 b/test-common/src/main/resources/tls/clientKeyAndCert.unencryptedkey.p12 new file mode 100644 index 0000000000000000000000000000000000000000..4e374234dc188453e0506400015a66d4bc5ca3c9 GIT binary patch literal 4157 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 Date: Fri, 15 Nov 2024 13:42:58 +0800 Subject: [PATCH 6/8] fix could not import non-bundle certificate-key pair --- .../SubprojectEnvironmentsEditorDialogView.kt | 12 +++++++----- .../main/resources/tls/clientKeyAndCert.p12 | 0 .../hellohttp/test/RequestResponseTestUtil.kt | 19 +++++++++++++++++-- 3 files changed, 24 insertions(+), 7 deletions(-) delete mode 100644 test-common/src/main/resources/tls/clientKeyAndCert.p12 diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt index 5ab43c14..23f0dcea 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt @@ -683,13 +683,15 @@ fun CertificateKeyPairImportForm(modifier: Modifier = Modifier, onAddItem: (Clie keyFile = keyFile!!, keyPassword = keyFilePassword ) + } else { + ClientCertificateKeyPair.importFrom( + bundleFile = bundleFile!!, + keyStorePassword = bundleFilePassword, + keyPassword = keyFilePassword + ) } - ClientCertificateKeyPair.importFrom( - bundleFile = bundleFile!!, - keyStorePassword = bundleFilePassword, - keyPassword = keyFilePassword - ) } catch (e: Throwable) { + log.w(e) { "Cannot import given certificate-key pair" } AppContext.ErrorMessagePromptViewModel.showErrorMessage(e.message ?: e::class.simpleName!!) return@AppTextButton } diff --git a/test-common/src/main/resources/tls/clientKeyAndCert.p12 b/test-common/src/main/resources/tls/clientKeyAndCert.p12 deleted file mode 100644 index e69de29b..00000000 diff --git a/ux-and-transport-test/src/test/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/RequestResponseTestUtil.kt b/ux-and-transport-test/src/test/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/RequestResponseTestUtil.kt index 563d208e..34b0bf43 100644 --- a/ux-and-transport-test/src/test/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/RequestResponseTestUtil.kt +++ b/ux-and-transport-test/src/test/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/RequestResponseTestUtil.kt @@ -261,12 +261,14 @@ suspend fun DesktopComposeUiTest.createProjectIfNeeded() { onNodeWithTag(TestTag.EnvironmentEditorSslTabContent.name, useUnmergedTree = true) .performScrollToNode(hasTestTag(buildTestTag( TestTagPart.EnvironmentSslClientCertificates, - TestTagPart.CreateButton, + TestTagPart.Bundle, + TestTagPart.FileButton, )!!)) waitUntil { onNodeWithTag(buildTestTag( TestTagPart.EnvironmentSslClientCertificates, - TestTagPart.CreateButton, + TestTagPart.Bundle, + TestTagPart.FileButton, )!!) .isDisplayed() } @@ -311,6 +313,19 @@ suspend fun DesktopComposeUiTest.createProjectIfNeeded() { .firstOrNull() == keyFile.name } + onNodeWithTag(TestTag.EnvironmentEditorSslTabContent.name, useUnmergedTree = true) + .performScrollToNode(hasTestTag(buildTestTag( + TestTagPart.EnvironmentSslClientCertificates, + TestTagPart.CreateButton, + )!!)) + waitUntil { + onNodeWithTag(buildTestTag( + TestTagPart.EnvironmentSslClientCertificates, + TestTagPart.CreateButton, + )!!) + .isDisplayed() + } + retryForUnresponsiveBuggyComposeTest { onNodeWithTag( buildTestTag( From 60668815f6e302c1ef1b04760ccff9dac3b26e2d Mon Sep 17 00:00:00 2001 From: Sunny Chung Date: Fri, 15 Nov 2024 13:43:42 +0800 Subject: [PATCH 7/8] fix could not import PEM certificates --- .../application/multiplatform/hellohttp/util/Pkix.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt index e9dd37e1..c0d5530a 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt @@ -255,7 +255,9 @@ fun ClientCertificateKeyPair.Companion.importFrom(bundleFile: File, keyStorePass } fun parseCaCertificates(bytes: ByteArray) : List { - val certBytes = bytes.tryToConvertPemToDer(startLine = "-----BEGIN PKCS7-----", endLine = "-----END PKCS7-----") + val certBytes = bytes + .tryToConvertPemToDer(startLine = "-----BEGIN CERTIFICATE-----", endLine = "-----END CERTIFICATE-----") + .tryToConvertPemToDer(startLine = "-----BEGIN PKCS7-----", endLine = "-----END PKCS7-----") return CertificateFactory.getInstance("X.509") .generateCertificates(ByteArrayInputStream(certBytes)).map { it as X509Certificate } } From 4cf8e319ad9a4a82f9fe06ae02f4a85486f94203 Mon Sep 17 00:00:00 2001 From: Sunny Chung Date: Fri, 15 Nov 2024 13:50:35 +0800 Subject: [PATCH 8/8] add delete buttons to the certificate-key pair import form to clear selected files --- .../ux/SubprojectEnvironmentsEditorDialogView.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt index 23f0dcea..cdcbc974 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SubprojectEnvironmentsEditorDialogView.kt @@ -606,6 +606,9 @@ fun CertificateKeyPairImportForm(modifier: Modifier = Modifier, onAddItem: (Clie )!! ) ) + AppDeleteButton(modifier = Modifier.padding(horizontal = 6.dp)) { + certFile = null + } } Row(verticalAlignment = Alignment.CenterVertically) { AppText(text = "Private Key", modifier = Modifier.width(headerColumnWidth)) @@ -620,6 +623,9 @@ fun CertificateKeyPairImportForm(modifier: Modifier = Modifier, onAddItem: (Clie )!! ) ) + AppDeleteButton(modifier = Modifier.padding(horizontal = 6.dp)) { + keyFile = null + } } } Box(contentAlignment = Alignment.Center) { @@ -646,6 +652,9 @@ fun CertificateKeyPairImportForm(modifier: Modifier = Modifier, onAddItem: (Clie )!! ) ) + AppDeleteButton(modifier = Modifier.padding(horizontal = 6.dp)) { + bundleFile = null + } } Row(verticalAlignment = Alignment.CenterVertically) {