[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:
Alexander Bubenchikov
2025-08-05 11:48:08 +02:00
committed by intellij-monorepo-bot
parent fdb652797d
commit cbc1b614c8
12 changed files with 403 additions and 44 deletions

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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
}
}
}

View File

@@ -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)

View File

@@ -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"/>

View File

@@ -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>

View File

@@ -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())
}
}

View File

@@ -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())
}
}

View File

@@ -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) }

View File

@@ -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() {

View File

@@ -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() {
}
}

View File

@@ -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())
}
}
}