blob: fd36727d14e44452fb2b5a1d984d66b82c477bfe [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.ace.connectionfactory.impl;
import java.io.Closeable;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Dictionary;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.apache.ace.connectionfactory.impl.UrlCredentials.AuthType;
/**
* Provides a helper class for obtaining access credentials from a dictionary.
*/
final class UrlCredentialsFactory {
public static final class MissingValueException extends RuntimeException {
private final String m_property;
/**
* @param property the name of the missing property;
* @param message the message explaining the missing property.
*/
public MissingValueException(String property) {
this(property, "No value for " + property + " given!");
}
/**
* @param property the name of the missing property;
* @param message the message explaining the missing property.
*/
public MissingValueException(String property, String message) {
super(message);
m_property = property;
}
/**
* Returns the name of the missing property.
* @return the property name, never <code>null</code>.
*/
public String getProperty() {
return m_property;
}
}
public static final String KEY_AUTH_BASE_URL = "authentication.baseURL";
public static final String KEY_AUTH_TYPE = "authentication.type";
public static final String KEY_AUTH_USER_NAME = "authentication.user.name";
public static final String KEY_AUTH_USER_PASSWORD = "authentication.user.password";
public static final String KEY_AUTH_KEYSTORE_FILE = "authentication.keystore.file";
public static final String KEY_AUTH_KEYSTORE_PASS = "authentication.keystore.storepass";
public static final String KEY_AUTH_TRUSTSTORE_FILE = "authentication.truststore.file";
public static final String KEY_AUTH_TRUSTSTORE_PASS = "authentication.truststore.storepass";
/**
* Not used.
*/
private UrlCredentialsFactory() {
// Nop
}
/**
* @param props the properties to take the access credentials from.
* @throws MissingValueException in case the given properties is missing values.
*/
public static UrlCredentials getCredentials(Dictionary<String, ?> props) throws MissingValueException {
return getCredentials(props, "");
}
/**
* @param props the properties to take the access credentials from;
* @param prefix the prefix to use to lookup the correct values in the given dictionary.
* @throws MissingValueException in case the given properties is missing values.
*/
public static UrlCredentials getCredentials(Dictionary<String, ?> props, String prefix) throws MissingValueException {
if (props == null) {
throw new IllegalArgumentException("Properties cannot be null!");
}
if (prefix == null) {
throw new IllegalArgumentException("Prefix cannot be null!");
}
AuthType type;
URL baseURL;
Object[] creds;
String baseUrlValue = getStringProperty(props, prefix.concat(KEY_AUTH_BASE_URL));
if (baseUrlValue == null) {
throw new MissingValueException(KEY_AUTH_BASE_URL);
}
try {
baseURL = new URL(baseUrlValue);
}
catch (MalformedURLException e) {
throw new MissingValueException(KEY_AUTH_BASE_URL, "Invalid base URL!");
}
String authType = getStringProperty(props, prefix.concat(KEY_AUTH_TYPE), "none");
try {
type = AuthType.valueOf(authType.toUpperCase());
}
catch (Exception e) {
throw new IllegalArgumentException("Unsupported authentication type: " + authType);
}
if (AuthType.NONE.equals(type)) {
creds = new Object[0];
} else if (AuthType.BASIC.equals(type)) {
String userName = getStringProperty(props, prefix.concat(KEY_AUTH_USER_NAME));
if (userName == null) {
throw new MissingValueException(prefix.concat(KEY_AUTH_USER_NAME));
}
String password = getStringProperty(props, prefix.concat(KEY_AUTH_USER_PASSWORD));
if (password == null) {
throw new MissingValueException(prefix.concat(KEY_AUTH_USER_PASSWORD));
}
creds = new Object[] { userName, password };
} else if (AuthType.CLIENT_CERT.equals(type)) {
String keystoreFile = getStringProperty(props, prefix.concat(KEY_AUTH_KEYSTORE_FILE));
String keystorePass = getStringProperty(props, prefix.concat(KEY_AUTH_KEYSTORE_PASS));
if ((keystoreFile != null) && (keystorePass == null)) {
throw new MissingValueException(prefix.concat(KEY_AUTH_KEYSTORE_PASS));
}
if ((keystoreFile == null) && (keystorePass != null)) {
throw new MissingValueException(prefix.concat(KEY_AUTH_KEYSTORE_FILE));
}
String truststoreFile = getStringProperty(props, prefix.concat(KEY_AUTH_TRUSTSTORE_FILE));
String truststorePass = getStringProperty(props, prefix.concat(KEY_AUTH_TRUSTSTORE_PASS));
if ((truststoreFile != null) && (truststorePass == null)) {
throw new MissingValueException(prefix.concat(KEY_AUTH_TRUSTSTORE_PASS));
}
if ((truststoreFile == null) && (truststorePass != null)) {
throw new MissingValueException(prefix.concat(KEY_AUTH_TRUSTSTORE_FILE));
}
if ((keystoreFile == null) && (truststoreFile == null)) {
try {
// No configuration given; use the system-wide defaults...
creds = new Object[] { SSLContext.getDefault() };
}
catch (Exception e) {
throw new IllegalArgumentException("Failed to obtain SSL context!", e);
}
} else {
try {
KeyManager[] keyManagers = getKeyManagerFactory(keystoreFile, keystorePass);
TrustManager[] trustManagers = getTrustManagerFactory(truststoreFile, truststorePass);
SSLContext context = SSLContext.getInstance("TLS");
context.init(keyManagers, trustManagers, new SecureRandom());
creds = new Object[] { context };
}
catch (Exception e) {
throw new IllegalArgumentException("Failed to load keystore!", e);
}
}
} else {
throw new IllegalArgumentException("Invalid/unhandled authentication type: " + authType);
}
return new UrlCredentials(type, baseURL, creds);
}
private static String getStringProperty(Dictionary<String, ?> dict, String key) {
Object value = dict.get(key);
if (value instanceof String) {
return (String) value;
} else if (value instanceof byte[]) {
return new String((byte[]) value);
}
return null;
}
private static String getStringProperty(Dictionary<String, ?> dict, String key, String defaultValue) {
String value = getStringProperty(dict, key);
return (value == null) ? defaultValue : value;
}
/**
* @param keystoreFile
* @param storePass
* @return
* @throws IOException
* @throws GeneralSecurityException
*/
private static KeyManager[] getKeyManagerFactory(String keystoreFile, String storePass) throws IOException, GeneralSecurityException {
if (keystoreFile == null) {
return null;
}
InputStream keyInput = null;
try {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyInput = new FileInputStream(keystoreFile);
keyStore.load(keyInput, storePass.toCharArray());
keyManagerFactory.init(keyStore, storePass.toCharArray());
return keyManagerFactory.getKeyManagers();
}
finally {
closeSafely(keyInput);
}
}
/**
* @param truststoreFile
* @param storePass
* @return
* @throws IOException
* @throws GeneralSecurityException
*/
private static TrustManager[] getTrustManagerFactory(String truststoreFile, String storePass) throws IOException, GeneralSecurityException {
if (truststoreFile == null) {
return null;
}
InputStream trustInput = null;
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustInput = new FileInputStream(truststoreFile);
trustStore.load(trustInput, storePass.toCharArray());
trustManagerFactory.init(trustStore);
return trustManagerFactory.getTrustManagers();
}
finally {
closeSafely(trustInput);
}
}
private static void closeSafely(Closeable resource) {
try {
if (resource != null) {
resource.close();
}
}
catch (IOException e) {
// Ignore; nothing we can/will do...
}
}
}