diff --git a/plugins/maven-server-api/src/main/java/org/jetbrains/idea/maven/server/security/ssl/SslIDEConfirmingTrustStore.java b/plugins/maven-server-api/src/main/java/org/jetbrains/idea/maven/server/security/ssl/SslIDEConfirmingTrustStore.java index 6dcc06ee7f40..354b6382b0cc 100644 --- a/plugins/maven-server-api/src/main/java/org/jetbrains/idea/maven/server/security/ssl/SslIDEConfirmingTrustStore.java +++ b/plugins/maven-server-api/src/main/java/org/jetbrains/idea/maven/server/security/ssl/SslIDEConfirmingTrustStore.java @@ -11,7 +11,6 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; -import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -36,17 +35,21 @@ public final class SslIDEConfirmingTrustStore { public static final String END_CERTIFICATE = "-----END CERTIFICATE-----"; public static void setup() { + if (System.getProperty("javax.net.ssl.keyStore") != null) { + MavenServerGlobals.getLogger().warn("Will not delegate SSL certificate management to IDE, " + + "looks like client certificate authentication is required"); + return; + } try { startMultiplexorThread(); TrustManager delegateTM = new IdeDelegateTrustManager(); + SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, new TrustManager[]{delegateTM}, null); - // You don't have to set this as the default context, - // it depends on the library you're using. SSLContext.setDefault(sslContext); } - catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) { + catch (Throwable e) { MavenServerGlobals.getLogger().error(e); } } @@ -119,7 +122,7 @@ public final class SslIDEConfirmingTrustStore { public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { for (X509TrustManager m : myTrustManagers) { try { - m.checkClientTrusted(chain, authType); + m.checkServerTrusted(chain, authType); return; } catch (CertificateException ignore) { diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/server/AbstractMavenServerRemoteProcessSupport.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/server/AbstractMavenServerRemoteProcessSupport.java index d6192cd9a85c..728226d581c1 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/server/AbstractMavenServerRemoteProcessSupport.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/server/AbstractMavenServerRemoteProcessSupport.java @@ -49,7 +49,7 @@ public abstract class AbstractMavenServerRemoteProcessSupport extends MavenRemot myImportEventProcessor = new MavenImportEventProcessor(project); AnsiEscapeDecoder myDecoder = new AnsiEscapeDecoder(); - mySslDelegateHandlerStateMachine = new SslDelegateHandlerConfirmingTrustManager(); + mySslDelegateHandlerStateMachine = new SslDelegateHandlerConfirmingTrustManager(project); myMavenSpyEventsBuffer = new MavenSpyEventsBuffer((l, k) -> { mySslDelegateHandlerStateMachine.addLine(l); diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/server/ssl/MavenTLSCertificateChecker.kt b/plugins/maven/src/main/java/org/jetbrains/idea/maven/server/ssl/MavenTLSCertificateChecker.kt new file mode 100644 index 000000000000..bf9906b0ed61 --- /dev/null +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/server/ssl/MavenTLSCertificateChecker.kt @@ -0,0 +1,37 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.idea.maven.server.ssl + +import com.intellij.openapi.project.Project +import com.intellij.util.net.ssl.CertificateManager +import com.intellij.util.net.ssl.ConfirmingTrustManager +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.idea.maven.project.MavenProjectBundle +import org.jetbrains.idea.maven.utils.MavenLog +import java.security.cert.CertificateException +import java.security.cert.X509Certificate + +@ApiStatus.Internal +interface MavenTLSCertificateChecker { + fun checkCertificates(chain: Array, authType: String): Boolean +} + +@ApiStatus.Internal +class IdeCertificateManagerMavenTLSCertificateChecker(val project: Project) : MavenTLSCertificateChecker { + override fun checkCertificates(chain: Array, authType: String): Boolean { + try { + val confirmationParameters = ConfirmingTrustManager.CertificateConfirmationParameters + .askConfirmation(false, + MavenProjectBundle.message("maven.server.ask.trust"), + null) + CertificateManager + .getInstance() + .trustManager + .checkServerTrusted(chain, authType, confirmationParameters) + return true + } + catch (e: CertificateException) { + MavenLog.LOG.warn(e) + return false + } + } +} \ No newline at end of file diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/server/ssl/SslDelegateHandlerStateMachine.kt b/plugins/maven/src/main/java/org/jetbrains/idea/maven/server/ssl/SslDelegateHandlerStateMachine.kt index 4f79ac4168bf..b6842473e250 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/server/ssl/SslDelegateHandlerStateMachine.kt +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/server/ssl/SslDelegateHandlerStateMachine.kt @@ -1,41 +1,23 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.idea.maven.server.ssl +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project import com.intellij.openapi.util.NlsSafe -import com.intellij.util.net.ssl.CertificateManager -import com.intellij.util.net.ssl.ConfirmingTrustManager -import org.jetbrains.idea.maven.project.MavenProjectBundle import org.jetbrains.idea.maven.server.security.ssl.SslIDEConfirmingTrustStore import java.io.ByteArrayOutputStream import java.io.OutputStream import java.io.PrintStream -import java.security.cert.CertificateException import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.util.* -class SslDelegateHandlerConfirmingTrustManager : SslDelegateHandlerStateMachine(::confirmServerTrust) - -private fun confirmServerTrust(arr: Array, authType: String): Boolean { - try { - val confirmationParameters = ConfirmingTrustManager.CertificateConfirmationParameters - .askConfirmation(false, - MavenProjectBundle.message("maven.server.ask.trust"), - null) - CertificateManager - .getInstance() - .trustManager - .checkServerTrusted(arr, authType, confirmationParameters) - return true - } - catch (e: CertificateException) { - return false - } -} +class SslDelegateHandlerConfirmingTrustManager(project: Project) + : SslDelegateHandlerStateMachine(project.service()) -open class SslDelegateHandlerStateMachine(val checkTrusted: (Array, String) -> Boolean) { +open class SslDelegateHandlerStateMachine(val checker: MavenTLSCertificateChecker) { private var currentState: State lateinit var output: OutputStream @@ -136,7 +118,7 @@ class ReadNextCertificate( class WaitEndAndExecute(val machine: SslDelegateHandlerStateMachine, val key: Int, val authType: String, val certificates: ArrayList) : State() { override fun addLine(text: String): State { if (text == SslIDEConfirmingTrustStore.CHECK_SERVER_TRUSTED) { - val trusted = machine.checkTrusted(certificates.toTypedArray(), authType) + val trusted = machine.checker.checkCertificates(certificates.toTypedArray(), authType) val os = ByteArrayOutputStream() PrintStream(os, true, "UTF-8").use { ps -> ps.println(SslIDEConfirmingTrustStore.IDE_DELEGATE_TRUST_MANAGER) diff --git a/plugins/maven/src/main/resources/META-INF/plugin.xml b/plugins/maven/src/main/resources/META-INF/plugin.xml index 2b451dd368a7..2ff98d324a39 100644 --- a/plugins/maven/src/main/resources/META-INF/plugin.xml +++ b/plugins/maven/src/main/resources/META-INF/plugin.xml @@ -116,6 +116,8 @@ serviceImplementation="org.jetbrains.idea.maven.project.MavenProjectsManagerEx"/> + diff --git a/plugins/maven/src/test/java/org/jetbrains/idea/maven/server/ssl/MavenSslServerClientAuthTest.kt b/plugins/maven/src/test/java/org/jetbrains/idea/maven/server/ssl/MavenSslServerClientAuthTest.kt new file mode 100644 index 000000000000..b7c7b817e72c --- /dev/null +++ b/plugins/maven/src/test/java/org/jetbrains/idea/maven/server/ssl/MavenSslServerClientAuthTest.kt @@ -0,0 +1,124 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.idea.maven.server.ssl + +import com.intellij.maven.testFramework.MavenMultiVersionImportingTestCase +import com.intellij.maven.testFramework.utils.MavenCertificateFixture +import com.intellij.maven.testFramework.utils.MavenHttpsRepositoryServerFixture +import com.intellij.testFramework.common.runAll +import com.intellij.testFramework.replaceService +import com.intellij.util.asDisposable +import kotlinx.coroutines.runBlocking +import org.jetbrains.idea.maven.MavenCustomRepositoryHelper +import org.junit.Test +import java.nio.file.Files +import java.security.cert.X509Certificate +import kotlin.io.path.createParentDirectories +import kotlin.io.path.isRegularFile + +class MavenSslServerClientAuthTest : MavenMultiVersionImportingTestCase() { + + private lateinit var httpsServerFixture: MavenHttpsRepositoryServerFixture + private lateinit var certificateFixture: MavenCertificateFixture + + public override fun setUp() { + super.setUp() + certificateFixture = MavenCertificateFixture() + certificateFixture.setUp() + val (cert, pKey) = certificateFixture.createServerCertificate("localhost") + httpsServerFixture = MavenHttpsRepositoryServerFixture( + cert, "localhost", pKey, + object : MavenTLSCertificateChecker { + override fun checkCertificates(chain: Array, authType: String): Boolean { + return true + } + + }, certificateFixture.rootCaCert + ).also { it.setUp() } + } + + override fun tearDown() { + runAll({ + certificateFixture.tearDown() + }, { + httpsServerFixture.tearDown() + }, { + super.tearDown() + }) + } + + + @Test + fun testShouldNotUseDelegateWithClientAuthentication() = runBlocking { + var requested = false + project.replaceService(MavenTLSCertificateChecker::class.java, object : MavenTLSCertificateChecker { + override fun checkCertificates(chain: Array, authType: String): Boolean { + requested = true + return false + } + }, asDisposable()) + + val keyStoreLocation = dir.resolve("keystore/mykeystore") + keyStoreLocation.createParentDirectories() + + val truststore = dir.resolve("keystore/mytrusttore") + truststore.createParentDirectories() + certificateFixture.saveCertificate(httpsServerFixture.myServerCertificate, + truststore, + "password", "localhost", "pkcs12", true) + + val (cert, pkey) = certificateFixture.createClientCertificate("User") + certificateFixture.savePrivateKey(cert, pkey, keyStoreLocation, "password", "pkcs12") + + projectsManager.importingSettings.vmOptionsForImporter = + "-Djavax.net.ssl.keyStore=${keyStoreLocation} " + + "-Djavax.net.ssl.keyStorePassword=password " + + "-Djavax.net.ssl.trustStore=$truststore " + + "-Djavax.net.ssl.trustStorePassword=password" + + + doImportMavenServerProject() + assertFalse("Certificate trust should not be requested", requested) + if (!repositoryPath.resolve("org/mytest/myartifact/1.0/myartifact-1.0.jar").isRegularFile()) { + fail("Cannnot resolve dependency:" + Files.readString(repositoryPath.resolve("org/mytest/myartifact/1.0/myartifact-1.0.pom.lastUpdated"))) + } + } + + + private suspend fun doImportMavenServerProject() { + val helper = MavenCustomRepositoryHelper(dir, "local1", "remote") + val remoteRepoPath = helper.getTestData("remote") + val localRepoPath = helper.getTestData("local1") + httpsServerFixture.startRepositoryFor(remoteRepoPath.toString()) + repositoryPath = localRepoPath + val settingsXml = createProjectSubFile( + "settings.xml", + """ + + $localRepoPath + + """.trimIndent()) + mavenGeneralSettings.setUserSettingsFile(settingsXml.canonicalPath) + removeFromLocalRepository("org/mytest/myartifact/") + assertFalse(helper.getTestData("local1/org/mytest/myartifact/1.0/myartifact-1.0.jar").isRegularFile()) + importProjectAsync(""" + test + project + 1 + + + org.mytest + myartifact + 1.0 + + + + + my-https-repository + my-https-repository + ${httpsServerFixture.url()} + + + """.trimIndent()) + + } +} \ No newline at end of file diff --git a/plugins/maven/src/test/java/org/jetbrains/idea/maven/server/ssl/MavenSslServerTest.kt b/plugins/maven/src/test/java/org/jetbrains/idea/maven/server/ssl/MavenSslServerTest.kt new file mode 100644 index 000000000000..f2815ae7c0e5 --- /dev/null +++ b/plugins/maven/src/test/java/org/jetbrains/idea/maven/server/ssl/MavenSslServerTest.kt @@ -0,0 +1,138 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.idea.maven.server.ssl + +import com.intellij.maven.testFramework.MavenMultiVersionImportingTestCase +import com.intellij.maven.testFramework.utils.MavenCertificateFixture +import com.intellij.maven.testFramework.utils.MavenHttpsRepositoryServerFixture +import com.intellij.testFramework.common.runAll +import com.intellij.testFramework.replaceService +import com.intellij.util.asDisposable +import kotlinx.coroutines.runBlocking +import org.jetbrains.idea.maven.MavenCustomRepositoryHelper +import org.junit.Test +import java.security.cert.X509Certificate +import kotlin.io.path.createDirectories +import kotlin.io.path.createParentDirectories +import kotlin.io.path.isRegularFile + +class MavenSslServerTest : MavenMultiVersionImportingTestCase() { + + private lateinit var httpsServerFixture: MavenHttpsRepositoryServerFixture + private lateinit var certificateFixture: MavenCertificateFixture + + public override fun setUp() { + super.setUp() + certificateFixture = MavenCertificateFixture() + certificateFixture.setUp() + val (cert, pKey) = certificateFixture.createServerCertificate("localhost") + httpsServerFixture = MavenHttpsRepositoryServerFixture( + cert, "localhost", pKey + ).also { it.setUp() } + } + + override fun tearDown() { + runAll({ + certificateFixture.tearDown() + }, { + httpsServerFixture.tearDown() + }, { + super.tearDown() + }) + } + + @Test + fun testShouldRequestIntellijTrustServiceWhenLoadingUntrustedDomain() = runBlocking { + var accepted = false + project.replaceService(MavenTLSCertificateChecker::class.java, object : MavenTLSCertificateChecker { + override fun checkCertificates(chain: Array, authType: String): Boolean { + accepted = chain[0] == httpsServerFixture.myServerCertificate + return true + } + }, asDisposable()) + doImportMavenServerProject() + assertTrue("Certificate should be accepted", accepted) + } + + @Test + fun testShouldNotRequestIntellijTrustServiceWhenTrustStoreIsPassed() = runBlocking { + val trustStoreLocation = dir.resolve("truststore/mytrustore") + trustStoreLocation.createParentDirectories() + certificateFixture.saveCertificate(httpsServerFixture.myServerCertificate, + trustStoreLocation, + "password", "localhost", "pkcs12", true) + var requsted = false + projectsManager.importingSettings.vmOptionsForImporter = "-Djavax.net.ssl.trustStore=${trustStoreLocation} -Djavax.net.ssl.trustStorePassword=password" + project.replaceService(MavenTLSCertificateChecker::class.java, object : MavenTLSCertificateChecker { + override fun checkCertificates(chain: Array, authType: String): Boolean { + requsted = true + return false + } + }, asDisposable()) + + doImportMavenServerProject() + assertFalse("Certificate should be accepted using inbuild truststore manager", requsted) + } + + + @Test + fun testShouldAllowClientAuthentication() = runBlocking { + val trustStoreLocationDir = dir.resolve("truststore") + trustStoreLocationDir.createDirectories() + val truststore = trustStoreLocationDir.resolve("mytrustore") + certificateFixture.saveCertificate(httpsServerFixture.myServerCertificate, + truststore, + "password", "localhost", "pkcs12", true) + var requsted = false + projectsManager.importingSettings.vmOptionsForImporter = "-Djavax.net.ssl.trustStore=${truststore} -Djavax.net.ssl.trustStorePassword=password" + project.replaceService(MavenTLSCertificateChecker::class.java, object : MavenTLSCertificateChecker { + override fun checkCertificates(chain: Array, authType: String): Boolean { + requsted = true + return false + } + }, asDisposable()) + + doImportMavenServerProject() + assertFalse("Certificate should be accepted using inbuild truststore manager", requsted) + } + + + + + private suspend fun doImportMavenServerProject() { + val helper = MavenCustomRepositoryHelper(dir, "local1", "remote") + val remoteRepoPath = helper.getTestData("remote") + val localRepoPath = helper.getTestData("local1") + httpsServerFixture.startRepositoryFor(remoteRepoPath.toString()) + repositoryPath = localRepoPath + val settingsXml = createProjectSubFile( + "settings.xml", + """ + + $localRepoPath + + """.trimIndent()) + mavenGeneralSettings.setUserSettingsFile(settingsXml.canonicalPath) + removeFromLocalRepository("org/mytest/myartifact/") + assertFalse(helper.getTestData("local1/org/mytest/myartifact/1.0/myartifact-1.0.jar").isRegularFile()) + importProjectAsync(""" + test + project + 1 + + + org.mytest + myartifact + 1.0 + + + + + my-https-repository + my-https-repository + ${httpsServerFixture.url()} + + + """.trimIndent()) + assertTrue(repositoryPath.resolve("org/mytest/myartifact/1.0/myartifact-1.0.jar").isRegularFile()) + } +} \ No newline at end of file diff --git a/plugins/maven/src/test/java/org/jetbrains/idea/maven/server/ssl/SslDelegateHandlerStateMachineTest.kt b/plugins/maven/src/test/java/org/jetbrains/idea/maven/server/ssl/SslDelegateHandlerStateMachineTest.kt index e14a2e616137..597104be61a5 100644 --- a/plugins/maven/src/test/java/org/jetbrains/idea/maven/server/ssl/SslDelegateHandlerStateMachineTest.kt +++ b/plugins/maven/src/test/java/org/jetbrains/idea/maven/server/ssl/SslDelegateHandlerStateMachineTest.kt @@ -6,13 +6,21 @@ import com.intellij.testFramework.UsefulTestCase import com.intellij.util.ArrayUtilRt import com.intellij.util.ResourceUtil import java.io.ByteArrayOutputStream +import java.security.cert.X509Certificate import java.util.* class SslDelegateHandlerStateMachineTest : UsefulTestCase() { + private val CheckTrue = object : MavenTLSCertificateChecker { + override fun checkCertificates(chain: Array, authType: String) = true + } + + private val CheckFalse = object : MavenTLSCertificateChecker { + override fun checkCertificates(chain: Array, authType: String) = false + } fun testResultOk() { val data = fromFile("ssl_remote_query.txt"); - val machine = SslDelegateHandlerStateMachine { _, _ -> true } + val machine = SslDelegateHandlerStateMachine(CheckTrue) val out = ByteArrayOutputStream() machine.output = out data.forEach { machine.addLine(it) } @@ -23,7 +31,7 @@ class SslDelegateHandlerStateMachineTest : UsefulTestCase() { fun testResultFail() { val data = fromFile("ssl_remote_query.txt"); - val machine = SslDelegateHandlerStateMachine { _, _ -> false } + val machine = SslDelegateHandlerStateMachine(CheckFalse) val out = ByteArrayOutputStream() machine.output = out data.forEach { machine.addLine(it) } diff --git a/plugins/maven/testFramework/src/com/intellij/maven/testFramework/utils/AbstractMavenRepositoryServerFixture.kt b/plugins/maven/testFramework/src/com/intellij/maven/testFramework/utils/AbstractMavenRepositoryServerFixture.kt index 553b32a1f68d..b59498ef4479 100644 --- a/plugins/maven/testFramework/src/com/intellij/maven/testFramework/utils/AbstractMavenRepositoryServerFixture.kt +++ b/plugins/maven/testFramework/src/com/intellij/maven/testFramework/utils/AbstractMavenRepositoryServerFixture.kt @@ -6,6 +6,7 @@ import com.intellij.testFramework.fixtures.IdeaTestFixture import com.sun.net.httpserver.Authenticator import com.sun.net.httpserver.BasicAuthenticator import com.sun.net.httpserver.HttpServer +import org.jetbrains.idea.maven.utils.MavenLog import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Path @@ -21,6 +22,7 @@ abstract class AbstractMavenRepositoryServerFixture : IdeaTestFixture { override fun setUp() { myServer = startServer() + MavenLog.LOG.debug("Starting Maven repository server for tests on ${url()}") } override fun tearDown() { diff --git a/plugins/maven/testFramework/src/com/intellij/maven/testFramework/utils/MavenCertificateFixture.kt b/plugins/maven/testFramework/src/com/intellij/maven/testFramework/utils/MavenCertificateFixture.kt index 94970f8291f7..f5f78e465271 100644 --- a/plugins/maven/testFramework/src/com/intellij/maven/testFramework/utils/MavenCertificateFixture.kt +++ b/plugins/maven/testFramework/src/com/intellij/maven/testFramework/utils/MavenCertificateFixture.kt @@ -16,14 +16,18 @@ import java.security.KeyStore import java.security.PrivateKey import java.security.cert.X509Certificate import java.util.* +import javax.net.ssl.TrustManagerFactory +import javax.net.ssl.X509TrustManager + class MavenCertificateFixture() : IdeaTestFixture { private val rootCaKeyPair: KeyPair = generateKeyPair() - private val rootCaCert: X509Certificate = generateRootCaCertificate() + val rootCaCert: X509Certificate = generateRootCaCertificate() override fun setUp() { } + private fun generateKeyPair(): KeyPair { val keyGen = KeyPairGenerator.getInstance("RSA") keyGen.initialize(2048) @@ -91,19 +95,53 @@ class MavenCertificateFixture() : IdeaTestFixture { } } - fun saveCertificates(cert: X509Certificate, store: Path, password: String, type: String = "pkcs12") { + fun saveCertificate( + cert: X509Certificate, store: Path, + password: String, + alias: String, + type: String = "pkcs12", + copyDefault: Boolean, + ) { val keyStore = KeyStore.getInstance(type) keyStore.load(null, null) - keyStore.setCertificateEntry("cert-${UUID.randomUUID()}", cert) + if (copyDefault) { + addDefaultCertificates(keyStore) + } + keyStore.setCertificateEntry(alias, cert) saveKeyStore(keyStore, store, password) } + private fun addDefaultCertificates(trustStore: KeyStore) { + val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + trustManagerFactory.init(null as KeyStore?) + for (trustManager in trustManagerFactory.getTrustManagers()) { + if (trustManager is X509TrustManager) { + for (acceptedIssuer in trustManager.getAcceptedIssuers()) { + trustStore.setCertificateEntry(acceptedIssuer.subjectX500Principal.name, acceptedIssuer) + } + } + } + } + private fun saveKeyStore(keyStore: KeyStore, storePath: Path, password: String) { storePath.toFile().outputStream().use { os -> keyStore.store(os, password.toCharArray()) } } + fun savePrivateKey( + cert: X509Certificate, privateKey: PrivateKey, store: Path, + password: String, + type: String = "pkcs12", + ): Path { + val keyStore = KeyStore.getInstance(type) + keyStore.load(null, null) + keyStore.setCertificateEntry("cert", cert) + keyStore.setKeyEntry("key", privateKey, password.toCharArray(), arrayOf(cert, rootCaCert)) + saveKeyStore(keyStore, store, password) + return store + } + override fun tearDown() { } } \ No newline at end of file diff --git a/plugins/maven/testFramework/src/com/intellij/maven/testFramework/utils/MavenHttpsRepositoryServerFixture.kt b/plugins/maven/testFramework/src/com/intellij/maven/testFramework/utils/MavenHttpsRepositoryServerFixture.kt index bf5d1537ed4a..579f3a257aba 100644 --- a/plugins/maven/testFramework/src/com/intellij/maven/testFramework/utils/MavenHttpsRepositoryServerFixture.kt +++ b/plugins/maven/testFramework/src/com/intellij/maven/testFramework/utils/MavenHttpsRepositoryServerFixture.kt @@ -5,9 +5,8 @@ import com.intellij.util.concurrency.AppExecutorUtil import com.sun.net.httpserver.HttpsConfigurator import com.sun.net.httpserver.HttpsParameters import com.sun.net.httpserver.HttpsServer -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import java.io.File +import org.jetbrains.idea.maven.server.ssl.MavenTLSCertificateChecker +import org.jetbrains.idea.maven.utils.MavenLog import java.net.InetSocketAddress import java.security.KeyStore import java.security.PrivateKey @@ -16,7 +15,7 @@ import java.security.cert.X509Certificate import javax.net.ssl.KeyManagerFactory import javax.net.ssl.SSLContext import javax.net.ssl.TrustManagerFactory -import kotlin.time.Duration.Companion.seconds +import javax.net.ssl.X509TrustManager private const val LOCALHOST = "127.0.0.1" private const val SERVER_KS_PASSWORD = "password" @@ -25,10 +24,12 @@ class MavenHttpsRepositoryServerFixture( val myServerCertificate: X509Certificate, val sslHostname: String, val myPrivateKey: PrivateKey, + val clientCertificateChecker: MavenTLSCertificateChecker? = null, + val myRootCA: X509Certificate? = null ) : AbstractMavenRepositoryServerFixture() { override fun url(): String { - return "https://$LOCALHOST:${myServer.address.port}" + return "https://localhost:${myServer.address.port}" } override fun startServer(): HttpsServer { @@ -39,7 +40,7 @@ class MavenHttpsRepositoryServerFixture( server.httpsConfigurator = object : HttpsConfigurator(sslContext) { override fun configure(params: HttpsParameters) { val engine = sslContext.createSSLEngine() - params.needClientAuth = false + params.needClientAuth = clientCertificateChecker != null params.cipherSuites = engine.enabledCipherSuites params.protocols = engine.enabledProtocols params.setSSLParameters(sslContext.defaultSSLParameters) @@ -67,12 +68,33 @@ class MavenHttpsRepositoryServerFixture( val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) kmf.init(keyStore, SERVER_KS_PASSWORD.toCharArray()) - // TrustManagerFactory to trust our own cert - val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) - tmf.init(keyStore) + val trustManagers = if (clientCertificateChecker == null) { + val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + tmf.init(keyStore) + tmf.trustManagers + } + else { + arrayOf(object : X509TrustManager { + override fun checkClientTrusted(chain: Array, authType: String) { + if (!clientCertificateChecker.checkCertificates(chain, authType)) { + throw SecurityException("Invalid client certificate") + } + } + + override fun checkServerTrusted(chain: Array, authType: String) { + if (!clientCertificateChecker.checkCertificates(chain, authType)) { + throw SecurityException("Invalid client certificate") + } + } + + override fun getAcceptedIssuers(): Array = arrayOf(myRootCA!!) + }) + } + + return SSLContext.getInstance("TLS").apply { - init(kmf.keyManagers, tmf.trustManagers, SecureRandom()) + init(kmf.keyManagers, trustManagers, SecureRandom()) } } } \ No newline at end of file