[maven] IDEA-225239 send maven certificate resolution to IDE side

GitOrigin-RevId: dd0b1f044fc195b4f922e313f9028d1ca38d0f78
This commit is contained in:
Alexander Bubenchikov
2024-07-02 14:02:19 +02:00
committed by intellij-monorepo-bot
parent 446ca88f51
commit 8e8dbf719a
9 changed files with 297 additions and 20 deletions

View File

@@ -31,19 +31,13 @@ class Multiplexor {
Integer key = Integer.parseInt(line);
line = myScanner.nextLine();
if (line.equals(SslIDEConfirmingTrustStore.DELEGATE_RESPONSE_ERROR)) {
line = myScanner.nextLine();
CompletableFuture<String> future = requests.get(key);
if (future != null) future.completeExceptionally(new CertificateException(line));
if (future != null) future.completeExceptionally(new CertificateException());
}
else if (line.equals(SslIDEConfirmingTrustStore.DELEGATE_RESPONSE_OK)) {
CompletableFuture<String> future = requests.get(key);
if (future != null) future.complete("ok");
}
else {
return;
}
myScanner.nextLine();
myScanner.nextLine();
}
catch (NumberFormatException ignore) {
}

View File

@@ -1,13 +1,10 @@
// 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.security.ssl;
import sun.security.provider.X509Factory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
@@ -18,7 +15,7 @@ import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Base64;
public class SslIDEConfirmingTrustStore {
public final class SslIDEConfirmingTrustStore {
public static final String CHECK_CLIENT_TRUSTED = "----------checkClientTrusted----------";
public static final String CHECK_SERVER_TRUSTED = "----------checkServerTrusted----------";
@@ -29,6 +26,8 @@ public class SslIDEConfirmingTrustStore {
private static final Multiplexor ourMultiplexor = new Multiplexor();
public static final String BEGIN_CERTIFICATE = "-----BEGIN CERTIFICATE-----";
public static final String END_CERTIFICATE = "-----END CERTIFICATE-----";
public static void setup() {
try {
@@ -56,7 +55,7 @@ public class SslIDEConfirmingTrustStore {
}
@SuppressWarnings("UseOfSystemOutOrSystemErr")
private void sendAndWaitForResponse(PrintStream out, Integer key) throws CertificateException {
private void sendAndWaitForResponse(String out, Integer key) throws CertificateException {
synchronized (this) {
System.out.println(out);
}
@@ -69,7 +68,7 @@ public class SslIDEConfirmingTrustStore {
}
private void checkTrusted(X509Certificate[] chain, String authType, String methodSignature) throws CertificateException {
OutputStream os = new ByteArrayOutputStream();
ByteArrayOutputStream os = new ByteArrayOutputStream();
Integer key = ourMultiplexor.getKey();
try (PrintStream out = new PrintStream(os, false, "ISO-8859-1")) {
out.println(IDE_DELEGATE_TRUST_MANAGER);
@@ -79,21 +78,19 @@ public class SslIDEConfirmingTrustStore {
out.println(authType);
for (X509Certificate certificate : chain) {
out.println(X509Factory.BEGIN_CERT);
out.println(BEGIN_CERTIFICATE);
try {
out.println(Base64.getMimeEncoder(80, "\n".getBytes(StandardCharsets.US_ASCII))
.encodeToString(certificate.getEncoded()));
}
catch (CertificateEncodingException e) {
out.println("#ERROR: " + e.getMessage());
}
out.println(X509Factory.END_CERT);
out.println(END_CERTIFICATE);
}
out.println(methodSignature);
sendAndWaitForResponse(out, key);
sendAndWaitForResponse(os.toString("ISO-8859-1"), key);
}
catch (UnsupportedEncodingException ignore) {
throw new CertificateException("Unsupported encoding");

View File

@@ -15,6 +15,8 @@ import org.jetbrains.idea.maven.buildtool.MavenImportEventProcessor;
import org.jetbrains.idea.maven.execution.MavenSpyEventsBuffer;
import org.jetbrains.idea.maven.project.MavenProjectsManager;
import org.jetbrains.idea.maven.server.security.TokenReader;
import org.jetbrains.idea.maven.server.ssl.SslDelegateHandlerConfirmingTrustManager;
import org.jetbrains.idea.maven.server.ssl.SslDelegateHandlerStateMachine;
import org.jetbrains.idea.maven.utils.MavenLog;
import java.io.IOException;
@@ -31,6 +33,7 @@ public abstract class AbstractMavenServerRemoteProcessSupport extends MavenRemot
@Nullable protected Consumer<ProcessEvent> onTerminate;
private final MavenImportEventProcessor myImportEventProcessor;
private final MavenSpyEventsBuffer myMavenSpyEventsBuffer;
private final SslDelegateHandlerStateMachine mySslDelegateHandlerStateMachine;
public AbstractMavenServerRemoteProcessSupport(@NotNull Sdk jdk,
@Nullable String vmOptions,
@@ -46,7 +49,12 @@ public abstract class AbstractMavenServerRemoteProcessSupport extends MavenRemot
myImportEventProcessor = new MavenImportEventProcessor(project);
AnsiEscapeDecoder myDecoder = new AnsiEscapeDecoder();
myMavenSpyEventsBuffer = new MavenSpyEventsBuffer((l, k) -> myDecoder.escapeText(l, k, myImportEventProcessor));
mySslDelegateHandlerStateMachine = new SslDelegateHandlerConfirmingTrustManager();
myMavenSpyEventsBuffer = new MavenSpyEventsBuffer((l, k) -> {
mySslDelegateHandlerStateMachine.addLine(l);
myDecoder.escapeText(l, k, myImportEventProcessor);
});
}
@@ -65,6 +73,7 @@ public abstract class AbstractMavenServerRemoteProcessSupport extends MavenRemot
if (handler.getProcessInput() == null) {
return;
}
mySslDelegateHandlerStateMachine.setOutput(handler.getProcessInput());
OutputStreamWriter writer = new OutputStreamWriter(handler.getProcessInput(), StandardCharsets.UTF_8);
try {
writer.write(TokenReader.PREFIX + MavenRemoteObjectWrapper.ourToken);
@@ -89,7 +98,7 @@ public abstract class AbstractMavenServerRemoteProcessSupport extends MavenRemot
if (eventConsumer != null) {
eventConsumer.accept(event);
}
if (event.getExitCode() == 0) {
return;
}

View File

@@ -0,0 +1,160 @@
// 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.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
}
}
open class SslDelegateHandlerStateMachine(val checkTrusted: (Array<X509Certificate>, String) -> Boolean) {
private var currentState: State
lateinit var output: OutputStream
init {
currentState = Idle(this)
}
fun addLine(text: @NlsSafe String) {
if (!this::output.isInitialized) return
currentState = currentState.addLine(text.trim('\n', '\r', '\t', ' '))
}
}
sealed class State {
abstract fun addLine(text: @NlsSafe String): State
}
class Idle(val machine: SslDelegateHandlerStateMachine) : State() {
override fun addLine(text: String): State {
if (text == SslIDEConfirmingTrustStore.IDE_DELEGATE_TRUST_MANAGER) return WaitForMethod(machine)
return this
}
}
class WaitForMethod(val machine: SslDelegateHandlerStateMachine) : State() {
override fun addLine(text: String): State {
if (text == SslIDEConfirmingTrustStore.CHECK_SERVER_TRUSTED) return WaitForKey(machine)
else return Idle(machine)
}
}
class WaitForKey(val machine: SslDelegateHandlerStateMachine) : State() {
override fun addLine(text: String): State {
val key = text.toIntOrNull()
if (key != null) return WaitForChainLen(machine, key)
return Idle(machine)
}
}
class WaitForChainLen(val machine: SslDelegateHandlerStateMachine, val key: Int) : State() {
override fun addLine(text: String): State {
val len = text.toIntOrNull()
if (len != null) return WaitForAuthType(machine, key, len)
return Idle(machine)
}
}
class WaitForAuthType(val machine: SslDelegateHandlerStateMachine, val key: Int, val len: Int) : State() {
override fun addLine(text: String): State {
return ReadForCertificates(machine, key, len, len, text, ArrayList<X509Certificate>())
}
}
class ReadForCertificates(val machine: SslDelegateHandlerStateMachine, val key: Int, val len: Int, val toRead: Int, val authType: String, val certificates: ArrayList<X509Certificate>) : State() {
override fun addLine(text: String): State {
val begin = text
if (begin == SslIDEConfirmingTrustStore.BEGIN_CERTIFICATE) {
return ReadNextCertificate(machine, key, len, toRead, authType, certificates, StringBuilder())
}
return Idle(machine)
}
}
class ReadNextCertificate(
val machine: SslDelegateHandlerStateMachine,
val key: Int,
val len: Int,
val toRead: Int,
val authType: String,
val certificates: ArrayList<X509Certificate>,
val stringBuilder: StringBuilder,
) : State() {
override fun addLine(text: String): State {
if (text == SslIDEConfirmingTrustStore.END_CERTIFICATE) {
val certificateFactory: CertificateFactory = CertificateFactory.getInstance("X.509")
val certificateBuffer: ByteArray = Base64.getMimeDecoder().decode(stringBuilder.toString())
val certificate: X509Certificate = certificateFactory.generateCertificate(certificateBuffer.inputStream()) as X509Certificate
certificates.add(certificate)
if (toRead == 1) {
return WaitEndAndExecute(machine, key, authType, certificates)
}
else {
return ReadForCertificates(machine, key, len, toRead - 1, authType, certificates)
}
}
stringBuilder.append(text)
return this
}
}
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 os = ByteArrayOutputStream()
PrintStream(os, true, "UTF-8").use { ps ->
ps.println(SslIDEConfirmingTrustStore.IDE_DELEGATE_TRUST_MANAGER)
ps.println(SslIDEConfirmingTrustStore.DELEGATE_RESPONSE)
ps.println(key)
if (trusted) {
ps.println(SslIDEConfirmingTrustStore.DELEGATE_RESPONSE_OK)
}
else {
ps.println(SslIDEConfirmingTrustStore.DELEGATE_RESPONSE_ERROR)
}
synchronized(machine) {
machine.output.write(os.toByteArray())
machine.output.flush()
}
}
}
return Idle(machine)
}
}

View File

@@ -295,3 +295,5 @@ wsl.jdk.downloading=Downloading jdk\u2026
notification.group.maven=Maven
maven.project.clean.restart.connectors=Restarting Maven connectors
maven.server.ask.trust=Maven Server tries to establish SSL connection with the repository

View File

@@ -0,0 +1,46 @@
// 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.testFramework.UsefulTestCase
import com.intellij.util.ArrayUtilRt
import com.intellij.util.ResourceUtil
import junit.framework.TestCase
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.util.*
class SslDelegateHandlerStateMachineTest : UsefulTestCase() {
fun testResultOk() {
val data = fromFile("ssl_remote_query.txt");
val machine = SslDelegateHandlerStateMachine { _, _ -> true }
val out = ByteArrayOutputStream()
machine.output = out
data.forEach { machine.addLine(it) }
TestCase.assertEquals(fromFile("ssl_remote_query_response_ok.txt").joinToString(""), out.toString())
}
fun testResultFail() {
val data = fromFile("ssl_remote_query.txt");
val machine = SslDelegateHandlerStateMachine { _, _ -> false }
val out = ByteArrayOutputStream()
machine.output = out
data.forEach { machine.addLine(it) }
TestCase.assertEquals(fromFile("ssl_remote_query_response_error.txt").joinToString(""), out.toString())
}
@Throws(IOException::class)
protected fun fromFile(file: String): Array<String> {
ResourceUtil.getResourceAsStream(SslDelegateHandlerStateMachineTest::class.java.classLoader, "org/jetbrains/maven/server/ssl", file).use { stream ->
Scanner(stream).use { scanner ->
val result: MutableList<String> = ArrayList()
while (scanner.hasNextLine()) {
result.add(scanner.nextLine() + "\n")
}
return ArrayUtilRt.toStringArray(result)
}
}
}
}

View File

@@ -0,0 +1,61 @@
Port://
blablabla
----------IdeDelegateTrustManager----------
----------checkServerTrusted----------
1
2
ECDHE_RSA
-----BEGIN CERTIFICATE-----
MIIGazCCBVOgAwIBAgIQAXLAZBooKf1RECf2cHoYsTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJC
RTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEuMCwGA1UEAxMlR2xvYmFsU2lnbiBBdGxhcyBSMyBE
ViBUTFMgQ0EgMjAyMyBRNDAeFw0yMzEyMTcxNzUxMTRaFw0yNTAxMTcxNzUxMTNaMCAxHjAcBgNVBAMM
FXJlcG8ubWF2ZW4uYXBhY2hlLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANPmLVYJ
BGGMOeAuuZ07bhuezPK0BZM8ymGvPeQG6dtqMFX+jvGcEOElXFIf1EJlYT8OjUfglhaocC3x4yOIAwpJ
OeKMTtKSp+hN+qrEU0gk9Suny8JmZ6gheTs+p+AomcQTWANXtwEomanUoAaBEHDG/z8zNISygPgkchnZ
wWhTZ9X+2utXNfQTM51G0rnYu0igYIdZ7LRcUoyGU1v2rfSjzJN+maHeXvHcuXCZulaxsw2NP0nHNu57
IPNxvTr3edxpN6v+3QKxIE61gr0RqSrpS1mCkeqZBO23Wgz2+rC14efTNkFdVJodg4A8ffHtIbemo6RY
Wd3Cdr7WoX3SYqkCAwEAAaOCA2cwggNjMCAGA1UdEQQZMBeCFXJlcG8ubWF2ZW4uYXBhY2hlLm9yZzAO
BgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQ93URT
65FichWIWGvbb0ze7R/3OjBXBgNVHSAEUDBOMAgGBmeBDAECATBCBgorBgEEAaAyCgEDMDQwMgYIKwYB
BQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkvMAwGA1UdEwEB/wQCMAAw
gZ4GCCsGAQUFBwEBBIGRMIGOMEAGCCsGAQUFBzABhjRodHRwOi8vb2NzcC5nbG9iYWxzaWduLmNvbS9j
YS9nc2F0bGFzcjNkdnRsc2NhMjAyM3E0MEoGCCsGAQUFBzAChj5odHRwOi8vc2VjdXJlLmdsb2JhbHNp
Z24uY29tL2NhY2VydC9nc2F0bGFzcjNkdnRsc2NhMjAyM3E0LmNydDAfBgNVHSMEGDAWgBSqEXGPlcRY
mIuxbg87UGgkvLgZvDBIBgNVHR8EQTA/MD2gO6A5hjdodHRwOi8vY3JsLmdsb2JhbHNpZ24uY29tL2Nh
L2dzYXRsYXNyM2R2dGxzY2EyMDIzcTQuY3JsMIIBfAYKKwYBBAHWeQIEAgSCAWwEggFoAWYAdQBOdaMn
XJoQwzhbbNTfP1LrHfDgjhuNacCx+mSxYpo53wAAAYx451JdAAAEAwBGMEQCIBeejHrzGcl7Lrb5L8rh
hOHlQni26PF1aEXfYZZvNFIaAiAj+WJH9iQ/rPI57uHhkw/WwjxmnNY1GpAj2NGfH54k5AB2AD8XS0/X
IkdYlB1lHIS+DRLtkDd/H4Vq68G/KIXs+GRuAAABjHjnUkMAAAQDAEcwRQIhANFI48P8IfmKY+LexiUL
7aV0UUCumQ4eQH0t3W4nnmSXAiBNdz0s6QmJHZiMVUbmBSxMGGhOgXAalRm1fkJ2fuuuoAB1AM8RVu7V
Lnyv84db2Wkum+kacWdKsBfsrAHSW3fOzDsIAAABjHjnUvEAAAQDAEYwRAIgQlPixnbAxCpnRSKQwGV8
x8+tzGinHCZK4C0ev3Y3mtYCIDsUGpNZWNQloNm7+QPlwtM2uEvebTbG0iYbjh6bQ2cVMA0GCSqGSIb3
DQEBCwUAA4IBAQCUrFNxKOcxSnxT1tRnETEdGTdSPV13FDH2kOA+14QMftTo0UuXohXTXTSBpFbD+Mir
XzQJ9la1/nIFFoKJxqWd4pXNDy+aJMT+x1023gJX7nsXOls7o4NdoqYlRgjdO1/GResQyq1CE0lnvbb+
ZXqQrva23HJOM2V3/03EBWhdOr5Ph4jL7ALAuPxiiyrdKgtJ28cYCdu/XDVutVC0QBCoqmcqTkFSr7Up
+2iks65+uLZDOT8Rs6LcuQb6ZPX5mue547OVj7OKR84KKErsEK+XmNOGetRTsIg8N2BH0zWgXqxGU4a+
IxFE48I7fCNxNCPB/1GMGmU7FoOXyZCK8DOw
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEjzCCA3egAwIBAgIQfx8sScYPx1YvWJnWzWAZszANBgkqhkiG9w0BAQsFADBMMSAwHgYDVQQLExdH
bG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs
U2lnbjAeFw0yMzA3MTkwMzQyMjlaFw0yNTA3MTkwMDAwMDBaMFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQK
ExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSAy
MDIzIFE0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArn7MODS0pNF1+r3pMVMb8tjOcHZE
zqIqk+OU738JzxqkKKO6o4eMdPQn3wuKPHQKmJFEiMwYRYlPzFqL5SJVfmDg2t47QPtrL5kPr/bU08qL
0N3LDeuv4d7c5FmY535/3AqlZqcnp9EFSHngn4ISG6EXp6s/LPdyObu6so4vApJbmm2xHHc1pgAeorBN
sDRr9IgcODwI1U5o3UtBP0F4oZFmIopTl1dbFKGYPr+xap3hAw+z5MnspBaDRkSiU3xfy0LngS73Vs52
eYdp11AEWlZzTyDYwc8zm/tRArLZzfqygl86AyydLvZ9tg8OzPx+Tzl5xLx5LtwiKsxglnv2ewIDAQAB
o4IBXzCCAVswDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNV
HRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSqEXGPlcRYmIuxbg87UGgkvLgZvDAfBgNVHSMEGDAWgBSP
8Et/qC5FJK5NUPpjmove4t0bvDB7BggrBgEFBQcBAQRvMG0wLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3Nw
Mi5nbG9iYWxzaWduLmNvbS9yb290cjMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9zZWN1cmUuZ2xvYmFsc2ln
bi5jb20vY2FjZXJ0L3Jvb3QtcjMuY3J0MDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwuZ2xvYmFs
c2lnbi5jb20vcm9vdC1yMy5jcmwwIQYDVR0gBBowGDAIBgZngQwBAgEwDAYKKwYBBAGgMgoBAzANBgkq
hkiG9w0BAQsFAAOCAQEAiLoUIGIiKDNmQ8cWRZv5BX3ChPqWie23uWZ1YaP/59gYVvM48TbQu7lA8ZeE
FieIUFE+B68wGtU4mxKamfkWhORTAl1OcQ2C2tfJ0t4v6TbtEgzS2CC7oDkr4+W5QDuBbGgNIzBB7xbL
9Uy/PJ602MFt46a8WYvr0Mp+Qn2buHQ8WaTi2PXiv8Z62YZ0zz3yEpWzh7sVUS/4t6CdvccPnYq6c0wM
IO4Dn+fO5HEy3M00y9BIXhG4WeMWhmIkes+tp6LilOYT/ZV2WPebhvmi66VnDzRPwk98aWrMYzkV3pgk
wLQIqzfp1N3gNAHeZCnzaCq5VstgX6g9l2vw5Qycag==
-----END CERTIFICATE-----
----------checkServerTrusted----------
someshit

View File

@@ -0,0 +1,4 @@
----------IdeDelegateTrustManager----------
----------RESPONSE----------
1
----------ERROR----------

View File

@@ -0,0 +1,4 @@
----------IdeDelegateTrustManager----------
----------RESPONSE----------
1
----------OK----------