Skip to content

Commit

Permalink
feat: add IDEATrustManager classes
Browse files Browse the repository at this point in the history
Signed-off-by: Stephane Bouchet <[email protected]>
  • Loading branch information
sbouchet authored and adietish committed Mar 19, 2024
1 parent 59539f2 commit c9630d8
Show file tree
Hide file tree
Showing 5 changed files with 360 additions and 6 deletions.
9 changes: 5 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ideaVersion = IC-2021.1
ideaVersion=IC-2021.1
projectVersion=1.9.4-SNAPSHOT
nexusUser=invalid
nexusPassword=invalid
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> metadataClutter = Arrays.asList(
"clusterName",
"creationTimestamp",
Expand Down
Original file line number Diff line number Diff line change
@@ -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<X509ExtendedTrustManager> 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<X509ExtendedTrustManager> 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<X509ExtendedTrustManager> clientTrustManagers
) {
List<X509ExtendedTrustManager> 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<X509ExtendedTrustManager> 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<X509TrustManager> managers = (List<X509TrustManager>) object;
List<X509TrustManager> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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<X509ExtendedTrustManager> newTrustManagers = Arrays.asList(
mock(X509ExtendedTrustManager.class),
mock(X509ExtendedTrustManager.class)
);
// when
operator.configure(newTrustManagers);
// then
assertThat(trustManager.mySystemManager)
.isInstanceOf(CompositeX509ExtendedTrustManager.class);
List<X509ExtendedTrustManager> 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<X509ExtendedTrustManager> 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<X509ExtendedTrustManager> 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<X509TrustManager> 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<X509TrustManager> managers = new ArrayList<>();
managers.add(mock(X509ExtendedTrustManager.class));
TrustManagerWithMySystemManagersField trustManager = new TrustManagerWithMySystemManagersField(managers);
IDEATrustManager operator = new IDEATrustManager(trustManager);
List<X509ExtendedTrustManager> newTrustManagers = Arrays.asList(
mock(X509ExtendedTrustManager.class),
mock(X509ExtendedTrustManager.class)
);
// when
operator.configure(newTrustManagers);
// then
Optional<CompositeX509ExtendedTrustManager> 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<X509TrustManager> managers = new ArrayList<>();
managers.add(existingTrustManager);
managers.add(existingCompositeManager);
TrustManagerWithMySystemManagersField trustManager = new TrustManagerWithMySystemManagersField(managers);
IDEATrustManager operator = new IDEATrustManager(trustManager);
List<X509ExtendedTrustManager> newTrustManagers = Arrays.asList(
mock(X509ExtendedTrustManager.class),
mock(X509ExtendedTrustManager.class)
);
// when
operator.configure(newTrustManagers);
// then
assertThat(trustManager.mySystemManagers).doesNotContain(existingCompositeManager);
Optional<CompositeX509ExtendedTrustManager> 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<X509TrustManager> mySystemManagers;

public TrustManagerWithMySystemManagersField(List<X509TrustManager> 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];
}
}
}

0 comments on commit c9630d8

Please sign in to comment.