blob: cfc48d077d3e6fcff092606da930f15e78a31247 [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.jmeter.util;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.security.KeyStore;
import java.security.Provider;
import java.security.Security;
import java.util.Locale;
import javax.swing.JOptionPane;
import org.apache.jmeter.gui.GuiPackage;
import org.apache.jmeter.util.keystore.JmeterKeyStore;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.jorphan.util.JOrphanUtils;
import org.apache.log.Logger;
/**
* The SSLManager handles the KeyStore information for JMeter. Basically, it
* handles all the logic for loading and initializing all the JSSE parameters
* and selecting the alias to authenticate against if it is available.
* SSLManager will try to automatically select the client certificate for you,
* but if it can't make a decision, it will pop open a dialog asking you for
* more information.
*
* TODO? - N.B. does not currently allow the selection of a client certificate.
*
*/
public abstract class SSLManager {
private static final Logger log = LoggingManager.getLoggerForClass();
private static final String SSL_TRUST_STORE = "javax.net.ssl.trustStore";// $NON-NLS-1$
private static final String KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword"; // $NON-NLS-1$
public static final String JAVAX_NET_SSL_KEY_STORE = "javax.net.ssl.keyStore"; // $NON-NLS-1$
private static final String JAVAX_NET_SSL_KEY_STORE_TYPE = "javax.net.ssl.keyStoreType"; // $NON-NLS-1$
private static final String PKCS12 = "pkcs12"; // $NON-NLS-1$
/** Singleton instance of the manager */
//@GuardedBy("this")
private static SSLManager manager;
private static final boolean isSSLSupported = true;
/** Cache the KeyStore instance */
private volatile JmeterKeyStore keyStore;
/** Cache the TrustStore instance - null if no truststore name was provided */
private KeyStore trustStore = null;
// Have we yet tried to load the truststore?
private volatile boolean truststoreLoaded=false;
/** Have the password available */
protected String defaultpw = System.getProperty(KEY_STORE_PASSWORD);
private int keystoreAliasStartIndex;
private int keystoreAliasEndIndex;
private String clientCertAliasVarName;
/**
* Resets the SSLManager so that we can create a new one with a new keystore
*/
public static synchronized void reset() {
SSLManager.manager = null;
}
public abstract void setContext(HttpURLConnection conn);
/**
* Default implementation of setting the Provider
*
* @param provider
* the provider to use
*/
protected void setProvider(Provider provider) {
if (null != provider) {
Security.addProvider(provider);
}
}
/**
* Opens and initializes the KeyStore. If the password for the KeyStore is
* not set, this method will prompt you to enter it. Unfortunately, there is
* no PasswordEntryField available from JOptionPane.
*
* @return the configured {@link JmeterKeyStore}
*/
protected synchronized JmeterKeyStore getKeyStore() {
if (null == this.keyStore) {
String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE,""); // empty if not provided
String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type
fileName.toLowerCase(Locale.ENGLISH).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name
log.info("JmeterKeyStore Location: " + fileName + " type " + fileType);
try {
this.keyStore = JmeterKeyStore.getInstance(fileType, keystoreAliasStartIndex, keystoreAliasEndIndex, clientCertAliasVarName);
log.info("KeyStore created OK");
} catch (Exception e) {
this.keyStore = null;
throw new RuntimeException("Could not create keystore: "+e.getMessage(), e);
}
InputStream fileInputStream = null;
try {
File initStore = new File(fileName);
if (fileName.length() >0 && initStore.exists()) {
fileInputStream = new BufferedInputStream(new FileInputStream(initStore));
this.keyStore.load(fileInputStream, getPassword());
if (log.isInfoEnabled()) {
log.info("Total of " + keyStore.getAliasCount() + " aliases loaded OK from keystore");
}
} else {
log.warn("Keystore file not found, loading empty keystore");
this.defaultpw = ""; // Ensure not null
this.keyStore.load(null, "");
}
} catch (Exception e) {
log.error("Problem loading keystore: " +e.getMessage(), e);
} finally {
JOrphanUtils.closeQuietly(fileInputStream);
}
log.debug("JmeterKeyStore type: " + this.keyStore.getClass().toString());
}
return this.keyStore;
}
/*
* The password can be defined as a property; this dialogue is provided to allow it
* to be entered at run-time.
*
* However, this does not gain much, as the dialogue does not (yet) support hidden input ...
*
*/
private String getPassword() {
String password = this.defaultpw;
if (null == password) {
final GuiPackage guiInstance = GuiPackage.getInstance();
if (guiInstance != null) {
synchronized (this) { // TODO is sync really needed?
this.defaultpw = JOptionPane.showInputDialog(
guiInstance.getMainFrame(),
JMeterUtils.getResString("ssl_pass_prompt"), // $NON-NLS-1$
JMeterUtils.getResString("ssl_pass_title"), // $NON-NLS-1$
JOptionPane.QUESTION_MESSAGE);
System.setProperty(KEY_STORE_PASSWORD, this.defaultpw);
password = this.defaultpw;
}
} else {
log.warn("No password provided, and no GUI present so cannot prompt");
}
}
return password;
}
/**
* Opens and initializes the TrustStore.
*
* There are 3 possibilities:
* - no truststore name provided, in which case the default Java truststore should be used
* - truststore name is provided, and loads OK
* - truststore name is provided, but is not found or does not load OK, in which case an empty
* truststore is created
*
* If the KeyStore object cannot be created, then this is currently treated the same
* as if no truststore name was provided.
*
* @return truststore
* - null: use Java truststore
* - otherwise, the truststore, which may be empty if the file could not be loaded.
*
*/
protected KeyStore getTrustStore() {
if (!truststoreLoaded) {
truststoreLoaded=true;// we've tried ...
String fileName = System.getProperty(SSL_TRUST_STORE);
if (fileName == null) {
return null;
}
log.info("TrustStore Location: " + fileName);
try {
this.trustStore = KeyStore.getInstance("JKS");
log.info("TrustStore created OK, Type: JKS");
} catch (Exception e) {
this.trustStore = null;
throw new RuntimeException("Problem creating truststore: "+e.getMessage(), e);
}
InputStream fileInputStream = null;
try {
File initStore = new File(fileName);
if (initStore.exists()) {
fileInputStream = new BufferedInputStream(new FileInputStream(initStore));
this.trustStore.load(fileInputStream, null);
log.info("Truststore loaded OK from file");
} else {
log.info("Truststore file not found, loading empty truststore");
this.trustStore.load(null, null);
}
} catch (Exception e) {
throw new RuntimeException("Can't load TrustStore: " + e.getMessage(), e);
} finally {
JOrphanUtils.closeQuietly(fileInputStream);
}
}
return this.trustStore;
}
/**
* Protected Constructor to remove the possibility of directly instantiating
* this object. Create the SSLContext, and wrap all the X509KeyManagers with
* our X509KeyManager so that we can choose our alias.
*/
protected SSLManager() {
}
/**
* Static accessor for the SSLManager object. The SSLManager is a singleton.
*
* @return the singleton {@link SSLManager}
*/
public static synchronized SSLManager getInstance() {
if (null == SSLManager.manager) {
SSLManager.manager = new JsseSSLManager(null);
}
return SSLManager.manager;
}
/**
* Test whether SSL is supported or not.
*
* @return flag whether SSL is supported
*/
public static boolean isSSLSupported() {
return SSLManager.isSSLSupported;
}
/**
* Configure Keystore
*
* @param preload
* flag whether the keystore should be opened within this method,
* or the opening should be delayed
* @param startIndex
* first index to consider for a key
* @param endIndex
* last index to consider for a key
* @param clientCertAliasVarName
* name of the default key, if empty the first key will be used
* as default key
*/
public void configureKeystore(boolean preload, int startIndex, int endIndex, String clientCertAliasVarName) {
this.keystoreAliasStartIndex = startIndex;
this.keystoreAliasEndIndex = endIndex;
this.clientCertAliasVarName = clientCertAliasVarName;
if(preload) {
keyStore = getKeyStore();
}
}
/**
* Destroy Keystore
*/
public void destroyKeystore() {
keyStore=null;
}
}