SSLContextFactory.java

/*
 * Copyright 2017-present the original author or authors.
 *
 * Licensed 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
 *
 *      https://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.springframework.cloud.configuration;

import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.UnrecoverableKeyException;

import javax.net.ssl.SSLContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hc.core5.ssl.SSLContextBuilder;

import org.springframework.core.io.Resource;

public class SSLContextFactory {

	private static Log logger = LogFactory.getLog(SSLContextFactory.class);

	private TlsProperties properties;

	public SSLContextFactory(TlsProperties properties) {
		this.properties = properties;
	}

	public SSLContext createSSLContext() throws GeneralSecurityException, IOException {
		SSLContextBuilder builder = new SSLContextBuilder();

		KeyStore trust = createTrustStore();
		if (trust != null) {
			builder.loadTrustMaterial(trust, null);
		}

		char[] keyPassword = properties.keyPassword();
		try {
			KeyStore keyStore = createKeyStore();

			try {
				builder.loadKeyMaterial(keyStore, keyPassword);
			}
			catch (UnrecoverableKeyException e) {
				if (keyPassword.length == 0) {
					// Retry if empty password, see
					// https://rt.openssl.org/Ticket/Display.html?id=1497&user=guest&pass=guest
					builder.loadKeyMaterial(keyStore, new char[] { '\0' });
				}
				else {
					logger.warn("Could not create keystore.", e);
				}
			}
		}
		catch (KeyStoreException e) {
			logger.warn("Could not create keystore.", e);
		}

		return builder.build();
	}

	public KeyStore createKeyStore() throws GeneralSecurityException, IOException {
		if (properties.getKeyStore() == null) {
			throw new KeyStoreException("Keystore not specified.");
		}
		if (!properties.getKeyStore().exists()) {
			throw new KeyStoreException("Keystore not exists: " + properties.getKeyStore());
		}

		KeyStore result = KeyStore.getInstance(properties.getKeyStoreType());
		char[] keyStorePassword = properties.keyStorePassword();

		try {
			loadKeyStore(result, properties.getKeyStore(), keyStorePassword);
		}
		catch (IOException e) {
			// Retry if empty password, see
			// https://rt.openssl.org/Ticket/Display.html?id=1497&user=guest&pass=guest
			if (keyStorePassword.length == 0) {
				loadKeyStore(result, properties.getKeyStore(), new char[] { '\0' });
			}
			else {
				throw e;
			}
		}

		return result;
	}

	private static void loadKeyStore(KeyStore keyStore, Resource keyStoreResource, char[] keyStorePassword)
			throws IOException, GeneralSecurityException {
		try (InputStream inputStream = keyStoreResource.getInputStream()) {
			keyStore.load(inputStream, keyStorePassword);
		}
	}

	public KeyStore createTrustStore() throws GeneralSecurityException, IOException {
		if (properties.getTrustStore() == null) {
			return null;
		}
		if (!properties.getTrustStore().exists()) {
			throw new KeyStoreException("KeyStore not exists: " + properties.getTrustStore());
		}

		KeyStore result = KeyStore.getInstance(properties.getTrustStoreType());
		try (InputStream input = properties.getTrustStore().getInputStream()) {
			result.load(input, properties.trustStorePassword());
		}
		return result;
	}

}