[java-opensaml] branch master updated: OSJ-272: Move HttpClient TLS TrustEngine use to a X509TrustManager impl

Brent Putman putmanb at georgetown.edu
Sun Feb 23 21:45:52 EST 2020


This is an automated email from the git hooks/post-receive script.

putmanb pushed a commit to branch master
in repository java-opensaml.

View the commit online:
http://git.shibboleth.net/view/?p=java-opensaml.git;a=commit;h=0a66c13e6658cf813064bfd67e9e64597d83bd59

The following commit(s) were added to refs/heads/master by this push:
       new  0a66c13   OSJ-272: Move HttpClient TLS TrustEngine use to a X509TrustManager impl
0a66c13 is described below

commit 0a66c13e6658cf813064bfd67e9e64597d83bd59
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Wed Apr 3 19:20:08 2019 -0400

    OSJ-272: Move HttpClient TLS TrustEngine use to a X509TrustManager impl
---
 .../resolver/filter/impl/EntityRoleFilterTest.java |   2 +-
 .../impl/FileBackedHTTPMetadataResolverTest.java   |  47 +--
 ...ctionDrivenDynamicHTTPMetadataResolverTest.java |  81 +++---
 .../resolver/impl/HTTPMetadataResolverTest.java    |  62 ++--
 .../src/test/resources/logback-test.xml            |  20 ++
 .../metadata/resolver/impl/repo.truststore.jks     | Bin 0 -> 1754 bytes
 .../metadata/resolver/impl/repo.truststore.txt     |   1 +
 .../metadata/resolver/impl/test.shibboleth.net.crt |  37 +++
 .../opensaml/security/trust/MockTrustEngine.java   |  55 ++++
 opensaml-security-impl/pom.xml                     |   8 +
 .../impl/SecurityEnhancedHttpClientSupport.java    |  18 +-
 .../impl/SecurityEnhancedTLSSocketFactory.java     | 315 ++++++---------------
 .../impl/ThreadLocalServerTLSHandler.java          |  62 ++++
 .../impl/ThreadLocalX509TrustEngineContext.java    | 146 ++++++++++
 .../x509/tls/impl/ThreadLocalX509TrustManager.java | 155 ++++++++++
 .../impl/SecurityEnhancedTLSSocketFactoryTest.java | 262 ++++++++---------
 .../tls/impl/ThreadLocalX509TrustManagerTest.java  | 170 +++++++++++
 17 files changed, 992 insertions(+), 449 deletions(-)

diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/filter/impl/EntityRoleFilterTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/filter/impl/EntityRoleFilterTest.java
index d143661..bf2d485 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/filter/impl/EntityRoleFilterTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/filter/impl/EntityRoleFilterTest.java
@@ -57,7 +57,7 @@ public class EntityRoleFilterTest extends XMLObjectBaseTestCase {
     protected void setUpClass() throws Exception {
         httpClientBuilder = new HttpClientBuilder();
         httpClientBuilder.setConnectionTimeout(Duration.ofSeconds(5));
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory());
         httpClient = httpClientBuilder.buildClient();
 
         httpClientParams = new HttpClientSecurityParameters();
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/FileBackedHTTPMetadataResolverTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/FileBackedHTTPMetadataResolverTest.java
index 4c9ea9d..e87938b 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/FileBackedHTTPMetadataResolverTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/FileBackedHTTPMetadataResolverTest.java
@@ -385,9 +385,9 @@ public class FileBackedHTTPMetadataResolverTest extends XMLObjectBaseTestCase {
     @Test
     public void testTrustEngineSocketFactoryNoHTTPSNoTrustEngine() throws Exception  {
         // Make sure resolver works when TrustEngine socket factory is configured but just using an HTTP URL.
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory(false));
+        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory(true));
         
-        metadataProvider = new FileBackedHTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps, backupFilePath);
+        metadataProvider = new FileBackedHTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttp, backupFilePath);
         metadataProvider.setParserPool(parserPool);
         metadataProvider.setId("test");
         metadataProvider.initialize();
@@ -400,7 +400,7 @@ public class FileBackedHTTPMetadataResolverTest extends XMLObjectBaseTestCase {
     @Test
     public void testTrustEngineSocketFactoryNoHTTPSWithTrustEngine() throws Exception  {
         // Make sure resolver works when TrustEngine socket factory is configured but just using an HTTP URL.
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory());
         
         metadataProvider = new FileBackedHTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps, backupFilePath);
         metadataProvider.setParserPool(parserPool);
@@ -417,21 +417,30 @@ public class FileBackedHTTPMetadataResolverTest extends XMLObjectBaseTestCase {
     
     @Test
     public void testHTTPSNoTrustEngine() throws Exception  {
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory(false));
-        
-        metadataProvider = new FileBackedHTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps, backupFilePath); 
-        metadataProvider.setParserPool(parserPool);
-        metadataProvider.setId("test");
-        metadataProvider.initialize();
-        
-        EntityDescriptor descriptor = metadataProvider.resolveSingle(criteriaSet);
-        Assert.assertNotNull(descriptor, "Retrieved entity descriptor was null");
-        Assert.assertEquals(descriptor.getEntityID(), entityID, "Entity's ID does not match requested ID");
+        try {
+            System.setProperty("javax.net.ssl.trustStore", getClass().getResource("repo.truststore.jks").getFile());
+            System.setProperty("javax.net.ssl.trustStorePassword", "shibboleth");
+            
+            httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory(false));
+
+            metadataProvider = new FileBackedHTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps, backupFilePath); 
+            metadataProvider.setParserPool(parserPool);
+            metadataProvider.setId("test");
+            metadataProvider.initialize();
+
+            EntityDescriptor descriptor = metadataProvider.resolveSingle(criteriaSet);
+            Assert.assertNotNull(descriptor, "Retrieved entity descriptor was null");
+            Assert.assertEquals(descriptor.getEntityID(), entityID, "Entity's ID does not match requested ID");
+        } finally {
+            System.setProperty("javax.net.ssl.trustStore", "");
+            System.setProperty("javax.net.ssl.trustStorePassword", "");
+
+        }
     }
     
     @Test
     public void testHTTPSTrustEngineExplicitKey() throws Exception  {
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory());
         
         metadataProvider = new FileBackedHTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps, backupFilePath);
         metadataProvider.setParserPool(parserPool);
@@ -449,7 +458,7 @@ public class FileBackedHTTPMetadataResolverTest extends XMLObjectBaseTestCase {
 
     @Test(expectedExceptions=ComponentInitializationException.class)
     public void testHTTPSTrustEngineInvalidKey() throws Exception  {
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory());
         
         metadataProvider = new FileBackedHTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps, backupFilePath);
         metadataProvider.setParserPool(parserPool);
@@ -466,7 +475,7 @@ public class FileBackedHTTPMetadataResolverTest extends XMLObjectBaseTestCase {
     
     @Test
     public void testHTTPSTrustEngineValidPKIX() throws Exception  {
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory());
         
         metadataProvider = new FileBackedHTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps, backupFilePath);
         metadataProvider.setParserPool(parserPool);
@@ -485,7 +494,7 @@ public class FileBackedHTTPMetadataResolverTest extends XMLObjectBaseTestCase {
     
     @Test
     public void testHTTPSTrustEngineValidPKIXExplicitName() throws Exception  {
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory());
         
         metadataProvider = new FileBackedHTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps, backupFilePath);
         metadataProvider.setParserPool(parserPool);
@@ -503,7 +512,7 @@ public class FileBackedHTTPMetadataResolverTest extends XMLObjectBaseTestCase {
     
     @Test(expectedExceptions=ComponentInitializationException.class)
     public void testHTTPSTrustEngineInvalidPKIX() throws Exception  {
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory());
         
         metadataProvider = new FileBackedHTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps, backupFilePath);
         metadataProvider.setParserPool(parserPool);
@@ -521,7 +530,7 @@ public class FileBackedHTTPMetadataResolverTest extends XMLObjectBaseTestCase {
     
     @Test(expectedExceptions=ComponentInitializationException.class)
     public void testHTTPSTrustEngineValidPKIXInvalidName() throws Exception  {
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory());
         
         metadataProvider = new FileBackedHTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps, backupFilePath);
         metadataProvider.setParserPool(parserPool);
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/FunctionDrivenDynamicHTTPMetadataResolverTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/FunctionDrivenDynamicHTTPMetadataResolverTest.java
index 2e6dbcf..2b6c30b 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/FunctionDrivenDynamicHTTPMetadataResolverTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/FunctionDrivenDynamicHTTPMetadataResolverTest.java
@@ -197,12 +197,17 @@ public class FunctionDrivenDynamicHTTPMetadataResolverTest extends XMLObjectBase
         
         HTTPEntityIDRequestURLBuilder requestURLBuilder = new HTTPEntityIDRequestURLBuilder();
         
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory(false));
+        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory());
         
         resolver = new FunctionDrivenDynamicHTTPMetadataResolver(httpClientBuilder.buildClient());
         resolver.setId("myDynamicResolver");
         resolver.setParserPool(parserPool);
         resolver.setRequestURLBuilder(requestURLBuilder);
+        
+        final HttpClientSecurityParameters params = new HttpClientSecurityParameters();
+        params.setTLSTrustEngine(HTTPMetadataResolverTest.buildExplicitKeyTrustEngine("test.shibboleth.net.crt"));
+        resolver.setHttpClientSecurityParameters(params);
+        
         resolver.initialize();
         
         CriteriaSet criteriaSet = new CriteriaSet( new EntityIdCriterion(entityID));
@@ -265,7 +270,7 @@ public class FunctionDrivenDynamicHTTPMetadataResolverTest extends XMLObjectBase
     
     @Test
     public void testTrustEngineSocketFactoryNoHTTPSNoTrustEngine() throws Exception {
-        String template = RepositorySupport.buildHTTPSResourceURL("java-opensaml", "opensaml-saml-impl/src/test/resources/org/opensaml/saml/metadata/resolver/impl/${entityID}.xml");
+        String template = RepositorySupport.buildHTTPResourceURL("java-opensaml", "opensaml-saml-impl/src/test/resources/org/opensaml/saml/metadata/resolver/impl/${entityID}.xml", false);
         String entityID = "https://www.example.org/sp";
         
         // Digesting the entityID is a little artificial for the test, but means we can test more easily against a path in the repo.
@@ -275,7 +280,7 @@ public class FunctionDrivenDynamicHTTPMetadataResolverTest extends XMLObjectBase
                 EncodingStyle.path, 
                 new StringDigester("SHA-1", OutputFormat.HEX_LOWER));
         
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory(false));
+        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory());
         
         resolver = new FunctionDrivenDynamicHTTPMetadataResolver(httpClientBuilder.buildClient());
         resolver.setId("myDynamicResolver");
@@ -303,7 +308,7 @@ public class FunctionDrivenDynamicHTTPMetadataResolverTest extends XMLObjectBase
                 EncodingStyle.path, 
                 new StringDigester("SHA-1", OutputFormat.HEX_LOWER));
         
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory());
         
         resolver = new FunctionDrivenDynamicHTTPMetadataResolver(httpClientBuilder.buildClient());
         resolver.setId("myDynamicResolver");
@@ -326,30 +331,38 @@ public class FunctionDrivenDynamicHTTPMetadataResolverTest extends XMLObjectBase
     
     @Test
     public void testHTTPSNoTrustEngine() throws Exception  {
-        String template = RepositorySupport.buildHTTPSResourceURL("java-opensaml", "opensaml-saml-impl/src/test/resources/org/opensaml/saml/metadata/resolver/impl/${entityID}.xml");
-        String entityID = "https://www.example.org/sp";
-        
-        // Digesting the entityID is a little artificial for the test, but means we can test more easily against a path in the repo.
-        TemplateRequestURLBuilder requestURLBuilder = new TemplateRequestURLBuilder(
-                VelocityEngine.newVelocityEngine(), 
-                template, 
-                EncodingStyle.path, 
-                new StringDigester("SHA-1", OutputFormat.HEX_LOWER));
-        
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory(false));
-        
-        resolver = new FunctionDrivenDynamicHTTPMetadataResolver(httpClientBuilder.buildClient());
-        resolver.setId("myDynamicResolver");
-        resolver.setParserPool(parserPool);
-        resolver.setRequestURLBuilder(requestURLBuilder);
-        resolver.initialize();
-        
-        CriteriaSet criteriaSet = new CriteriaSet( new EntityIdCriterion(entityID));
-        
-        EntityDescriptor ed = resolver.resolveSingle(criteriaSet);
-        Assert.assertNotNull(ed);
-        Assert.assertEquals(ed.getEntityID(), entityID);
-        Assert.assertNull(ed.getDOM());
+        try {
+            System.setProperty("javax.net.ssl.trustStore", getClass().getResource("repo.truststore.jks").getFile());
+            System.setProperty("javax.net.ssl.trustStorePassword", "shibboleth");
+            
+            String template = RepositorySupport.buildHTTPSResourceURL("java-opensaml", "opensaml-saml-impl/src/test/resources/org/opensaml/saml/metadata/resolver/impl/${entityID}.xml");
+            String entityID = "https://www.example.org/sp";
+
+            // Digesting the entityID is a little artificial for the test, but means we can test more easily against a path in the repo.
+            TemplateRequestURLBuilder requestURLBuilder = new TemplateRequestURLBuilder(
+                    VelocityEngine.newVelocityEngine(), 
+                    template, 
+                    EncodingStyle.path, 
+                    new StringDigester("SHA-1", OutputFormat.HEX_LOWER));
+
+            httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory(false));
+
+            resolver = new FunctionDrivenDynamicHTTPMetadataResolver(httpClientBuilder.buildClient());
+            resolver.setId("myDynamicResolver");
+            resolver.setParserPool(parserPool);
+            resolver.setRequestURLBuilder(requestURLBuilder);
+            resolver.initialize();
+
+            CriteriaSet criteriaSet = new CriteriaSet( new EntityIdCriterion(entityID));
+
+            EntityDescriptor ed = resolver.resolveSingle(criteriaSet);
+            Assert.assertNotNull(ed);
+            Assert.assertEquals(ed.getEntityID(), entityID);
+            Assert.assertNull(ed.getDOM());
+        } finally {
+            System.setProperty("javax.net.ssl.trustStore", "");
+            System.setProperty("javax.net.ssl.trustStorePassword", "");
+        }
     }
     
     @Test
@@ -364,7 +377,7 @@ public class FunctionDrivenDynamicHTTPMetadataResolverTest extends XMLObjectBase
                 EncodingStyle.path, 
                 new StringDigester("SHA-1", OutputFormat.HEX_LOWER));
         
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory());
         
         resolver = new FunctionDrivenDynamicHTTPMetadataResolver(httpClientBuilder.buildClient());
         resolver.setId("myDynamicResolver");
@@ -397,7 +410,7 @@ public class FunctionDrivenDynamicHTTPMetadataResolverTest extends XMLObjectBase
                 EncodingStyle.path, 
                 new StringDigester("SHA-1", OutputFormat.HEX_LOWER));
         
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory());
         
         resolver = new FunctionDrivenDynamicHTTPMetadataResolver(httpClientBuilder.buildClient());
         resolver.setId("myDynamicResolver");
@@ -428,7 +441,7 @@ public class FunctionDrivenDynamicHTTPMetadataResolverTest extends XMLObjectBase
                 EncodingStyle.path, 
                 new StringDigester("SHA-1", OutputFormat.HEX_LOWER));
         
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory());
         
         resolver = new FunctionDrivenDynamicHTTPMetadataResolver(httpClientBuilder.buildClient());
         resolver.setId("myDynamicResolver");
@@ -461,7 +474,7 @@ public class FunctionDrivenDynamicHTTPMetadataResolverTest extends XMLObjectBase
                 EncodingStyle.path, 
                 new StringDigester("SHA-1", OutputFormat.HEX_LOWER));
         
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory());
         
         resolver = new FunctionDrivenDynamicHTTPMetadataResolver(httpClientBuilder.buildClient());
         resolver.setId("myDynamicResolver");
@@ -494,7 +507,7 @@ public class FunctionDrivenDynamicHTTPMetadataResolverTest extends XMLObjectBase
                 EncodingStyle.path, 
                 new StringDigester("SHA-1", OutputFormat.HEX_LOWER));
         
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory());
         
         resolver = new FunctionDrivenDynamicHTTPMetadataResolver(httpClientBuilder.buildClient());
         resolver.setId("myDynamicResolver");
@@ -525,7 +538,7 @@ public class FunctionDrivenDynamicHTTPMetadataResolverTest extends XMLObjectBase
                 EncodingStyle.path, 
                 new StringDigester("SHA-1", OutputFormat.HEX_LOWER));
         
-        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(HTTPMetadataResolverTest.buildSocketFactory());
         
         resolver = new FunctionDrivenDynamicHTTPMetadataResolver(httpClientBuilder.buildClient());
         resolver.setId("myDynamicResolver");
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/HTTPMetadataResolverTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/HTTPMetadataResolverTest.java
index c37c242..d105217 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/HTTPMetadataResolverTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/metadata/resolver/impl/HTTPMetadataResolverTest.java
@@ -32,6 +32,7 @@ import org.opensaml.core.xml.XMLObjectBaseTestCase;
 import org.opensaml.saml.saml2.metadata.EntityDescriptor;
 import org.opensaml.security.credential.impl.StaticCredentialResolver;
 import org.opensaml.security.httpclient.HttpClientSecurityParameters;
+import org.opensaml.security.httpclient.impl.SecurityEnhancedHttpClientSupport;
 import org.opensaml.security.httpclient.impl.SecurityEnhancedTLSSocketFactory;
 import org.opensaml.security.trust.TrustEngine;
 import org.opensaml.security.trust.impl.ExplicitKeyTrustEngine;
@@ -45,6 +46,7 @@ import org.opensaml.security.x509.impl.CertPathPKIXTrustEvaluator;
 import org.opensaml.security.x509.impl.PKIXX509CredentialTrustEngine;
 import org.opensaml.security.x509.impl.StaticPKIXValidationInformationResolver;
 import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -55,6 +57,7 @@ import net.shibboleth.utilities.java.support.component.ComponentInitializationEx
 import net.shibboleth.utilities.java.support.httpclient.HttpClientBuilder;
 import net.shibboleth.utilities.java.support.httpclient.HttpClientSupport;
 import net.shibboleth.utilities.java.support.httpclient.TLSSocketFactory;
+import net.shibboleth.utilities.java.support.httpclient.TLSSocketFactoryBuilder;
 import net.shibboleth.utilities.java.support.repository.RepositorySupport;
 import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
 import net.shibboleth.utilities.java.support.resolver.ResolverException;
@@ -156,9 +159,9 @@ public class HTTPMetadataResolverTest extends XMLObjectBaseTestCase {
     @Test
     public void testTrustEngineSocketFactoryNoHTTPSNoTrustEngine() throws Exception  {
         // Make sure resolver works when TrustEngine socket factory is configured but just using an HTTP URL.
-        httpClientBuilder.setTLSSocketFactory(buildTrustEngineSocketFactory(false));
+        httpClientBuilder.setTLSSocketFactory(buildSocketFactory(true));
         
-        metadataProvider = new HTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps);
+        metadataProvider = new HTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttp);
         metadataProvider.setParserPool(parserPool);
         metadataProvider.setId("test");
         metadataProvider.initialize();
@@ -171,7 +174,7 @@ public class HTTPMetadataResolverTest extends XMLObjectBaseTestCase {
     @Test
     public void testTrustEngineSocketFactoryNoHTTPSWithTrustEngine() throws Exception  {
         // Make sure resolver works when TrustEngine socket factory is configured but just using an HTTP URL.
-        httpClientBuilder.setTLSSocketFactory(buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(buildSocketFactory());
         
         metadataProvider = new HTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps);
         metadataProvider.setParserPool(parserPool);
@@ -189,21 +192,29 @@ public class HTTPMetadataResolverTest extends XMLObjectBaseTestCase {
     
     @Test
     public void testHTTPSNoTrustEngine() throws Exception  {
-        httpClientBuilder.setTLSSocketFactory(buildTrustEngineSocketFactory(false));
-        
-        metadataProvider = new HTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps);
-        metadataProvider.setParserPool(parserPool);
-        metadataProvider.setId("test");
-        metadataProvider.initialize();
-        
-        EntityDescriptor descriptor = metadataProvider.resolveSingle(criteriaSet);
-        Assert.assertNotNull(descriptor, "Retrieved entity descriptor was null");
-        Assert.assertEquals(descriptor.getEntityID(), entityID, "Entity's ID does not match requested ID");
+        try {
+            System.setProperty("javax.net.ssl.trustStore", getClass().getResource("repo.truststore.jks").getFile());
+            System.setProperty("javax.net.ssl.trustStorePassword", "shibboleth");
+
+            httpClientBuilder.setTLSSocketFactory(buildSocketFactory(false));
+
+            metadataProvider = new HTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps);
+            metadataProvider.setParserPool(parserPool);
+            metadataProvider.setId("test");
+            metadataProvider.initialize();
+
+            EntityDescriptor descriptor = metadataProvider.resolveSingle(criteriaSet);
+            Assert.assertNotNull(descriptor, "Retrieved entity descriptor was null");
+            Assert.assertEquals(descriptor.getEntityID(), entityID, "Entity's ID does not match requested ID");
+        } finally {
+            System.setProperty("javax.net.ssl.trustStore", "");
+            System.setProperty("javax.net.ssl.trustStorePassword", "");        
+        }
     }
     
     @Test
     public void testHTTPSTrustEngineExplicitKey() throws Exception  {
-        httpClientBuilder.setTLSSocketFactory(buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(buildSocketFactory());
         
         metadataProvider = new HTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps);
         metadataProvider.setParserPool(parserPool);
@@ -223,7 +234,7 @@ public class HTTPMetadataResolverTest extends XMLObjectBaseTestCase {
 
     @Test(expectedExceptions=ComponentInitializationException.class)
     public void testHTTPSTrustEngineInvalidKey() throws Exception  {
-        httpClientBuilder.setTLSSocketFactory(buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(buildSocketFactory());
         
         metadataProvider = new HTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps);
         metadataProvider.setParserPool(parserPool);
@@ -242,7 +253,7 @@ public class HTTPMetadataResolverTest extends XMLObjectBaseTestCase {
     
     @Test
     public void testHTTPSTrustEngineValidPKIX() throws Exception  {
-        httpClientBuilder.setTLSSocketFactory(buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(buildSocketFactory());
         
         metadataProvider = new HTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps);
         metadataProvider.setParserPool(parserPool);
@@ -261,7 +272,7 @@ public class HTTPMetadataResolverTest extends XMLObjectBaseTestCase {
     
     @Test
     public void testHTTPSTrustEngineValidPKIXExplicitName() throws Exception  {
-        httpClientBuilder.setTLSSocketFactory(buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(buildSocketFactory());
         
         metadataProvider = new HTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps);
         metadataProvider.setParserPool(parserPool);
@@ -280,7 +291,7 @@ public class HTTPMetadataResolverTest extends XMLObjectBaseTestCase {
     
     @Test(expectedExceptions=ComponentInitializationException.class)
     public void testHTTPSTrustEngineInvalidPKIX() throws Exception  {
-        httpClientBuilder.setTLSSocketFactory(buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(buildSocketFactory());
         
         metadataProvider = new HTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps);
         metadataProvider.setParserPool(parserPool);
@@ -299,7 +310,7 @@ public class HTTPMetadataResolverTest extends XMLObjectBaseTestCase {
     
     @Test(expectedExceptions=ComponentInitializationException.class)
     public void testHTTPSTrustEngineValidPKIXInvalidName() throws Exception  {
-        httpClientBuilder.setTLSSocketFactory(buildTrustEngineSocketFactory());
+        httpClientBuilder.setTLSSocketFactory(buildSocketFactory());
         
         metadataProvider = new HTTPMetadataResolver(httpClientBuilder.buildClient(), metadataURLHttps);
         metadataProvider.setParserPool(parserPool);
@@ -355,17 +366,12 @@ public class HTTPMetadataResolverTest extends XMLObjectBaseTestCase {
         
     }
 
-    public static LayeredConnectionSocketFactory buildTrustEngineSocketFactory() {
-        return buildTrustEngineSocketFactory(true);
+    public static LayeredConnectionSocketFactory buildSocketFactory() {
+        return buildSocketFactory(true);
     }
     
-    public static LayeredConnectionSocketFactory buildTrustEngineSocketFactory(boolean trustEngineRequired) {
-        SecurityEnhancedTLSSocketFactory factory = new SecurityEnhancedTLSSocketFactory(
-                HttpClientSupport.buildNoTrustTLSSocketFactory(),
-                TLSSocketFactory.STRICT_HOSTNAME_VERIFIER,
-                trustEngineRequired
-                );
-        return factory;
+    public static LayeredConnectionSocketFactory buildSocketFactory(boolean supportTrustEngine) {
+        return SecurityEnhancedHttpClientSupport.buildTLSSocketFactory(supportTrustEngine, false);
     }
 
 }
diff --git a/opensaml-saml-impl/src/test/resources/logback-test.xml b/opensaml-saml-impl/src/test/resources/logback-test.xml
index 232a61c..ac9ab81 100644
--- a/opensaml-saml-impl/src/test/resources/logback-test.xml
+++ b/opensaml-saml-impl/src/test/resources/logback-test.xml
@@ -8,6 +8,26 @@
         </encoder>
     </appender>
     
+    <logger name="org.apache.http">
+        <level value="WARN"/>
+    </logger>
+    
+    <logger name="org.apache.http.wire">
+        <level value="WARN"/>
+    </logger>
+    
+    <logger name="org.opensaml.security.x509.tls.impl.ThreadLocalX509TrustManager">
+        <level value="INFO"/>
+    </logger>
+    
+    <logger name="org.opensaml.security.httpclient.impl.SecurityEnhancedTLSSocketFactory">
+        <level value="INFO"/>
+    </logger>
+    
+    <logger name="org.opensaml.security.httpclient.impl.ThreadLocalServerTLSHandler">
+        <level value="INFO"/>
+    </logger>
+    
     <logger name="org.opensaml.messaging">
         <level value="INFO"/>
     </logger>
diff --git a/opensaml-saml-impl/src/test/resources/org/opensaml/saml/metadata/resolver/impl/repo.truststore.jks b/opensaml-saml-impl/src/test/resources/org/opensaml/saml/metadata/resolver/impl/repo.truststore.jks
new file mode 100644
index 0000000..5f12f9d
Binary files /dev/null and b/opensaml-saml-impl/src/test/resources/org/opensaml/saml/metadata/resolver/impl/repo.truststore.jks differ
diff --git a/opensaml-saml-impl/src/test/resources/org/opensaml/saml/metadata/resolver/impl/repo.truststore.txt b/opensaml-saml-impl/src/test/resources/org/opensaml/saml/metadata/resolver/impl/repo.truststore.txt
new file mode 100644
index 0000000..2d4153e
--- /dev/null
+++ b/opensaml-saml-impl/src/test/resources/org/opensaml/saml/metadata/resolver/impl/repo.truststore.txt
@@ -0,0 +1 @@
+store password is: shibboleth
diff --git a/opensaml-saml-impl/src/test/resources/org/opensaml/saml/metadata/resolver/impl/test.shibboleth.net.crt b/opensaml-saml-impl/src/test/resources/org/opensaml/saml/metadata/resolver/impl/test.shibboleth.net.crt
new file mode 100644
index 0000000..739de83
--- /dev/null
+++ b/opensaml-saml-impl/src/test/resources/org/opensaml/saml/metadata/resolver/impl/test.shibboleth.net.crt
@@ -0,0 +1,37 @@
+-----BEGIN CERTIFICATE-----
+MIIGazCCBFOgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwaTELMAkGA1UEBhMCVVMx
+DTALBgNVBAgMBE9oaW8xHjAcBgNVBAoMFVNoaWJib2xldGggQ29uc29ydGl1bTEr
+MCkGA1UEAwwiU2hpYmJvbGV0aCBQcm9qZWN0IEludGVybWVkaWF0ZSBDQTAeFw0x
+ODEyMTExNTE5NDlaFw0zODEyMDYxNTE5NDlaMFoxCzAJBgNVBAYTAlVTMQ0wCwYD
+VQQIDARPaGlvMR4wHAYDVQQKDBVTaGliYm9sZXRoIENvbnNvcnRpdW0xHDAaBgNV
+BAMME3Rlc3Quc2hpYmJvbGV0aC5uZXQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
+ggIKAoICAQDI39TBFEbOkf0Bb2h/qnG4bbJRF5Ga9OBgSAxPsx2PNgVmf1cvJYlq
+uRCzATu3O4PPclomow5VU7hB+umpk2s03FHejUtO8w85qQx7bkSeyfLpMYloX3SD
+AWDFSpw54Tg2Dfja6jYLnE0aYWUCTXjcFJEIrT0QP1Azdk8b9PeKLqGIagECUMzC
+UjmJxEcyqhbe6mB7tdSsBng+R4DRdVdouN4rtHknNuEyAc5eEppGxY33KXhElYol
+4IK6YxAqXI6iQIDdcVxrH061/iKh7qT1bCAEmzeeoS4st4v4oyIhxUk7yUxYOuFc
+NhYUd5U93Ws4P8xPFzcPhD2TIZDAS/bnm2EJ+JxLG6Gp1jhBN5qTpdTPWiG9+Tmm
+HFuYOrSefgZk0edkJhe0dycfV/FWFX2fNrmkpQhyPQzb3ZyZ5mACn020TXDaF5o3
+fX8ndLl4RghTORJs5n51TuLX85DEeZWkJe5f8Hsip1mpmxlMTl9QMqHrN70n5gBi
+uCYo9g/Jw5xoropCq7Jri9K9FWtbORncUIXutsTVo+fXeHZ6IDRoovR004gHHEJ7
+ks46fZQYNbd8bB/mPlCdYiFyJnfUiOu89O5aLqvhrJNgPUhMt/gmhSV7zw+/3/cd
+o7pwWH2h2ObuS8v8gVUZAE04tefqRJZEB0YsWA3DASDT7nVqndOp2QIDAQABo4IB
+KjCCASYwCQYDVR0TBAIwADARBglghkgBhvhCAQEEBAMCBkAwMwYJYIZIAYb4QgEN
+BCYWJE9wZW5TU0wgR2VuZXJhdGVkIFNlcnZlciBDZXJ0aWZpY2F0ZTAdBgNVHQ4E
+FgQUG3Vg4ubsJVaylBUCPtqVc9mHWfYwgYwGA1UdIwSBhDCBgYAUELzfpij2mkrs
+9B4CEsXYmoN6cKuhZaRjMGExCzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARPaGlvMR4w
+HAYDVQQKDBVTaGliYm9sZXRoIENvbnNvcnRpdW0xIzAhBgNVBAMMGlNoaWJib2xl
+dGggUHJvamVjdCBUZXN0IENBggIQADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAww
+CgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggIBAFZQF7KzKGSPieV2eQWbFt30
+kVZzVe/T5UAvrr8n7mRqMfye4QtHHOVM3MHvZv93AXVUQU4PH9KPGtv7poEtwNWk
+7hNbq74z5x5tSvE0EYYI9UMolDL6il+QR5AgLw7YqgMmPPhtm+crmLg8+uMoQDyU
+uSH8ej4rMrdhL2xKlgvL/rhWycpYu1cFLmeolljOZGqr7ITwWJ06BQsLtt4/cyYj
+WiVldHQRZMGAuHLHlX+ukaEg7Gc/N7o936bS5d7AqXwtmtkXiA8An5q5rhncEK1G
+kwUUQN2y5iyx/nD4B2k1IcgFlu/bM4iXZQtmMUtLptUqRssuxS45ukiweiM9UU8n
+WxyxJMzFTctJJ2f/y4Bg5ggsr4WQU/YhoDaQVYxyjXiBt3oT+7eqsoep9HK9xwbG
+xriLzrlao+K3EcKA3vjKGYh15gpEDUbn0Cr5V74TUSdkHjhG7ocaJ5u9/vCS4+AR
+iU5ge2zN6QcwoZKT8+8XGKeXqVc/3hXeXTn3FyvMitPDZsmg8wUhnb/pq5MyKqUA
+bNse4a7oGAeAUGkLf4Q+eLCLSZmL5udrGXdHIffFYqZVcZS+zVWQ1TRfodTDPkFb
+KTz1mcr4KjLNCtplu4CfFpHwC20uk3hPEslOUd+ugj0+HGNH93L7H0WL1GDQoCPh
+RcdRISVwORcD/dit15zD
+-----END CERTIFICATE-----
diff --git a/opensaml-security-api/src/test/java/org/opensaml/security/trust/MockTrustEngine.java b/opensaml-security-api/src/test/java/org/opensaml/security/trust/MockTrustEngine.java
new file mode 100644
index 0000000..a837443
--- /dev/null
+++ b/opensaml-security-api/src/test/java/org/opensaml/security/trust/MockTrustEngine.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opensaml.security.trust;
+
+import org.opensaml.security.SecurityException;
+
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+
+public class MockTrustEngine<TokenType> implements TrustEngine<TokenType> {
+    
+    private boolean trusted;
+    
+    private Throwable throwable;
+
+    public MockTrustEngine(boolean flag) {
+        trusted = flag;
+    }
+
+    public MockTrustEngine(Throwable t) {
+        throwable = t;
+    }
+
+    /** {@inheritDoc} */
+    public boolean validate(TokenType token, CriteriaSet trustBasisCriteria) throws SecurityException {
+        if (throwable != null) {
+            if (SecurityException.class.isInstance(throwable)) {
+                throw SecurityException.class.cast(throwable);
+            } else if (RuntimeException.class.isInstance(throwable)) {
+                throw RuntimeException.class.cast(throwable);
+            } else if (Error.class.isInstance(throwable)){
+                throw Error.class.cast(throwable);
+            } else {
+                throw new RuntimeException(throwable);
+            }
+        }
+        
+        return trusted;
+    }
+
+}
diff --git a/opensaml-security-impl/pom.xml b/opensaml-security-impl/pom.xml
index 8fd2fea..be2a00a 100644
--- a/opensaml-security-impl/pom.xml
+++ b/opensaml-security-impl/pom.xml
@@ -90,6 +90,14 @@
         </dependency>
         
         <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>opensaml-security-api</artifactId>
+            <version>${project.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
             <groupId>org.ldaptive</groupId>
             <artifactId>ldaptive</artifactId>
             <scope>test</scope>
diff --git a/opensaml-security-impl/src/main/java/org/opensaml/security/httpclient/impl/SecurityEnhancedHttpClientSupport.java b/opensaml-security-impl/src/main/java/org/opensaml/security/httpclient/impl/SecurityEnhancedHttpClientSupport.java
index f18ed5b..98c7878 100644
--- a/opensaml-security-impl/src/main/java/org/opensaml/security/httpclient/impl/SecurityEnhancedHttpClientSupport.java
+++ b/opensaml-security-impl/src/main/java/org/opensaml/security/httpclient/impl/SecurityEnhancedHttpClientSupport.java
@@ -27,13 +27,14 @@ import javax.net.ssl.X509TrustManager;
 
 import org.apache.http.client.HttpClient;
 import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
-import org.apache.http.conn.ssl.DefaultHostnameVerifier;
 import org.apache.http.conn.ssl.NoopHostnameVerifier;
 import org.opensaml.security.trust.TrustEngine;
 import org.opensaml.security.x509.tls.impl.ThreadLocalX509CredentialKeyManager;
+import org.opensaml.security.x509.tls.impl.ThreadLocalX509TrustManager;
 
 import net.shibboleth.utilities.java.support.httpclient.HttpClientSupport;
 import net.shibboleth.utilities.java.support.httpclient.TLSSocketFactoryBuilder;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
 
 /**
  * Support class for working with security-enhanced components related to use of {@link HttpClient}.
@@ -104,12 +105,17 @@ public final class SecurityEnhancedHttpClientSupport {
      * 
      * <p>
      * If <code>supportTrustEngine</code> is true, then the wrapped factory will be configured
-     * with a "no trust" {@link X509TrustManager}, to allow the actual server TLS trust evaluation
-     * to be performed by a mandatory {@link TrustEngine} supplied at runtime,
+     * with a {@link X509TrustManager} that supports per-request specification of a mandatory
+     * server TLS {@TrustEngine} and optional {@link CriteriaSet},
      * as documented in {@link SecurityEnhancedTLSSocketFactory}.
      * </p>
      * 
      * <p>
+     * If <code>supportTrustEngine</code> is false, then the wrapped factory will be configured
+     * for server TLS based on the default JSSE trust mechanism.
+     * </p>
+     * 
+     * <p>
      * If <code>supportClientTLS</code> is true, then the wrapped factory will be configured
      * with a {@link X509KeyManager} that supports per-request specification of a client TLS
      * credential, as documented in {@link SecurityEnhancedTLSSocketFactory}.
@@ -126,11 +132,10 @@ public final class SecurityEnhancedHttpClientSupport {
         final TLSSocketFactoryBuilder wrappedFactoryBuilder = new TLSSocketFactoryBuilder();
         
         if (supportTrustEngine || supportClientTLS) {
-            wrappedFactoryBuilder.setHostnameVerifier(new NoopHostnameVerifier());
             
             if (supportTrustEngine) {
                 wrappedFactoryBuilder.setTrustManagers(
-                        Collections.<TrustManager>singletonList(HttpClientSupport.buildNoTrustX509TrustManager()));
+                        Collections.<TrustManager>singletonList(new ThreadLocalX509TrustManager()));
             }
 
             if (supportClientTLS) {
@@ -138,8 +143,7 @@ public final class SecurityEnhancedHttpClientSupport {
                         Collections.<KeyManager>singletonList(new ThreadLocalX509CredentialKeyManager()));
             }
             
-            return new SecurityEnhancedTLSSocketFactory(wrappedFactoryBuilder.build(), new DefaultHostnameVerifier(), 
-                    supportTrustEngine);
+            return new SecurityEnhancedTLSSocketFactory(wrappedFactoryBuilder.build());
             
         }
         
diff --git a/opensaml-security-impl/src/main/java/org/opensaml/security/httpclient/impl/SecurityEnhancedTLSSocketFactory.java b/opensaml-security-impl/src/main/java/org/opensaml/security/httpclient/impl/SecurityEnhancedTLSSocketFactory.java
index 9bb0aab..34840e3 100644
--- a/opensaml-security-impl/src/main/java/org/opensaml/security/httpclient/impl/SecurityEnhancedTLSSocketFactory.java
+++ b/opensaml-security-impl/src/main/java/org/opensaml/security/httpclient/impl/SecurityEnhancedTLSSocketFactory.java
@@ -20,35 +20,29 @@ package org.opensaml.security.httpclient.impl;
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.net.Socket;
-import java.security.cert.Certificate;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
 import java.util.Collections;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
-import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocket;
+import javax.net.ssl.X509TrustManager;
 
 import org.apache.http.HttpHost;
 import org.apache.http.client.protocol.HttpClientContext;
 import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
 import org.apache.http.protocol.HttpContext;
-import org.opensaml.security.SecurityException;
 import org.opensaml.security.credential.UsageType;
 import org.opensaml.security.criteria.UsageCriterion;
 import org.opensaml.security.httpclient.HttpClientSecurityConstants;
 import org.opensaml.security.trust.TrustEngine;
-import org.opensaml.security.x509.BasicX509Credential;
 import org.opensaml.security.x509.TrustedNamesCriterion;
 import org.opensaml.security.x509.X509Credential;
 import org.opensaml.security.x509.tls.impl.ThreadLocalX509CredentialContext;
+import org.opensaml.security.x509.tls.impl.ThreadLocalX509TrustEngineContext;
+import org.opensaml.security.x509.tls.impl.ThreadLocalX509TrustManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.httpclient.HttpClientSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
@@ -60,9 +54,9 @@ import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
  * This implementation wraps an existing TLS socket factory instance, decorating it with additional support for:
  * </p>
  * <ul>
- *     <li>Verifying the server TLS certificate and chain via a
+ *     <li>Loading and clearing thread-local instances of
  *         {@link TrustEngine}<code><</code>{@link org.opensaml.security.credential.Credential}<code>></code>
- *         and {@link CriteriaSet} supplied by the HttpClient caller via the {@link HttpContext}.</li>
+ *         and {@link CriteriaSet} used for server TLS.</li>
  *         
  *     <li>Loading and clearing a thread-local instance of {@link X509Credential} used for client TLS.</li>
  * </ul>
@@ -86,37 +80,37 @@ import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
  * </ul>
  * 
  * <p>
- * If the trust engine context attribute is not populated by the caller and {@link #isTrustEngineRequired()} 
- * is <code>true</code> (the default), then an {@link SSLPeerUnverifiedException} is thrown.
- * </p>
- * 
- * <p>
- * If the trust engine context attribute is not populated by the caller and {@link #isTrustEngineRequired()}
- * is <code>false</code>, then no trust evaluation is performed. This allows use of this implementation 
- * with use cases where, given a particular HttpClient instance, sometimes TLS trust engine evaluation is to 
- * be performed, and sometimes not. The caller is then responsible for ensuring they supply a trust engine or not,
- * as appropriate.
+ * Support for server TLS via trust engine evaluation requires use of a compatible {@link javax.net.ssl.TrustManager}
+ * implementation configured in the
+ * {@link javax.net.ssl.SSLContext} of the wrapped {@link LayeredConnectionSocketFactory}, such as
+ * {@link org.opensaml.security.x509.tls.impl.ThreadLocalX509TrustManager}.
  * </p>
  * 
  * <p>
- * Since this implementation may typically be used with and wrap a "no trust" SSL socket factory,
- * an optional instance of {@link javax.net.ssl.HostnameVerifier} may also be supplied.
+ * Support for client TLS requires use of a compatible {@link javax.net.ssl.KeyManager}
+ * implementation configured in the
+ * {@link javax.net.ssl.SSLContext} of the wrapped {@link LayeredConnectionSocketFactory}, such as
+ * {@link org.opensaml.security.x509.tls.impl.ThreadLocalX509CredentialKeyManager}.
  * </p>
  * 
  * <p>
- * If the client TLS credential context attribute is not populated by the caller, then client TLS is not attempted.
+ * If the trust engine context attribute is not populated by the caller, then no server TLS thread-local
+ * data is populated.  If the wrapped socket factory's {@link X509TrustManager} implementation requires
+ * this data (for example {@link ThreadLocalX509TrustManager}), then a fatal exception is expected to be thrown.
  * </p>
  * 
  * <p>
- * Client TLS support requires use of a compatible {@link javax.net.ssl.KeyManager} implementation configured in the 
- * {@link javax.net.ssl.SSLContext} of the wrapped {@link LayeredConnectionSocketFactory}, such as
- * {@link org.opensaml.security.x509.tls.impl.ThreadLocalX509CredentialKeyManager}.
+ * If the client TLS credential context attribute is not populated by the caller, then no client TLS thread-local data
+ * is populated, and client TLS will not be attempted.
  * </p>
- * 
  */
 public class SecurityEnhancedTLSSocketFactory implements LayeredConnectionSocketFactory {
     
     /** Instance of {@link ThreadLocalClientTLSCredentialHandler} to use.  */
+    private static final ThreadLocalServerTLSHandler SERVER_TLS_HANDLER =
+            new ThreadLocalServerTLSHandler();
+
+    /** Instance of {@link ThreadLocalClientTLSCredentialHandler} to use.  */
     private static final ThreadLocalClientTLSCredentialHandler CLIENT_TLS_HANDLER =
             new ThreadLocalClientTLSCredentialHandler();
 
@@ -126,74 +120,13 @@ public class SecurityEnhancedTLSSocketFactory implements LayeredConnectionSocket
     /** The HttpClient socket factory instance wrapped by this implementation. */
     @Nonnull private LayeredConnectionSocketFactory wrappedFactory;
     
-    /** The hostname verifier evaluated by this implementation. */
-    @Nullable private HostnameVerifier hostnameVerifier;
-    
-    /** Flag indicating whether a context trust engine attribute is required for TLS server validation. 
-     * Default: true. */
-    private boolean engineRequired = true;
-    
     /**
      * Constructor. 
      * 
-     * <p>No hostname verifier is configured in this implementation. (Does not affect whether hostname 
-     * is or is not evaluated by the wrapped socket factory).</p>
-     * 
-     * @param factory the underlying HttpClient socket factory wrapped by this implementation.
-     * @param trustEngineRequired flag indicating whether a context trust engine attribute is required 
-     *         for TLS server validation.
-     */
-    public SecurityEnhancedTLSSocketFactory(@Nonnull final LayeredConnectionSocketFactory factory,
-            final boolean trustEngineRequired) {
-        this(factory, null, trustEngineRequired);
-    }
-    
-    /**
-     * Constructor. 
-     * 
-     * <p>No hostname verifier is configured in this implementation. (Does not affect whether hostname 
-     * is or is not evaluated by the wrapped socket factory).</p>
-     * 
      * @param factory the underlying HttpClient socket factory wrapped by this implementation.
      */
     public SecurityEnhancedTLSSocketFactory(@Nonnull final LayeredConnectionSocketFactory factory) {
-        this(factory, null, true);
-    }
-
-    /**
-     * Constructor. 
-     * 
-     * @param factory the underlying HttpClient socket factory wrapped by this implementation.
-     * @param verifier the hostname verifier evaluated by this implementation
-     */
-    public SecurityEnhancedTLSSocketFactory(@Nonnull final LayeredConnectionSocketFactory factory, 
-            @Nullable final HostnameVerifier verifier) {
-        this(factory, verifier, true);
-    }
-    /**
-     * Constructor. 
-     * 
-     * @param factory the underlying HttpClient socket factory wrapped by this implementation.
-     * @param verifier the hostname verifier evaluated by this implementation
-     * @param trustEngineRequired flag indicating whether a context trust engine attribute is required 
-     *         for TLS server validation.
-     */
-    public SecurityEnhancedTLSSocketFactory(@Nonnull final LayeredConnectionSocketFactory factory, 
-            @Nullable final HostnameVerifier verifier, final boolean trustEngineRequired) {
         wrappedFactory = Constraint.isNotNull(factory, "Socket factory was null");
-        hostnameVerifier = verifier;
-        engineRequired = trustEngineRequired;
-    }
-
-    /**
-     * Get the flag indicating whether a context trust engine attribute is required for TLS server validation. 
-     * 
-     * <p>Default: true.</p>
-     * 
-     * @return true if trust engine is required, false if not
-     */
-    public boolean isTrustEngineRequired() {
-        return engineRequired;
     }
 
     /** {@inheritDoc} */
@@ -210,12 +143,8 @@ public class SecurityEnhancedTLSSocketFactory implements LayeredConnectionSocket
         
         log.trace("In connectSocket");
         try {
-            setup(context);
-            final Socket socket = 
-                    wrappedFactory.connectSocket(connectTimeout, sock, host, remoteAddress, localAddress, context);
-            performTrustEval(socket, host.getHostName(), context);
-            performHostnameVerification(socket, host.getHostName(), context);
-            return socket;
+            setup(context, host.getHostName());
+            return wrappedFactory.connectSocket(connectTimeout, sock, host, remoteAddress, localAddress, context);
         } finally {
             teardown(context);
         }
@@ -227,163 +156,96 @@ public class SecurityEnhancedTLSSocketFactory implements LayeredConnectionSocket
             final HttpContext context) throws IOException {
         log.trace("In createLayeredSocket");
         try {
-            setup(context);
-            final Socket layeredSocket = wrappedFactory.createLayeredSocket(socket, target, port, context);
-            performTrustEval(layeredSocket, target, context);
-            performHostnameVerification(layeredSocket, target, context);
-            return layeredSocket;
+            setup(context, target);
+            return wrappedFactory.createLayeredSocket(socket, target, port, context);
         } finally {
             teardown(context);
         }
     }
     
     /**
-     * Perform trust evaluation by extracting the server TLS {@link X509Credential} from the 
-     * {@link SSLSession} and evaluating it via a
-     * {@link TrustEngine}<code><</code>{@link org.opensaml.security.credential.Credential}<code>></code>
-     * and {@link CriteriaSet} supplied by the caller via the {@link HttpContext}.
+     * Setup calling execution environment for server TLS and client TLS based on information supplied in the
+     * {@link HttpContext}.
      * 
-     * @param socket the socket instance being processed
-     * @param hostname the hostname being processed
-     * @param context the HttpClient context being processed
-     * 
-     * @throws IOException if the server TLS credential is untrusted, or if there is a fatal error
-     *           attempting trust evaluation.
+     * @param context the HttpContext instance
+     * @param hostname the hostname for the connection
+     *  
+     * @throws SSLPeerUnverifiedException if required data is not available from the context
      */
-    protected void performTrustEval(@Nonnull final Socket socket, @Nonnull @NotEmpty final String hostname, 
-            @Nonnull final HttpContext context) throws IOException {
+    protected void setup(@Nullable final HttpContext context, @Nonnull final String hostname)
+            throws SSLPeerUnverifiedException {
         
-        if (!(socket instanceof SSLSocket)) {
-            log.debug("Socket was not an instance of SSLSocket, skipping trust eval");
-            return;
-        }
-        final SSLSocket sslSocket = (SSLSocket) socket;
-        
-        log.debug("Attempting to evaluate server TLS credential against supplied TrustEngine and CriteriaSet");
-        
-        @SuppressWarnings("unchecked")
-        final TrustEngine<? super X509Credential> trustEngine =
-            (TrustEngine<? super X509Credential>) context.getAttribute(
-                    HttpClientSecurityConstants.CONTEXT_KEY_TRUST_ENGINE);
-        if (trustEngine == null) {
-            if (isTrustEngineRequired()) {
-                log.warn("The required trust engine was not supplied by the caller, failing socket TLS creation");
-                throw new SSLPeerUnverifiedException("The required trust engine was not supplied by the caller");
-            }
-            log.debug("No trust engine supplied by caller, skipping trust eval");
+        log.trace("Attempting to setup thread-local data for TLS evaluation");
+        if (context == null) {
+            log.trace("HttpContext was null, skipping thread-local setup");
             return;
         }
         
-        log.trace("Saw trust engine of type: {}", trustEngine.getClass().getName());
-        
-        CriteriaSet criteriaSet = (CriteriaSet) context.getAttribute(
-                HttpClientSecurityConstants.CONTEXT_KEY_CRITERIA_SET);
-        if (criteriaSet == null) {
-            log.debug("No criteria set supplied by caller, building new criteria set with signing " 
-                    + "and trusted names criteria");
-            criteriaSet = new CriteriaSet(new UsageCriterion(UsageType.SIGNING));
-            criteriaSet.add(new TrustedNamesCriterion(Collections.singleton(hostname)));
-        } else {
-            log.trace("Saw CriteriaSet: {}", criteriaSet);
-        }
+        setupServerTLS(context, hostname);
 
-        final X509Credential credential = extractCredential(sslSocket);
-        
-        try {
-            if (trustEngine.validate(credential, criteriaSet)) {
-                log.debug("Credential evaluated as trusted");
-                context.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_SERVER_TLS_CREDENTIAL_TRUSTED, 
-                        Boolean.TRUE);
-            } else {
-                context.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_SERVER_TLS_CREDENTIAL_TRUSTED, 
-                        Boolean.FALSE);
-                final Boolean fatal = (Boolean)context.getAttribute(
-                        HttpClientSecurityConstants.CONTEXT_KEY_SERVER_TLS_FAILURE_IS_FATAL);
-                if (fatal == null || fatal) {
-                    log.debug("Credential evaluated as untrusted, failure indicated as fatal");
-                    throw new SSLPeerUnverifiedException(
-                            "Trust engine could not establish trust of server TLS credential");
-                }
-                log.debug("Credential evaluated as untrusted, failure indicated as non-fatal");
-            }
-        } catch (final SecurityException e) {
-            log.error("Trust engine error evaluating credential: {}", e.getMessage());
-            throw new IOException("Trust engine error evaluating credential", e);
-        }
-        
+        setupClientTLS(context);
     }
 
     /**
-     * Extract the server TLS {@link X509Credential} from the supplied {@link SSLSocket}.
-     * 
-     * @param sslSocket the SSL socket instance to process
-     * @return an X509Credential representing the server TLS entity certificate as well as the 
-     *          supplied supporting intermediate certificate chain (if any)
-     * @throws IOException if credential data can not be extracted from the socket
-     */
-    @Nonnull protected X509Credential extractCredential(@Nonnull final SSLSocket sslSocket) throws IOException {
-        final SSLSession session = sslSocket.getSession();
-        final Certificate[] peerCertificates = session.getPeerCertificates();
-        if (peerCertificates == null || peerCertificates.length < 1) {
-            throw new SSLPeerUnverifiedException("SSLSession peer certificates array was null or empty");
-        }
-        
-        final ArrayList<X509Certificate> certChain = new ArrayList<>();
-        for (final Certificate cert : peerCertificates) {
-            certChain.add((X509Certificate) cert);
-        }
-        
-        final X509Certificate entityCert = certChain.get(0);
-        
-        final BasicX509Credential credential = new BasicX509Credential(entityCert);
-        credential.setEntityCertificateChain(certChain);
-        
-        return credential;
-    }
-    
-    /**
-     * Perform hostname verification on the connection represented by the supplied socket.
-     * 
-     * @param socket the socket instance being processed
-     * @param hostname the hostname against which to verify
-     * @param context the current HttpClient context instance
-     * @throws IOException if an I/O error occurs or the verification process fails
-     */
-    protected void performHostnameVerification(final Socket socket, final String hostname, final HttpContext context)
-            throws IOException {
-        if (hostnameVerifier != null && socket instanceof SSLSocket) {
-            if (! hostnameVerifier.verify(hostname, ((SSLSocket) socket).getSession())) {
-                throw new SSLPeerUnverifiedException("TLS hostname verification failed for hostname: " + hostname);
-            }
-        }
-    }
-    
-    /**
-     * Load the {@link ThreadLocalX509CredentialContext} with the client TLS credential obtained from 
-     * the {@link HttpContext}.
+     * Setup thread-local data for server TLS and client TLS based on information supplied in the
+     * {@link HttpContext}.
      * 
      * @param context the HttpContext instance
      */
-    protected void setup(@Nullable final HttpContext context) {
-        log.trace("Attempting to setup thread-local client TLS X509Credential");
-        if (context == null) {
-            log.trace("HttpContext was null, skipping thread-local setup");
-            return;
-        }
-
+    protected void setupClientTLS(@Nonnull final HttpContext context) {
         final X509Credential credential =
                 (X509Credential) context.getAttribute(HttpClientSecurityConstants.CONTEXT_KEY_CLIENT_TLS_CREDENTIAL);
+        
         if (credential != null) {
             log.trace("Loading ThreadLocalX509CredentialContext with client TLS credential: {}", credential);
             if (ThreadLocalX509CredentialContext.haveCurrent()) {
                 log.trace("ThreadLocalX509CredentialContext was already loaded with client TLS credential, "
-                        + "will be overwritten with credential from HttpContext");
+                        + "will be overwritten with data from HttpContext");
             }
             ThreadLocalX509CredentialContext.loadCurrent(credential);
         } else {
-            log.trace("HttpContext did not contain a client TLS credential, nothing to do");
+            log.trace("X509Credential not supplied by caller, skipping ThreadLocalX509CredentialContext population");
         }
+    }
 
+    /**
+     * Setup thread-local data for server TLS.
+     * 
+     * @param context the HttpContext instance
+     * @param hostname the hostname for the connection
+     */
+    protected void setupServerTLS(@Nonnull final HttpContext context, @Nonnull final String hostname) {
+        
+        @SuppressWarnings("unchecked")
+        final TrustEngine<? super X509Credential> trustEngine =
+            (TrustEngine<? super X509Credential>) context.getAttribute(
+                    HttpClientSecurityConstants.CONTEXT_KEY_TRUST_ENGINE);
+        
+        if (trustEngine != null) {
+            CriteriaSet criteriaSet = (CriteriaSet) context.getAttribute(
+                    HttpClientSecurityConstants.CONTEXT_KEY_CRITERIA_SET);
+            if (criteriaSet == null) {
+                log.debug("No CriteriaSet supplied by caller, building new instance with signing " 
+                        + "and trusted names criteria");
+                criteriaSet = new CriteriaSet(new UsageCriterion(UsageType.SIGNING));
+                criteriaSet.add(new TrustedNamesCriterion(Collections.singleton(hostname)));
+            } else {
+                log.trace("Saw CriteriaSet: {}", criteriaSet);
+            }
+            
+            final Boolean isFailureFatal = (Boolean) context.getAttribute(
+                    HttpClientSecurityConstants.CONTEXT_KEY_SERVER_TLS_FAILURE_IS_FATAL);
+        
+            if (ThreadLocalX509TrustEngineContext.haveCurrent()) {
+                log.trace("ThreadLocalX509TrustEngineContext was already loaded with trust engine and criteria, "
+                        + "will be overwritten with data from HttpContext");
+            }
+            
+            ThreadLocalX509TrustEngineContext.loadCurrent(trustEngine, criteriaSet, isFailureFatal);
+            
+        } else {
+            log.debug("TrustEngine not supplied by the caller, skipping ThreadLocalX509TrustEngineContext population");
+        }
     }
     
     /**
@@ -393,9 +255,16 @@ public class SecurityEnhancedTLSSocketFactory implements LayeredConnectionSocket
      * @param context the HttpContext instance
      */
     protected void teardown(@Nullable final HttpContext context) {
+        final HttpClientContext clientContext = HttpClientContext.adapt(context);
+        
+        if (ThreadLocalX509TrustEngineContext.haveCurrent()) {
+            log.trace("Scheduling deferred clearing of thread-local server TLS TrustEngine and CriteriaSet");
+            HttpClientSupport.addDynamicContextHandlerLast(clientContext, SERVER_TLS_HANDLER, true);
+        }
+        
         if (ThreadLocalX509CredentialContext.haveCurrent()) {
             log.trace("Scheduling deferred clearing of thread-local client TLS X509Credential");
-            HttpClientSupport.addDynamicContextHandlerLast(HttpClientContext.adapt(context), CLIENT_TLS_HANDLER);
+            HttpClientSupport.addDynamicContextHandlerLast(clientContext, CLIENT_TLS_HANDLER, true);
         }
     }
 
diff --git a/opensaml-security-impl/src/main/java/org/opensaml/security/httpclient/impl/ThreadLocalServerTLSHandler.java b/opensaml-security-impl/src/main/java/org/opensaml/security/httpclient/impl/ThreadLocalServerTLSHandler.java
new file mode 100644
index 0000000..5122f2a
--- /dev/null
+++ b/opensaml-security-impl/src/main/java/org/opensaml/security/httpclient/impl/ThreadLocalServerTLSHandler.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opensaml.security.httpclient.impl;
+
+import java.io.IOException;
+
+import javax.annotation.Nonnull;
+
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.opensaml.security.httpclient.HttpClientSecurityConstants;
+import org.opensaml.security.x509.tls.impl.ThreadLocalX509TrustEngineContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.utilities.java.support.httpclient.HttpClientContextHandler;
+
+/**
+ * An implementation of {@link HttpClientContextHandler} which handles cleanup and transfer of
+ * data used for server TLS held by {@link ThreadLocalX509TrustEngineContext}.
+ */
+public class ThreadLocalServerTLSHandler implements HttpClientContextHandler {
+    
+    /** Logger. */
+    private final Logger log = LoggerFactory.getLogger(ThreadLocalServerTLSHandler.class);
+
+    /** {@inheritDoc} */
+    public void invokeBefore(@Nonnull final HttpClientContext context, @Nonnull final HttpUriRequest request)
+            throws IOException {
+        // Do nothing here
+        
+    }
+
+    /** {@inheritDoc} */
+    public void invokeAfter(@Nonnull final HttpClientContext context, @Nonnull final HttpUriRequest request)
+            throws IOException {
+        
+        log.trace("Saw ThreadLocalX509TrustEngineContext.getTrusted: {}",
+                ThreadLocalX509TrustEngineContext.getTrusted());
+        context.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_SERVER_TLS_CREDENTIAL_TRUSTED,
+                ThreadLocalX509TrustEngineContext.getTrusted());
+        
+        log.trace("Clearing ThreadLocalX509TrustEngineContext");
+        ThreadLocalX509TrustEngineContext.clearCurrent();
+    }
+
+}
diff --git a/opensaml-security-impl/src/main/java/org/opensaml/security/x509/tls/impl/ThreadLocalX509TrustEngineContext.java b/opensaml-security-impl/src/main/java/org/opensaml/security/x509/tls/impl/ThreadLocalX509TrustEngineContext.java
new file mode 100644
index 0000000..6f6801c
--- /dev/null
+++ b/opensaml-security-impl/src/main/java/org/opensaml/security/x509/tls/impl/ThreadLocalX509TrustEngineContext.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opensaml.security.x509.tls.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.trust.TrustEngine;
+import org.opensaml.security.x509.X509Credential;
+
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+
+/**
+ * Class which holds and makes available instances of
+ * {@link TrustEngine}<code><? super </code>{@link org.opensaml.security.x509.X509Credential}<code>></code>
+ * and {@link CriteriaSet} via ThreadLocal storage, typically used for server TLS authentication
+ * via {@link ThreadLocalX509TrustManager}.
+ */
+public final class ThreadLocalX509TrustEngineContext {
+    
+    private static final class Data {
+        
+        /** Trust engine. */
+        private TrustEngine<? super X509Credential> trustEngine;
+        
+        /** Criteria. */
+        private CriteriaSet criteriaSet;
+        
+        /** Whether evaluation target was trusted. */
+        private Boolean trusted;
+        
+        /** Whether trust engine evaluation failure should be treated as fatal. Defaults to true. */
+        private Boolean failureFatal;
+        
+        private Data(@Nonnull final TrustEngine<? super X509Credential> engine, @Nonnull final CriteriaSet criteria,
+                @Nullable final Boolean fatal) {
+            
+            trustEngine = engine;
+            criteriaSet = criteria;
+            failureFatal = fatal != null ? fatal : Boolean.TRUE;
+        }
+        
+    }
+
+    /** ThreadLocal storage for trust engine. */
+    private static ThreadLocal<Data> current = new ThreadLocal<>();
+
+    /** Constructor. */
+    private ThreadLocalX509TrustEngineContext() { }
+
+    /**
+     * Load the thread-local storage with the current credential.
+     * 
+     * @param trustEngine the current trust engine
+     * @param criteria the current criteria
+     * @param fatal whether trust engine evaluation failure should be treated as fatal. Defaults to true. 
+     */
+    public static void loadCurrent(@Nonnull final TrustEngine<? super X509Credential> trustEngine,
+            @Nonnull final CriteriaSet criteria, @Nullable final Boolean fatal) {
+        
+        Constraint.isNotNull(trustEngine, "TrustEngine may not be null");
+        Constraint.isNotNull(criteria, "CriteriaSet may not be null");
+
+        current.set(new Data(trustEngine, criteria, fatal));
+    }
+
+    /**
+     * Clear the current thread-local credential.
+     */
+    public static void clearCurrent() {
+        current.remove();
+    }
+    
+    /**
+     * Get whether the current thread-local is populated with non-null data.
+     * 
+     * @return true if thread-local has non-null data, false otherwise
+     */
+    public static boolean haveCurrent() {
+        return current.get() != null;
+    }
+
+    /**
+     * Return the current thread-local trust engine instance.
+     * 
+     * @return the current trust engine
+     */
+    @Nullable public static TrustEngine<? super X509Credential> getTrustEngine() {
+        return current.get() != null ? current.get().trustEngine : null;
+    }
+    
+    /**
+     * Return the current thread-local criteria instance.
+     * 
+     * @return the current criteria
+     */
+    @Nullable public static CriteriaSet getCriteria() {
+        return current.get() != null ? current.get().criteriaSet : null;
+    }
+    
+    /**
+     * Return whether failure of server TLS is to be treated as fatal.
+     * 
+     * @return true if fatal, false if not
+     */
+    @Nonnull public static Boolean isFailureFatal() {
+        return current.get() != null ? current.get().failureFatal : Boolean.TRUE;
+    }
+    
+    /**
+     * Return the current thread-local trust evaluation data.
+     * 
+     * @return the current trust evaluation data
+     */
+    @Nullable public static Boolean getTrusted() {
+        return current.get() != null ? current.get().trusted : null;
+    }
+
+    /**
+     * Set the current thread-local trust evaluation data.
+     * 
+     * @param trusted the current trust evaluation data
+     */
+    public static void setTrusted(@Nullable final Boolean trusted) {
+        if (current.get() != null) {
+            current.get().trusted = trusted;
+        }
+    }
+
+}
diff --git a/opensaml-security-impl/src/main/java/org/opensaml/security/x509/tls/impl/ThreadLocalX509TrustManager.java b/opensaml-security-impl/src/main/java/org/opensaml/security/x509/tls/impl/ThreadLocalX509TrustManager.java
new file mode 100644
index 0000000..9e05903
--- /dev/null
+++ b/opensaml-security-impl/src/main/java/org/opensaml/security/x509/tls/impl/ThreadLocalX509TrustManager.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opensaml.security.x509.tls.impl;
+
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.X509TrustManager;
+
+import org.opensaml.security.trust.TrustEngine;
+import org.opensaml.security.x509.BasicX509Credential;
+import org.opensaml.security.x509.X509Credential;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+
+/**
+ * An implementation of {@link X509TrustManager} which performs its evaluation using trust engine and criteria
+ * instances available from {@link ThreadLocalX509TrustEngineContext}.
+ */
+public class ThreadLocalX509TrustManager implements X509TrustManager {
+    
+    /** Logger. */
+    private Logger log = LoggerFactory.getLogger(ThreadLocalX509TrustManager.class);
+
+    /** {@inheritDoc} */
+    public X509Certificate[] getAcceptedIssuers() {
+        return new X509Certificate[]{};
+    }
+    
+    /** {@inheritDoc} */
+    public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {
+        performTrustEval(chain, authType);
+    }
+    
+    /** {@inheritDoc} */
+    public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {
+        performTrustEval(chain, authType);
+    }
+
+    /**
+     * Perform trust evaluation on the specified certificate chain using the trust engine and criteria
+     * available from {@link ThreadLocalX509TrustEngineContext}.
+     * 
+     * @param chain the peer certificate chain
+     * @param authType the authentication type based on the client certificate
+     * 
+     * @throws CertificateException if the certificate chain is not trusted by this TrustManager.
+     */
+    protected void performTrustEval(final X509Certificate[] chain, final String authType) throws CertificateException {
+        // These checks are per the documentation for this interface
+        if (chain == null || chain.length == 0) {
+            throw new IllegalArgumentException("Certificate chain was null or empty");
+        }
+        if (authType == null || authType.isEmpty()) {
+            throw new IllegalArgumentException("AuthType was null or empty");
+        }
+        
+        if (!ThreadLocalX509TrustEngineContext.haveCurrent()) {
+            throw new CertificateException("Trust of X509Certificate could not be established, "
+                    + "ThreadLocalX509TrustEngineContext is not populated");
+        }
+        
+        if (performTrustEval(chain,
+                ThreadLocalX509TrustEngineContext.getTrustEngine(),
+                ThreadLocalX509TrustEngineContext.getCriteria())) {
+            ThreadLocalX509TrustEngineContext.setTrusted(true);
+        } else {
+            ThreadLocalX509TrustEngineContext.setTrusted(false);
+            if (ThreadLocalX509TrustEngineContext.isFailureFatal()) {
+                log.debug("Credential evaluated as untrusted, failure indicated as fatal");
+                throw new CertificateException("Trust engine could not establish trust of presented TLS credential");
+            }
+            log.debug("Credential evaluated as untrusted, failure indicated as non-fatal");
+        }
+    }
+    
+    /**
+     * Perform trust evaluation on the specified certificate chain using the supplied trust engine and criteria.
+     * 
+     * @param chain the certificate chain to be evaluated
+     * @param trustEngine the trust engine
+     * @param criteriaSet the criteria set
+     * 
+     * @return true if certificate was established as trusted, false if not
+     * 
+     * @throws CertificateException if the trust of the certificate
+     */
+    protected boolean performTrustEval(@Nonnull final X509Certificate[] chain,
+            @Nonnull final TrustEngine<? super X509Credential> trustEngine,
+            @Nonnull final CriteriaSet criteriaSet) throws CertificateException {
+        
+        log.debug("Attempting to evaluate server TLS credential against supplied TrustEngine and CriteriaSet");
+        
+        final X509Credential credential = extractCredential(chain);
+        
+        log.trace("Saw trust engine of type: {}", trustEngine.getClass().getName());
+
+        try {
+            if (trustEngine.validate(credential, criteriaSet)) {
+                log.debug("Credential evaluated as trusted");
+                return true;
+            }
+            log.debug("Credential evaluated as untrusted");
+            return false;
+        } catch (final Throwable t) {
+            log.error("Fatal trust engine error evaluating credential", t);
+            return false;
+        }
+        
+    }
+
+    /**
+     * Extract the server TLS {@link X509Credential} from the supplied {@link SSLSocket}.
+     * 
+     * @param chain the chain of X509 certificates
+     * @return an X509Credential representing the entity certificate as well as the 
+     *          supplied supporting intermediate certificate chain (if any)
+     * @throws CertificateException if credential data can not be extracted from the socket
+     */
+    @Nonnull protected X509Credential extractCredential(@Nonnull @NotEmpty final X509Certificate[] chain)
+            throws CertificateException {
+        
+        final List<X509Certificate> certChain = Arrays.asList(chain);
+        
+        final X509Certificate entityCert = certChain.get(0);
+        
+        final BasicX509Credential credential = new BasicX509Credential(entityCert);
+        credential.setEntityCertificateChain(certChain);
+        
+        return credential;
+    }
+
+}
diff --git a/opensaml-security-impl/src/test/java/org/opensaml/security/httpclient/impl/SecurityEnhancedTLSSocketFactoryTest.java b/opensaml-security-impl/src/test/java/org/opensaml/security/httpclient/impl/SecurityEnhancedTLSSocketFactoryTest.java
index 206a2cd..f1e1f95 100644
--- a/opensaml-security-impl/src/test/java/org/opensaml/security/httpclient/impl/SecurityEnhancedTLSSocketFactoryTest.java
+++ b/opensaml-security-impl/src/test/java/org/opensaml/security/httpclient/impl/SecurityEnhancedTLSSocketFactoryTest.java
@@ -22,30 +22,30 @@ import java.io.InputStream;
 import java.net.Socket;
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLPeerUnverifiedException;
-
 import org.apache.http.HttpHost;
 import org.apache.http.client.protocol.HttpClientContext;
 import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
-import org.apache.http.conn.ssl.DefaultHostnameVerifier;
 import org.apache.http.protocol.HttpContext;
-import org.opensaml.security.credential.Credential;
 import org.opensaml.security.credential.impl.StaticCredentialResolver;
 import org.opensaml.security.httpclient.HttpClientSecurityConstants;
 import org.opensaml.security.trust.impl.ExplicitKeyTrustEngine;
 import org.opensaml.security.x509.BasicX509Credential;
 import org.opensaml.security.x509.X509Credential;
 import org.opensaml.security.x509.X509Support;
+import org.opensaml.security.x509.tls.impl.ThreadLocalX509CredentialContext;
+import org.opensaml.security.x509.tls.impl.ThreadLocalX509TrustEngineContext;
 import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import net.shibboleth.utilities.java.support.httpclient.HttpClientSupport;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+
 public class SecurityEnhancedTLSSocketFactoryTest {
     
     private static final String DATA_PATH = "/org/opensaml/security/x509/impl/";
@@ -61,156 +61,144 @@ public class SecurityEnhancedTLSSocketFactoryTest {
         httpContext = new HttpClientContext();
     }
     
+    @AfterMethod
+    public void clearThreadLocals() {
+        ThreadLocalX509TrustEngineContext.clearCurrent();
+        ThreadLocalX509CredentialContext.clearCurrent();
+    }
+    
     @Test
-    public void testNonSSL() throws IOException {
-        securityEnhancedSocketFactory = new SecurityEnhancedTLSSocketFactory(buildInnerSSLFactory(null, hostname), null);
+    public void testNoContextParametersHTTP() throws IOException {
+        securityEnhancedSocketFactory = new SecurityEnhancedTLSSocketFactory(buildInnerSSLFactory(null, hostname));
         Socket socket = securityEnhancedSocketFactory.createSocket(httpContext);
-        
+
         securityEnhancedSocketFactory.connectSocket(0, socket, new HttpHost(hostname, 80, "http"), null, null, httpContext);
+
+        Assert.assertFalse(ThreadLocalX509TrustEngineContext.haveCurrent());
+        Assert.assertFalse(HttpClientSupport.getDynamicContextHandlerList(
+                HttpClientContext.adapt(httpContext)).stream().anyMatch(h -> ThreadLocalServerTLSHandler.class.isInstance(h)));
         
-        Assert.assertNull(httpContext.getAttribute(HttpClientSecurityConstants.CONTEXT_KEY_SERVER_TLS_CREDENTIAL_TRUSTED));
-    }
-    
-    @Test(expectedExceptions=SSLPeerUnverifiedException.class)
-    public void testDefaultFailNoTrustEngine() throws IOException {
-       X509Credential cred = getCredential("foo-1A1-good.crt");
-       
-       securityEnhancedSocketFactory = new SecurityEnhancedTLSSocketFactory(buildInnerSSLFactory(
-               Collections.singletonList((Certificate)cred.getEntityCertificate()), hostname), null);
-       Socket socket = securityEnhancedSocketFactory.createSocket(httpContext);
-       
-       securityEnhancedSocketFactory.connectSocket(0, socket, new HttpHost(hostname, 443, "https"), null, null, httpContext);
+        Assert.assertFalse(ThreadLocalX509CredentialContext.haveCurrent());
+        Assert.assertFalse(HttpClientSupport.getDynamicContextHandlerList(
+                HttpClientContext.adapt(httpContext)).stream().anyMatch(h -> ThreadLocalClientTLSCredentialHandler.class.isInstance(h)));
     }
     
     @Test
-    public void testSuccessNoTrustEngine() throws IOException {
-       X509Credential cred = getCredential("foo-1A1-good.crt");
-       
-       securityEnhancedSocketFactory = new SecurityEnhancedTLSSocketFactory(buildInnerSSLFactory(
-               Collections.singletonList((Certificate)cred.getEntityCertificate()), hostname), null, false);
-       Socket socket = securityEnhancedSocketFactory.createSocket(httpContext);
-       
-       securityEnhancedSocketFactory.connectSocket(0, socket, new HttpHost(hostname, 443, "https"), null, null, httpContext);
-       
-       Assert.assertNull(httpContext.getAttribute(HttpClientSecurityConstants.CONTEXT_KEY_SERVER_TLS_CREDENTIAL_TRUSTED));
+    public void testNoContextParametersHTTPS() throws IOException {
+        X509Credential cred = getCredential("foo-1A1-good.crt");
+
+        securityEnhancedSocketFactory = new SecurityEnhancedTLSSocketFactory(buildInnerSSLFactory(
+                Collections.singletonList((Certificate)cred.getEntityCertificate()), hostname));
+        Socket socket = securityEnhancedSocketFactory.createSocket(httpContext);
+
+        securityEnhancedSocketFactory.connectSocket(0, socket, new HttpHost(hostname, 443, "https"), null, null, httpContext);
+
+        Assert.assertFalse(ThreadLocalX509TrustEngineContext.haveCurrent());
+        Assert.assertFalse(HttpClientSupport.getDynamicContextHandlerList(
+                HttpClientContext.adapt(httpContext)).stream().anyMatch(h -> ThreadLocalServerTLSHandler.class.isInstance(h)));
+        
+        Assert.assertFalse(ThreadLocalX509CredentialContext.haveCurrent());
+        Assert.assertFalse(HttpClientSupport.getDynamicContextHandlerList(
+                HttpClientContext.adapt(httpContext)).stream().anyMatch(h -> ThreadLocalClientTLSCredentialHandler.class.isInstance(h)));
     }
     
     @Test
-    public void testSuccessWithEngine() throws IOException {
-       X509Credential cred = getCredential("foo-1A1-good.crt");
-       ExplicitKeyTrustEngine trustEngine = new ExplicitKeyTrustEngine(new StaticCredentialResolver(cred));
-       httpContext.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_TRUST_ENGINE, trustEngine);
-       
-       securityEnhancedSocketFactory = new SecurityEnhancedTLSSocketFactory(buildInnerSSLFactory(
-               Collections.singletonList((Certificate)cred.getEntityCertificate()), hostname), null);
-       Socket socket = securityEnhancedSocketFactory.createSocket(httpContext);
-       
-       securityEnhancedSocketFactory.connectSocket(0, socket, new HttpHost(hostname, 443, "https"), null, null, httpContext);
-       
-       Assert.assertEquals(httpContext.getAttribute(HttpClientSecurityConstants.CONTEXT_KEY_SERVER_TLS_CREDENTIAL_TRUSTED), Boolean.TRUE);
+    public void testEngineParamWithDefaultCriteria() throws IOException {
+        X509Credential cred = getCredential("foo-1A1-good.crt");
+        ExplicitKeyTrustEngine trustEngine = new ExplicitKeyTrustEngine(new StaticCredentialResolver(cred));
+        httpContext.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_TRUST_ENGINE, trustEngine);
+
+        securityEnhancedSocketFactory = new SecurityEnhancedTLSSocketFactory(buildInnerSSLFactory(
+                Collections.singletonList((Certificate)cred.getEntityCertificate()), hostname));
+        Socket socket = securityEnhancedSocketFactory.createSocket(httpContext);
+
+        securityEnhancedSocketFactory.connectSocket(0, socket, new HttpHost(hostname, 443, "https"), null, null, httpContext);
+       
+        Assert.assertTrue(ThreadLocalX509TrustEngineContext.haveCurrent());
+        Assert.assertSame(ThreadLocalX509TrustEngineContext.getTrustEngine(), trustEngine);
+        Assert.assertNotNull(ThreadLocalX509TrustEngineContext.getCriteria());
+        Assert.assertTrue(ThreadLocalX509TrustEngineContext.isFailureFatal());
+        Assert.assertTrue(HttpClientSupport.getDynamicContextHandlerList(
+                HttpClientContext.adapt(httpContext)).stream().anyMatch(h -> ThreadLocalServerTLSHandler.class.isInstance(h)));
+        
+        Assert.assertFalse(ThreadLocalX509CredentialContext.haveCurrent());
+        Assert.assertFalse(HttpClientSupport.getDynamicContextHandlerList(
+                HttpClientContext.adapt(httpContext)).stream().anyMatch(h -> ThreadLocalClientTLSCredentialHandler.class.isInstance(h)));
     }
     
     @Test
-    public void testSuccessWithEngineAndVerifier() throws IOException {
-       X509Credential cred = getCredential("foo-1A1-good.crt");
-       ExplicitKeyTrustEngine trustEngine = new ExplicitKeyTrustEngine(new StaticCredentialResolver(cred));
-       httpContext.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_TRUST_ENGINE, trustEngine);
-       
-       securityEnhancedSocketFactory = new SecurityEnhancedTLSSocketFactory(buildInnerSSLFactory(
-               Collections.singletonList((Certificate)cred.getEntityCertificate()), hostname), new DefaultHostnameVerifier());
-       Socket socket = securityEnhancedSocketFactory.createSocket(httpContext);
-       
-       securityEnhancedSocketFactory.connectSocket(0, socket, new HttpHost(hostname, 443, "https"), null, null, httpContext);
-       
-       Assert.assertEquals(httpContext.getAttribute(HttpClientSecurityConstants.CONTEXT_KEY_SERVER_TLS_CREDENTIAL_TRUSTED), Boolean.TRUE);
-    }
-    
-    @Test(expectedExceptions=SSLPeerUnverifiedException.class)
-    public void testFailUntrustedCert() throws IOException {
-       X509Credential cred = getCredential("foo-1A1-good.crt");
-       List<Credential> emptyCreds = new ArrayList<>();
-       ExplicitKeyTrustEngine trustEngine = new ExplicitKeyTrustEngine(new StaticCredentialResolver(emptyCreds));
-       httpContext.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_TRUST_ENGINE, trustEngine);
-       
-       securityEnhancedSocketFactory = new SecurityEnhancedTLSSocketFactory(buildInnerSSLFactory(
-               Collections.singletonList((Certificate)cred.getEntityCertificate()), hostname), new DefaultHostnameVerifier());
-       Socket socket = securityEnhancedSocketFactory.createSocket(httpContext);
-       
-       try {
-           securityEnhancedSocketFactory.connectSocket(0, socket, new HttpHost(hostname, 443, "https"), null, null, httpContext);
-       } catch (Exception e) {
-           Assert.assertEquals(httpContext.getAttribute(HttpClientSecurityConstants.CONTEXT_KEY_SERVER_TLS_CREDENTIAL_TRUSTED), Boolean.FALSE);
-           throw e;
-       }
-    }
-    
-    @Test(expectedExceptions=SSLPeerUnverifiedException.class)
-    public void testFailUntrustedCertExplicitFatal() throws IOException {
-       X509Credential cred = getCredential("foo-1A1-good.crt");
-       List<Credential> emptyCreds = new ArrayList<>();
-       ExplicitKeyTrustEngine trustEngine = new ExplicitKeyTrustEngine(new StaticCredentialResolver(emptyCreds));
-       httpContext.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_TRUST_ENGINE, trustEngine);
-       httpContext.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_SERVER_TLS_FAILURE_IS_FATAL, Boolean.TRUE);
-       
-       securityEnhancedSocketFactory = new SecurityEnhancedTLSSocketFactory(buildInnerSSLFactory(
-               Collections.singletonList((Certificate)cred.getEntityCertificate()), hostname), new DefaultHostnameVerifier());
-       Socket socket = securityEnhancedSocketFactory.createSocket(httpContext);
-       
-       try {
-           securityEnhancedSocketFactory.connectSocket(0, socket, new HttpHost(hostname, 443, "https"), null, null, httpContext);
-       } catch (Exception e) {
-           Assert.assertEquals(httpContext.getAttribute(HttpClientSecurityConstants.CONTEXT_KEY_SERVER_TLS_CREDENTIAL_TRUSTED), Boolean.FALSE);
-           throw e;
-       }
+    public void testEngineParamWithExplicitCriteria() throws IOException {
+        X509Credential cred = getCredential("foo-1A1-good.crt");
+        ExplicitKeyTrustEngine trustEngine = new ExplicitKeyTrustEngine(new StaticCredentialResolver(cred));
+        httpContext.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_TRUST_ENGINE, trustEngine);
+
+        CriteriaSet criteria = new CriteriaSet();
+        httpContext.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_CRITERIA_SET, criteria);
+
+        securityEnhancedSocketFactory = new SecurityEnhancedTLSSocketFactory(buildInnerSSLFactory(
+                Collections.singletonList((Certificate)cred.getEntityCertificate()), hostname));
+        Socket socket = securityEnhancedSocketFactory.createSocket(httpContext);
+
+        securityEnhancedSocketFactory.connectSocket(0, socket, new HttpHost(hostname, 443, "https"), null, null, httpContext);
+       
+        Assert.assertTrue(ThreadLocalX509TrustEngineContext.haveCurrent());
+        Assert.assertSame(ThreadLocalX509TrustEngineContext.getTrustEngine(), trustEngine);
+        Assert.assertSame(ThreadLocalX509TrustEngineContext.getCriteria(), criteria);
+        Assert.assertTrue(ThreadLocalX509TrustEngineContext.isFailureFatal());
+        Assert.assertTrue(HttpClientSupport.getDynamicContextHandlerList(
+                HttpClientContext.adapt(httpContext)).stream().anyMatch(h -> ThreadLocalServerTLSHandler.class.isInstance(h)));
+        
+        Assert.assertFalse(ThreadLocalX509CredentialContext.haveCurrent());
+        Assert.assertFalse(HttpClientSupport.getDynamicContextHandlerList(
+                HttpClientContext.adapt(httpContext)).stream().anyMatch(h -> ThreadLocalClientTLSCredentialHandler.class.isInstance(h)));
     }
     
     @Test
-    public void testFailUntrustedCertNonFatal() throws IOException {
-       X509Credential cred = getCredential("foo-1A1-good.crt");
-       List<Credential> emptyCreds = new ArrayList<>();
-       ExplicitKeyTrustEngine trustEngine = new ExplicitKeyTrustEngine(new StaticCredentialResolver(emptyCreds));
-       httpContext.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_TRUST_ENGINE, trustEngine);
-       httpContext.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_SERVER_TLS_FAILURE_IS_FATAL, Boolean.FALSE);
-       
-       securityEnhancedSocketFactory = new SecurityEnhancedTLSSocketFactory(buildInnerSSLFactory(
-               Collections.singletonList((Certificate)cred.getEntityCertificate()), hostname), new DefaultHostnameVerifier());
-       Socket socket = securityEnhancedSocketFactory.createSocket(httpContext);
-       
-       securityEnhancedSocketFactory.connectSocket(0, socket, new HttpHost(hostname, 443, "https"), null, null, httpContext);
-       Assert.assertEquals(httpContext.getAttribute(HttpClientSecurityConstants.CONTEXT_KEY_SERVER_TLS_CREDENTIAL_TRUSTED), Boolean.FALSE);
+    public void testEngineParamWithFailureNonFatal() throws IOException {
+        X509Credential cred = getCredential("foo-1A1-good.crt");
+        ExplicitKeyTrustEngine trustEngine = new ExplicitKeyTrustEngine(new StaticCredentialResolver(cred));
+        httpContext.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_TRUST_ENGINE, trustEngine);
+        httpContext.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_SERVER_TLS_FAILURE_IS_FATAL, Boolean.FALSE);
+
+        CriteriaSet criteria = new CriteriaSet();
+        httpContext.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_CRITERIA_SET, criteria);
+
+        securityEnhancedSocketFactory = new SecurityEnhancedTLSSocketFactory(buildInnerSSLFactory(
+                Collections.singletonList((Certificate)cred.getEntityCertificate()), hostname));
+        Socket socket = securityEnhancedSocketFactory.createSocket(httpContext);
+
+        securityEnhancedSocketFactory.connectSocket(0, socket, new HttpHost(hostname, 443, "https"), null, null, httpContext);
+       
+        Assert.assertTrue(ThreadLocalX509TrustEngineContext.haveCurrent());
+        Assert.assertSame(ThreadLocalX509TrustEngineContext.getTrustEngine(), trustEngine);
+        Assert.assertNotNull(ThreadLocalX509TrustEngineContext.getCriteria());
+        Assert.assertFalse(ThreadLocalX509TrustEngineContext.isFailureFatal());
+        Assert.assertTrue(HttpClientSupport.getDynamicContextHandlerList(
+                HttpClientContext.adapt(httpContext)).stream().anyMatch(h -> ThreadLocalServerTLSHandler.class.isInstance(h)));
+        
+        Assert.assertFalse(ThreadLocalX509CredentialContext.haveCurrent());
+        Assert.assertFalse(HttpClientSupport.getDynamicContextHandlerList(
+                HttpClientContext.adapt(httpContext)).stream().anyMatch(h -> ThreadLocalClientTLSCredentialHandler.class.isInstance(h)));
     }
     
-    @Test(expectedExceptions=SSLException.class)
-    public void testFailBadHostname() throws IOException {
-       X509Credential cred = getCredential("foo-1A1-good.crt");
-       ExplicitKeyTrustEngine trustEngine = new ExplicitKeyTrustEngine(new StaticCredentialResolver(cred));
-       httpContext.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_TRUST_ENGINE, trustEngine);
-       
-       securityEnhancedSocketFactory = new SecurityEnhancedTLSSocketFactory(buildInnerSSLFactory(
-               Collections.singletonList((Certificate)cred.getEntityCertificate()), "bogus.example.com"), new DefaultHostnameVerifier());
-       Socket socket = securityEnhancedSocketFactory.createSocket(httpContext);
-       
-       
-       try {
-           securityEnhancedSocketFactory.connectSocket(0, socket, new HttpHost("bogus.example.com", 443, "https"), null, null, httpContext);
-       } catch (Exception e) {
-           Assert.assertEquals(httpContext.getAttribute(HttpClientSecurityConstants.CONTEXT_KEY_SERVER_TLS_CREDENTIAL_TRUSTED), Boolean.TRUE);
-           throw e;
-       }
-    }
+    @Test
+    public void testClientTLSParam() throws IOException {
+        X509Credential cred = getCredential("foo-1A1-good.crt");
+        httpContext.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_CLIENT_TLS_CREDENTIAL, cred);
+        
+        securityEnhancedSocketFactory = new SecurityEnhancedTLSSocketFactory(buildInnerSSLFactory(null, hostname));
+        Socket socket = securityEnhancedSocketFactory.createSocket(httpContext);
 
-    @Test(expectedExceptions=SSLPeerUnverifiedException.class)
-    public void testFailNoCertsInSession() throws IOException {
-       X509Credential cred = getCredential("foo-1A1-good.crt");
-       ExplicitKeyTrustEngine trustEngine = new ExplicitKeyTrustEngine(new StaticCredentialResolver(cred));
-       httpContext.setAttribute(HttpClientSecurityConstants.CONTEXT_KEY_TRUST_ENGINE, trustEngine);
-       
-       // Pass an empty cert list, to simulate unlikely condition of SSLSession not having any peerCertificates
-       securityEnhancedSocketFactory = new SecurityEnhancedTLSSocketFactory(buildInnerSSLFactory(
-               new ArrayList<Certificate>(), hostname), new DefaultHostnameVerifier());
-       Socket socket = securityEnhancedSocketFactory.createSocket(httpContext);
-       
-       securityEnhancedSocketFactory.connectSocket(0, socket, new HttpHost(hostname, 443, "https"), null, null, httpContext);
+        securityEnhancedSocketFactory.connectSocket(0, socket, new HttpHost(hostname, 443, "https"), null, null, httpContext);
+        
+        Assert.assertFalse(ThreadLocalX509TrustEngineContext.haveCurrent());
+        Assert.assertFalse(HttpClientSupport.getDynamicContextHandlerList(
+                HttpClientContext.adapt(httpContext)).stream().anyMatch(h -> ThreadLocalServerTLSHandler.class.isInstance(h)));
+        
+        Assert.assertTrue(ThreadLocalX509CredentialContext.haveCurrent());
+        Assert.assertSame(ThreadLocalX509CredentialContext.getCredential(), cred);
+        Assert.assertTrue(HttpClientSupport.getDynamicContextHandlerList(
+                HttpClientContext.adapt(httpContext)).stream().anyMatch(h -> ThreadLocalClientTLSCredentialHandler.class.isInstance(h)));
     }
     
     // Helper methods
diff --git a/opensaml-security-impl/src/test/java/org/opensaml/security/x509/tls/impl/ThreadLocalX509TrustManagerTest.java b/opensaml-security-impl/src/test/java/org/opensaml/security/x509/tls/impl/ThreadLocalX509TrustManagerTest.java
new file mode 100644
index 0000000..b4cdecc
--- /dev/null
+++ b/opensaml-security-impl/src/test/java/org/opensaml/security/x509/tls/impl/ThreadLocalX509TrustManagerTest.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opensaml.security.x509.tls.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+
+import org.opensaml.security.SecurityException;
+import org.opensaml.security.trust.MockTrustEngine;
+import org.opensaml.security.x509.X509Support;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+
+public class ThreadLocalX509TrustManagerTest {
+    
+    private ThreadLocalX509TrustManager trustManager = new ThreadLocalX509TrustManager();
+    
+    private X509Certificate[] chain;
+    
+    private CriteriaSet criteria;
+    
+    @BeforeClass
+    public void beforeClass() throws CertificateException, IOException {
+        try (final InputStream is = this.getClass().getResourceAsStream("/data/certificate.pem")) {
+            final Collection<X509Certificate> certs = X509Support.decodeCertificates(is);
+            chain = certs.stream().toArray(X509Certificate[]::new);
+        }
+    }
+    
+    @BeforeMethod
+    public void beforeMethod() {
+       ThreadLocalX509TrustEngineContext.clearCurrent(); 
+       criteria = new CriteriaSet();
+    }
+     
+    @AfterMethod
+    public void afterMethod() {
+       ThreadLocalX509TrustEngineContext.clearCurrent(); 
+    }
+    
+    @Test
+    public void trusted() throws CertificateException {
+        ThreadLocalX509TrustEngineContext.loadCurrent(new MockTrustEngine<>(true), criteria, true);
+        
+        trustManager.checkServerTrusted(chain, "RSA");
+        
+        Assert.assertTrue(ThreadLocalX509TrustEngineContext.getTrusted());
+    }
+
+    @Test
+    public void notTrusted() throws CertificateException {
+        ThreadLocalX509TrustEngineContext.loadCurrent(new MockTrustEngine<>(false), criteria, true);
+        
+        try {
+            trustManager.checkServerTrusted(chain, "RSA");
+            Assert.fail("Trust manager should have thrown");
+        } catch (CertificateException e) {
+            Assert.assertFalse(ThreadLocalX509TrustEngineContext.getTrusted());
+        }
+    }
+
+    @Test
+    public void notTrustedNotFatal() throws CertificateException {
+        ThreadLocalX509TrustEngineContext.loadCurrent(new MockTrustEngine<>(false), criteria, false);
+        
+        trustManager.checkServerTrusted(chain, "RSA");
+        Assert.assertFalse(ThreadLocalX509TrustEngineContext.getTrusted());
+    }
+
+    @Test
+    public void trustEngineThrowsSecurityException() throws CertificateException {
+        ThreadLocalX509TrustEngineContext.loadCurrent(new MockTrustEngine<>(new SecurityException()), criteria, true);
+        
+        try {
+            trustManager.checkServerTrusted(chain, "RSA");
+            Assert.fail("Trust manager should have thrown");
+        } catch (CertificateException e) {
+            Assert.assertFalse(ThreadLocalX509TrustEngineContext.getTrusted());
+        }
+    }
+
+    @Test
+    public void trustEngineThrowsSecurityExceptionNotFatal() throws CertificateException {
+        ThreadLocalX509TrustEngineContext.loadCurrent(new MockTrustEngine<>(new SecurityException()), criteria, false);
+        
+        trustManager.checkServerTrusted(chain, "RSA");
+        Assert.assertFalse(ThreadLocalX509TrustEngineContext.getTrusted());
+    }
+
+    @Test
+    public void trustEngineThrowsRuntimeException() throws CertificateException {
+        ThreadLocalX509TrustEngineContext.loadCurrent(new MockTrustEngine<>(new RuntimeException()), criteria, true);
+        
+        try {
+            trustManager.checkServerTrusted(chain, "RSA");
+            Assert.fail("Trust manager should have thrown");
+        } catch (CertificateException e) {
+            Assert.assertFalse(ThreadLocalX509TrustEngineContext.getTrusted());
+        }
+        
+    }
+
+    @Test
+    public void trustEngineThrowsRuntimeExceptionNotFatal() throws CertificateException {
+        ThreadLocalX509TrustEngineContext.loadCurrent(new MockTrustEngine<>(new RuntimeException()), criteria, false);
+        
+        trustManager.checkServerTrusted(chain, "RSA");
+        Assert.assertFalse(ThreadLocalX509TrustEngineContext.getTrusted());
+        
+    }
+
+    @Test
+    public void threadLocalNotLoaded() throws CertificateException {
+        
+        try {
+            trustManager.checkServerTrusted(chain, "RSA");
+            Assert.fail("Trust manager should have thrown");
+        } catch (CertificateException e) {
+            Assert.assertNull(ThreadLocalX509TrustEngineContext.getTrusted());
+        }
+    }
+    
+    @Test(expectedExceptions=IllegalArgumentException.class) 
+    public void nullChain() throws CertificateException {
+        ThreadLocalX509TrustEngineContext.loadCurrent(new MockTrustEngine<>(true), criteria, true);
+        trustManager.checkServerTrusted(null, "RSA");
+    }
+
+    @Test(expectedExceptions=IllegalArgumentException.class) 
+    public void emptyChain() throws CertificateException {
+        ThreadLocalX509TrustEngineContext.loadCurrent(new MockTrustEngine<>(true), criteria, true);
+        trustManager.checkServerTrusted(new X509Certificate[] {}, "RSA");
+    }
+
+    @Test(expectedExceptions=IllegalArgumentException.class) 
+    public void nullAuthType() throws CertificateException {
+        ThreadLocalX509TrustEngineContext.loadCurrent(new MockTrustEngine<>(true), criteria, true);
+        trustManager.checkServerTrusted(chain, null);
+    }
+
+    @Test(expectedExceptions=IllegalArgumentException.class) 
+    public void emptyAuthType() throws CertificateException {
+        ThreadLocalX509TrustEngineContext.loadCurrent(new MockTrustEngine<>(true), criteria, true);
+        trustManager.checkServerTrusted(chain, "");
+    }
+
+}

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list