mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[maven] [IDEA-368366] create SSL infrastructure for tests
GitOrigin-RevId: a3346d15cce231be4c37c8e8f70375153923859a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
98b827095f
commit
ebfefbf34b
@@ -32,6 +32,7 @@ jvm_library(
|
||||
"//platform/backend/observation",
|
||||
"//platform/backend/workspace",
|
||||
"//platform/workspace/storage",
|
||||
"@lib//:bouncy-castle-provider",
|
||||
]
|
||||
)
|
||||
### auto-generated section `build intellij.maven.testFramework` end
|
||||
@@ -49,5 +49,6 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.backend.observation" />
|
||||
<orderEntry type="module" module-name="intellij.platform.backend.workspace" />
|
||||
<orderEntry type="module" module-name="intellij.platform.workspace.storage" />
|
||||
<orderEntry type="library" name="bouncy-castle-provider" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.maven.testFramework.utils
|
||||
|
||||
import com.intellij.openapi.util.io.StreamUtil
|
||||
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 java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardOpenOption
|
||||
import java.util.stream.Collectors
|
||||
import kotlin.use
|
||||
|
||||
abstract class AbstractMavenRepositoryServerFixture : IdeaTestFixture {
|
||||
protected lateinit var myServer: HttpServer
|
||||
private set
|
||||
|
||||
abstract fun url(): String
|
||||
|
||||
override fun setUp() {
|
||||
myServer = startServer()
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
if (this::myServer.isInitialized) {
|
||||
myServer.stop(10)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun startRepositoryFor(repo: Path, expectedUsername: String, expectedPassword: String) {
|
||||
startRepositoryFor(repo, "/", expectedUsername, expectedPassword)
|
||||
}
|
||||
|
||||
fun startRepositoryFor(repo: Path, contextPath: String = "/", expectedUsername: String? = null, expectedPassword: String? = null) {
|
||||
val authenticator: Authenticator? = if (expectedUsername == null) null
|
||||
else object : BasicAuthenticator("/") {
|
||||
override fun checkCredentials(username: String?, password: String?): Boolean {
|
||||
return expectedUsername == username && expectedPassword == password && expectedPassword != null
|
||||
}
|
||||
}
|
||||
setupRemoteRepositoryServerReadFiles(repo, contextPath, authenticator)
|
||||
}
|
||||
|
||||
fun startRepositoryFor(repo: String) {
|
||||
startRepositoryFor(Path.of(repo), "/", null, null)
|
||||
}
|
||||
|
||||
private fun setupRemoteRepositoryServerReadFiles(repo: Path, contextPath: String, authenticator: Authenticator?) {
|
||||
val httpContext = myServer.createContext(contextPath) { exchange ->
|
||||
val path = exchange.requestURI.path
|
||||
//MavenLog.LOG.warn("Got request for $path")
|
||||
val file = repo.resolve(path.removePrefix("/")).normalize()
|
||||
if (Files.isDirectory(file)) {
|
||||
exchange.responseHeaders.add("Content-Type", "text/html")
|
||||
exchange.sendResponseHeaders(200, 0)
|
||||
val listing = Files.list(file).use { stream ->
|
||||
stream.map { it.fileName.toString() }
|
||||
.collect(Collectors.toList())
|
||||
.toTypedArray()
|
||||
}
|
||||
|
||||
val list = java.lang.String.join(",\n<br/>", *listing)
|
||||
exchange.responseBody.write(list.toByteArray(StandardCharsets.UTF_8))
|
||||
}
|
||||
else if (Files.isRegularFile(file)) {
|
||||
exchange.responseHeaders.add("Content-Type", "application/octet-stream")
|
||||
exchange.sendResponseHeaders(200, 0)
|
||||
Files.newInputStream(file, StandardOpenOption.READ).use {
|
||||
StreamUtil.copy(it, exchange.responseBody)
|
||||
}
|
||||
}
|
||||
else {
|
||||
exchange.sendResponseHeaders(404, -1)
|
||||
}
|
||||
exchange.close()
|
||||
//MavenLog.LOG.warn("Sent response for $path")
|
||||
}
|
||||
httpContext.authenticator = authenticator
|
||||
}
|
||||
|
||||
protected abstract fun startServer(): HttpServer
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.maven.testFramework.utils
|
||||
|
||||
import com.intellij.testFramework.fixtures.IdeaTestFixture
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.BasicConstraints
|
||||
import org.bouncycastle.asn1.x509.Extension
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
||||
import java.math.BigInteger
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.KeyStore
|
||||
import java.security.PrivateKey
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
|
||||
class MavenCertificateFixture() : IdeaTestFixture {
|
||||
private val rootCaKeyPair: KeyPair = generateKeyPair()
|
||||
private val rootCaCert: X509Certificate = generateRootCaCertificate()
|
||||
|
||||
override fun setUp() {
|
||||
}
|
||||
|
||||
private fun generateKeyPair(): KeyPair {
|
||||
val keyGen = KeyPairGenerator.getInstance("RSA")
|
||||
keyGen.initialize(2048)
|
||||
return keyGen.generateKeyPair()
|
||||
}
|
||||
|
||||
private fun generateRootCaCertificate(): X509Certificate {
|
||||
val issuer = X500Name("CN=Test Root CA")
|
||||
val serial = BigInteger.valueOf(System.currentTimeMillis())
|
||||
val now = Date()
|
||||
val expiry = Date(now.time + 365L * 24 * 60 * 60 * 1000)
|
||||
|
||||
val certBuilder = JcaX509v3CertificateBuilder(
|
||||
issuer, serial, now, expiry, issuer, rootCaKeyPair.public
|
||||
)
|
||||
certBuilder.addExtension(Extension.basicConstraints, true, BasicConstraints(true))
|
||||
|
||||
val signer = JcaContentSignerBuilder("SHA256withRSA").build(rootCaKeyPair.private)
|
||||
return JcaX509CertificateConverter().getCertificate(certBuilder.build(signer))
|
||||
}
|
||||
|
||||
fun createServerCertificate(hostname: String): Pair<X509Certificate, PrivateKey> {
|
||||
val keyPair = generateKeyPair()
|
||||
val subject = X500Name("CN=$hostname")
|
||||
val serial = BigInteger.valueOf(System.currentTimeMillis())
|
||||
val now = Date()
|
||||
val expiry = Date(now.time + 365L * 24 * 60 * 60 * 1000)
|
||||
|
||||
val certBuilder = JcaX509v3CertificateBuilder(
|
||||
X500Name(rootCaCert.subjectX500Principal.name),
|
||||
serial, now, expiry, subject, keyPair.public
|
||||
)
|
||||
|
||||
val signer = JcaContentSignerBuilder("SHA256withRSA").build(rootCaKeyPair.private)
|
||||
return JcaX509CertificateConverter().getCertificate(certBuilder.build(signer)) to keyPair.private
|
||||
}
|
||||
|
||||
fun createClientCertificate(cn: String): Pair<X509Certificate, PrivateKey> {
|
||||
val keyPair = generateKeyPair()
|
||||
val subject = X500Name("CN=$cn")
|
||||
val serial = BigInteger.valueOf(System.currentTimeMillis())
|
||||
val now = Date()
|
||||
val expiry = Date(now.time + 365L * 24 * 60 * 60 * 1000)
|
||||
|
||||
val certBuilder = JcaX509v3CertificateBuilder(
|
||||
X500Name(rootCaCert.subjectX500Principal.name),
|
||||
serial, now, expiry, subject, keyPair.public
|
||||
)
|
||||
|
||||
val signer = JcaContentSignerBuilder("SHA256withRSA").build(rootCaKeyPair.private)
|
||||
return JcaX509CertificateConverter().getCertificate(certBuilder.build(signer)) to keyPair.private
|
||||
}
|
||||
|
||||
fun checkClientCertificate(cert: X509Certificate) {
|
||||
try {
|
||||
cert.verify(rootCaCert.publicKey)
|
||||
val issuerDN = cert.issuerX500Principal
|
||||
val rootDN = rootCaCert.subjectX500Principal
|
||||
if (issuerDN != rootDN) {
|
||||
throw SecurityException("Certificate was not issued by the root CA")
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
throw SecurityException("Invalid client certificate", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun saveCertificates(cert: X509Certificate, store: Path, password: String, type: String = "pkcs12") {
|
||||
val keyStore = KeyStore.getInstance(type)
|
||||
keyStore.load(null, null)
|
||||
keyStore.setCertificateEntry("cert-${UUID.randomUUID()}", cert)
|
||||
saveKeyStore(keyStore, store, password)
|
||||
}
|
||||
|
||||
private fun saveKeyStore(keyStore: KeyStore, storePath: Path, password: String) {
|
||||
storePath.toFile().outputStream().use { os ->
|
||||
keyStore.store(os, password.toCharArray())
|
||||
}
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
}
|
||||
}
|
||||
+9
-80
@@ -1,94 +1,23 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.maven.testFramework.utils
|
||||
|
||||
import com.intellij.ReviseWhenPortedToJDK
|
||||
import com.intellij.openapi.util.io.StreamUtil
|
||||
import com.intellij.testFramework.fixtures.IdeaTestFixture
|
||||
import com.intellij.util.concurrency.AppExecutorUtil
|
||||
import com.sun.net.httpserver.Authenticator
|
||||
import com.sun.net.httpserver.BasicAuthenticator
|
||||
import com.sun.net.httpserver.HttpServer
|
||||
import java.net.InetSocketAddress
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardOpenOption
|
||||
import java.util.stream.Collectors
|
||||
|
||||
private const val LOCALHOST = "127.0.0.1"
|
||||
|
||||
@ReviseWhenPortedToJDK("18") //replace with SimpleFileServers
|
||||
class MavenHttpRepositoryServerFixture : IdeaTestFixture {
|
||||
private lateinit var myServer: HttpServer
|
||||
fun url(): String {
|
||||
if (!this::myServer.isInitialized) {
|
||||
throw IllegalStateException("Url is not ready yet, call setUp first")
|
||||
}
|
||||
class MavenHttpRepositoryServerFixture : AbstractMavenRepositoryServerFixture() {
|
||||
|
||||
override fun url(): String {
|
||||
return "http://" + LOCALHOST + ":" + myServer.address.port
|
||||
|
||||
}
|
||||
|
||||
override fun setUp() {
|
||||
myServer = HttpServer.create()
|
||||
myServer.setExecutor(AppExecutorUtil.getAppExecutorService())
|
||||
myServer.bind(InetSocketAddress(LOCALHOST, 0), 5)
|
||||
myServer.start()
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
if (this::myServer.isInitialized) {
|
||||
myServer.stop(10)
|
||||
}
|
||||
}
|
||||
|
||||
fun startRepositoryFor(repo: Path, expectedUsername: String, expectedPassword: String) {
|
||||
startRepositoryFor(repo, "/", expectedUsername, expectedPassword)
|
||||
}
|
||||
|
||||
fun startRepositoryFor(repo: Path, contextPath: String = "/", expectedUsername: String? = null, expectedPassword: String? = null) {
|
||||
val authenticator: Authenticator? = if (expectedUsername == null) null
|
||||
else object : BasicAuthenticator("/") {
|
||||
override fun checkCredentials(username: String?, password: String?): Boolean {
|
||||
return expectedUsername == username && expectedPassword == password && expectedPassword != null
|
||||
}
|
||||
}
|
||||
setupRemoteRepositoryServerReadFiles(repo, contextPath, authenticator)
|
||||
}
|
||||
|
||||
fun startRepositoryFor(repo: String) {
|
||||
startRepositoryFor(Path.of(repo), "/", null, null)
|
||||
}
|
||||
|
||||
private fun setupRemoteRepositoryServerReadFiles(repo: Path, contextPath: String, authenticator: Authenticator?) {
|
||||
val httpContext = myServer.createContext(contextPath) { exchange ->
|
||||
val path = exchange.requestURI.path
|
||||
//MavenLog.LOG.warn("Got request for $path")
|
||||
val file = repo.resolve(path.removePrefix("/")).normalize()
|
||||
if (Files.isDirectory(file)) {
|
||||
exchange.responseHeaders.add("Content-Type", "text/html")
|
||||
exchange.sendResponseHeaders(200, 0)
|
||||
val listing = Files.list(file).use { stream ->
|
||||
stream.map { it.fileName.toString() }
|
||||
.collect(Collectors.toList())
|
||||
.toTypedArray()
|
||||
}
|
||||
|
||||
val list = java.lang.String.join(",\n<br/>", *listing)
|
||||
exchange.responseBody.write(list.toByteArray(StandardCharsets.UTF_8))
|
||||
}
|
||||
else if (Files.isRegularFile(file)) {
|
||||
exchange.responseHeaders.add("Content-Type", "application/octet-stream")
|
||||
exchange.sendResponseHeaders(200, 0)
|
||||
Files.newInputStream(file, StandardOpenOption.READ).use {
|
||||
StreamUtil.copy(it, exchange.responseBody)
|
||||
}
|
||||
}
|
||||
else {
|
||||
exchange.sendResponseHeaders(404, -1)
|
||||
}
|
||||
exchange.close()
|
||||
//MavenLog.LOG.warn("Sent response for $path")
|
||||
}
|
||||
httpContext.authenticator = authenticator
|
||||
override fun startServer(): HttpServer {
|
||||
val server = HttpServer.create()
|
||||
server.setExecutor(AppExecutorUtil.getAppExecutorService())
|
||||
server.bind(InetSocketAddress(LOCALHOST, 0), 5)
|
||||
server.start()
|
||||
return server
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.maven.testFramework.utils
|
||||
|
||||
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 java.net.InetSocketAddress
|
||||
import java.security.KeyStore
|
||||
import java.security.PrivateKey
|
||||
import java.security.SecureRandom
|
||||
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
|
||||
|
||||
private const val LOCALHOST = "127.0.0.1"
|
||||
private const val SERVER_KS_PASSWORD = "password"
|
||||
|
||||
class MavenHttpsRepositoryServerFixture(
|
||||
val myServerCertificate: X509Certificate,
|
||||
val sslHostname: String,
|
||||
val myPrivateKey: PrivateKey,
|
||||
) : AbstractMavenRepositoryServerFixture() {
|
||||
|
||||
override fun url(): String {
|
||||
return "https://$LOCALHOST:${myServer.address.port}"
|
||||
}
|
||||
|
||||
override fun startServer(): HttpsServer {
|
||||
val server = HttpsServer.create()
|
||||
server.bind(InetSocketAddress(LOCALHOST, 0), 0)
|
||||
|
||||
val sslContext = createSSLContext()
|
||||
server.httpsConfigurator = object : HttpsConfigurator(sslContext) {
|
||||
override fun configure(params: HttpsParameters) {
|
||||
val engine = sslContext.createSSLEngine()
|
||||
params.needClientAuth = false
|
||||
params.cipherSuites = engine.enabledCipherSuites
|
||||
params.protocols = engine.enabledProtocols
|
||||
params.setSSLParameters(sslContext.defaultSSLParameters)
|
||||
}
|
||||
}
|
||||
|
||||
server.executor = AppExecutorUtil.getAppExecutorService()
|
||||
server.start()
|
||||
return server
|
||||
}
|
||||
|
||||
private fun createSSLContext(): SSLContext {
|
||||
// Initialize an in-memory KeyStore
|
||||
val keyStore = KeyStore.getInstance("JKS").apply {
|
||||
load(null, null)
|
||||
setKeyEntry(
|
||||
sslHostname,
|
||||
myPrivateKey,
|
||||
SERVER_KS_PASSWORD.toCharArray(),
|
||||
arrayOf(myServerCertificate)
|
||||
)
|
||||
}
|
||||
|
||||
// KeyManagerFactory for server certificate
|
||||
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)
|
||||
|
||||
return SSLContext.getInstance("TLS").apply {
|
||||
init(kmf.keyManagers, tmf.trustManagers, SecureRandom())
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user