Issue #1108 - adding SslContextFactory.dump() selection details

+ Protocol selection details
+ Cipher Suites selection details

Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>

Conflicts:
	jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java
This commit is contained in:
Joakim Erdfelt 2016-11-22 11:26:53 -07:00
parent 9ba7b713d2
commit 5e0d11cfc3
3 changed files with 250 additions and 70 deletions

View File

@ -35,6 +35,7 @@ import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@ -64,6 +65,8 @@ import javax.net.ssl.X509TrustManager;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
@ -78,7 +81,7 @@ import org.eclipse.jetty.util.security.Password;
* creates SSL context based on these parameters to be
* used by the SSL connectors.
*/
public class SslContextFactory extends AbstractLifeCycle
public class SslContextFactory extends AbstractLifeCycle implements Dumpable
{
public final static TrustManager[] TRUST_ALL_CERTS = new X509TrustManager[]{new X509TrustManager()
{
@ -314,7 +317,39 @@ public class SslContextFactory extends AbstractLifeCycle
}
}
}
@Override
public String dump()
{
return ContainerLifeCycle.dump(this);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
out.append(String.valueOf(this)).append(" trustAll=").append(Boolean.toString(_trustAll)).append(System.lineSeparator());
SSLEngine sslEngine = newSSLEngine();
List<Object> selections = new ArrayList<>();
// protocols
selections.add(new SslSelectionDump("Protocol",
sslEngine.getSupportedProtocols(),
sslEngine.getEnabledProtocols(),
getExcludeProtocols(),
getIncludeProtocols()));
// ciphers
selections.add(new SslSelectionDump("Cipher Suite",
sslEngine.getSupportedCipherSuites(),
sslEngine.getEnabledCipherSuites(),
getExcludeCipherSuites(),
getIncludeCipherSuites()));
ContainerLifeCycle.dump(out, indent, selections);
}
@Override
protected void doStop() throws Exception
{

View File

@ -0,0 +1,184 @@
package org.eclipse.jetty.util.ssl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
public class SslSelectionDump extends ContainerLifeCycle implements Dumpable
{
private static class CaptionedList extends ArrayList<String> implements Dumpable
{
private final String caption;
public CaptionedList(String caption)
{
this.caption = caption;
}
@Override
public String dump()
{
return ContainerLifeCycle.dump(SslSelectionDump.CaptionedList.this);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
out.append(caption);
out.append(" (size=").append(Integer.toString(size())).append(")");
out.append(System.lineSeparator());
ContainerLifeCycle.dump(out, indent, this);
}
}
private final String type;
private SslSelectionDump.CaptionedList enabled = new SslSelectionDump.CaptionedList("Enabled");
private SslSelectionDump.CaptionedList disabled = new SslSelectionDump.CaptionedList("Disabled");
public SslSelectionDump(String type,
String[] supportedByJVM,
String[] enabledByJVM,
String[] excludedByConfig,
String[] includedByConfig)
{
this.type = type;
addBean(enabled);
addBean(disabled);
List<String> jvmEnabled = Arrays.asList(enabledByJVM);
List<Pattern> excludedPatterns = toPatternList(excludedByConfig);
List<Pattern> includedPatterns = toPatternList(includedByConfig);
for(String entry: toSortedList(supportedByJVM))
{
boolean isPresent = true;
StringBuilder s = new StringBuilder();
s.append(entry);
if (!jvmEnabled.contains(entry))
{
if (isPresent)
{
s.append(" -");
isPresent = false;
}
s.append(" JreDisabled:java.security");
}
for (Pattern pattern : excludedPatterns)
{
Matcher m = pattern.matcher(entry);
if (m.matches())
{
if (isPresent)
{
s.append(" -");
isPresent = false;
}
else
{
s.append(",");
}
s.append(" ConfigExcluded:'").append(pattern.pattern()).append('\'');
}
}
if (!includedPatterns.isEmpty())
{
boolean isIncluded = false;
for (Pattern pattern : includedPatterns)
{
Matcher m = pattern.matcher(entry);
if (m.matches())
{
isIncluded = true;
break;
}
}
if (!isIncluded)
{
if (isPresent)
{
s.append(" -");
isPresent = false;
}
else
{
s.append(",");
}
s.append(" ConfigIncluded:NotSpecified");
}
}
if (isPresent)
{
enabled.add(s.toString());
}
else
{
disabled.add(s.toString());
}
}
}
private List<String> toSortedList(String[] strings)
{
List<String> sorted = new ArrayList<>();
for (String entry : strings)
{
sorted.add(entry);
}
Collections.sort(sorted, new Comparator<String>()
{
@Override
public int compare(String o1, String o2)
{
return o1.compareTo(o2);
}
});
return sorted;
}
private static List<Pattern> toPatternList(String[] configs)
{
// Arrays.stream(configs)
// .map((entry) -> Pattern.compile(entry))
// .collect(Collectors.toList());
List<Pattern> ret = new ArrayList<>();
for (String entry : configs)
{
ret.add(Pattern.compile(entry));
}
return ret;
}
@Override
public String dump()
{
return ContainerLifeCycle.dump(this);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
dumpBeans(out, indent);
}
@Override
protected void dumpThis(Appendable out) throws IOException
{
out.append(type).append(" Selections").append(System.lineSeparator());
}
}

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.util.ssl;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
@ -32,19 +33,20 @@ import java.security.KeyStore;
import javax.net.ssl.SSLEngine;
import org.eclipse.jetty.toolchain.test.JDK;
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.StdErrLog;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.eclipse.jetty.util.resource.Resource;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
public class SslContextFactoryTest
{
@Rule
public ExpectedException expectedException = ExpectedException.none();
private SslContextFactory cf;
@ -54,10 +56,20 @@ public class SslContextFactoryTest
cf = new SslContextFactory();
}
@Test
public void testSLOTH() throws Exception
{
cf.setKeyStorePassword("storepwd");
cf.setKeyManagerPassword("keypwd");
cf.start();
cf.dump(System.out, "");
}
@Test
public void testNoTsFileKs() throws Exception
{
String keystorePath = System.getProperty("basedir",".") + "/src/test/resources/keystore";
cf.setKeyStorePassword("storepwd");
cf.setKeyManagerPassword("keypwd");
@ -104,7 +116,7 @@ public class SslContextFactoryTest
cf.setKeyStoreResource(keystoreResource);
cf.setKeyStorePassword("storepwd");
cf.setKeyManagerPassword("keypwd");
cf.start();
assertTrue(cf.getSslContext()!=null);
@ -130,7 +142,6 @@ public class SslContextFactoryTest
@Test
public void testResourceTsResourceKsWrongPW() throws Exception
{
SslContextFactory.LOG.info("EXPECT SslContextFactory@????????(null,null): java.security.UnrecoverableKeyException: Cannot recover key...");
Resource keystoreResource = Resource.newSystemResource("keystore");
Resource truststoreResource = Resource.newSystemResource("keystore");
@ -140,21 +151,16 @@ public class SslContextFactoryTest
cf.setKeyManagerPassword("wrong_keypwd");
cf.setTrustStorePassword("storepwd");
try
try (StacklessLogging ignored = new StacklessLogging(AbstractLifeCycle.class))
{
((StdErrLog)Log.getLogger(AbstractLifeCycle.class)).setHideStacks(true);
expectedException.expect(java.security.UnrecoverableKeyException.class);
cf.start();
Assert.fail();
}
catch(java.security.UnrecoverableKeyException e)
{
}
}
@Test
public void testResourceTsWrongPWResourceKs() throws Exception
{
SslContextFactory.LOG.info("EXPECT SslContextFactory@????????(null,null): java.io.IOException: Keystore was tampered with ...");
Resource keystoreResource = Resource.newSystemResource("keystore");
Resource truststoreResource = Resource.newSystemResource("keystore");
@ -164,35 +170,23 @@ public class SslContextFactoryTest
cf.setKeyManagerPassword("keypwd");
cf.setTrustStorePassword("wrong_storepwd");
try
try (StacklessLogging ignored = new StacklessLogging(AbstractLifeCycle.class))
{
((StdErrLog)Log.getLogger(AbstractLifeCycle.class)).setHideStacks(true);
expectedException.expect(IOException.class);
expectedException.expectMessage(containsString("Keystore was tampered with, or password was incorrect"));
cf.start();
Assert.fail();
}
catch(IOException e)
{
}
}
@Test
public void testNoKeyConfig() throws Exception
{
try
try (StacklessLogging ignored = new StacklessLogging(AbstractLifeCycle.class))
{
SslContextFactory.LOG.info("EXPECT SslContextFactory@????????(null,/foo): java.lang.IllegalStateException: SSL doesn't have a valid keystore...");
((StdErrLog)Log.getLogger(AbstractLifeCycle.class)).setHideStacks(true);
cf.setTrustStorePath("/foo");
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage(containsString("SSL doesn't have a valid keystore"));
cf.start();
Assert.fail();
}
catch (IllegalStateException e)
{
}
catch (Exception e)
{
Assert.fail("Unexpected exception");
}
}
@ -211,40 +205,15 @@ public class SslContextFactoryTest
@Test
public void testSetIncludeCipherSuitesRegex() throws Exception
{
// Test does not work on JDK 8+ (RC4 is disabled)
cf.setIncludeCipherSuites(".*ECDHE.*",".*WIBBLE.*");
Assume.assumeFalse(JDK.IS_8);
cf.setIncludeCipherSuites(".*RC4.*");
cf.start();
SSLEngine sslEngine = cf.newSSLEngine();
String[] enabledCipherSuites = sslEngine.getEnabledCipherSuites();
assertThat("At least 1 cipherSuite is enabled", enabledCipherSuites.length, greaterThan(0));
assertThat("At least 1 cipherSuite is enabled", enabledCipherSuites.length, greaterThan(1));
for (String enabledCipherSuite : enabledCipherSuites)
assertThat("CipherSuite contains RC4", enabledCipherSuite.contains("RC4"), is(true));
}
@Test
public void testSetIncludeCipherSuitesPreservesOrder()
{
String[] supportedCipherSuites = new String[]{"cipher4", "cipher2", "cipher1", "cipher3"};
String[] includeCipherSuites = {"cipher1", "cipher3", "cipher4"};
cf.setIncludeCipherSuites(includeCipherSuites);
String[] selectedCipherSuites = cf.selectCipherSuites(null, supportedCipherSuites);
assertSelectedMatchesIncluded(includeCipherSuites, selectedCipherSuites);
}
@Test
public void testSetIncludeProtocolsPreservesOrder()
{
String[] supportedProtocol = new String[]{"cipher4", "cipher2", "cipher1", "cipher3"};
String[] includeProtocol = {"cipher1", "cipher3", "cipher4"};
cf.setIncludeProtocols(includeProtocol);
String[] selectedProtocol = cf.selectProtocols(null, supportedProtocol);
assertSelectedMatchesIncluded(includeProtocol, selectedProtocol);
assertThat("CipherSuite contains ECDHE", enabledCipherSuite.contains("ECDHE"), equalTo(true));
}
@Test
@ -255,12 +224,4 @@ public class SslContextFactoryTest
assertNotNull(cf.getExcludeCipherSuites());
assertNotNull(cf.getIncludeCipherSuites());
}
private void assertSelectedMatchesIncluded(String[] includeStrings, String[] selectedStrings)
{
assertThat(includeStrings.length + " strings are selected", selectedStrings.length, is(includeStrings.length));
assertThat("order from includeStrings is preserved", selectedStrings[0], equalTo(includeStrings[0]));
assertThat("order from includeStrings is preserved", selectedStrings[1], equalTo(includeStrings[1]));
assertThat("order from includeStrings is preserved", selectedStrings[2], equalTo(includeStrings[2]));
}
}