diff --git a/CHANGELOG.md b/CHANGELOG.md index 69a5fa55..f6a6b749 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ 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. +- PKCS#12 (known as p12) and PFX files can now be imported as client certificates ### Changed - The main monospace font has been changed to Pitagon Sans Mono and unified among all platforms @@ -29,7 +31,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 2fbd66cb..998a8bd7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -88,6 +88,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") @@ -97,6 +100,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..cfa1f1b5 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,14 @@ 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 -``` +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. -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/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..c0d5530a --- /dev/null +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/util/Pkix.kt @@ -0,0 +1,286 @@ +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 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.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 +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) + } + + val keyBytes = keyFile.readBytes() + val privateKey = try { + parsePrivateKey(keyBytes = keyBytes, keyPassword = 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 = privateKey.encoded, // store decrypted bytes + ), + createdWhen = now, + isEnabled = true, + ) +} + +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) + } + + 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 { + 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) + } +} + +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() + return if (firstLine == startLine) { + val base64Encoded = buildString { + while (reader.ready()) { + val line = reader.readLine() + if (line != endLine) { + append(line) + } else { + break + } + } + } + Base64.getDecoder().decode(base64Encoded) + } else { // der / pem + this + } + } +} + +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 CERTIFICATE-----", endLine = "-----END CERTIFICATE-----") + .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) + + 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..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 @@ -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.model.importCaCertificates -import com.sunnychung.application.multiplatform.hellohttp.model.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,102 @@ 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 #8 DER 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, + )!! + ) + ) + AppDeleteButton(modifier = Modifier.padding(horizontal = 6.dp)) { + certFile = null + } + } + 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, + )!! + ) + ) + AppDeleteButton(modifier = Modifier.padding(horizontal = 6.dp)) { + keyFile = null + } + } + } + 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, + )!! + ) + ) + AppDeleteButton(modifier = Modifier.padding(horizontal = 6.dp)) { + bundleFile = null + } + } + + 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,20 +678,29 @@ 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.") - ClientCertificateKeyPair.importFrom( - certFile = certFile!!, - keyFile = keyFile!!, - keyPassword = keyFilePassword - ) + 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 + ) + } else { + 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 } @@ -640,7 +715,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 +723,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 +946,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/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..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.model.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/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 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 00000000..b6c7c7f8 Binary files /dev/null and b/src/jvmTest/resources/ssl/clientKey.pkcs1.der differ 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----- +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 +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/clientKeyAndCert.encrypted.password-is-zxcv.p12 b/test-common/src/main/resources/tls/clientKeyAndCert.encrypted.password-is-zxcv.p12 new file mode 100644 index 00000000..8bb93151 Binary files /dev/null and b/test-common/src/main/resources/tls/clientKeyAndCert.encrypted.password-is-zxcv.p12 differ 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 00000000..4e374234 Binary files /dev/null and b/test-common/src/main/resources/tls/clientKeyAndCert.unencryptedkey.p12 differ 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----- 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(