mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 04:51:24 +07:00
[jcef] IDEA-305100 support certificates from custom trust manager
GitOrigin-RevId: 0f6457be2239d544f658357d18edfc67ca094cfd
This commit is contained in:
committed by
intellij-monorepo-bot
parent
3f0578251b
commit
1cee44cfbb
6
.idea/libraries/jcef.xml
generated
6
.idea/libraries/jcef.xml
generated
@@ -1,12 +1,12 @@
|
||||
<component name="libraryTable">
|
||||
<library name="jcef" type="repository">
|
||||
<properties maven-id="org.jetbrains.intellij.deps.jcef:jcef:104.4.26-g4180781-chromium-104.0.5112.102-api-1.8" />
|
||||
<properties maven-id="org.jetbrains.intellij.deps.jcef:jcef:104.4.26-g4180781-chromium-104.0.5112.102-api-1.9" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/intellij/deps/jcef/jcef/104.4.26-g4180781-chromium-104.0.5112.102-api-1.8/jcef-104.4.26-g4180781-chromium-104.0.5112.102-api-1.8.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/intellij/deps/jcef/jcef/104.4.26-g4180781-chromium-104.0.5112.102-api-1.9/jcef-104.4.26-g4180781-chromium-104.0.5112.102-api-1.9.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/intellij/deps/jcef/jcef/104.4.26-g4180781-chromium-104.0.5112.102-api-1.8/jcef-104.4.26-g4180781-chromium-104.0.5112.102-api-1.8-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/intellij/deps/jcef/jcef/104.4.26-g4180781-chromium-104.0.5112.102-api-1.9/jcef-104.4.26-g4180781-chromium-104.0.5112.102-api-1.9-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -65,7 +65,7 @@ public final class JBCefApp {
|
||||
|
||||
private static final int MIN_SUPPORTED_CEF_MAJOR_VERSION = 104;
|
||||
private static final int MIN_SUPPORTED_JCEF_API_MAJOR_VERSION = 1;
|
||||
private static final int MIN_SUPPORTED_JCEF_API_MINOR_VERSION = 8;
|
||||
private static final int MIN_SUPPORTED_JCEF_API_MINOR_VERSION = 9;
|
||||
|
||||
@NotNull private final CefApp myCefApp;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ package com.intellij.ui.jcef;
|
||||
import com.intellij.credentialStore.Credentials;
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.ide.BrowserUtil;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.colors.EditorColorsManager;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
@@ -15,16 +16,16 @@ import com.intellij.ui.scale.ScaleContext;
|
||||
import com.intellij.util.IconUtil;
|
||||
import com.intellij.util.LazyInitializer;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.net.ssl.CertificateListener;
|
||||
import com.intellij.util.net.ssl.CertificateManager;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.cef.CefClient;
|
||||
import org.cef.browser.*;
|
||||
import org.cef.callback.CefAuthCallback;
|
||||
import org.cef.callback.CefContextMenuParams;
|
||||
import org.cef.callback.CefMenuModel;
|
||||
import org.cef.callback.CefNativeAdapter;
|
||||
import org.cef.callback.*;
|
||||
import org.cef.handler.*;
|
||||
import org.cef.network.CefCookieManager;
|
||||
import org.cef.network.CefRequest;
|
||||
import org.cef.security.CefSSLInfo;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -38,6 +39,8 @@ import java.beans.PropertyChangeListener;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -90,6 +93,8 @@ public abstract class JBCefBrowserBase implements JBCefDisposable {
|
||||
private final @NotNull AtomicBoolean myIsCreateStarted = new AtomicBoolean(false);
|
||||
private @Nullable CefRequestHandler myHrefProcessingRequestHandler;
|
||||
|
||||
private final @NotNull CertificateListener myCertificateListener;
|
||||
|
||||
private static final LazyInitializer.LazyValue<@NotNull String> ERROR_PAGE_READER = LazyInitializer.create(() -> {
|
||||
try {
|
||||
return new String(FileUtil.loadBytes(Objects.requireNonNull(
|
||||
@@ -229,6 +234,24 @@ public abstract class JBCefBrowserBase implements JBCefDisposable {
|
||||
}
|
||||
return super.getAuthCredentials(browser, origin_url, isProxy, host, port, realm, scheme, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCertificateError(CefBrowser browser,
|
||||
CefLoadHandler.ErrorCode cert_error,
|
||||
String request_url,
|
||||
CefSSLInfo sslInfo,
|
||||
CefCallback callback) {
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
try {
|
||||
CertificateManager.getInstance().getTrustManager().checkServerTrusted(sslInfo.certificate.getCertificatesChain(), "UNKNOWN");
|
||||
callback.Continue();
|
||||
}
|
||||
catch (CertificateException e) {
|
||||
callback.cancel();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}, getCefBrowser());
|
||||
|
||||
myCefClient.addContextMenuHandler(myContextMenuHandler = createDefaultContextMenuHandler(), getCefBrowser());
|
||||
@@ -241,6 +264,19 @@ public abstract class JBCefBrowserBase implements JBCefDisposable {
|
||||
}
|
||||
|
||||
if (builder.myCreateImmediately) createImmediately();
|
||||
|
||||
myCertificateListener = new CertificateListener() {
|
||||
@Override
|
||||
public void certificateAdded(X509Certificate certificate) { }
|
||||
|
||||
@Override
|
||||
public void certificateRemoved(X509Certificate certificate) {
|
||||
getCefBrowser().getRequestContext().ClearCertificateExceptions(null);
|
||||
getCefBrowser().getRequestContext().CloseAllConnections(null);
|
||||
}
|
||||
};
|
||||
|
||||
CertificateManager.getInstance().getCustomTrustManager().addListener(myCertificateListener);
|
||||
}
|
||||
|
||||
private @NotNull CefBrowserOsrWithHandler createOsrBrowser(@NotNull JBCefOSRHandlerFactory factory,
|
||||
@@ -475,6 +511,8 @@ public abstract class JBCefBrowserBase implements JBCefDisposable {
|
||||
if (myHrefProcessingRequestHandler != null) getJBCefClient().removeRequestHandler(myHrefProcessingRequestHandler, getCefBrowser());
|
||||
if (myContextMenuHandler != null) getJBCefClient().removeContextMenuHandler(myContextMenuHandler, getCefBrowser());
|
||||
|
||||
CertificateManager.getInstance().getCustomTrustManager().removeListener(myCertificateListener);
|
||||
|
||||
myCefBrowser.stopLoad();
|
||||
myCefBrowser.setCloseAllowed();
|
||||
myCefBrowser.close(true);
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.cef.callback.*;
|
||||
import org.cef.handler.*;
|
||||
import org.cef.misc.BoolRef;
|
||||
import org.cef.network.CefRequest;
|
||||
import org.cef.security.CefSSLInfo;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -623,9 +624,10 @@ public final class JBCefClient implements JBCefDisposable {
|
||||
public boolean onCertificateError(CefBrowser browser,
|
||||
CefLoadHandler.ErrorCode cert_error,
|
||||
String request_url,
|
||||
CefSSLInfo sslInfo,
|
||||
CefCallback callback) {
|
||||
return myRequestHandler.handleBoolean(browser, handler -> {
|
||||
return handler.onCertificateError(browser, cert_error, request_url, callback);
|
||||
return handler.onCertificateError(browser, cert_error, request_url, sslInfo, callback);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -239,7 +239,8 @@ public final class ConfirmingTrustManager extends ClientOnlyTrustManager {
|
||||
LOG.debug("Image Fetcher thread is detected. Certificate check will be skipped.");
|
||||
return true;
|
||||
}
|
||||
if (app.isUnitTestMode() || app.isHeadlessEnvironment() || CertificateManager.getInstance().getState().ACCEPT_AUTOMATICALLY) {
|
||||
|
||||
if (app.isHeadlessEnvironment() || CertificateManager.getInstance().getState().ACCEPT_AUTOMATICALLY) {
|
||||
LOG.debug("Certificate will be accepted automatically");
|
||||
if (parameters.myAddToKeyStore) {
|
||||
myCustomManager.addCertificate(endPoint);
|
||||
@@ -247,6 +248,10 @@ public final class ConfirmingTrustManager extends ClientOnlyTrustManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (app.isUnitTestMode()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean accepted = false;
|
||||
if (parameters.myAskUser) {
|
||||
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.ui.jcef;
|
||||
|
||||
import com.intellij.testFramework.ApplicationRule;
|
||||
import com.intellij.ui.scale.TestScaleHelper;
|
||||
import com.intellij.util.net.ssl.CertificateManager;
|
||||
import com.intellij.util.net.ssl.CertificateUtil;
|
||||
import org.cef.CefClient;
|
||||
import org.cef.callback.CefCallback;
|
||||
import org.cef.handler.CefLoadHandler;
|
||||
import org.cef.security.CefSSLInfo;
|
||||
import org.cef.security.CefX509Certificate;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Base64;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static com.intellij.ui.jcef.JBCefTestHelper.await;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class JBCefSSLCertificateTest {
|
||||
static {
|
||||
TestScaleHelper.setSystemProperty("java.awt.headless", "false");
|
||||
}
|
||||
|
||||
@ClassRule public static final ApplicationRule appRule = new ApplicationRule();
|
||||
|
||||
final static String CER_BASE64 =
|
||||
"MIIDijCCAnKgAwIBAgIJANeDdOXTlBwfMA0GCSqGSIb3DQEBCwUAMHMxCzAJBgNVBAYTAkRFMQ8w" +
|
||||
"DQYDVQQIEwZCZXJsaW4xDzANBgNVBAcTBkJlcmxpbjESMBAGA1UEChMJSmV0QnJhaW5zMRAwDgYD" +
|
||||
"VQQLEwdSdW50aW1lMRwwGgYDVQQDExNWbGFkaW1pciBLaGFyaXRvbm92MB4XDTIyMTAxNzA5MzAx" +
|
||||
"NFoXDTQyMDcwNDA5MzAxNFowczELMAkGA1UEBhMCREUxDzANBgNVBAgTBkJlcmxpbjEPMA0GA1UE" +
|
||||
"BxMGQmVybGluMRIwEAYDVQQKEwlKZXRCcmFpbnMxEDAOBgNVBAsTB1J1bnRpbWUxHDAaBgNVBAMT" +
|
||||
"E1ZsYWRpbWlyIEtoYXJpdG9ub3YwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBNDzY" +
|
||||
"IncY2OpfpDHMFV1mcTyy14rBCLIV9DVYQmIIGWNIOVIvC42PROX/55cXAsbxTMci9QUq6P0QRvBE" +
|
||||
"dOpIQb8VviR35JEo3e1+vg3a8aggWzSIvbG11NqRN7p/8IZ6ANGgJA+KlblssLjuk+e08RMm/0FL" +
|
||||
"4HHYCHx5TkrAlGX5IhIfATp5EZEwboRksAJudrGpjlMwjibMycNL3zFD4qM751N2J9a1r+cfykUY" +
|
||||
"Ahpl/+o1+K0OhIUCim+qs4xm6yPlbkGaE8TJJB7QA2lsd8nC0LcrMe7ORRF3L0nAg75rWbsVNQME" +
|
||||
"iI0tZZbhm16gUZfwfXxto8YX+QoJYCshAgMBAAGjITAfMB0GA1UdDgQWBBT0UpMP8dBViYg+MJUA" +
|
||||
"Iac6rqfAAzANBgkqhkiG9w0BAQsFAAOCAQEATswa9pPVLEww+oXcwlCCEYL4hAqaZji5Lnto6Rrc" +
|
||||
"KXqQ++VHN7xMdaJPTx2edtE3Zl4Krkw0RIkDx4cRVY4+CrbdlYEfJlxzxyKmB6dqRqtM7n3oO+NT" +
|
||||
"DL1p4JspkwMHsJHTfugbpxd/Dm+6mi+rt9lzY67idjk0AZCzwJbDPxcjC84bDrdELyr3SFsIEczx" +
|
||||
"ABWSCtQXGQx2mXIBLHyEjoPAMFzZcPVK63nJz17rkBDp5k9XaG+iEi5Q36wwDgQyoYoOCrR+Wcxx" +
|
||||
"WEHAvWuOha1AywLjaPowUTexRMZTUpTJZ/fei+6CdTnwGYtq6gn04pPSw6pQ9RCtrLUYkjQIzw==";
|
||||
private CefSSLInfo mySSLInfo = null;
|
||||
private JBCefBrowser myBrowser = null;
|
||||
|
||||
@Before
|
||||
public void before() throws CertificateException {
|
||||
myBrowser = new JBCefBrowser("");
|
||||
mySSLInfo = makeSSLInfo();
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
TestScaleHelper.restoreProperties();
|
||||
|
||||
// Clean up the certificate storage if needed
|
||||
var trustManager = CertificateManager.getInstance().getTrustManager().getCustomManager();
|
||||
if (mySSLInfo != null && mySSLInfo.certificate != null) {
|
||||
for (X509Certificate certificate : mySSLInfo.certificate.getCertificatesChain()) {
|
||||
if (trustManager.containsCertificate(CertificateUtil.getCommonName(certificate))) {
|
||||
trustManager.removeCertificate(certificate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class CertificateErrorCallback implements CefCallback {
|
||||
CountDownLatch myLatch = new CountDownLatch(1);
|
||||
AtomicBoolean myContinueCalled = new AtomicBoolean(false);
|
||||
AtomicBoolean myCancelCalled = new AtomicBoolean(false);
|
||||
|
||||
@Override
|
||||
public void Continue() {
|
||||
myContinueCalled.set(true);
|
||||
myLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
myCancelCalled.set(true);
|
||||
myLatch.countDown();
|
||||
}
|
||||
|
||||
public void waitCall() {
|
||||
await(myLatch);
|
||||
}
|
||||
|
||||
public boolean continueCalled() {
|
||||
return myContinueCalled.get();
|
||||
}
|
||||
|
||||
public boolean cancelCalled() {
|
||||
return myCancelCalled.get();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
CefClient client = myBrowser.getJBCefClient().getCefClient();
|
||||
// Call CertificateErrorCallback with an unknown(for the custom trust manager) certificate
|
||||
{
|
||||
var callback = new CertificateErrorCallback();
|
||||
boolean exit_code = client.onCertificateError(myBrowser.getCefBrowser(),
|
||||
CefLoadHandler.ErrorCode.ERR_CERT_AUTHORITY_INVALID,
|
||||
"some_url",
|
||||
mySSLInfo,
|
||||
callback
|
||||
);
|
||||
callback.waitCall();
|
||||
assertTrue(exit_code);
|
||||
assertTrue(callback.cancelCalled());
|
||||
assertFalse(callback.continueCalled());
|
||||
}
|
||||
|
||||
for (var certificate : mySSLInfo.certificate.getCertificatesChain()) {
|
||||
CertificateManager.getInstance().getTrustManager().getCustomManager().addCertificate(certificate);
|
||||
}
|
||||
|
||||
// Call CertificateErrorCallback with a known(for the custom trust manager) certificate
|
||||
{
|
||||
var callback = new CertificateErrorCallback();
|
||||
boolean exit_code = client.onCertificateError(myBrowser.getCefBrowser(),
|
||||
CefLoadHandler.ErrorCode.ERR_CERT_AUTHORITY_INVALID,
|
||||
"some_url",
|
||||
mySSLInfo,
|
||||
callback
|
||||
);
|
||||
callback.waitCall();
|
||||
assertTrue(exit_code);
|
||||
assertTrue(callback.continueCalled());
|
||||
assertFalse(callback.cancelCalled());
|
||||
}
|
||||
|
||||
for (var certificate : mySSLInfo.certificate.getCertificatesChain()) {
|
||||
CertificateManager.getInstance().getTrustManager().getCustomManager().removeCertificate(certificate);
|
||||
}
|
||||
|
||||
// Remove the certificate from the custom trust manger
|
||||
{
|
||||
var callback = new CertificateErrorCallback();
|
||||
boolean exit_code = client.onCertificateError(myBrowser.getCefBrowser(),
|
||||
CefLoadHandler.ErrorCode.ERR_CERT_AUTHORITY_INVALID,
|
||||
"some_url",
|
||||
mySSLInfo,
|
||||
callback
|
||||
);
|
||||
callback.waitCall();
|
||||
assertTrue(exit_code);
|
||||
assertFalse(callback.continueCalled());
|
||||
assertTrue(callback.cancelCalled());
|
||||
}
|
||||
}
|
||||
|
||||
static private CefSSLInfo makeSSLInfo() {
|
||||
return new CefSSLInfo(4 /*CERT_STATUS_AUTHORITY_INVALID*/,
|
||||
new CefX509Certificate(new byte[][]{Base64.getDecoder().decode(CER_BASE64)}));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user