-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Stephane Bouchet <[email protected]>
- Loading branch information
Showing
5 changed files
with
360 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
143 changes: 143 additions & 0 deletions
143
src/main/java/com/redhat/devtools/intellij/common/ssl/IDEATrustManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
210 changes: 210 additions & 0 deletions
210
src/test/java/com/redhat/devtools/intellij/common/ssl/IDEATrustManagerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]; | ||
} | ||
} | ||
} |