OnlineCrlRepository.java

/*
 * Java Trust Project.
 * Copyright (C) 2009 FedICT.
 * Copyright (C) 2014-2023 e-Contract.be BV.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version
 * 3.0 as published by the Free Software Foundation.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, see 
 * http://www.gnu.org/licenses/.
 */
package be.fedict.trust.crl;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.security.NoSuchProviderException;
import java.security.cert.CRLException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
import org.bouncycastle.x509.NoSuchParserException;
import org.bouncycastle.x509.util.StreamParsingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import be.fedict.trust.Credentials;
import be.fedict.trust.NetworkConfig;

/**
 * Online CRL repository. This CRL repository implementation will download the
 * CRLs from the given CRL URIs.
 *
 * @author Frank Cornelis
 */
public class OnlineCrlRepository implements CrlRepository {

	private static final Logger LOGGER = LoggerFactory.getLogger(OnlineCrlRepository.class);

	private final NetworkConfig networkConfig;

	private Credentials credentials;

	/**
	 * Main construtor.
	 *
	 * @param networkConfig the optional network configuration used for downloading
	 *                      CRLs.
	 */
	public OnlineCrlRepository(NetworkConfig networkConfig) {
		this.networkConfig = networkConfig;
	}

	/**
	 * Default constructor.
	 */
	public OnlineCrlRepository() {
		this(null);
	}

	/**
	 * Sets the credentials to use to access protected CRL services.
	 *
	 * @param credentials
	 */
	public void setCredentials(Credentials credentials) {
		this.credentials = credentials;
	}

	@Override
	public X509CRL findCrl(URI crlUri, X509Certificate issuerCertificate, Date validationDate) {
		try {
			return getCrl(crlUri);
		} catch (CRLException e) {
			LOGGER.debug("error parsing CRL: {}", e.getMessage(), e);
			return null;
		} catch (Exception e) {
			LOGGER.error("find CRL error: {}", e.getMessage(), e);
			return null;
		}
	}

	private X509CRL getCrl(URI crlUri) throws IOException, CertificateException, CRLException, NoSuchProviderException,
			NoSuchParserException, StreamParsingException {
		int timeout = 10;
		HttpClientContext httpClientContext = HttpClientContext.create();
		if (null != this.credentials) {
			this.credentials.init(httpClientContext);
		}
		RequestConfig.Builder requestConfigBuilder = RequestConfig.custom().setConnectionRequestTimeout(timeout,
				TimeUnit.SECONDS);
		RequestConfig requestConfig = requestConfigBuilder.build();
		HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
		httpClientBuilder.setDefaultRequestConfig(requestConfig);
		if (null != this.networkConfig) {
			HttpHost proxy = new HttpHost(this.networkConfig.getProxyHost(), this.networkConfig.getProxyPort());
			httpClientBuilder.setProxy(proxy);
		}
		BasicHttpClientConnectionManager basicHttpClientConnectionManager = new BasicHttpClientConnectionManager();
		ConnectionConfig connectionConfig = ConnectionConfig.custom().setConnectTimeout(timeout, TimeUnit.SECONDS)
				.setSocketTimeout(timeout, TimeUnit.SECONDS).build();
		basicHttpClientConnectionManager.setConnectionConfig(connectionConfig);
		httpClientBuilder.setConnectionManager(basicHttpClientConnectionManager);
		try (CloseableHttpClient httpClient = httpClientBuilder.build()) {
			String downloadUrl = crlUri.toURL().toString();
			LOGGER.debug("downloading CRL from: {}", downloadUrl);
			HttpGet httpGet = new HttpGet(downloadUrl);
			httpGet.addHeader("User-Agent", "jTrust CRL Client");
			HttpClientResponseHandler<X509CRL> responseHandler = (ClassicHttpResponse httpResponse) -> {
				int statusCode = httpResponse.getCode();
				if (HttpURLConnection.HTTP_OK != statusCode) {
					LOGGER.error("HTTP status code: {}", statusCode);
					return null;
				}

				// not guaranteed to be thread-safe
				CertificateFactory certificateFactory;
				try {
					certificateFactory = CertificateFactory.getInstance("X.509", "BC");
				} catch (CertificateException | NoSuchProviderException ex) {
					LOGGER.error("error: " + ex.getMessage(), ex);
					return null;
				}
				HttpEntity httpEntity = httpResponse.getEntity();
				X509CRL crl;
				try {
					crl = (X509CRL) certificateFactory.generateCRL(httpEntity.getContent());
				} catch (CRLException ex) {
					LOGGER.error("error: " + ex.getMessage(), ex);
					return null;
				}
				if (null == crl) {
					LOGGER.error("null CRL");
					return null;
				}
				try {
					LOGGER.debug("CRL size: {} bytes", crl.getEncoded().length);
				} catch (CRLException ex) {
					LOGGER.error("error: " + ex.getMessage(), ex);
					return null;
				}
				return crl;
			};
			X509CRL crl = httpClient.execute(httpGet, httpClientContext, responseHandler);
			return crl;
		}
	}
}