net.shibboleth.idp.cas.proxy.impl.HttpClientProxyAuthenticator.java Source code

Java tutorial

Introduction

Here is the source code for net.shibboleth.idp.cas.proxy.impl.HttpClientProxyAuthenticator.java

Source

/*
 * 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 {

        private final TrustEngine<? super X509Credential> trustEngine;

        /** Class logger. */
        private final Logger log = LoggerFactory.getLogger(TrustEngineTrustStrategy.class);

        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 (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 timeout = 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) {
        this.timeout = (int) Constraint.isGreaterThan(timeout, 0, "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(timeout).setSocketTimeout(timeout).build());
            response = httpClient.execute(request);
            return response.getStatusLine().getStatusCode();
        } catch (ClientProtocolException e) {
            throw new GeneralSecurityException("HTTP protocol error", e);
        } catch (SSLException e) {
            if (e.getCause() instanceof CertificateException) {
                throw (CertificateException) e.getCause();
            }
            throw new GeneralSecurityException("SSL connection error", e);
        } catch (IOException e) {
            throw new GeneralSecurityException("IO error", e);
        } finally {
            close(response);
            close(httpClient);
        }
    }

    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 (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();
    }

    private void close(Closeable resource) {
        if (resource != null) {
            try {
                resource.close();
            } catch (IOException e) {
                log.warn("Error closing " + resource, e);
            }
        }
    }
}