mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-06 11:50:54 +07:00
[maven] [IDEA-368366] maven tests for SSL and disabling delegation if client certificate authorization required
(cherry picked from commit 1a89d3c0e072f8b511d1999c5c678e8b701f38f7) IJ-CR-172020 # Conflicts: # community/plugins/maven/src/test/java/org/jetbrains/idea/maven/server/ssl/SslDelegateHandlerStateMachineTest.kt # community/plugins/maven/testFramework/src/com/intellij/maven/testFramework/utils/MavenHttpsRepositoryServerFixture.kt GitOrigin-RevId: 32603ba98d2da16546ac28ba2bccf6eadc509d9e
This commit is contained in:
committed by
intellij-monorepo-bot
parent
fdb652797d
commit
cbc1b614c8
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<X509Certificate>, authType: String): Boolean
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
class IdeCertificateManagerMavenTLSCertificateChecker(val project: Project) : MavenTLSCertificateChecker {
|
||||
override fun checkCertificates(chain: Array<X509Certificate>, 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<X509Certificate>, 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<MavenTLSCertificateChecker>())
|
||||
|
||||
|
||||
open class SslDelegateHandlerStateMachine(val checkTrusted: (Array<X509Certificate>, 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<X509Certificate>) : 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)
|
||||
|
||||
@@ -116,6 +116,8 @@
|
||||
serviceImplementation="org.jetbrains.idea.maven.project.MavenProjectsManagerEx"/>
|
||||
<projectService serviceImplementation="org.jetbrains.idea.maven.navigator.MavenProjectsNavigator"/>
|
||||
<projectService serviceImplementation="org.jetbrains.idea.maven.tasks.MavenShortcutsManager"/>
|
||||
<projectService serviceImplementation="org.jetbrains.idea.maven.server.ssl.IdeCertificateManagerMavenTLSCertificateChecker"
|
||||
serviceInterface="org.jetbrains.idea.maven.server.ssl.MavenTLSCertificateChecker"/>
|
||||
|
||||
<applicationService serviceInterface="com.intellij.openapi.roots.ui.configuration.actions.ModuleDeleteProvider"
|
||||
serviceImplementation="org.jetbrains.idea.maven.project.actions.MavenModuleDeleteProvider" overrides="true"/>
|
||||
|
||||
@@ -144,7 +144,7 @@ class MavenRepositoriesDownloadingTest : MavenMultiVersionImportingTestCase() {
|
||||
val helper = MavenCustomRepositoryHelper(dir, "local1", "remote")
|
||||
val remoteRepoPath = helper.getTestData("remote")
|
||||
val localRepoPath = helper.getTestData("local1")
|
||||
httpServerFixture.startRepositoryFor(remoteRepoPath.toFile(), USERNAME, PASSWORD)
|
||||
httpServerFixture.startRepositoryFor(remoteRepoPath, USERNAME, PASSWORD)
|
||||
repositoryPath = localRepoPath
|
||||
val settingsXml = createProjectSubFile(
|
||||
"settings.xml",
|
||||
@@ -174,7 +174,7 @@ class MavenRepositoriesDownloadingTest : MavenMultiVersionImportingTestCase() {
|
||||
val helper = MavenCustomRepositoryHelper(dir, "local1", "remote")
|
||||
val remoteRepoPath = helper.getTestData("remote")
|
||||
val localRepoPath = helper.getTestData("local1")
|
||||
httpServerFixture.startRepositoryFor(remoteRepoPath.toFile(), USERNAME, PASSWORD)
|
||||
httpServerFixture.startRepositoryFor(remoteRepoPath, USERNAME, PASSWORD)
|
||||
repositoryPath = localRepoPath
|
||||
val settingsXml = createProjectSubFile(
|
||||
"settings.xml",
|
||||
@@ -213,7 +213,7 @@ class MavenRepositoriesDownloadingTest : MavenMultiVersionImportingTestCase() {
|
||||
val helper = MavenCustomRepositoryHelper(dir, "local1", "remote")
|
||||
val remoteRepoPath = helper.getTestData("remote")
|
||||
val localRepoPath = helper.getTestData("local1")
|
||||
httpServerFixture.startRepositoryFor(remoteRepoPath.toFile(), USERNAME, PASSWORD)
|
||||
httpServerFixture.startRepositoryFor(remoteRepoPath, USERNAME, PASSWORD)
|
||||
repositoryPath = localRepoPath
|
||||
@Language(value = "XML") val settingsXmlText = """
|
||||
<settings>
|
||||
|
||||
@@ -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<X509Certificate>, 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<X509Certificate>, 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",
|
||||
"""
|
||||
<settings>
|
||||
<localRepository>$localRepoPath</localRepository>
|
||||
</settings>
|
||||
""".trimIndent())
|
||||
mavenGeneralSettings.setUserSettingsFile(settingsXml.canonicalPath)
|
||||
removeFromLocalRepository("org/mytest/myartifact/")
|
||||
assertFalse(helper.getTestData("local1/org/mytest/myartifact/1.0/myartifact-1.0.jar").isRegularFile())
|
||||
importProjectAsync("""
|
||||
<groupId>test</groupId>
|
||||
<artifactId>project</artifactId>
|
||||
<version>1</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.mytest</groupId>
|
||||
<artifactId>myartifact</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>my-https-repository</id>
|
||||
<name>my-https-repository</name>
|
||||
<url>${httpsServerFixture.url()}</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
""".trimIndent())
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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<X509Certificate>, 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<X509Certificate>, 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<X509Certificate>, 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",
|
||||
"""
|
||||
<settings>
|
||||
<localRepository>$localRepoPath</localRepository>
|
||||
</settings>
|
||||
""".trimIndent())
|
||||
mavenGeneralSettings.setUserSettingsFile(settingsXml.canonicalPath)
|
||||
removeFromLocalRepository("org/mytest/myartifact/")
|
||||
assertFalse(helper.getTestData("local1/org/mytest/myartifact/1.0/myartifact-1.0.jar").isRegularFile())
|
||||
importProjectAsync("""
|
||||
<groupId>test</groupId>
|
||||
<artifactId>project</artifactId>
|
||||
<version>1</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.mytest</groupId>
|
||||
<artifactId>myartifact</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>my-https-repository</id>
|
||||
<name>my-https-repository</name>
|
||||
<url>${httpsServerFixture.url()}</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
""".trimIndent())
|
||||
assertTrue(repositoryPath.resolve("org/mytest/myartifact/1.0/myartifact-1.0.jar").isRegularFile())
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,23 @@ import com.intellij.util.ArrayUtilRt
|
||||
import com.intellij.util.ResourceUtil
|
||||
import junit.framework.TestCase
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.security.cert.X509Certificate
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class SslDelegateHandlerStateMachineTest : UsefulTestCase() {
|
||||
|
||||
|
||||
private val CheckTrue = object : MavenTLSCertificateChecker {
|
||||
override fun checkCertificates(chain: Array<X509Certificate>, authType: String) = true
|
||||
}
|
||||
|
||||
private val CheckFalse = object : MavenTLSCertificateChecker {
|
||||
override fun checkCertificates(chain: Array<X509Certificate>, 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) }
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ 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 org.jetbrains.idea.maven.server.ssl.MavenTLSCertificateChecker
|
||||
import java.net.InetSocketAddress
|
||||
import java.security.KeyStore
|
||||
import java.security.PrivateKey
|
||||
@@ -13,6 +14,7 @@ import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.KeyManagerFactory
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
private const val LOCALHOST = "127.0.0.1"
|
||||
private const val SERVER_KS_PASSWORD = "password"
|
||||
@@ -21,10 +23,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 {
|
||||
@@ -35,7 +39,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)
|
||||
@@ -63,12 +67,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<X509Certificate>, authType: String) {
|
||||
if (!clientCertificateChecker.checkCertificates(chain, authType)) {
|
||||
throw SecurityException("Invalid client certificate")
|
||||
}
|
||||
}
|
||||
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
|
||||
if (!clientCertificateChecker.checkCertificates(chain, authType)) {
|
||||
throw SecurityException("Invalid client certificate")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf(myRootCA!!)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
return SSLContext.getInstance("TLS").apply {
|
||||
init(kmf.keyManagers, tmf.trustManagers, SecureRandom())
|
||||
init(kmf.keyManagers, trustManagers, SecureRandom())
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user