[java-identity-provider] branch master updated: IDP-644 Use SAML metadata as source of CAS proxy trust.

Marvin S. Addison marvin.addison at gmail.com
Fri Sep 21 12:10:57 EDT 2018


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

serac pushed a commit to branch master
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=a9e8a35f36fc502b002cc54f047ad81ab3317a11

The following commit(s) were added to refs/heads/master by this push:
       new  a9e8a35   IDP-644 Use SAML metadata as source of CAS proxy trust.
a9e8a35 is described below

commit a9e8a35f36fc502b002cc54f047ad81ab3317a11
Author: Marvin S. Addison <serac at vt.edu>
AuthorDate: Tue Jun 19 17:03:40 2018 -0400

    IDP-644 Use SAML metadata as source of CAS proxy trust.
    
    Leverage a chaining trust engine to search SAML metadata for
    explicitly trusted certificates with fallback to PKIX-based validation
    over a list of CA certificates.
    
    The ProxyAuthenticator interface was deprecated by this work; it is
    superseded by ProxyValidator. The ability to customize the proxy
    validation component in user-space configuration has been replaced
    by a facility to edit the list of trusted CA certificates, which is
    the commonplace configuration requirement. The default is an empty
    list.
    
    See https://issues.shibboleth.net/jira/browse/IDP-644
    See https://issues.shibboleth.net/jira/browse/IDP-701
---
 .../idp/cas/proxy/ProxyAuthenticator.java          |   4 +
 ...ProxyAuthenticator.java => ProxyValidator.java} |  21 +-
 .../cas/flow/impl/ValidateProxyCallbackAction.java |  18 +-
 .../cas/proxy/impl/AbstractProxyAuthenticator.java |  95 -------
 .../proxy/impl/HttpClientProxyAuthenticator.java   | 193 --------------
 .../cas/proxy/impl/HttpClientProxyValidator.java   | 282 +++++++++++++++++++++
 .../idp/cas/flow/impl/AbstractFlowActionTest.java  |  13 +
 .../flow/impl/ValidateProxyCallbackActionTest.java |  13 +-
 ...Test.java => HttpClientProxyValidatorTest.java} |  79 +++---
 .../src/test/resources/credentials/key.pem         |  27 --
 .../src/test/resources/credentials/nobody-1.p12    | Bin 3005 -> 2437 bytes
 .../src/test/resources/credentials/nobody-1.pem    |  48 ++--
 .../test/resources/metadata/cas-test-metadata.xml  |  38 +++
 .../src/test/resources/spring/proxy-authn-test.xml |  59 -----
 .../src/test/resources/spring/test-flow-beans.xml  | 102 +++++++-
 idp-conf/src/main/resources/conf/cas-protocol.xml  |  19 +-
 .../resources/system/conf/cas-protocol-system.xml  |  31 ++-
 .../idp/test/flows/cas/ProxyValidateFlowTest.java  |   6 +-
 .../test/flows/cas/ServiceValidateFlowTest.java    |   6 +-
 ...yAuthenticator.java => TestProxyValidator.java} |  12 +-
 .../src/test/resources/test/test-cas-beans.xml     |   4 +-
 21 files changed, 579 insertions(+), 491 deletions(-)

diff --git a/idp-cas-api/src/main/java/net/shibboleth/idp/cas/proxy/ProxyAuthenticator.java b/idp-cas-api/src/main/java/net/shibboleth/idp/cas/proxy/ProxyAuthenticator.java
index f15e777..882df01 100644
--- a/idp-cas-api/src/main/java/net/shibboleth/idp/cas/proxy/ProxyAuthenticator.java
+++ b/idp-cas-api/src/main/java/net/shibboleth/idp/cas/proxy/ProxyAuthenticator.java
@@ -22,12 +22,16 @@ import java.net.URI;
 import java.security.GeneralSecurityException;
 
 /**
+ * Deprecated as of 3.4.0. Superseded by {@link ProxyValidator} interface.
+ * <p>
  * Strategy pattern component for proxy callback authentication.
  *
  * @param <CriteriaType> Proxy validation criteria type.
  *
  * @author Marvin S. Addison
+ * @see ProxyValidator
  */
+ at Deprecated
 public interface ProxyAuthenticator<CriteriaType> {
     /**
      * Authenticates the proxy callback URI.
diff --git a/idp-cas-api/src/main/java/net/shibboleth/idp/cas/proxy/ProxyAuthenticator.java b/idp-cas-api/src/main/java/net/shibboleth/idp/cas/proxy/ProxyValidator.java
similarity index 66%
copy from idp-cas-api/src/main/java/net/shibboleth/idp/cas/proxy/ProxyAuthenticator.java
copy to idp-cas-api/src/main/java/net/shibboleth/idp/cas/proxy/ProxyValidator.java
index f15e777..3b66e9d 100644
--- a/idp-cas-api/src/main/java/net/shibboleth/idp/cas/proxy/ProxyAuthenticator.java
+++ b/idp-cas-api/src/main/java/net/shibboleth/idp/cas/proxy/ProxyValidator.java
@@ -17,25 +17,26 @@
 
 package net.shibboleth.idp.cas.proxy;
 
-import javax.annotation.Nonnull;
 import java.net.URI;
 import java.security.GeneralSecurityException;
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
 
 /**
- * Strategy pattern component for proxy callback authentication.
- *
- * @param <CriteriaType> Proxy validation criteria type.
+ * Strategy pattern component for proxy callback endpoint validation.
  *
  * @author Marvin S. Addison
  */
-public interface ProxyAuthenticator<CriteriaType> {
+public interface ProxyValidator {
     /**
-     * Authenticates the proxy callback URI.
+     * Validates the proxy callback endpoint.
      *
-     * @param uri Proxy callback URI to validate.
-     * @param criteria Validation criteria.
+     * @param profileRequestContext Profile request context.
+     * @param proxyCallbackUri Proxy callback URI to validate.
      *
-     * @throws java.security.GeneralSecurityException On authentication failure.
+     * @throws GeneralSecurityException On validation failure.
      */
-    void authenticate(@Nonnull URI uri, CriteriaType criteria) throws GeneralSecurityException;
+    void validate(@Nonnull ProfileRequestContext profileRequestContext, @Nonnull URI proxyCallbackUri)
+            throws GeneralSecurityException;
 }
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/ValidateProxyCallbackAction.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/ValidateProxyCallbackAction.java
index 7b81ef7..a8abb17 100644
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/ValidateProxyCallbackAction.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/ValidateProxyCallbackAction.java
@@ -26,6 +26,7 @@ import net.shibboleth.idp.cas.config.impl.ConfigLookupFunction;
 import net.shibboleth.idp.cas.config.impl.ValidateConfiguration;
 import net.shibboleth.idp.cas.proxy.ProxyAuthenticator;
 import net.shibboleth.idp.cas.proxy.ProxyIdentifiers;
+import net.shibboleth.idp.cas.proxy.ProxyValidator;
 import net.shibboleth.idp.cas.ticket.ProxyTicket;
 import net.shibboleth.idp.cas.ticket.ServiceTicket;
 import net.shibboleth.idp.cas.ticket.Ticket;
@@ -72,7 +73,7 @@ public class ValidateProxyCallbackAction
 
     /** Validates the proxy callback endpoint. */
     @Nonnull
-    private final ProxyAuthenticator<TrustEngine<? super X509Credential>> proxyAuthnticator;
+    private final ProxyValidator proxyValidator;
 
     /** Manages CAS tickets. */
     @Nonnull
@@ -82,13 +83,13 @@ public class ValidateProxyCallbackAction
     /**
      * Creates a new instance.
      *
-     * @param proxyAuthenticator Component that validates the proxy callback endpoint.
+     * @param validator Component that validates the proxy callback endpoint.
      * @param ticketService Ticket service component.
      */
     public ValidateProxyCallbackAction(
-            @Nonnull final ProxyAuthenticator<TrustEngine<? super X509Credential>> proxyAuthenticator,
+            @Nonnull final ProxyValidator validator,
             @Nonnull final TicketServiceEx ticketService) {
-        proxyAuthnticator = Constraint.isNotNull(proxyAuthenticator, "ProxyAuthenticator cannot be null");
+        proxyValidator = Constraint.isNotNull(validator, "ProxyValidator cannot be null");
         ticketServiceEx = Constraint.isNotNull(ticketService, "TicketService cannot be null");
     }
 
@@ -126,14 +127,7 @@ public class ValidateProxyCallbackAction
         }
         try {
             log.debug("Attempting proxy authentication to {}", proxyCallbackUri);
-            final TrustEngine<? super X509Credential> engine;
-            if (config.getSecurityConfiguration().getClientTLSValidationConfiguration() != null) {
-                engine = config.getSecurityConfiguration().getClientTLSValidationConfiguration().getX509TrustEngine();
-            } else {
-                log.debug("Proxy-granting ticket configuration does not define ClientTLSValidationConfiguration");
-                engine = null;
-            }
-            proxyAuthnticator.authenticate(proxyCallbackUri, engine);
+            proxyValidator.validate(profileRequestContext, proxyCallbackUri);
             final Instant expiration = DateTime.now().plus(config.getTicketValidityPeriod()).toInstant();
             if (ticket instanceof ServiceTicket) {
                 ticketServiceEx.createProxyGrantingTicket(proxyIds.getPgtId(), expiration, (ServiceTicket) ticket);
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/proxy/impl/AbstractProxyAuthenticator.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/proxy/impl/AbstractProxyAuthenticator.java
deleted file mode 100644
index 19d4a30..0000000
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/proxy/impl/AbstractProxyAuthenticator.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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 net.shibboleth.idp.cas.proxy.impl;
-
-import java.net.URI;
-import java.security.GeneralSecurityException;
-import java.util.Collections;
-import java.util.Set;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.security.auth.login.FailedLoginException;
-
-import net.shibboleth.idp.cas.proxy.ProxyAuthenticator;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
-import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-import org.opensaml.security.trust.TrustEngine;
-import org.opensaml.security.x509.X509Credential;
-
-/**
- * Base class for CAS proxy authenticators that make an HTTPS connection to the endpoint at the callback URI.
- * The response code and TLS certificate are examined as the basis for authentication.
- *
- * @author Marvin S. Addison
- */
-public abstract class AbstractProxyAuthenticator implements ProxyAuthenticator<TrustEngine<? super X509Credential>> {
-
-    /** Required https scheme for proxy callbacks. */
-    protected static final String HTTPS_SCHEME = "https";
-
-    /** List of HTTP response codes permitted for successful proxy callback. */
-    @NotEmpty
-    @NonnullElements
-    private Set<Integer> allowedResponseCodes = Collections.singleton(200);
-
-    /**
-     * Sets the HTTP response codes permitted for successful authentication of the proxy callback URL.
-     *
-     * @param responseCodes One or more HTTP response codes.
-     */
-    public void setAllowedResponseCodes(@NotEmpty @NonnullElements final Set<Integer> responseCodes) {
-        Constraint.isNotEmpty(responseCodes, "Response codes cannot be null or empty.");
-        Constraint.noNullItems(responseCodes.toArray(), "Response codes cannot contain null elements.");
-        this.allowedResponseCodes = responseCodes;
-    }
-
-    @Override public final void authenticate(@Nonnull final URI credential,
-            @Nullable final TrustEngine<? super X509Credential> criteria) throws GeneralSecurityException {
-
-        Constraint.isNotNull(credential, "URI to authenticate cannot be null.");
-        if (!HTTPS_SCHEME.equalsIgnoreCase(credential.getScheme())) {
-            throw new GeneralSecurityException(credential + " is not an https URI as required.");
-        }
-        final int status = authenticateProxyCallback(credential, criteria);
-        if (!allowedResponseCodes.contains(status)) {
-            throw new FailedLoginException(credential + " returned unacceptable HTTP status code " + status);
-        }
-    }
-
-    /**
-     * Authenticates the proxy callback URI by making an HTTP GET request and returning the HTTP response code.
-     * The TLS trust evaluation on the certificate at the HTTPS endpoint MUST be performed as part of the request
-     * process.
-     *
-     * @param callbackUri Proxy callback URI containing requisite CAS protocol parameters, <code>pgtId</code> and
-     *                    <code>pgtIou</code>.
-     * @param x509TrustEngine X.509 trust engine used to perform trust calcluation on TLS certificate of URI endpoint.
-     *
-     * @return Status code from HTTP GET request.
-     *
-     * @throws GeneralSecurityException On a failure related to establishing the HTTP connection due to SSL/TLS errors.
-     * @throws RuntimeException On networking errors (IO, HTTP protocol).
-     */
-    protected abstract int authenticateProxyCallback(
-            @Nonnull URI callbackUri,
-            @Nullable TrustEngine<? super X509Credential> x509TrustEngine)
-            throws GeneralSecurityException;
-
-}
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/proxy/impl/HttpClientProxyAuthenticator.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/proxy/impl/HttpClientProxyAuthenticator.java
deleted file mode 100644
index 9647804..0000000
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/proxy/impl/HttpClientProxyAuthenticator.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * 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 net.shibboleth.idp.cas.proxy.impl;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.net.URI;
-import java.security.GeneralSecurityException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-
-import javax.annotation.Nonnull;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLException;
-
-import com.beust.jcommander.internal.Nullable;
-import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.config.Registry;
-import org.apache.http.config.RegistryBuilder;
-import org.apache.http.conn.socket.ConnectionSocketFactory;
-import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
-import org.apache.http.conn.ssl.SSLContexts;
-import org.apache.http.conn.ssl.TrustStrategy;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
-import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
-import org.opensaml.security.SecurityException;
-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;
-
-/**
- * Authenticates a CAS proxy callback endpoint using an {@link org.apache.http.client.HttpClient} instance.
- *
- * @author Marvin S. Addison
- */
-public class HttpClientProxyAuthenticator extends AbstractProxyAuthenticator {
-
-    /**
-     * Delegates X.509 certificate trust to an underlying OpenSAML <code>TrustEngine</code>.
-     */
-    private static class TrustEngineTrustStrategy implements TrustStrategy {
-
-        /** Trust engine. */
-        private final TrustEngine<? super X509Credential> trustEngine;
-
-        /** Class logger. */
-        private final Logger log = LoggerFactory.getLogger(TrustEngineTrustStrategy.class);
-
-        /**
-         * Constructor.
-         *
-         * @param engine trust engine
-         */
-        public TrustEngineTrustStrategy(final TrustEngine<? super X509Credential> engine) {
-            trustEngine = engine;
-        }
-
-        @Override
-        public boolean isTrusted(final X509Certificate[] certificates, final String authType)
-                throws CertificateException {
-            if (trustEngine == null || certificates == null || certificates.length < 1) {
-                return false;
-            }
-            // Assume the first certificate is the end-entity cert
-            try {
-                log.debug("Validating cert {} issued by {}",
-                        certificates[0].getSubjectDN().getName(),
-                        certificates[0].getIssuerDN().getName());
-                return trustEngine.validate(new BasicX509Credential(certificates[0]), new CriteriaSet());
-            } catch (final SecurityException e) {
-                throw new CertificateException("X509 validation error", e);
-            }
-        }
-    }
-
-    /** Default connection and socket timeout in ms. */
-    private static final int DEFAULT_TIMEOUT = 800;
-
-    /** Class logger. */
-    private final Logger log = LoggerFactory.getLogger(HttpClientProxyAuthenticator.class);
-
-    /** Connection and socket timeout. */
-    @Positive
-    private int t = DEFAULT_TIMEOUT;
-
-
-    /**
-     * Sets connect and socket timeouts for HTTP connection to proxy callback endpoint.
-     *
-     * @param timeout Non-zero timeout in milliseconds for both connection and socket timeouts.
-     */
-    public void setTimeout(@Positive final int timeout) {
-        t = (int) Constraint.isGreaterThan(0, timeout, "Timeout must be positive");
-    }
-
-    @Override
-    protected int authenticateProxyCallback(
-            @Nonnull final URI callbackUri,
-            @Nullable final TrustEngine<? super X509Credential> x509TrustEngine)
-            throws GeneralSecurityException {
-
-        CloseableHttpClient httpClient = null;
-        CloseableHttpResponse response = null;
-        try {
-            httpClient = createHttpClient(x509TrustEngine);
-            log.debug("Attempting to connect to {}", callbackUri);
-            final HttpGet request = new HttpGet(callbackUri);
-            request.setConfig(
-                    RequestConfig.custom()
-                            .setConnectTimeout(t)
-                            .setSocketTimeout(t)
-                            .build());
-            response = httpClient.execute(request);
-            return response.getStatusLine().getStatusCode();
-        } catch (final ClientProtocolException e) {
-            throw new GeneralSecurityException("HTTP protocol error", e);
-        } catch (final SSLException e) {
-            if (e.getCause() instanceof CertificateException) {
-                throw (CertificateException) e.getCause();
-            }
-            throw new GeneralSecurityException("SSL connection error", e);
-        } catch (final IOException e) {
-            throw new GeneralSecurityException("IO error", e);
-        } finally {
-            close(response);
-            close(httpClient);
-        }
-    }
-
-    /**
-     * Build HTTP client.
-     * 
-     * @param x509TrustEngine trust engine
-     * @return HTTP client
-     */
-    private CloseableHttpClient createHttpClient(final TrustEngine<? super X509Credential> x509TrustEngine) {
-        final SSLConnectionSocketFactory socketFactory;
-        try {
-            final SSLContext sslContext = SSLContexts.custom()
-                    .useTLS()
-                    .loadTrustMaterial(null, new TrustEngineTrustStrategy(x509TrustEngine))
-                    .build();
-            socketFactory = new SSLConnectionSocketFactory(
-                    sslContext,
-                    SSLConnectionSocketFactory.STRICT_HOSTNAME_VERIFIER);
-        } catch (final Exception e) {
-            throw new RuntimeException("SSL initialization error", e);
-        }
-        final Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
-                .register(HTTPS_SCHEME, socketFactory).build();
-        final BasicHttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager(registry);
-        return HttpClients.custom().setConnectionManager(connectionManager).build();
-    }
-
-    /**
-     * Close the resource.
-     * 
-     * @param resource the resource to close
-     */
-    private void close(final Closeable resource) {
-        if (resource != null) {
-            try {
-                resource.close();
-            } catch (final IOException e) {
-                log.warn("Error closing " + resource, e);
-            }
-        }
-    }
-}
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/proxy/impl/HttpClientProxyValidator.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/proxy/impl/HttpClientProxyValidator.java
new file mode 100644
index 0000000..32a733c
--- /dev/null
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/proxy/impl/HttpClientProxyValidator.java
@@ -0,0 +1,282 @@
+/*
+ * 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 net.shibboleth.idp.cas.proxy.impl;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.URI;
+import java.security.GeneralSecurityException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.security.auth.login.FailedLoginException;
+
+import com.google.common.base.Function;
+import net.shibboleth.idp.cas.config.impl.AbstractProtocolConfiguration;
+import net.shibboleth.idp.cas.proxy.ProxyValidator;
+import net.shibboleth.idp.cas.service.Service;
+import net.shibboleth.idp.cas.service.ServiceContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.annotation.constraint.Positive;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.conn.ssl.TrustStrategy;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
+import org.apache.http.ssl.SSLContextBuilder;
+import org.opensaml.core.criterion.EntityIdCriterion;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.criterion.EntityRoleCriterion;
+import org.opensaml.saml.criterion.ProtocolCriterion;
+import org.opensaml.saml.saml2.metadata.SPSSODescriptor;
+import org.opensaml.security.SecurityException;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.criteria.UsageCriterion;
+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;
+
+/**
+ * Authenticates a CAS proxy callback endpoint using an {@link org.apache.http.client.HttpClient} instance to establish
+ * the connection and a {@link TrustEngine} to verify the TLS certificate presented by the remote peer. The endpoint
+ * is validated if and only if the following requirements are met:
+ *
+ * <ol>
+ *     <li>Proxy callback URI specifies the <code>https</code> scheme.</li>
+ *     <li>The TLS certificate presented by the remote peer is trusted.</li>
+ *     <li>The HTTP response status code is in the set of {@link #allowedResponseCodes} (only 200 by default).</li>
+ * </ol>
+ *
+ * @author Marvin S. Addison
+ */
+public class HttpClientProxyValidator implements ProxyValidator {
+
+    /** Required https scheme for proxy callbacks. */
+    protected static final String HTTPS_SCHEME = "https";
+
+    /** Default connection and socket timeout in ms. */
+    private static final int DEFAULT_TIMEOUT = 800;
+
+    /** Class logger. */
+    private final Logger log = LoggerFactory.getLogger(HttpClientProxyValidator.class);
+
+    /** Trust engine that validates proxy endpoint TLS certificates. */
+    @Nonnull
+    private final TrustEngine<? super X509Credential> trustEngine;
+
+    /** Looks up a ServiceContext from the profile request context. */
+    private final Function<ProfileRequestContext, ServiceContext> serviceCtxLookupFunction =
+            new ChildContextLookup<>(ServiceContext.class);
+
+    /** List of HTTP response codes permitted for successful proxy callback. */
+    @NotEmpty
+    @NonnullElements
+    private Set<Integer> allowedResponseCodes = Collections.singleton(200);
+
+    /** Connection and socket timeout. */
+    @Positive
+    private int timeout = DEFAULT_TIMEOUT;
+
+
+    /**
+     * Creates a new instance.
+     *
+     * @param engine Trust engine to use for validating proxy X.509 certificate credentials.
+     */
+    public HttpClientProxyValidator(@Nonnull final TrustEngine<? super X509Credential> engine) {
+        trustEngine = Constraint.isNotNull(engine, "Trust engine cannot be null");
+    }
+
+    /**
+     * Sets connect and socket timeouts for HTTP connection to proxy callback endpoint.
+     *
+     * @param timeoutMillis Non-zero timeout in milliseconds for both connection and socket timeouts.
+     */
+    public void setTimeout(@Positive final int timeoutMillis) {
+        timeout = (int) Constraint.isGreaterThan(0, timeoutMillis, "Timeout must be positive");
+    }
+
+    /**
+     * Sets the HTTP response codes permitted for successful authentication of the proxy callback URL.
+     *
+     * @param responseCodes One or more HTTP response codes.
+     */
+    public void setAllowedResponseCodes(@NotEmpty @NonnullElements final Set<Integer> responseCodes) {
+        Constraint.isNotEmpty(responseCodes, "Response codes cannot be null or empty.");
+        Constraint.noNullItems(responseCodes.toArray(), "Response codes cannot contain null elements.");
+        allowedResponseCodes = responseCodes;
+    }
+
+    @Override
+    public void validate (
+            @Nonnull final ProfileRequestContext profileRequestContext, @Nonnull final URI proxyCallbackUri)
+            throws GeneralSecurityException {
+
+        Constraint.isNotNull(proxyCallbackUri, "Proxy callback URI cannot be null");
+        if (!HTTPS_SCHEME.equalsIgnoreCase(proxyCallbackUri.getScheme())) {
+            throw new GeneralSecurityException(proxyCallbackUri + " is not an https URI as required.");
+        }
+        final ServiceContext serviceContext = serviceCtxLookupFunction.apply(profileRequestContext);
+        if (serviceContext == null) {
+            throw new IllegalStateException("Service context not found in profile request context as required");
+        }
+        final int status = connect(proxyCallbackUri, serviceContext.getService());
+        if (!allowedResponseCodes.contains(status)) {
+            throw new FailedLoginException(proxyCallbackUri + " returned unacceptable HTTP status code: " + status);
+        }
+    }
+
+    /**
+     * Connect to the given CAS proxy callback endpoint and return the HTTP response code. TLS peer certificate
+     * validation is an essential security aspect of establishing the connection.
+     *
+     * @param uri CAS proxy callback URI to connect to.
+     * @param service CAS service requesting the connection.
+     * @return HTTP response code.
+     * @throws GeneralSecurityException On connection errors, e.g. invalid/untrusted cert.
+     */
+    protected int connect(@Nonnull final URI uri, @Nonnull Service service) throws GeneralSecurityException {
+
+        CloseableHttpClient httpClient = null;
+        CloseableHttpResponse response = null;
+        try {
+            httpClient = createHttpClient(service);
+            log.debug("Attempting to connect to {}", uri);
+            final HttpGet request = new HttpGet(uri);
+            request.setConfig(
+                    RequestConfig.custom()
+                            .setConnectTimeout(timeout)
+                            .setSocketTimeout(timeout)
+                            .build());
+            response = httpClient.execute(request);
+            return response.getStatusLine().getStatusCode();
+        } catch (final ClientProtocolException e) {
+            throw new GeneralSecurityException("HTTP protocol error", e);
+        } catch (final SSLException e) {
+            if (e.getCause() instanceof CertificateException) {
+                throw (CertificateException) e.getCause();
+            }
+            throw new GeneralSecurityException("SSL connection error", e);
+        } catch (final IOException e) {
+            throw new GeneralSecurityException("IO error", e);
+        } finally {
+            close(response);
+            close(httpClient);
+        }
+    }
+
+    /**
+     * Build HTTP client.
+     * 
+     * @param service CAS service.
+     * @return HTTP client
+     */
+    protected CloseableHttpClient createHttpClient(final Service service) {
+        final SSLConnectionSocketFactory socketFactory;
+        try {
+            final SSLContext sslContext = SSLContextBuilder.create()
+                    .loadTrustMaterial(null, new TrustEngineTrustStrategy(service))
+                    .build();
+            socketFactory = new SSLConnectionSocketFactory(sslContext);
+        } catch (final Exception e) {
+            throw new RuntimeException("SSL initialization error", e);
+        }
+        final Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
+                .register(HTTPS_SCHEME, socketFactory).build();
+        final BasicHttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager(registry);
+        return HttpClients.custom().setConnectionManager(connectionManager).build();
+    }
+
+    /**
+     * Close the resource.
+     * 
+     * @param resource the resource to close
+     */
+    private void close(final Closeable resource) {
+        if (resource != null) {
+            try {
+                resource.close();
+            } catch (final IOException e) {
+                log.warn("Error closing " + resource, e);
+            }
+        }
+    }
+
+    /**
+     * Delegates X.509 certificate trust to an underlying OpenSAML <code>TrustEngine</code>.
+     */
+    private class TrustEngineTrustStrategy implements TrustStrategy {
+
+        /** Class logger. */
+        private final Logger log = LoggerFactory.getLogger(TrustEngineTrustStrategy.class);
+
+        /** CAS protocol service. */
+        private final Service service;
+
+
+        public TrustEngineTrustStrategy(final Service s) {
+            service = s;
+        }
+
+        @Override
+        public boolean isTrusted(final X509Certificate[] certificates, final String authType)
+                throws CertificateException {
+            if (certificates == null || certificates.length < 1) {
+                return false;
+            }
+            // Assume the first certificate is the end-entity cert
+            try {
+                log.debug("Validating cert {} issued by {}",
+                        certificates[0].getSubjectDN().getName(),
+                        certificates[0].getIssuerDN().getName());
+                final String entityID;
+                if (service.getEntityDescriptor() != null) {
+                    entityID = service.getEntityDescriptor().getEntityID();
+                } else {
+                    entityID = service.getName();
+                }
+                final CriteriaSet criteria = new CriteriaSet(
+                        new EntityIdCriterion(entityID),
+                        new EntityRoleCriterion(SPSSODescriptor.DEFAULT_ELEMENT_NAME),
+                        new ProtocolCriterion(AbstractProtocolConfiguration.PROTOCOL_URI),
+                        new UsageCriterion(UsageType.SIGNING));
+                return trustEngine.validate(new BasicX509Credential(certificates[0]), criteria);
+            } catch (final SecurityException e) {
+                throw new CertificateException("X509 validation error", e);
+            }
+        }
+    }
+}
diff --git a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/AbstractFlowActionTest.java b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/AbstractFlowActionTest.java
index 11a184c..b3b6210 100644
--- a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/AbstractFlowActionTest.java
+++ b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/AbstractFlowActionTest.java
@@ -32,6 +32,8 @@ import net.shibboleth.idp.session.SessionException;
 import net.shibboleth.idp.spring.IdPPropertiesApplicationContextInitializer;
 import org.joda.time.DateTime;
 import org.joda.time.Instant;
+import org.opensaml.core.config.InitializationException;
+import org.opensaml.core.config.InitializationService;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.test.context.ContextConfiguration;
@@ -39,6 +41,7 @@ import org.springframework.test.context.TestPropertySource;
 import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
 import org.springframework.test.context.web.WebAppConfiguration;
 import org.springframework.webflow.execution.RequestContext;
+import org.testng.annotations.BeforeSuite;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -125,4 +128,14 @@ public abstract class AbstractFlowActionTest extends AbstractTestNGSpringContext
     protected ProxyGrantingTicket createProxyGrantingTicket(final ProxyTicket pt) {
         return ticketService.createProxyGrantingTicket(generateProxyGrantingTicketId(), expiry(), pt);
     }
+
+    /**
+     *  Initialize OpenSAML.
+     *
+     * @throws InitializationException
+     */
+    @BeforeSuite
+    public void initOpenSAML() throws InitializationException {
+        InitializationService.initialize();
+    }
 }
diff --git a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/ValidateProxyCallbackActionTest.java b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/ValidateProxyCallbackActionTest.java
index 8e14c0d..772fa64 100644
--- a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/ValidateProxyCallbackActionTest.java
+++ b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/flow/impl/ValidateProxyCallbackActionTest.java
@@ -24,12 +24,11 @@ import net.shibboleth.idp.cas.config.impl.ValidateConfiguration;
 import net.shibboleth.idp.cas.protocol.ProtocolError;
 import net.shibboleth.idp.cas.protocol.TicketValidationRequest;
 import net.shibboleth.idp.cas.protocol.TicketValidationResponse;
-import net.shibboleth.idp.cas.proxy.ProxyAuthenticator;
+import net.shibboleth.idp.cas.proxy.ProxyValidator;
 import net.shibboleth.idp.cas.ticket.ServiceTicket;
 import net.shibboleth.idp.cas.ticket.TicketState;
 import org.joda.time.Instant;
-import org.opensaml.security.trust.TrustEngine;
-import org.opensaml.security.x509.X509Credential;
+import org.opensaml.profile.context.ProfileRequestContext;
 import org.springframework.webflow.execution.RequestContext;
 import org.testng.annotations.Test;
 
@@ -65,13 +64,13 @@ public class ValidateProxyCallbackActionTest extends AbstractFlowActionTest {
                 ProtocolError.ProxyCallbackAuthenticationFailure.name());
     }
 
-    private static ProxyAuthenticator<TrustEngine<? super X509Credential>> mockProxyAuthenticator(final Exception toBeThrown)
+    private static ProxyValidator mockProxyAuthenticator(final Exception toBeThrown)
             throws Exception {
-        final ProxyAuthenticator<TrustEngine<? super X509Credential>> authenticator = mock(ProxyAuthenticator.class);
+        final ProxyValidator validator = mock(ProxyValidator.class);
         if (toBeThrown != null) {
-            doThrow(toBeThrown).when(authenticator).authenticate(any(URI.class), any(TrustEngine.class));
+            doThrow(toBeThrown).when(validator).validate(any(ProfileRequestContext.class), any(URI.class));
         }
-        return authenticator;
+        return validator;
     }
 
     private static RequestContext newRequestContext(final String pgtURL) {
diff --git a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/proxy/impl/HttpClientProxyAuthenticatorTest.java b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/proxy/impl/HttpClientProxyValidatorTest.java
similarity index 64%
rename from idp-cas-impl/src/test/java/net/shibboleth/idp/cas/proxy/impl/HttpClientProxyAuthenticatorTest.java
rename to idp-cas-impl/src/test/java/net/shibboleth/idp/cas/proxy/impl/HttpClientProxyValidatorTest.java
index faa2036..09b6a36 100644
--- a/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/proxy/impl/HttpClientProxyAuthenticatorTest.java
+++ b/idp-cas-impl/src/test/java/net/shibboleth/idp/cas/proxy/impl/HttpClientProxyValidatorTest.java
@@ -26,13 +26,14 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import net.shibboleth.idp.cas.flow.impl.AbstractFlowActionTest;
+import net.shibboleth.idp.cas.service.Service;
+import net.shibboleth.idp.cas.service.ServiceContext;
 import net.shibboleth.idp.spring.IdPPropertiesApplicationContextInitializer;
 import org.eclipse.jetty.server.*;
 import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.opensaml.security.trust.TrustEngine;
-import org.opensaml.security.x509.X509Credential;
-import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.opensaml.profile.context.ProfileRequestContext;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
 import org.springframework.test.context.ContextConfiguration;
@@ -45,18 +46,14 @@ import org.testng.annotations.Test;
 import static org.testng.Assert.*;
 
 /**
- * Unit test for {@link HttpClientProxyAuthenticator} class.
+ * Unit test for {@link HttpClientProxyValidator} class.
  *
  * @author Marvin S. Addison
  */
- at ContextConfiguration(
-        locations = "/spring/proxy-authn-test.xml",
-        initializers = IdPPropertiesApplicationContextInitializer.class)
- at WebAppConfiguration
- at TestPropertySource(properties = {"idp.initializer.failFast = false"})
-public class HttpClientProxyAuthenticatorTest extends AbstractTestNGSpringContextTests {
+public class HttpClientProxyValidatorTest extends AbstractFlowActionTest {
 
-    private HttpClientProxyAuthenticator authenticator = new HttpClientProxyAuthenticator();
+    @Autowired
+    private HttpClientProxyValidator validator;
 
     @Autowired
     private ApplicationContext context;
@@ -64,34 +61,46 @@ public class HttpClientProxyAuthenticatorTest extends AbstractTestNGSpringContex
     @DataProvider(name = "data")
     public Object[][] buildTestData() {
         return new Object[][] {
-                // Trusted cert and acceptable response code
-                new Object[] { "testCase1", 200, null },
-
-                // Trusted cert and unacceptable response code
-                new Object[] { "testCase1", 404, new FailedLoginException() },
+                // Trusted cert from static trust source and acceptable response code
+                new Object[] { "https://localhost:8443", "src/test/resources/credentials/localhost.p12", 200, null },
+
+                // Trusted cert from metadata source and acceptable response code
+                new Object[] {
+                        "https://nobody-1.middleware.vt.edu/",
+                        "src/test/resources/credentials/nobody-1.p12",
+                        200,
+                        null,
+                },
+
+                // Trusted cert from static trust source and unacceptable response code
+                new Object[] {
+                        "https://localhost:8443",
+                        "src/test/resources/credentials/localhost.p12",
+                        404,
+                        new FailedLoginException()
+                },
 
                 // Untrusted cert
-                new Object[] { "testCase2", 200, new CertificateException() },
-
-                // Ensure cert is rejected when no trust engine is configured
-                new Object[] { "doesNotExist", 200, new CertificateException() },
+                new Object[] {
+                        "https://localhost:8443",
+                        "src/test/resources/credentials/nobody-2.p12",
+                        200,
+                        new CertificateException(),
+                },
         };
     }
 
     @Test(dataProvider = "data")
-    public void testAuthenticate(final String trustEngineBean, final int status, final Exception expected)
+    public void testAuthenticate(
+            final String serviceURL, final String keyStorePath, final int status, final Exception expected)
             throws Exception {
         Server server = null;
         try {
-            server = startServer(new ConfigurableStatusHandler(status));
-            TrustEngine<X509Credential> trustEngine;
-            try {
-                trustEngine = context.getBean(trustEngineBean, TrustEngine.class);
-            } catch (NoSuchBeanDefinitionException e) {
-                trustEngine = null;
-            }
-            authenticator.setTimeout(5000); // 5s timeout for Windows
-            authenticator.authenticate(new URI("https://localhost:8443/?pgtId=A&pgtIOU=B"), trustEngine);
+            server = startServer(keyStorePath, new ConfigurableStatusHandler(status));
+            validator.setTimeout(5000); // 5s timeout for Windows
+            validator.validate(
+                    buildProfileRequestContext(serviceURL),
+                    new URI("https://localhost:8443/?pgtId=A&pgtIOU=B"));
             if (expected != null) {
                 fail("Proxy authentication should have failed with " + expected);
             }
@@ -107,12 +116,12 @@ public class HttpClientProxyAuthenticatorTest extends AbstractTestNGSpringContex
         }
     }
 
-    private Server startServer(final Handler handler) {
+    private Server startServer(final String keyStorePath, final Handler handler) {
         final Server server = new Server();
 
         final SslContextFactory sslContextFactory = new SslContextFactory();
         sslContextFactory.setKeyStoreType("PKCS12");
-        sslContextFactory.setKeyStorePath("src/test/resources/credentials/localhost.p12");
+        sslContextFactory.setKeyStorePath(keyStorePath);
         sslContextFactory.setKeyStorePassword("changeit");
         final ServerConnector connector = new ServerConnector(server, sslContextFactory);
         connector.setHost("127.0.0.1");
@@ -160,4 +169,10 @@ public class HttpClientProxyAuthenticatorTest extends AbstractTestNGSpringContex
             servletResponse.getWriter().println("OK");
         }
     }
+
+    private ProfileRequestContext buildProfileRequestContext(final String serviceUrl) {
+        final ProfileRequestContext prc = new ProfileRequestContext();
+        prc.addSubcontext(new ServiceContext(new Service(serviceUrl, "unknown", true, false)));
+        return prc;
+    }
 }
diff --git a/idp-cas-impl/src/test/resources/credentials/key.pem b/idp-cas-impl/src/test/resources/credentials/key.pem
deleted file mode 100644
index 72286ae..0000000
--- a/idp-cas-impl/src/test/resources/credentials/key.pem
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEpgIBAAKCAQEA6qltZdTHBsCtldcjb5ROMB+RBJRGIjO+n8E+IaIfu4AW/M7a
-eWBbQF7Mmq6cpxhxVBNqWdt0pDuvu9YRT8KwwCMMm+4BYh1a1aJrT+QNuQPCtx8Q
-LSRNAb8YFjnpX5Y92ZAGpHkLTZTUFXTM0mSWPLCiVcFHQjMdtFoQYk/PINvYoN//
-Mqb44Sas7JHccr17TCYDvh7G4yG+U2MFumD6Mnly4OCVnG04giPNBCCkdCt6p4tF
-uuoBTrTrGyN3Wd62cdHO5DBQ8b4ESjz6QutpIYOjJIFZYEW6OeKwiMDKgo4xaOzN
-4hIlBCj9j0Wfe9gGoHpKfirCxFOHSfOqB8OMSwIDAQABAoIBAQCCTTgw7ljhuAXr
-iT3lDF3qPO0CtO7IuSDlhqFgfazPKc4Z7SbFdSrWcI1+au0Tn+/19p38bW60b2wi
-ijisqHgLCKtMvnlIKPKyY/DguMqh7KOnmXkbB+g2ywyt8tRSvpNzhpUZqRkMqFKn
-42aEgVQnORG7Ooq+CLI63jPw0045eKm7qUZEYktW2zHnYe/0Zzyw1ZMMRemRN/0P
-osvNvvt6jWGLslQ9uikTEOhy/2R6/Zzt4Bf8I+UXbXVobk2mljEjmHLIJ6u7H6dW
-cvKXB4PPMi8yZ0QhlvqHufJMvsNSoh4E7n5y3LNNFUB3DcAbPuz47WB5OSBM6fF7
-yLitCCF5AoGBAP/Yg91Nll49ipViT9FHYfjSdjL7SvSpwad5CdXCZG6LBcFdfnyD
-rMmbkPKqj6GB0wvKVvNf2BlVrM5m/nE6vBIZIs6rsBJ/14Z0wwEJ+jRaM99fioYX
-8OTKrdi8HwRL967T+XaGx31uCKLM9PxjEkRTL9qlLMnDvqQXY3efquTXAoGBAOrN
-pJVWMzlKsb8YfmuOd9SrGMDFsNpQKCeDhR+A8I6Kpx+bF04zh624ETwebw8D+Ug9
-d5arCpTVmBCBm/y88/nFNf6FZrOjNgBng9QxIDv78DIKk4w7p3ECTkmNfrhIH1LQ
-2sCUSSe0OyVXpW6sleSwR9wAhBCGqx1HKNGTXHGtAoGBALq5c+wVT1Ryh+1k9iog
-dgGa4QduXhNYveyTqzjmilQfhNDOgp0voPD3Z/+jufY/AR/Oyk0Pu3EqFVgC1Vsg
-adivnbbGmVe3FD6egyAD/ycsWkWBvmEFohvpTJ4tFloVc6yWKrB1x6zknMrs2TRG
-vjM9n2RtPtX21fF+3GqXDqu7AoGBALRR/WVab7g97sdM7jl3jfta8oWIzMq76DqA
-PIn3Ica2IKabGojJ9hapV0MONEgZyyV5Xw/shxHJ/yAeCUdNbSiSFWdD1515aA1j
-cdBRcenfD5W+nZWCcpLNLIY8DedELBoTnwtqVcwSE6IX70pRbEPWRTllhAaVBXBU
-p7pbKxF5AoGBAMCm2RXJXd4HDfQLMpclvut4dw5vQazzzTYCTT9V+OIFGczzNO4N
-JGWFBVYE+Vi9LRXGAS22inqVx/krCUTsx0w6zSfB61jrbjDeYgMAL4JzbjtvzZlf
-k5XhGPwJETNjmX7w88m/Ps8R3Eb46OujXSZtHVpWLzV49ZsX+QkqzyDj
------END RSA PRIVATE KEY-----
diff --git a/idp-cas-impl/src/test/resources/credentials/nobody-1.p12 b/idp-cas-impl/src/test/resources/credentials/nobody-1.p12
index 268acd3..6f82ff3 100644
Binary files a/idp-cas-impl/src/test/resources/credentials/nobody-1.p12 and b/idp-cas-impl/src/test/resources/credentials/nobody-1.p12 differ
diff --git a/idp-cas-impl/src/test/resources/credentials/nobody-1.pem b/idp-cas-impl/src/test/resources/credentials/nobody-1.pem
index 6e650d4..3e64e95 100644
--- a/idp-cas-impl/src/test/resources/credentials/nobody-1.pem
+++ b/idp-cas-impl/src/test/resources/credentials/nobody-1.pem
@@ -1,32 +1,20 @@
 -----BEGIN CERTIFICATE-----
-MIIFdTCCBF2gAwIBAgIJAMkgCRPPZlWUMA0GCSqGSIb3DQEBBQUAMIHWMRMwEQYK
-CZImiZPyLGQBGRYDZWR1MRIwEAYKCZImiZPyLGQBGRYCdnQxCzAJBgNVBAYTAlVT
-MREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVyZzE8MDoGA1UE
-ChMzVmlyZ2luaWEgUG9seXRlY2huaWMgSW5zdGl0dXRlIGFuZCBTdGF0ZSBVbml2
-ZXJzaXR5MRMwEQYDVQQLEwpNaWRkbGV3YXJlMSMwIQYDVQQDExpub2JvZHktMS5t
-aWRkbGV3YXJlLnZ0LmVkdTAeFw0xMzEwMjgxODI2NTRaFw0xMzEwMjkxODI2NTRa
-MIHWMRMwEQYKCZImiZPyLGQBGRYDZWR1MRIwEAYKCZImiZPyLGQBGRYCdnQxCzAJ
-BgNVBAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTETMBEGA1UEBxMKQmxhY2tzYnVy
-ZzE8MDoGA1UEChMzVmlyZ2luaWEgUG9seXRlY2huaWMgSW5zdGl0dXRlIGFuZCBT
-dGF0ZSBVbml2ZXJzaXR5MRMwEQYDVQQLEwpNaWRkbGV3YXJlMSMwIQYDVQQDExpu
-b2JvZHktMS5taWRkbGV3YXJlLnZ0LmVkdTCCASIwDQYJKoZIhvcNAQEBBQADggEP
-ADCCAQoCggEBAOqpbWXUxwbArZXXI2+UTjAfkQSURiIzvp/BPiGiH7uAFvzO2nlg
-W0BezJqunKcYcVQTalnbdKQ7r7vWEU/CsMAjDJvuAWIdWtWia0/kDbkDwrcfEC0k
-TQG/GBY56V+WPdmQBqR5C02U1BV0zNJkljywolXBR0IzHbRaEGJPzyDb2KDf/zKm
-+OEmrOyR3HK9e0wmA74exuMhvlNjBbpg+jJ5cuDglZxtOIIjzQQgpHQreqeLRbrq
-AU606xsjd1netnHRzuQwUPG+BEo8+kLraSGDoySBWWBFujnisIjAyoKOMWjszeIS
-JQQo/Y9Fn3vYBqB6Sn4qwsRTh0nzqgfDjEsCAwEAAaOCAUIwggE+MB0GA1UdDgQW
-BBTOwc8F7f3BuMpdBs+eqw9CJX9YOjCCAQ0GA1UdIwSCAQQwggEAgBTOwc8F7f3B
-uMpdBs+eqw9CJX9YOqGB3KSB2TCB1jETMBEGCgmSJomT8ixkARkWA2VkdTESMBAG
-CgmSJomT8ixkARkWAnZ0MQswCQYDVQQGEwJVUzERMA8GA1UECBMIVmlyZ2luaWEx
-EzARBgNVBAcTCkJsYWNrc2J1cmcxPDA6BgNVBAoTM1ZpcmdpbmlhIFBvbHl0ZWNo
-bmljIEluc3RpdHV0ZSBhbmQgU3RhdGUgVW5pdmVyc2l0eTETMBEGA1UECxMKTWlk
-ZGxld2FyZTEjMCEGA1UEAxMabm9ib2R5LTEubWlkZGxld2FyZS52dC5lZHWCCQDJ
-IAkTz2ZVlDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQDObMj/tqrx
-UATFvFEqtwX2+XAsARDkykVebmQ4FIdEXkHLaekDmD/F5iHA/8cx1njQZ7RKHmDh
-agVq4wl8SI+X30gcUHo9+YP/Eqkm6klb5zu8HzFdo1s5cqGs+VU24C8h15jDPvh9
-z39GX3i1KTiiiTqdws8ItTOmJZhNDGZKDEnLkADHY9fzX2X9z8BjY3HeM5MsOIhA
-WJF3u019zc/r090KB4KGQhkmgBCj+SNse2+whPJ/L+nEMt2oRqrmGlOHepFUJ8MP
-qTGyrndSW8Tbl7RT5Ajb8wrTbi/pG4XLiaCQc9EM+jzCFm6QlnfYAGK9/5mrbiUF
-flGs7q/KiJxm
+MIIDPDCCAiSgAwIBAgIGAWQZpmybMA0GCSqGSIb3DQEBCwUAMFIxIzAhBgNVBAMM
+Gm5vYm9keS0xLm1pZGRsZXdhcmUudnQuZWR1MRMwEQYDVQQLDApNaWRkbGV3YXJl
+MRYwFAYDVQQKDA1WaXJnaW5pYSBUZWNoMB4XDTE4MDYxOTIwMDQ1NFoXDTI4MDYx
+NjIwMDQ1NFowUjEjMCEGA1UEAwwabm9ib2R5LTEubWlkZGxld2FyZS52dC5lZHUx
+EzARBgNVBAsMCk1pZGRsZXdhcmUxFjAUBgNVBAoMDVZpcmdpbmlhIFRlY2gwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCkGXo1bVrEJ2rc4SV2jREraYaE
+h2jJ+B+vCSq2HYEyfXQpqn+yZL+Uo2wI3P9KyS11KOMTG1u5d5DPx+r052BqgZoz
+AU1j/lRbrAS5dfHzXW9G15hrQ6S4YHCnweyPcpXNXLeFG1Gh0R/wdW6ZVNUZrQok
+Pb/a1MK+gDlIuIuK3VSQilEb9UN+xmuuETniqPuzwYW2LZorUIg1eb+qSUdV24OW
+neVxhdp4uHM/rAtEIyJaAmjv3CF85yJ2FqMPATtl886alMj4hNA4j3Cyz27hRcYN
+L/c9BDjBRa1O9/zoC5D780HcC1+XX5KdBx2QWy200mmOK23UpzPEx1x/lVRFAgMB
+AAGjGDAWMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEA
+bq4BNVm+sFivrX8gnwcB2rnJK42wD01ZRZvm+QfmrPvzn1VHFJOt4quqhcSBCwl1
+xLla2c5+EWFN8EP0RPJuna8h92QC5H6fmebKoY/h7QoUs0HcJYORyR2XcAnx35jw
+8gBqd5i+orLdHa2tYTrDjZ2H6BOc6+Hf/YdsPffokH9Fs+gRzMBLz8kwSJl+Wfq/
+Fdv2QemEzfjpK4cZ+fXfSQvAIvpO/AfA9V8pufzoKp/THd3F8LYpH0gpolLdMwFk
+vTfFHoBotoh4+zh6TJP0OMrm04etuBZ46BOAITqqaUDM6QPR6JrLNR8m4wDfeVFP
+tsnEAiP3/AthxkAp0iQhFQ==
 -----END CERTIFICATE-----
diff --git a/idp-cas-impl/src/test/resources/metadata/cas-test-metadata.xml b/idp-cas-impl/src/test/resources/metadata/cas-test-metadata.xml
index 908a785..a8a2350 100644
--- a/idp-cas-impl/src/test/resources/metadata/cas-test-metadata.xml
+++ b/idp-cas-impl/src/test/resources/metadata/cas-test-metadata.xml
@@ -235,4 +235,42 @@
         </SPSSODescriptor>
     </EntityDescriptor>
 
+    <!--
+       | Nobody-1 (authorizedToProxy="true", singleLogoutParticipant="false")
+       -->
+    <EntityDescriptor entityID="https://nobody-1.middleware.vt.edu/">
+        <SPSSODescriptor protocolSupportEnumeration="https://www.apereo.org/cas/protocol">
+            <KeyDescriptor use="signing">
+                <ds:KeyInfo>
+                    <ds:X509Data>
+                        <ds:X509Certificate>
+                            MIIDPDCCAiSgAwIBAgIGAWQZpmybMA0GCSqGSIb3DQEBCwUAMFIxIzAhBgNVBAMM
+                            Gm5vYm9keS0xLm1pZGRsZXdhcmUudnQuZWR1MRMwEQYDVQQLDApNaWRkbGV3YXJl
+                            MRYwFAYDVQQKDA1WaXJnaW5pYSBUZWNoMB4XDTE4MDYxOTIwMDQ1NFoXDTI4MDYx
+                            NjIwMDQ1NFowUjEjMCEGA1UEAwwabm9ib2R5LTEubWlkZGxld2FyZS52dC5lZHUx
+                            EzARBgNVBAsMCk1pZGRsZXdhcmUxFjAUBgNVBAoMDVZpcmdpbmlhIFRlY2gwggEi
+                            MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCkGXo1bVrEJ2rc4SV2jREraYaE
+                            h2jJ+B+vCSq2HYEyfXQpqn+yZL+Uo2wI3P9KyS11KOMTG1u5d5DPx+r052BqgZoz
+                            AU1j/lRbrAS5dfHzXW9G15hrQ6S4YHCnweyPcpXNXLeFG1Gh0R/wdW6ZVNUZrQok
+                            Pb/a1MK+gDlIuIuK3VSQilEb9UN+xmuuETniqPuzwYW2LZorUIg1eb+qSUdV24OW
+                            neVxhdp4uHM/rAtEIyJaAmjv3CF85yJ2FqMPATtl886alMj4hNA4j3Cyz27hRcYN
+                            L/c9BDjBRa1O9/zoC5D780HcC1+XX5KdBx2QWy200mmOK23UpzPEx1x/lVRFAgMB
+                            AAGjGDAWMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEA
+                            bq4BNVm+sFivrX8gnwcB2rnJK42wD01ZRZvm+QfmrPvzn1VHFJOt4quqhcSBCwl1
+                            xLla2c5+EWFN8EP0RPJuna8h92QC5H6fmebKoY/h7QoUs0HcJYORyR2XcAnx35jw
+                            8gBqd5i+orLdHa2tYTrDjZ2H6BOc6+Hf/YdsPffokH9Fs+gRzMBLz8kwSJl+Wfq/
+                            Fdv2QemEzfjpK4cZ+fXfSQvAIvpO/AfA9V8pufzoKp/THd3F8LYpH0gpolLdMwFk
+                            vTfFHoBotoh4+zh6TJP0OMrm04etuBZ46BOAITqqaUDM6QPR6JrLNR8m4wDfeVFP
+                            tsnEAiP3/AthxkAp0iQhFQ==
+                        </ds:X509Certificate>
+                    </ds:X509Data>
+                </ds:KeyInfo>
+            </KeyDescriptor>
+            <AssertionConsumerService
+                    Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
+                    Location="https://localhost:8443/"
+                    index="1"/>
+        </SPSSODescriptor>
+    </EntityDescriptor>
+
 </EntitiesDescriptor>
diff --git a/idp-cas-impl/src/test/resources/spring/proxy-authn-test.xml b/idp-cas-impl/src/test/resources/spring/proxy-authn-test.xml
deleted file mode 100644
index 2db9691..0000000
--- a/idp-cas-impl/src/test/resources/spring/proxy-authn-test.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-       xmlns:c="http://www.springframework.org/schema/c"
-       xmlns:p="http://www.springframework.org/schema/p"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
-       default-init-method="initialize"
-       default-destroy-method="destroy">
-
-
-    <bean id="testCase1" class="org.opensaml.security.x509.impl.PKIXX509CredentialTrustEngine">
-        <constructor-arg name="resolver">
-            <bean class="org.opensaml.security.x509.impl.StaticPKIXValidationInformationResolver"
-                  c:names="#{null}">
-                <constructor-arg name="info">
-                    <bean class="org.opensaml.security.x509.impl.BasicPKIXValidationInformation"
-                          c:crls="#{null}"
-                          c:depth="5">
-                        <constructor-arg name="anchors">
-                            <list>
-                                <bean class="net.shibboleth.ext.spring.factory.X509CertificateFactoryBean"
-                                      p:resource="classpath:credentials/localhost.pem" />
-                            </list>
-                        </constructor-arg>
-                    </bean>
-                </constructor-arg>
-            </bean>
-        </constructor-arg>
-        <constructor-arg name="pkixEvaluator">
-            <bean class="org.opensaml.security.x509.impl.CertPathPKIXTrustEvaluator" />
-        </constructor-arg>
-        <constructor-arg name="nameEvaluator"><null/></constructor-arg>
-    </bean>
-
-    <bean id="testCase2" class="org.opensaml.security.x509.impl.PKIXX509CredentialTrustEngine">
-        <constructor-arg name="resolver">
-            <bean class="org.opensaml.security.x509.impl.StaticPKIXValidationInformationResolver"
-                  c:names="#{null}">
-                <constructor-arg name="info">
-                    <bean class="org.opensaml.security.x509.impl.BasicPKIXValidationInformation"
-                          c:crls="#{null}"
-                          c:depth="5">
-                        <constructor-arg name="anchors">
-                            <list>
-                                <bean class="net.shibboleth.ext.spring.factory.X509CertificateFactoryBean"
-                                      p:resource="classpath:credentials/nobody-2.pem" />
-                            </list>
-                        </constructor-arg>
-                    </bean>
-                </constructor-arg>
-            </bean>
-        </constructor-arg>
-        <constructor-arg name="pkixEvaluator">
-            <bean class="org.opensaml.security.x509.impl.CertPathPKIXTrustEvaluator" />
-        </constructor-arg>
-        <constructor-arg name="nameEvaluator"><null/></constructor-arg>
-    </bean>
-
-</beans>
\ No newline at end of file
diff --git a/idp-cas-impl/src/test/resources/spring/test-flow-beans.xml b/idp-cas-impl/src/test/resources/spring/test-flow-beans.xml
index 0addcd1..bb2aa42 100644
--- a/idp-cas-impl/src/test/resources/spring/test-flow-beans.xml
+++ b/idp-cas-impl/src/test/resources/spring/test-flow-beans.xml
@@ -3,9 +3,12 @@
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:c="http://www.springframework.org/schema/c"
        xmlns:p="http://www.springframework.org/schema/p"
+       xmlns:util="http://www.springframework.org/schema/util"
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans
-           http://www.springframework.org/schema/beans/spring-beans.xsd"
+           http://www.springframework.org/schema/beans/spring-beans.xsd
+           http://www.springframework.org/schema/util
+           http://www.springframework.org/schema/util/spring-util.xsd"
        default-init-method="initialize"
        default-destroy-method="destroy">
 
@@ -78,8 +81,97 @@
         </property>
     </bean>
 
-    <bean id="shibboleth.DefaultCASProxyAuthenticator"
-          class="net.shibboleth.idp.cas.proxy.impl.HttpClientProxyAuthenticator" />
+    <util:list id="shibboleth.DefaultCASServiceRegistries">
+        <ref bean="shibboleth.CASServiceRegistry" />
+    </util:list>
+
+    <util:list id="shibboleth.CASProxyTrustedCertificates">
+        <bean class="net.shibboleth.ext.spring.factory.X509CertificateFactoryBean"
+                  p:resource="classpath:/credentials/localhost.pem" />
+    </util:list>
+
+    <bean id="shibboleth.MetadataCredentialResolver"
+          class="org.opensaml.saml.security.impl.MetadataCredentialResolver"
+          p:roleDescriptorResolver-ref="shibboleth.RoleDescriptorResolver"
+          p:keyInfoCredentialResolver-ref="shibboleth.KeyInfoCredentialResolver">
+    </bean>
+
+    <bean id="shibboleth.KeyInfoCredentialResolver"
+          class="org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap"
+          factory-method="buildBasicInlineKeyInfoCredentialResolver" />
+
+    <bean id="shibboleth.MetadataResolver"
+          class="org.opensaml.saml.metadata.resolver.impl.FilesystemMetadataResolver"
+          p:parserPool-ref="shibboleth.ParserPool">
+        <constructor-arg name="metadata">
+            <bean class="java.io.File" c:pathname="src/test/resources/metadata/cas-test-metadata.xml" />
+        </constructor-arg>
+    </bean>
+
+    <bean id="shibboleth.RoleDescriptorResolver"
+          class="org.opensaml.saml.metadata.resolver.impl.PredicateRoleDescriptorResolver"
+          c:mdResolver-ref="shibboleth.MetadataResolver" />
+
+    <bean id="shibboleth.OpenSAMLConfig" class="net.shibboleth.idp.spring.OpenSAMLConfigBean"
+          depends-on="shibboleth.ParserPool"
+          p:parserPool-ref="shibboleth.ParserPool" />
+
+    <bean id="shibboleth.ParserPool" class="net.shibboleth.utilities.java.support.xml.BasicParserPool"
+          p:maxPoolSize="100"
+          p:coalescing="true"
+          p:ignoreComments="true"
+          p:ignoreElementContentWhitespace="true"
+          p:namespaceAware="true">
+        <property name="builderAttributes">
+            <map>
+                <!-- Sun/Oracle is the default, for Xerces, set property to org.apache.xerces.util.SecurityManager -->
+                <entry key="http://apache.org/xml/properties/security-manager">
+                    <bean class="com.sun.org.apache.xerces.internal.util.SecurityManager" />
+                </entry>
+            </map>
+        </property>
+        <property name="builderFeatures">
+            <map>
+                <entry key="http://apache.org/xml/features/disallow-doctype-decl">
+                    <util:constant static-field="java.lang.Boolean.TRUE" />
+                </entry>
+                <entry key="http://apache.org/xml/features/validation/schema/normalized-value">
+                    <util:constant static-field="java.lang.Boolean.FALSE" />
+                </entry>
+                <entry key="http://javax.xml.XMLConstants/feature/secure-processing">
+                    <util:constant static-field="java.lang.Boolean.TRUE" />
+                </entry>
+            </map>
+        </property>
+    </bean>
+
+    <bean id="proxyTrustEngine" class="org.opensaml.security.trust.impl.ChainingTrustEngine">
+        <constructor-arg name="chain">
+            <list>
+                <bean class="org.opensaml.security.trust.impl.ExplicitX509CertificateTrustEngine"
+                      c:resolver-ref="shibboleth.MetadataCredentialResolver" />
+                <bean class="org.opensaml.security.x509.impl.PKIXX509CredentialTrustEngine" c:nameEvaluator="#{null}">
+                    <constructor-arg name="resolver">
+                        <bean class="org.opensaml.security.x509.impl.StaticPKIXValidationInformationResolver" c:names="#{null}">
+                            <constructor-arg name="info">
+                                <bean class="org.opensaml.security.x509.impl.BasicPKIXValidationInformation"
+                                      c:anchors="#{getObject('shibboleth.CASProxyTrustedCertificates') ?: getObject('shibboleth.DefaultCASProxyTrustedCertificates')}"
+                                      c:crls="#{null}"
+                                      c:depth="5" />
+                            </constructor-arg>
+                        </bean>
+                    </constructor-arg>
+                    <constructor-arg name="pkixEvaluator">
+                        <bean class="org.opensaml.security.x509.impl.CertPathPKIXTrustEvaluator" />
+                    </constructor-arg>
+                </bean>
+            </list>
+        </constructor-arg>
+    </bean>
+
+    <bean id="proxyValidator"
+          class="net.shibboleth.idp.cas.proxy.impl.HttpClientProxyValidator"
+          c:engine-ref="proxyTrustEngine" />
 
 
     <!-- Flow beans -->
@@ -89,7 +181,7 @@
 
     <bean id="buildRelyingPartyContextAction"
           class="net.shibboleth.idp.cas.flow.impl.BuildRelyingPartyContextAction"
-          c:registry-ref="shibboleth.CASServiceRegistry"/>
+          c:registries-ref="shibboleth.DefaultCASServiceRegistries" />
 
     <bean id="buildSAMLMetadataContextAction"
           class="net.shibboleth.idp.cas.flow.impl.BuildSAMLMetadataContextAction" />
@@ -108,7 +200,7 @@
 
     <bean id="validateProxyCallbackAction"
           class="net.shibboleth.idp.cas.flow.impl.ValidateProxyCallbackAction"
-          c:proxyAuthenticator-ref="shibboleth.DefaultCASProxyAuthenticator"
+          c:validator-ref="proxyValidator"
           c:ticketService-ref="shibboleth.CASTicketService" />
 
     <bean id="validateRenewAction"
diff --git a/idp-conf/src/main/resources/conf/cas-protocol.xml b/idp-conf/src/main/resources/conf/cas-protocol.xml
index bddae19..c7ece39 100644
--- a/idp-conf/src/main/resources/conf/cas-protocol.xml
+++ b/idp-conf/src/main/resources/conf/cas-protocol.xml
@@ -3,6 +3,7 @@
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:c="http://www.springframework.org/schema/c"
        xmlns:p="http://www.springframework.org/schema/p"
+       xmlns:util="http://www.springframework.org/schema/util"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
@@ -85,6 +86,18 @@
           class="net.shibboleth.idp.cas.service.impl.DefaultServiceComparator"
           c:parameterNames="[a-z]+sessionid" />-->
 
+    <!--
+       | Define the list of static certificates that you trust to secure CAS proxy callback endpoints.
+       | Typically these are CA certificates and apply to _all_ CAS proxy callback endpoints.
+       | This facility complements the capability to supply relying-party-specific certificates in SAML metadata,
+       | which is the preferred mechanism to specify CAS proxy trust material. In the case of metadata, self-signed
+       | certificates are recommended.
+       -->
+    <util:list id="shibboleth.CASProxyTrustedCertificates">
+        <!--<bean class="net.shibboleth.ext.spring.factory.X509CertificateFactoryBean"
+                  p:resource="%{idp.home}/credentials/your_ca.pem" /> -->
+    </util:list>
+
 
     <!-- ============== Advanced CAS Configuration ============== -->
 
@@ -93,10 +106,4 @@
     <bean id="shibboleth.CASTicketService"
           class="org.example.idp.cas.CustomTicketService" />
     -->
-
-    <!-- Configure a third-party proxy authenticator. -->
-    <!--
-    <bean id="shibboleth.CASProxyAuthenticator"
-          class="org.example.idp.cas.CustomProxyAuthenticator" />
-    -->
 </beans>
\ No newline at end of file
diff --git a/idp-conf/src/main/resources/system/conf/cas-protocol-system.xml b/idp-conf/src/main/resources/system/conf/cas-protocol-system.xml
index a3661db..d388059 100644
--- a/idp-conf/src/main/resources/system/conf/cas-protocol-system.xml
+++ b/idp-conf/src/main/resources/system/conf/cas-protocol-system.xml
@@ -36,8 +36,33 @@
 
     <alias name="simpleTicketService" alias="shibboleth.DefaultCASTicketService" />
 
-    <bean id="shibboleth.DefaultCASProxyAuthenticator"
-          class="net.shibboleth.idp.cas.proxy.impl.HttpClientProxyAuthenticator" />
+    <bean id="proxyTrustEngine" class="org.opensaml.security.trust.impl.ChainingTrustEngine">
+        <constructor-arg name="chain">
+            <list>
+                <bean class="org.opensaml.security.trust.impl.ExplicitX509CertificateTrustEngine"
+                      c:resolver-ref="shibboleth.MetadataCredentialResolver" />
+                <bean class="org.opensaml.security.x509.impl.PKIXX509CredentialTrustEngine" c:nameEvaluator="#{null}">
+                    <constructor-arg name="resolver">
+                        <bean class="org.opensaml.security.x509.impl.StaticPKIXValidationInformationResolver" c:names="#{null}">
+                            <constructor-arg name="info">
+                                <bean class="org.opensaml.security.x509.impl.BasicPKIXValidationInformation"
+                                      c:anchors="#{getObject('shibboleth.CASProxyTrustedCertificates') ?: getObject('shibboleth.DefaultCASProxyTrustedCertificates')}"
+                                      c:crls="#{null}"
+                                      c:depth="5" />
+                            </constructor-arg>
+                        </bean>
+                    </constructor-arg>
+                    <constructor-arg name="pkixEvaluator">
+                        <bean class="org.opensaml.security.x509.impl.CertPathPKIXTrustEvaluator" />
+                    </constructor-arg>
+                </bean>
+            </list>
+        </constructor-arg>
+    </bean>
+
+    <bean id="proxyValidator"
+          class="net.shibboleth.idp.cas.proxy.impl.HttpClientProxyValidator"
+          c:engine-ref="proxyTrustEngine" />
 
     <bean id="shibboleth.DefaultCASProxyValidateIdPSessionPredicate"
           class="com.google.common.base.Predicates"
@@ -61,6 +86,8 @@
         <ref bean="shibboleth.CASMetadataIndex" />
     </util:set>
 
+    <util:list id="shibboleth.DefaultCASProxyTrustedCertificates" />
+
     <import resource="../../conf/cas-protocol.xml" />
 
 </beans>
diff --git a/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/ProxyValidateFlowTest.java b/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/ProxyValidateFlowTest.java
index 44dbcfb..26f6a29 100644
--- a/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/ProxyValidateFlowTest.java
+++ b/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/ProxyValidateFlowTest.java
@@ -61,7 +61,7 @@ public class ProxyValidateFlowTest extends AbstractFlowTest {
     private SessionManager sessionManager;
 
     @Autowired
-    private TestProxyAuthenticator testProxyAuthenticator;
+    private TestProxyValidator testProxyValidator;
 
     @Test
     public void testSuccess() throws Exception {
@@ -105,7 +105,7 @@ public class ProxyValidateFlowTest extends AbstractFlowTest {
         externalContext.getMockRequestParameterMap().put("ticket", ticket.getId());
         externalContext.getMockRequestParameterMap().put("pgtUrl", "https://proxy.example.com/");
 
-        testProxyAuthenticator.setFailureFlag(false);
+        testProxyValidator.setFailureFlag(false);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
 
@@ -129,7 +129,7 @@ public class ProxyValidateFlowTest extends AbstractFlowTest {
         externalContext.getMockRequestParameterMap().put("ticket", ticket.getId());
         externalContext.getMockRequestParameterMap().put("pgtUrl", "https://proxy.example.com/");
 
-        testProxyAuthenticator.setFailureFlag(true);
+        testProxyValidator.setFailureFlag(true);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
 
diff --git a/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/ServiceValidateFlowTest.java b/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/ServiceValidateFlowTest.java
index ad31bc7..54ec48c 100644
--- a/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/ServiceValidateFlowTest.java
+++ b/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/ServiceValidateFlowTest.java
@@ -76,7 +76,7 @@ public class ServiceValidateFlowTest extends AbstractFlowTest {
     private SessionResolver sessionResolver;
 
     @Autowired
-    private TestProxyAuthenticator testProxyAuthenticator;
+    private TestProxyValidator testProxyValidator;
 
     @Test
     public void testInvalidRequestNoTicket() throws Exception {
@@ -202,7 +202,7 @@ public class ServiceValidateFlowTest extends AbstractFlowTest {
         externalContext.getMockRequestParameterMap().put("ticket", ticket.getId());
         externalContext.getMockRequestParameterMap().put("pgtUrl", "https://proxy.example.com/");
 
-        testProxyAuthenticator.setFailureFlag(false);
+        testProxyValidator.setFailureFlag(false);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
 
@@ -232,7 +232,7 @@ public class ServiceValidateFlowTest extends AbstractFlowTest {
         externalContext.getMockRequestParameterMap().put("pgtUrl", "https://proxy.example.com/");
         overrideEndStateOutput(FLOW_ID, "ValidateSuccess");
 
-        testProxyAuthenticator.setFailureFlag(true);
+        testProxyValidator.setFailureFlag(true);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
 
diff --git a/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/TestProxyAuthenticator.java b/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/TestProxyValidator.java
similarity index 78%
rename from idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/TestProxyAuthenticator.java
rename to idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/TestProxyValidator.java
index 9d12590..e28f169 100644
--- a/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/TestProxyAuthenticator.java
+++ b/idp-conf/src/test/java/net/shibboleth/idp/test/flows/cas/TestProxyValidator.java
@@ -17,18 +17,19 @@
 
 package net.shibboleth.idp.test.flows.cas;
 
-import net.shibboleth.idp.cas.proxy.ProxyAuthenticator;
-import org.opensaml.security.trust.TrustEngine;
-import org.opensaml.security.x509.X509Credential;
+import net.shibboleth.idp.cas.proxy.ProxyValidator;
+import org.opensaml.profile.context.ProfileRequestContext;
 
 import javax.annotation.Nonnull;
 import java.net.URI;
 import java.security.GeneralSecurityException;
 
 /**
+ * Test proxy validator component.
+ *
  * @author Marvin S. Addison
  */
-public class TestProxyAuthenticator implements ProxyAuthenticator<TrustEngine<X509Credential>> {
+public class TestProxyValidator implements ProxyValidator {
 
     /** Whether to fail or not. */
     private boolean failureFlag;
@@ -38,7 +39,8 @@ public class TestProxyAuthenticator implements ProxyAuthenticator<TrustEngine<X5
     }
 
     @Override
-    public void authenticate(@Nonnull URI uri, TrustEngine<X509Credential> criteria) throws GeneralSecurityException {
+    public void validate(@Nonnull final ProfileRequestContext profileRequestContext, @Nonnull final URI uri)
+            throws GeneralSecurityException {
         if (failureFlag) {
             throw new GeneralSecurityException("Proxy callback authentication failed (failureFlag==true)");
         }
diff --git a/idp-conf/src/test/resources/test/test-cas-beans.xml b/idp-conf/src/test/resources/test/test-cas-beans.xml
index d5d4a52..5f0e401 100644
--- a/idp-conf/src/test/resources/test/test-cas-beans.xml
+++ b/idp-conf/src/test/resources/test/test-cas-beans.xml
@@ -52,8 +52,8 @@
         </property>
     </bean>
 
-    <bean id="shibboleth.CASProxyAuthenticator"
-          class="net.shibboleth.idp.test.flows.cas.TestProxyAuthenticator" />
+    <bean id="proxyValidator"
+          class="net.shibboleth.idp.test.flows.cas.TestProxyValidator" />
 
     <bean id="shibboleth.CASProxyValidateIdPSessionPredicate"
           class="net.shibboleth.idp.test.flows.cas.ToggleablePredicate" />

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


More information about the commits mailing list