[jcef] IDEA-305100 support certificates from custom trust manager

GitOrigin-RevId: 0f6457be2239d544f658357d18edfc67ca094cfd
This commit is contained in:
Vladimir Kharitonov
2022-10-25 20:12:15 +02:00
committed by intellij-monorepo-bot
parent 3f0578251b
commit 1cee44cfbb
6 changed files with 222 additions and 10 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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