From c9630d81fe209956bb97e7b0be7e5b72ffb09a15 Mon Sep 17 00:00:00 2001 From: Stephane Bouchet Date: Tue, 12 Mar 2024 17:30:34 +0100 Subject: [PATCH] feat: add IDEATrustManager classes Signed-off-by: Stephane Bouchet --- build.gradle | 9 +- gradle.properties | 2 +- .../intellij/common/CommonConstants.java | 2 +- .../intellij/common/ssl/IDEATrustManager.java | 143 ++++++++++++ .../common/ssl/IDEATrustManagerTest.java | 210 ++++++++++++++++++ 5 files changed, 360 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/redhat/devtools/intellij/common/ssl/IDEATrustManager.java create mode 100644 src/test/java/com/redhat/devtools/intellij/common/ssl/IDEATrustManagerTest.java diff --git a/build.gradle b/build.gradle index cc3c9e4..630eb65 100755 --- a/build.gradle +++ b/build.gradle @@ -51,12 +51,13 @@ java { dependencies { implementation ( - 'io.fabric8:kubernetes-client:6.4.0', - 'io.fabric8:openshift-client:6.4.0', - 'io.fabric8:kubernetes-httpclient-okhttp:6.4.0', + 'io.fabric8:kubernetes-client:6.4.1', + 'io.fabric8:openshift-client:6.4.1', + 'io.fabric8:kubernetes-httpclient-okhttp:6.4.1', 'org.apache.commons:commons-exec:1.3', 'org.apache.commons:commons-lang3:3.12.0', - 'com.twelvemonkeys.common:common-lang:3.9.4' + 'com.twelvemonkeys.common:common-lang:3.9.4', + 'io.github.hakky54:sslcontext-kickstart:8.3.2' ) testImplementation( 'org.assertj:assertj-core:3.17.1', diff --git a/gradle.properties b/gradle.properties index 1a782f5..7a95ec8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -ideaVersion = IC-2021.1 +ideaVersion=IC-2021.1 projectVersion=1.9.4-SNAPSHOT nexusUser=invalid nexusPassword=invalid diff --git a/src/main/java/com/redhat/devtools/intellij/common/CommonConstants.java b/src/main/java/com/redhat/devtools/intellij/common/CommonConstants.java index 3af5b63..9c5f009 100644 --- a/src/main/java/com/redhat/devtools/intellij/common/CommonConstants.java +++ b/src/main/java/com/redhat/devtools/intellij/common/CommonConstants.java @@ -30,7 +30,7 @@ public class CommonConstants { * * @deprecated since 1.8.0, use {@link MetadataClutter#properties} instead */ - @Deprecated + @Deprecated (since = "1.8.0") public static final List metadataClutter = Arrays.asList( "clusterName", "creationTimestamp", diff --git a/src/main/java/com/redhat/devtools/intellij/common/ssl/IDEATrustManager.java b/src/main/java/com/redhat/devtools/intellij/common/ssl/IDEATrustManager.java new file mode 100644 index 0000000..4bf44d9 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/common/ssl/IDEATrustManager.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.intellij.common.ssl; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.util.net.ssl.CertificateManager; +import nl.altindag.ssl.trustmanager.CompositeX509ExtendedTrustManager; +import org.apache.commons.lang3.reflect.FieldUtils; + +import javax.net.ssl.X509ExtendedTrustManager; +import javax.net.ssl.X509TrustManager; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class IDEATrustManager { + + private static final Logger LOG = Logger.getInstance(IDEATrustManager.class); + + private final X509TrustManager trustManager; + + + public IDEATrustManager(){ + trustManager = CertificateManager.getInstance().getTrustManager(); + } + public IDEATrustManager(X509TrustManager trustManager) { + this.trustManager = trustManager; + } + + public X509TrustManager configure(List toAdd) { + try { + if (hasSystemManagerField()) { + // < IC-2022.2 + setCompositeManager(toAdd, trustManager); + } else { + // >= IC-2022.2 + addCompositeManager(toAdd, trustManager); + } + } catch (RuntimeException | IllegalAccessException e) { + LOG.warn("Could not configure IDEA trust manager.", e); + } + return trustManager; + } + + /** + * Returns `true` if [ConfirmingTrustManager] has a private field `mySystemManager`. + * Returns `false` otherwise. + * IDEA < IC-2022.2 manages a single [X509TrustManager] in a private field called `mySystemManager`. + * IDEA >= IC-2022.2 manages a list of [X509TrustManager]s in a private list called `mySystemManagers`. + * + * @return true if com.intellij.util.net.ssl.ConfirmingTrustManager has a field mySystemManager. False otherwise. + */ + private boolean hasSystemManagerField() { + return getSystemManagerField() != null; + } + + private Field getSystemManagerField() { + return FieldUtils.getDeclaredField( + trustManager.getClass(), + "mySystemManager", + true + ); + } + + /** + * Sets a [CompositeX509ExtendedTrustManager] with the given [X509TrustManager]s + * to the given destination [X509TrustManager]. + * If a [CompositeX509ExtendedTrustManager] already exists, his first entry is taken and set to a new + * [CompositeX509ExtendedTrustManager] that replaces the existing one. + * + * @param trustManagers the trust managers that should be set to the destination trust manager + * @param destination the destination trust manager that should receive the trust managers + */ + private void setCompositeManager( + List trustManagers, + X509TrustManager destination + ) throws IllegalAccessException { + Field systemManagerField = getSystemManagerField(); + if (systemManagerField == null) + return; + Object object = systemManagerField.get(destination); + if (!(object instanceof X509ExtendedTrustManager)) { + return; + } + X509ExtendedTrustManager systemManager = (X509ExtendedTrustManager) object; + X509ExtendedTrustManager compositeTrustManager = createCompositeTrustManager(systemManager, trustManagers); + systemManagerField.set(destination, compositeTrustManager); + } + + private X509ExtendedTrustManager createCompositeTrustManager( + X509ExtendedTrustManager systemManager, + List clientTrustManagers + ) { + List trustManagers = new ArrayList<>(); + if (systemManager instanceof CompositeX509ExtendedTrustManager) { + // already patched CertificateManager, take 1st entry in existing system manager + trustManagers.add(((CompositeX509ExtendedTrustManager) systemManager).getInnerTrustManagers().get(0)); + } else { + // unpatched CertificateManager, take system manager + trustManagers.add(systemManager); + } + trustManagers.addAll(clientTrustManagers); + return new CompositeX509ExtendedTrustManager(trustManagers); + } + + /** + * Adds a [CompositeX509ExtendedTrustManager] to the given destination [X509TrustManager]. + * If a [CompositeX509ExtendedTrustManager] already exists, it is replaced by a new [CompositeX509ExtendedTrustManager]. + * + * @param trustManagers the trust managers that should be added to destination trust manager + * @param destination the trust manager that should receive the given trust managers + */ + private void addCompositeManager( + List trustManagers, + X509TrustManager destination + ) throws IllegalAccessException { + Field systemManagersField = FieldUtils.getDeclaredField( + destination.getClass(), + "mySystemManagers", + true); + if (systemManagersField == null) { + return; + } + Object object = systemManagersField.get(destination); + if (!(object instanceof List)) + return; + List managers = (List) object; + List nonCompositeManagers = managers.stream().filter(x509TrustManager -> !(x509TrustManager instanceof CompositeX509ExtendedTrustManager)).collect(Collectors.toList()); + CompositeX509ExtendedTrustManager clientTrustManager = new CompositeX509ExtendedTrustManager(new ArrayList<>(trustManagers)); + managers.clear(); + managers.addAll(nonCompositeManagers); + managers.add(clientTrustManager); + } +} \ No newline at end of file diff --git a/src/test/java/com/redhat/devtools/intellij/common/ssl/IDEATrustManagerTest.java b/src/test/java/com/redhat/devtools/intellij/common/ssl/IDEATrustManagerTest.java new file mode 100644 index 0000000..8fcf570 --- /dev/null +++ b/src/test/java/com/redhat/devtools/intellij/common/ssl/IDEATrustManagerTest.java @@ -0,0 +1,210 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.intellij.common.ssl; + +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import javax.net.ssl.X509ExtendedTrustManager; +import javax.net.ssl.X509TrustManager; + +import nl.altindag.ssl.trustmanager.CompositeX509ExtendedTrustManager; +import org.junit.Test; + +import static junit.framework.TestCase.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class IDEATrustManagerTest { + + @Test + public void single_system_manager_field_should_replace_existing_trust_manager_with_new_composite_trust_manager() { + // given + TrustManagerWithMySystemManagerField trustManager = new TrustManagerWithMySystemManagerField(mock(X509ExtendedTrustManager.class)); + IDEATrustManager operator = new IDEATrustManager(trustManager); + assertThat(trustManager.mySystemManager) + .isNotInstanceOf(CompositeX509ExtendedTrustManager.class); + // when + operator.configure(Collections.emptyList()); + // then + assertThat(trustManager.mySystemManager) + .isInstanceOf(CompositeX509ExtendedTrustManager.class); + } + + @Test + public void single_system_manager_field_should_replace_existing_trust_manager_with_new_composite_trust_manager_that_contains_given_trust_managers() { + // given + TrustManagerWithMySystemManagerField trustManager = new TrustManagerWithMySystemManagerField(mock(X509ExtendedTrustManager.class)); + IDEATrustManager operator = new IDEATrustManager(trustManager); + List newTrustManagers = Arrays.asList( + mock(X509ExtendedTrustManager.class), + mock(X509ExtendedTrustManager.class) + ); + // when + operator.configure(newTrustManagers); + // then + assertThat(trustManager.mySystemManager) + .isInstanceOf(CompositeX509ExtendedTrustManager.class); + List afterConfigure = ((CompositeX509ExtendedTrustManager)trustManager.mySystemManager).getInnerTrustManagers(); + assertThat(afterConfigure) + .containsAll(newTrustManagers); // new instance contains list given to configure() + } + + @Test + public void single_system_manager_field_should_replace_existing_trust_manager_with_new_composite_trust_manager_that_has_replaced_trust_manager_as_1st_entry() { + // given + X509ExtendedTrustManager beforeReplace = mock(X509ExtendedTrustManager.class); + TrustManagerWithMySystemManagerField trustManager = new TrustManagerWithMySystemManagerField(beforeReplace); + IDEATrustManager operator = new IDEATrustManager(trustManager); + // when + operator.configure( + Arrays.asList( + mock(X509ExtendedTrustManager.class), + mock(X509ExtendedTrustManager.class) + ) + ); + // then + assertThat(trustManager.mySystemManager) + .isInstanceOf(CompositeX509ExtendedTrustManager.class); + List afterConfigure = ((CompositeX509ExtendedTrustManager)trustManager.mySystemManager).getInnerTrustManagers(); + assertThat(afterConfigure.get(0)) // new instance contains 1st entry of replaced instance + .isEqualTo(beforeReplace); + } + + @Test + public void single_system_manager_field_should_replace_composite_trust_manager_with_new_instance_that_has_1st_entry_of_replaced_composite_manager() { + // given + X509ExtendedTrustManager toInclude = mock(X509ExtendedTrustManager.class); + X509ExtendedTrustManager toExclude = mock(X509ExtendedTrustManager.class); + CompositeX509ExtendedTrustManager compositeTrustManager = new CompositeX509ExtendedTrustManager(Arrays.asList(toInclude, toExclude)); + TrustManagerWithMySystemManagerField trustManager = new TrustManagerWithMySystemManagerField(compositeTrustManager); + IDEATrustManager manager = new IDEATrustManager(trustManager); + // when + manager.configure( + Arrays.asList( + mock(X509ExtendedTrustManager.class), + mock(X509ExtendedTrustManager.class) + ) + ); + // then + assertThat(trustManager.mySystemManager) + .isNotSameAs(compositeTrustManager) // a new instance was created + .isInstanceOf(CompositeX509ExtendedTrustManager.class); + List afterConfigure = ((CompositeX509ExtendedTrustManager)trustManager.mySystemManager).getInnerTrustManagers(); + assertThat(afterConfigure.get(0)) // new instance contains 1st entry of replaced instance + .isEqualTo(toInclude); + } + + @Test + public void multi_system_managers_field_should_still_contain_existing_trust_managers() { + // given + X509ExtendedTrustManager existing = mock(X509ExtendedTrustManager.class); + List managers = Collections.singletonList(existing); + TrustManagerWithMySystemManagersField trustManager = new TrustManagerWithMySystemManagersField(managers); + IDEATrustManager operator = new IDEATrustManager(trustManager); + // when + operator.configure(Collections.emptyList()); + // then + assertThat(trustManager.mySystemManagers) + .contains(existing); + } + + @Test + public void multi_system_managers_field_should_add_composite_manager_that_contains_new_trust_managers() { + // given + List managers = new ArrayList<>(); + managers.add(mock(X509ExtendedTrustManager.class)); + TrustManagerWithMySystemManagersField trustManager = new TrustManagerWithMySystemManagersField(managers); + IDEATrustManager operator = new IDEATrustManager(trustManager); + List newTrustManagers = Arrays.asList( + mock(X509ExtendedTrustManager.class), + mock(X509ExtendedTrustManager.class) + ); + // when + operator.configure(newTrustManagers); + // then + Optional composite = trustManager.mySystemManagers.stream().filter(CompositeX509ExtendedTrustManager.class::isInstance).map(CompositeX509ExtendedTrustManager.class::cast ).findFirst(); + assertTrue(composite.isPresent()); + assertThat(composite.get().getInnerTrustManagers()).containsAll(newTrustManagers); + } + + @Test + public void multi_system_managers_field_should_replace_existing_composite_manager_that_contains_new_trust_managers() { + // given + X509ExtendedTrustManager existingTrustManager = mock(X509ExtendedTrustManager.class); + CompositeX509ExtendedTrustManager existingCompositeManager = new CompositeX509ExtendedTrustManager(Collections.singletonList(mock(X509ExtendedTrustManager.class))); + List managers = new ArrayList<>(); + managers.add(existingTrustManager); + managers.add(existingCompositeManager); + TrustManagerWithMySystemManagersField trustManager = new TrustManagerWithMySystemManagersField(managers); + IDEATrustManager operator = new IDEATrustManager(trustManager); + List newTrustManagers = Arrays.asList( + mock(X509ExtendedTrustManager.class), + mock(X509ExtendedTrustManager.class) + ); + // when + operator.configure(newTrustManagers); + // then + assertThat(trustManager.mySystemManagers).doesNotContain(existingCompositeManager); + Optional composite = trustManager.mySystemManagers.stream().filter(CompositeX509ExtendedTrustManager.class::isInstance).map(CompositeX509ExtendedTrustManager.class::cast ).findFirst(); + assertTrue(composite.isPresent()); + assertThat(composite.get().getInnerTrustManagers()).containsAll(newTrustManagers); + } + + /** [com.intellij.util.net.ssl.ConfirmingTrustManager] in < IC-2022.2 */ + private static class TrustManagerWithMySystemManagerField implements X509TrustManager { + + X509TrustManager mySystemManager; + + public TrustManagerWithMySystemManagerField(X509TrustManager mySystemManager) { + this.mySystemManager = mySystemManager; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } + + /** [com.intellij.util.net.ssl.ConfirmingTrustManager] in >= IC-2022.2 */ + private static class TrustManagerWithMySystemManagersField implements X509TrustManager { + + List mySystemManagers; + + public TrustManagerWithMySystemManagersField(List mySystemManagers){ + this.mySystemManagers = mySystemManagers; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + + } + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } +} \ No newline at end of file