mirror of https://github.com/apache/jmeter.git
Proxy SSL recording does not handle external embedded resources well
Reworked; setup is now done by ProxyControl on pressing Start
TODO: better notification of progress of keystore init
Bugzilla Id: 55507
git-svn-id: https://svn.apache.org/repos/asf/jmeter/trunk@1521320 13f79535-47bb-0310-9956-ffa450edef68
Former-commit-id: 80e2196c14
This commit is contained in:
parent
0202d3d09d
commit
f440fc8fc3
|
|
@ -550,6 +550,9 @@ upgrade_properties=/bin/upgrade.properties
|
||||||
#proxy.cert.alias=<none>
|
#proxy.cert.alias=<none>
|
||||||
# The default validity for certificates created by JMeter
|
# The default validity for certificates created by JMeter
|
||||||
#proxy.cert.validity=7
|
#proxy.cert.validity=7
|
||||||
|
# Use dynamic key generation (if supported by JMeter/JVM)
|
||||||
|
# If false, will revert to using a single key with no certificate
|
||||||
|
#proxy.cert.dynamic_keys=true
|
||||||
|
|
||||||
# SSL configuration
|
# SSL configuration
|
||||||
#proxy.ssl.protocol=SSLv3
|
#proxy.ssl.protocol=SSLv3
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -723,6 +723,7 @@ proxy_content_type_filter=Content-type filter
|
||||||
proxy_content_type_include=Include\:
|
proxy_content_type_include=Include\:
|
||||||
proxy_daemon_bind_error=Could not create proxy - port in use. Choose another port.
|
proxy_daemon_bind_error=Could not create proxy - port in use. Choose another port.
|
||||||
proxy_daemon_error=Could not create proxy - see log for details
|
proxy_daemon_error=Could not create proxy - see log for details
|
||||||
|
proxy_domains=HTTPS Domains \:
|
||||||
proxy_general_settings=Global Settings
|
proxy_general_settings=Global Settings
|
||||||
proxy_headers=Capture HTTP Headers
|
proxy_headers=Capture HTTP Headers
|
||||||
proxy_regex=Regex matching
|
proxy_regex=Regex matching
|
||||||
|
|
|
||||||
|
|
@ -716,6 +716,7 @@ proxy_content_type_filter=Filtre de type de contenu
|
||||||
proxy_content_type_include=Inclure \:
|
proxy_content_type_include=Inclure \:
|
||||||
proxy_daemon_bind_error=Impossible de lancer le serveur proxy, le port est d\u00E9j\u00E0 utilis\u00E9. Choisissez un autre port.
|
proxy_daemon_bind_error=Impossible de lancer le serveur proxy, le port est d\u00E9j\u00E0 utilis\u00E9. Choisissez un autre port.
|
||||||
proxy_daemon_error=Impossible de lancer le serveur proxy, voir le journal pour plus de d\u00E9tails
|
proxy_daemon_error=Impossible de lancer le serveur proxy, voir le journal pour plus de d\u00E9tails
|
||||||
|
proxy_domains=Domaines HTTPS \:
|
||||||
proxy_general_settings=Param\u00E8tres g\u00E9n\u00E9raux
|
proxy_general_settings=Param\u00E8tres g\u00E9n\u00E9raux
|
||||||
proxy_headers=Capturer les ent\u00EAtes HTTP
|
proxy_headers=Capturer les ent\u00EAtes HTTP
|
||||||
proxy_regex=Correspondance des variables par regex ?
|
proxy_regex=Correspondance des variables par regex ?
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,7 @@ import java.io.BufferedInputStream;
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
|
@ -34,9 +30,9 @@ import java.net.URL;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.prefs.Preferences;
|
|
||||||
|
|
||||||
import javax.net.ssl.KeyManager;
|
import javax.net.ssl.KeyManager;
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
|
@ -44,8 +40,6 @@ import javax.net.ssl.SSLContext;
|
||||||
import javax.net.ssl.SSLSocket;
|
import javax.net.ssl.SSLSocket;
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
|
||||||
import org.apache.jmeter.protocol.http.control.HeaderManager;
|
import org.apache.jmeter.protocol.http.control.HeaderManager;
|
||||||
import org.apache.jmeter.protocol.http.parser.HTMLParseException;
|
import org.apache.jmeter.protocol.http.parser.HTMLParseException;
|
||||||
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase;
|
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase;
|
||||||
|
|
@ -54,7 +48,6 @@ import org.apache.jmeter.protocol.http.util.HTTPConstants;
|
||||||
import org.apache.jmeter.samplers.SampleResult;
|
import org.apache.jmeter.samplers.SampleResult;
|
||||||
import org.apache.jmeter.testelement.TestElement;
|
import org.apache.jmeter.testelement.TestElement;
|
||||||
import org.apache.jmeter.util.JMeterUtils;
|
import org.apache.jmeter.util.JMeterUtils;
|
||||||
import org.apache.jorphan.exec.KeyToolUtils;
|
|
||||||
import org.apache.jorphan.logging.LoggingManager;
|
import org.apache.jorphan.logging.LoggingManager;
|
||||||
import org.apache.jorphan.util.JMeterException;
|
import org.apache.jorphan.util.JMeterException;
|
||||||
import org.apache.jorphan.util.JOrphanUtils;
|
import org.apache.jorphan.util.JOrphanUtils;
|
||||||
|
|
@ -84,10 +77,6 @@ public class Proxy extends Thread {
|
||||||
|
|
||||||
private static final String PROXY_HEADERS_REMOVE_SEPARATOR = ","; // $NON-NLS-1$
|
private static final String PROXY_HEADERS_REMOVE_SEPARATOR = ","; // $NON-NLS-1$
|
||||||
|
|
||||||
// for ssl connection
|
|
||||||
private static final String KEYSTORE_TYPE =
|
|
||||||
JMeterUtils.getPropDefault("proxy.cert.type", "JKS"); // $NON-NLS-1$ $NON-NLS-2$
|
|
||||||
|
|
||||||
private static final String KEYMANAGERFACTORY =
|
private static final String KEYMANAGERFACTORY =
|
||||||
JMeterUtils.getPropDefault("proxy.cert.factory", "SunX509"); // $NON-NLS-1$ $NON-NLS-2$
|
JMeterUtils.getPropDefault("proxy.cert.factory", "SunX509"); // $NON-NLS-1$ $NON-NLS-2$
|
||||||
|
|
||||||
|
|
@ -97,30 +86,8 @@ public class Proxy extends Thread {
|
||||||
// HashMap to save ssl connection between Jmeter proxy and browser
|
// HashMap to save ssl connection between Jmeter proxy and browser
|
||||||
private static final HashMap<String, SSLSocketFactory> hashHost = new HashMap<String, SSLSocketFactory>();
|
private static final HashMap<String, SSLSocketFactory> hashHost = new HashMap<String, SSLSocketFactory>();
|
||||||
|
|
||||||
// Proxy configuration SSL
|
|
||||||
private static final String CERT_DIRECTORY =
|
|
||||||
JMeterUtils.getPropDefault("proxy.cert.directory", JMeterUtils.getJMeterBinDir()); // $NON-NLS-1$
|
|
||||||
|
|
||||||
private static final String CERT_FILE_DEFAULT = "proxyserver.jks";// $NON-NLS-1$
|
|
||||||
|
|
||||||
private static final String CERT_FILE =
|
|
||||||
JMeterUtils.getPropDefault("proxy.cert.file", CERT_FILE_DEFAULT); // $NON-NLS-1$
|
|
||||||
|
|
||||||
// The alias to be used if dynamic host names are not possible
|
|
||||||
private static final String JMETER_SERVER_ALIAS = ":jmeter:"; // $NON-NLS-1$
|
|
||||||
|
|
||||||
private static final int CERT_VALIDITY = JMeterUtils.getPropDefault("proxy.cert.validity", 7); // $NON-NLS-1$
|
|
||||||
|
|
||||||
private static final String DEFAULT_PASSWORD = "password"; // $NON-NLS-1$
|
|
||||||
|
|
||||||
private static final SamplerCreatorFactory factory = new SamplerCreatorFactory();
|
private static final SamplerCreatorFactory factory = new SamplerCreatorFactory();
|
||||||
|
|
||||||
// Keys for user preferences
|
|
||||||
private static final String USER_PASSWORD_KEY = "proxy_cert_PASSWORD";
|
|
||||||
|
|
||||||
private static final Preferences prefs = Preferences.userNodeForPackage(Proxy.class);
|
|
||||||
// Note: Windows user preferences are stored relative to: HKEY_CURRENT_USER\Software\JavaSoft\Prefs
|
|
||||||
|
|
||||||
// Use with SSL connection
|
// Use with SSL connection
|
||||||
private OutputStream outStreamClient = null;
|
private OutputStream outStreamClient = null;
|
||||||
|
|
||||||
|
|
@ -146,6 +113,10 @@ public class Proxy extends Thread {
|
||||||
|
|
||||||
private String port; // For identifying log messages
|
private String port; // For identifying log messages
|
||||||
|
|
||||||
|
private KeyStore keyStore; // keystore for SSL keys; fixed at config except for dynamic host key generation
|
||||||
|
|
||||||
|
private String keyPassword;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default constructor - used by newInstance call in Daemon
|
* Default constructor - used by newInstance call in Daemon
|
||||||
*/
|
*/
|
||||||
|
|
@ -175,6 +146,8 @@ public class Proxy extends Thread {
|
||||||
this.pageEncodings = _pageEncodings;
|
this.pageEncodings = _pageEncodings;
|
||||||
this.formEncodings = _formEncodings;
|
this.formEncodings = _formEncodings;
|
||||||
this.port = "["+ clientSocket.getPort() + "] ";
|
this.port = "["+ clientSocket.getPort() + "] ";
|
||||||
|
this.keyStore = _target.getKeyStore();
|
||||||
|
this.keyPassword = _target.getKeyPassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -315,17 +288,57 @@ public class Proxy extends Thread {
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
private SSLSocketFactory getSSLSocketFactory(String host) {
|
private SSLSocketFactory getSSLSocketFactory(String host) {
|
||||||
|
if (keyStore == null) {
|
||||||
|
log.error(port + "No keystore available, cannot record SSL");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final String hashAlias;
|
||||||
|
final String keyAlias;
|
||||||
|
switch(ProxyControl.KEYSTORE_MODE) {
|
||||||
|
case DYNAMIC_KEYSTORE:
|
||||||
|
try {
|
||||||
|
keyStore = target.getKeyStore(); // pick up any recent changes from other threads
|
||||||
|
String alias = getDomainMatch(keyStore, host);
|
||||||
|
if (alias == null) {
|
||||||
|
hashAlias = host;
|
||||||
|
keyAlias = host;
|
||||||
|
keyStore = target.updateKeyStore(port, keyAlias);
|
||||||
|
} else if (alias.equals(host)) { // the host has a key already
|
||||||
|
hashAlias = host;
|
||||||
|
keyAlias = host;
|
||||||
|
} else { // the host matches a domain; use its key
|
||||||
|
hashAlias = alias;
|
||||||
|
keyAlias = alias;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(port + "Problem with keystore", e);
|
||||||
|
return null;
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
log.error(port + "Problem with keystore", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case JMETER_KEYSTORE:
|
||||||
|
hashAlias = keyAlias = ProxyControl.JMETER_SERVER_ALIAS;
|
||||||
|
break;
|
||||||
|
case USER_KEYSTORE:
|
||||||
|
hashAlias = keyAlias = ProxyControl.CERT_ALIAS;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Impossible case: " + ProxyControl.KEYSTORE_MODE);
|
||||||
|
}
|
||||||
synchronized (hashHost) {
|
synchronized (hashHost) {
|
||||||
if (hashHost.containsKey(host)) {
|
final SSLSocketFactory sslSocketFactory = hashHost.get(hashAlias);
|
||||||
log.debug(port + "Good, already in map, host=" + host);
|
if (sslSocketFactory != null) {
|
||||||
return hashHost.get(host);
|
log.debug(port + "Good, already in map, host=" + host + " using alias " + hashAlias);
|
||||||
|
return sslSocketFactory;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
SSLContext sslcontext = SSLContext.getInstance(SSLCONTEXT_PROTOCOL);
|
SSLContext sslcontext = SSLContext.getInstance(SSLCONTEXT_PROTOCOL);
|
||||||
sslcontext.init(getKeyManagers(host), null, null);
|
sslcontext.init(getWrappedKeyManagers(keyAlias), null, null);
|
||||||
SSLSocketFactory sslFactory = sslcontext.getSocketFactory();
|
SSLSocketFactory sslFactory = sslcontext.getSocketFactory();
|
||||||
hashHost.put(host, sslFactory);
|
hashHost.put(hashAlias, sslFactory);
|
||||||
log.info(port + "KeyStore for SSL loaded OK and put host in map ("+host+")");
|
log.info(port + "KeyStore for SSL loaded OK and put host '" + host + "' in map with key ("+hashAlias+")");
|
||||||
return sslFactory;
|
return sslFactory;
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
log.error(port + "Problem with SSL certificate", e);
|
log.error(port + "Problem with SSL certificate", e);
|
||||||
|
|
@ -337,115 +350,57 @@ public class Proxy extends Thread {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the key managers, wrapped if necessary to return a specific alias
|
* Get matching alias for a host from keyStore that may contain domain aliases.
|
||||||
*
|
* Assumes domains must have at least 2 parts (apache.org);
|
||||||
* @param serverAlias the alias to return, or null to use whatever is present
|
* does not check if TLD requires more (google.co.uk).
|
||||||
* @param host the target host
|
* ProxyControl checks for valid domains before adding them, and any subsequent
|
||||||
* @return the key managers
|
* additions by the Proxy class will be hosts, not domains.
|
||||||
* @throws GeneralSecurityException
|
* @param keyStore the KeyStore to search
|
||||||
* @throws IOException if the store cannot be opened or read or the alias is missing
|
* @param host the hostname to match
|
||||||
|
* @return
|
||||||
|
* @throws KeyStoreException
|
||||||
*/
|
*/
|
||||||
private KeyManager[] getKeyManagers(String host) throws GeneralSecurityException, IOException {
|
private String getDomainMatch(KeyStore keyStore, String host) throws KeyStoreException {
|
||||||
final KeyStore ks;
|
if (keyStore.containsAlias(host)) {
|
||||||
final String serverAlias;
|
return host;
|
||||||
String keyPass;
|
}
|
||||||
switch(ProxyControl.keystoreType) {
|
String parts[] = host.split("\\."); // get the component parts
|
||||||
case JMETER_KEYSTORE:
|
// Assume domains must have at least 2 parts, e.g. apache.org
|
||||||
ks = getJMeterKeyStore(getPassword(), (String) null);
|
// Don't try matching against *.org; however we don't check *.co.uk here
|
||||||
keyPass = getPassword(); // above call may have updated the stored password
|
for(int i = 1; i <= parts.length - 2; i++) {
|
||||||
serverAlias = JMETER_SERVER_ALIAS;
|
StringBuilder sb = new StringBuilder("*");
|
||||||
break;
|
for(int j = i; j < parts.length ; j++) { // add the remaining parts
|
||||||
case DYNAMIC_KEYSTORE:
|
sb.append('.');
|
||||||
ks = getJMeterKeyStore(getPassword(), host);
|
sb.append(parts[j]);
|
||||||
keyPass = getPassword(); // above call may have updated the stored password
|
}
|
||||||
serverAlias = host;
|
String alias = sb.toString();
|
||||||
break;
|
if (keyStore.containsAlias(alias)) {
|
||||||
case USER_KEYSTORE:
|
return alias;
|
||||||
default: // Not really needed, but avoids complaints about non-init password strings
|
}
|
||||||
String keyStorePass = JMeterUtils.getPropDefault("proxy.cert.keystorepass", DEFAULT_PASSWORD); // $NON-NLS-1$
|
}
|
||||||
ks = getKeyStore(keyStorePass.toCharArray());
|
return null;
|
||||||
keyPass = JMeterUtils.getPropDefault("proxy.cert.keypassword", DEFAULT_PASSWORD); // $NON-NLS-1$
|
}
|
||||||
serverAlias = ProxyControl.CERT_ALIAS;
|
|
||||||
break;
|
/**
|
||||||
|
* Return the key managers, wrapped to return a specific alias
|
||||||
|
*/
|
||||||
|
private KeyManager[] getWrappedKeyManagers(final String keyAlias)
|
||||||
|
throws GeneralSecurityException, IOException {
|
||||||
|
if (!keyStore.containsAlias(keyAlias)) {
|
||||||
|
throw new IOException("Keystore does not contain alias " + keyAlias);
|
||||||
}
|
}
|
||||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEYMANAGERFACTORY);
|
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEYMANAGERFACTORY);
|
||||||
kmf.init(ks, keyPass.toCharArray());
|
kmf.init(keyStore, keyPassword.toCharArray());
|
||||||
final KeyManager[] keyManagers = kmf.getKeyManagers();
|
final KeyManager[] keyManagers = kmf.getKeyManagers();
|
||||||
if (serverAlias == null) {
|
// Check if alias is suitable here, rather than waiting for connection to fail
|
||||||
return keyManagers;
|
final int keyManagerCount = keyManagers.length;
|
||||||
} else {
|
final KeyManager[] wrappedKeyManagers = new KeyManager[keyManagerCount];
|
||||||
// Check if alias is suitable here, rather than waiting for connection to fail
|
for (int i =0; i < keyManagerCount; i++) {
|
||||||
if (!ks.containsAlias(serverAlias)) {
|
wrappedKeyManagers[i] = new ServerAliasKeyManager(keyManagers[i], keyAlias);
|
||||||
throw new IOException("Keystore does not contain alias " + serverAlias);
|
|
||||||
}
|
|
||||||
final int keyManagerCount = keyManagers.length;
|
|
||||||
final KeyManager[] wrappedKeyManagers = new KeyManager[keyManagerCount];
|
|
||||||
for (int i =0; i < keyManagerCount; i++) {
|
|
||||||
wrappedKeyManagers[i] = new ServerAliasKeyManager(keyManagers[i], serverAlias);
|
|
||||||
}
|
|
||||||
return wrappedKeyManagers;
|
|
||||||
}
|
}
|
||||||
|
return wrappedKeyManagers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyStore getKeyStore(char[] password) throws GeneralSecurityException, IOException {
|
|
||||||
File certFile = new File(CERT_DIRECTORY, CERT_FILE);
|
|
||||||
InputStream in = null;
|
|
||||||
final String certPath = certFile.getAbsolutePath();
|
|
||||||
try {
|
|
||||||
in = new BufferedInputStream(new FileInputStream(certFile));
|
|
||||||
log.info(port + "Opened Keystore file: " + certPath);
|
|
||||||
KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE);
|
|
||||||
ks.load(in, password);
|
|
||||||
return ks;
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
log.error(port + "Could not open Keystore file: " + certPath, e);
|
|
||||||
throw e;
|
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(in);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If host == null, we are not using dynamic keys
|
|
||||||
private KeyStore getJMeterKeyStore(String keyStorePass, String host) throws GeneralSecurityException, IOException {
|
|
||||||
final File certFile = new File(CERT_DIRECTORY, CERT_FILE);
|
|
||||||
final String subject = host == null ? JMETER_SERVER_ALIAS : host;
|
|
||||||
KeyStore keyStore = null;
|
|
||||||
final String canonicalPath = certFile.getCanonicalPath();
|
|
||||||
if (keyStorePass != null) { // Assume we have already created the store
|
|
||||||
try {
|
|
||||||
keyStore = getKeyStore(keyStorePass.toCharArray());
|
|
||||||
} catch (Exception e) { // store is faulty, we need to recreate it
|
|
||||||
log.warn(port + "Could not open expected file " + canonicalPath + " " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (keyStore == null) { // no existing file or not valid
|
|
||||||
keyStorePass = RandomStringUtils.randomAscii(20);
|
|
||||||
setPassword(keyStorePass);
|
|
||||||
if (host != null) { // i.e. Java 7
|
|
||||||
log.info(port + "Creating Proxy CA in " + canonicalPath);
|
|
||||||
KeyToolUtils.generateProxyCA(certFile, keyStorePass, CERT_VALIDITY);
|
|
||||||
log.info(port + "Creating entry " + subject + " in " + canonicalPath);
|
|
||||||
KeyToolUtils.generateHostCert(certFile, keyStorePass, subject, CERT_VALIDITY);
|
|
||||||
log.info(port + "Created keystore in " + canonicalPath);
|
|
||||||
} else {
|
|
||||||
log.info(port + "Generating standard keypair in " + canonicalPath);
|
|
||||||
// Must not exist
|
|
||||||
if(certFile.exists() && !certFile.delete()) {
|
|
||||||
throw new IOException("Could not delete file:"+certFile.getAbsolutePath()+", this is needed for certificate generation");
|
|
||||||
}
|
|
||||||
KeyToolUtils.genkeypair(certFile, JMETER_SERVER_ALIAS, keyStorePass, CERT_VALIDITY, null, null);
|
|
||||||
}
|
|
||||||
keyStore = getKeyStore(keyStorePass.toCharArray()); // This should now work
|
|
||||||
}
|
|
||||||
// keyStorePass should not be null here; checking it avoids a possible NPE warning below
|
|
||||||
if (keyStorePass != null && host != null && !keyStore.containsAlias(host)) {
|
|
||||||
log.info(port + "Creating entry '" + host + "' in " + canonicalPath);
|
|
||||||
// Requires Java 7
|
|
||||||
KeyToolUtils.generateHostCert(certFile, keyStorePass, host, CERT_VALIDITY);
|
|
||||||
keyStore = getKeyStore(keyStorePass.toCharArray()); // reload
|
|
||||||
}
|
|
||||||
return keyStore;
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Negotiate a SSL connection.
|
* Negotiate a SSL connection.
|
||||||
*
|
*
|
||||||
|
|
@ -639,13 +594,4 @@ public class Proxy extends Thread {
|
||||||
}
|
}
|
||||||
return urlWithoutQuery;
|
return urlWithoutQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getPassword() {
|
|
||||||
return prefs.get(USER_PASSWORD_KEY, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setPassword(String password) {
|
|
||||||
prefs.put(USER_PASSWORD_KEY, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,18 @@
|
||||||
|
|
||||||
package org.apache.jmeter.protocol.http.proxy;
|
package org.apache.jmeter.protocol.http.proxy;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
@ -29,7 +38,12 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
|
import org.apache.http.conn.ssl.AbstractVerifier;
|
||||||
import org.apache.jmeter.assertions.ResponseAssertion;
|
import org.apache.jmeter.assertions.ResponseAssertion;
|
||||||
import org.apache.jmeter.assertions.gui.AssertionGui;
|
import org.apache.jmeter.assertions.gui.AssertionGui;
|
||||||
import org.apache.jmeter.config.Arguments;
|
import org.apache.jmeter.config.Arguments;
|
||||||
|
|
@ -104,6 +118,8 @@ public class ProxyControl extends GenericController {
|
||||||
//+ JMX file attributes
|
//+ JMX file attributes
|
||||||
private static final String PORT = "ProxyControlGui.port"; // $NON-NLS-1$
|
private static final String PORT = "ProxyControlGui.port"; // $NON-NLS-1$
|
||||||
|
|
||||||
|
private static final String DOMAINS = "ProxyControlGui.domains"; // $NON-NLS-1$
|
||||||
|
|
||||||
private static final String EXCLUDE_LIST = "ProxyControlGui.exclude_list"; // $NON-NLS-1$
|
private static final String EXCLUDE_LIST = "ProxyControlGui.exclude_list"; // $NON-NLS-1$
|
||||||
|
|
||||||
private static final String INCLUDE_LIST = "ProxyControlGui.include_list"; // $NON-NLS-1$
|
private static final String INCLUDE_LIST = "ProxyControlGui.include_list"; // $NON-NLS-1$
|
||||||
|
|
@ -150,31 +166,66 @@ public class ProxyControl extends GenericController {
|
||||||
JMeterUtils.getPropDefault("proxy.pause", 1000); // $NON-NLS-1$
|
JMeterUtils.getPropDefault("proxy.pause", 1000); // $NON-NLS-1$
|
||||||
// Detect if user has pressed a new link
|
// Detect if user has pressed a new link
|
||||||
|
|
||||||
public static final String CERT_ALIAS = JMeterUtils.getProperty("proxy.cert.alias"); // $NON-NLS-1$
|
// for ssl connection
|
||||||
|
private static final String KEYSTORE_TYPE =
|
||||||
|
JMeterUtils.getPropDefault("proxy.cert.type", "JKS"); // $NON-NLS-1$ $NON-NLS-2$
|
||||||
|
|
||||||
public static enum KEYSTORE_IMPL {
|
// Proxy configuration SSL
|
||||||
|
private static final String CERT_DIRECTORY =
|
||||||
|
JMeterUtils.getPropDefault("proxy.cert.directory", JMeterUtils.getJMeterBinDir()); // $NON-NLS-1$
|
||||||
|
|
||||||
|
private static final String CERT_FILE_DEFAULT = "proxyserver.jks";// $NON-NLS-1$
|
||||||
|
|
||||||
|
private static final String CERT_FILE =
|
||||||
|
JMeterUtils.getPropDefault("proxy.cert.file", CERT_FILE_DEFAULT); // $NON-NLS-1$
|
||||||
|
|
||||||
|
private static final File CERT_PATH = new File(CERT_DIRECTORY, CERT_FILE);
|
||||||
|
|
||||||
|
private static final String CERT_PATH_ABS = CERT_PATH.getAbsolutePath();
|
||||||
|
|
||||||
|
private static final String DEFAULT_PASSWORD = "password"; // $NON-NLS-1$
|
||||||
|
|
||||||
|
// Keys for user preferences
|
||||||
|
private static final String USER_PASSWORD_KEY = "proxy_cert_password";
|
||||||
|
|
||||||
|
private static final Preferences prefs = Preferences.userNodeForPackage(ProxyControl.class);
|
||||||
|
// Note: Windows user preferences are stored relative to: HKEY_CURRENT_USER\Software\JavaSoft\Prefs
|
||||||
|
|
||||||
|
// Whether to use dymanic key generation (if supported)
|
||||||
|
private static final boolean USE_DYNAMIC_KEYS = JMeterUtils.getPropDefault("proxy.cert.dynamic_keys", true); // $NON-NLS-1$;
|
||||||
|
|
||||||
|
// The alias to be used if dynamic host names are not possible
|
||||||
|
static final String JMETER_SERVER_ALIAS = ":jmeter:"; // $NON-NLS-1$
|
||||||
|
|
||||||
|
static final int CERT_VALIDITY = JMeterUtils.getPropDefault("proxy.cert.validity", 7); // $NON-NLS-1$
|
||||||
|
|
||||||
|
static final String CERT_ALIAS = JMeterUtils.getProperty("proxy.cert.alias"); // $NON-NLS-1$
|
||||||
|
|
||||||
|
public static enum KeystoreMode {
|
||||||
USER_KEYSTORE, // user-provided keystore
|
USER_KEYSTORE, // user-provided keystore
|
||||||
JMETER_KEYSTORE, // keystore generated by JMeter; single entry
|
JMETER_KEYSTORE, // keystore generated by JMeter; single entry
|
||||||
DYNAMIC_KEYSTORE
|
DYNAMIC_KEYSTORE
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final KEYSTORE_IMPL keystoreType;
|
static final KeystoreMode KEYSTORE_MODE;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
if (CERT_ALIAS != null) {
|
if (CERT_ALIAS != null) {
|
||||||
log.info("Proxy Server will use the specified SSL keystore with the alias: '" + CERT_ALIAS + "'");
|
KEYSTORE_MODE = KeystoreMode.USER_KEYSTORE;
|
||||||
keystoreType = KEYSTORE_IMPL.USER_KEYSTORE;
|
log.info("Proxy Server will use the keystore '"+ CERT_PATH_ABS + "' with the alias: '" + CERT_ALIAS + "'");
|
||||||
} else {
|
} else {
|
||||||
if (KeyToolUtils.SUPPORTS_HOST_CERT) {
|
if (KeyToolUtils.SUPPORTS_HOST_CERT && USE_DYNAMIC_KEYS) {
|
||||||
keystoreType = KEYSTORE_IMPL.DYNAMIC_KEYSTORE;
|
KEYSTORE_MODE = KeystoreMode.DYNAMIC_KEYSTORE;
|
||||||
log.info("Java 7 detected: Proxy Server SSL Proxy will use keys that support embedded 3rd party resources");
|
log.info("Proxy Server SSL Proxy will use keys that support embedded 3rd party resources in file " + CERT_PATH_ABS);
|
||||||
} else {
|
} else {
|
||||||
keystoreType = KEYSTORE_IMPL.JMETER_KEYSTORE;
|
KEYSTORE_MODE = KeystoreMode.JMETER_KEYSTORE;
|
||||||
log.warn("Java 7 not detected: Proxy Server SSL Proxy will use keys that may not work for embedded resources");
|
log.warn("Proxy Server SSL Proxy will use keys that may not work for embedded resources in file " + CERT_PATH_ABS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private transient KeyStore keyStore;
|
||||||
|
|
||||||
private AtomicBoolean addAssertions = new AtomicBoolean(false);
|
private AtomicBoolean addAssertions = new AtomicBoolean(false);
|
||||||
|
|
||||||
private AtomicInteger groupingMode = new AtomicInteger(0);
|
private AtomicInteger groupingMode = new AtomicInteger(0);
|
||||||
|
|
@ -196,6 +247,10 @@ public class ProxyControl extends GenericController {
|
||||||
*/
|
*/
|
||||||
private JMeterTreeNode target;
|
private JMeterTreeNode target;
|
||||||
|
|
||||||
|
private String storePassword;
|
||||||
|
|
||||||
|
private String keyPassword;
|
||||||
|
|
||||||
public ProxyControl() {
|
public ProxyControl() {
|
||||||
setPort(DEFAULT_PORT);
|
setPort(DEFAULT_PORT);
|
||||||
setExcludeList(new HashSet<String>());
|
setExcludeList(new HashSet<String>());
|
||||||
|
|
@ -211,6 +266,14 @@ public class ProxyControl extends GenericController {
|
||||||
setProperty(PORT, port);
|
setProperty(PORT, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSslDomains(String domains) {
|
||||||
|
setProperty(DOMAINS, domains, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSslDomains() {
|
||||||
|
return getPropertyAsString(DOMAINS,"");
|
||||||
|
}
|
||||||
|
|
||||||
public void setCaptureHttpHeaders(boolean capture) {
|
public void setCaptureHttpHeaders(boolean capture) {
|
||||||
setProperty(new BooleanProperty(CAPTURE_HTTP_HEADERS, capture));
|
setProperty(new BooleanProperty(CAPTURE_HTTP_HEADERS, capture));
|
||||||
}
|
}
|
||||||
|
|
@ -310,9 +373,9 @@ public class ProxyControl extends GenericController {
|
||||||
if (SAMPLER_TYPE_HTTP_SAMPLER_JAVA.equals(type)){
|
if (SAMPLER_TYPE_HTTP_SAMPLER_JAVA.equals(type)){
|
||||||
type = HTTPSamplerFactory.IMPL_JAVA;
|
type = HTTPSamplerFactory.IMPL_JAVA;
|
||||||
} else if (SAMPLER_TYPE_HTTP_SAMPLER_HC3_1.equals(type)){
|
} else if (SAMPLER_TYPE_HTTP_SAMPLER_HC3_1.equals(type)){
|
||||||
type = HTTPSamplerFactory.IMPL_HTTP_CLIENT3_1;
|
type = HTTPSamplerFactory.IMPL_HTTP_CLIENT3_1;
|
||||||
} else if (SAMPLER_TYPE_HTTP_SAMPLER_HC4.equals(type)){
|
} else if (SAMPLER_TYPE_HTTP_SAMPLER_HC4.equals(type)){
|
||||||
type = HTTPSamplerFactory.IMPL_HTTP_CLIENT4;
|
type = HTTPSamplerFactory.IMPL_HTTP_CLIENT4;
|
||||||
}
|
}
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
@ -350,6 +413,15 @@ public class ProxyControl extends GenericController {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startProxy() throws IOException {
|
public void startProxy() throws IOException {
|
||||||
|
try {
|
||||||
|
initKeyStore(); // TODO display warning dialog as this can take some time
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
log.error("Could not initialise key store", e);
|
||||||
|
throw new IOException("Could not create keystore", e);
|
||||||
|
} catch (IOException e) { // make sure we log the error
|
||||||
|
log.error("Could not initialise key store", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
notifyTestListenersOfStart();
|
notifyTestListenersOfStart();
|
||||||
try {
|
try {
|
||||||
server = new Daemon(getPort(), this);
|
server = new Daemon(getPort(), this);
|
||||||
|
|
@ -419,14 +491,14 @@ public class ProxyControl extends GenericController {
|
||||||
Collection<ConfigTestElement> defaultConfigurations = (Collection<ConfigTestElement>) findApplicableElements(myTarget, ConfigTestElement.class, false);
|
Collection<ConfigTestElement> defaultConfigurations = (Collection<ConfigTestElement>) findApplicableElements(myTarget, ConfigTestElement.class, false);
|
||||||
@SuppressWarnings("unchecked") // OK, because find only returns correct element types
|
@SuppressWarnings("unchecked") // OK, because find only returns correct element types
|
||||||
Collection<Arguments> userDefinedVariables = (Collection<Arguments>) findApplicableElements(myTarget, Arguments.class, true);
|
Collection<Arguments> userDefinedVariables = (Collection<Arguments>) findApplicableElements(myTarget, Arguments.class, true);
|
||||||
|
|
||||||
removeValuesFromSampler(sampler, defaultConfigurations);
|
removeValuesFromSampler(sampler, defaultConfigurations);
|
||||||
replaceValues(sampler, subConfigs, userDefinedVariables);
|
replaceValues(sampler, subConfigs, userDefinedVariables);
|
||||||
sampler.setAutoRedirects(samplerRedirectAutomatically.get());
|
sampler.setAutoRedirects(samplerRedirectAutomatically.get());
|
||||||
sampler.setFollowRedirects(samplerFollowRedirects.get());
|
sampler.setFollowRedirects(samplerFollowRedirects.get());
|
||||||
sampler.setUseKeepAlive(useKeepAlive.get());
|
sampler.setUseKeepAlive(useKeepAlive.get());
|
||||||
sampler.setImageParser(samplerDownloadImages.get());
|
sampler.setImageParser(samplerDownloadImages.get());
|
||||||
|
|
||||||
placeSampler(sampler, subConfigs, myTarget);
|
placeSampler(sampler, subConfigs, myTarget);
|
||||||
} else {
|
} else {
|
||||||
if(log.isDebugEnabled()) {
|
if(log.isDebugEnabled()) {
|
||||||
|
|
@ -509,13 +581,13 @@ public class ProxyControl extends GenericController {
|
||||||
if(log.isDebugEnabled()) {
|
if(log.isDebugEnabled()) {
|
||||||
log.debug("Content-type to filter : " + sampleContentType);
|
log.debug("Content-type to filter : " + sampleContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the include pattern is matched
|
// Check if the include pattern is matched
|
||||||
boolean matched = testPattern(includeExp, sampleContentType, true);
|
boolean matched = testPattern(includeExp, sampleContentType, true);
|
||||||
if(!matched) {
|
if(!matched) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the exclude pattern is matched
|
// Check if the exclude pattern is matched
|
||||||
matched = testPattern(excludeExp, sampleContentType, false);
|
matched = testPattern(excludeExp, sampleContentType, false);
|
||||||
if(!matched) {
|
if(!matched) {
|
||||||
|
|
@ -529,7 +601,7 @@ public class ProxyControl extends GenericController {
|
||||||
* Returns true if matching pattern was different from expectedToMatch
|
* Returns true if matching pattern was different from expectedToMatch
|
||||||
* @param expression Expression to match
|
* @param expression Expression to match
|
||||||
* @param sampleContentType
|
* @param sampleContentType
|
||||||
* @return boolean true if Matching expression
|
* @return boolean true if Matching expression
|
||||||
*/
|
*/
|
||||||
private final boolean testPattern(String expression, String sampleContentType, boolean expectedToMatch) {
|
private final boolean testPattern(String expression, String sampleContentType, boolean expectedToMatch) {
|
||||||
if(expression != null && expression.length() > 0) {
|
if(expression != null && expression.length() > 0) {
|
||||||
|
|
@ -591,8 +663,8 @@ public class ProxyControl extends GenericController {
|
||||||
* Node in the tree where we will add the Controller
|
* Node in the tree where we will add the Controller
|
||||||
* @param name
|
* @param name
|
||||||
* A name for the Controller
|
* A name for the Controller
|
||||||
* @throws InvocationTargetException
|
* @throws InvocationTargetException
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
*/
|
*/
|
||||||
private void addSimpleController(final JMeterTreeModel model, final JMeterTreeNode node, String name)
|
private void addSimpleController(final JMeterTreeModel model, final JMeterTreeNode node, String name)
|
||||||
throws InterruptedException, InvocationTargetException {
|
throws InterruptedException, InvocationTargetException {
|
||||||
|
|
@ -621,8 +693,8 @@ public class ProxyControl extends GenericController {
|
||||||
* Node in the tree where we will add the Controller
|
* Node in the tree where we will add the Controller
|
||||||
* @param name
|
* @param name
|
||||||
* A name for the Controller
|
* A name for the Controller
|
||||||
* @throws InvocationTargetException
|
* @throws InvocationTargetException
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
*/
|
*/
|
||||||
private void addTransactionController(final JMeterTreeModel model, final JMeterTreeNode node, String name)
|
private void addTransactionController(final JMeterTreeModel model, final JMeterTreeNode node, String name)
|
||||||
throws InterruptedException, InvocationTargetException {
|
throws InterruptedException, InvocationTargetException {
|
||||||
|
|
@ -815,7 +887,7 @@ public class ProxyControl extends GenericController {
|
||||||
return elements;
|
return elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void placeSampler(final HTTPSamplerBase sampler, final TestElement[] subConfigs,
|
private void placeSampler(final HTTPSamplerBase sampler, final TestElement[] subConfigs,
|
||||||
JMeterTreeNode myTarget) {
|
JMeterTreeNode myTarget) {
|
||||||
try {
|
try {
|
||||||
final JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel();
|
final JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel();
|
||||||
|
|
@ -893,7 +965,7 @@ public class ProxyControl extends GenericController {
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
JMeterUtils.reportErrorToUser(e.getMessage());
|
JMeterUtils.reportErrorToUser(e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1070,4 +1142,178 @@ public class ProxyControl extends GenericController {
|
||||||
return null == server;
|
return null == server;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initKeyStore() throws IOException, GeneralSecurityException {
|
||||||
|
switch(KEYSTORE_MODE) {
|
||||||
|
case DYNAMIC_KEYSTORE:
|
||||||
|
storePassword = getPassword();
|
||||||
|
keyPassword = getPassword();
|
||||||
|
initDynamicKeyStore();
|
||||||
|
break;
|
||||||
|
case JMETER_KEYSTORE:
|
||||||
|
storePassword = getPassword();
|
||||||
|
keyPassword = getPassword();
|
||||||
|
initJMeterKeyStore();
|
||||||
|
break;
|
||||||
|
case USER_KEYSTORE:
|
||||||
|
storePassword = JMeterUtils.getPropDefault("proxy.cert.keystorepass", DEFAULT_PASSWORD); // $NON-NLS-1$;
|
||||||
|
keyPassword = JMeterUtils.getPropDefault("proxy.cert.keypassword", DEFAULT_PASSWORD); // $NON-NLS-1$;
|
||||||
|
log.info("Proxy Server will use the keystore '"+ CERT_PATH_ABS + "' with the alias: '" + CERT_ALIAS + "'");
|
||||||
|
initUserKeyStore();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Impossible case: " + KEYSTORE_MODE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise the user-provided keystore
|
||||||
|
*/
|
||||||
|
private void initUserKeyStore() {
|
||||||
|
try {
|
||||||
|
keyStore = getKeyStore(storePassword.toCharArray());
|
||||||
|
X509Certificate caCert = (X509Certificate) keyStore.getCertificate(CERT_ALIAS);
|
||||||
|
if (caCert == null) {
|
||||||
|
log.error("Could not find key with alias " + CERT_ALIAS);
|
||||||
|
keyStore = null;
|
||||||
|
} else {
|
||||||
|
caCert.checkValidity(new Date(System.currentTimeMillis()+DateUtils.MILLIS_PER_DAY));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
keyStore = null;
|
||||||
|
log.error("Could not open keystore or certificate is not valid " + CERT_PATH_ABS + " " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise the dynamic domain keystore
|
||||||
|
*/
|
||||||
|
private void initDynamicKeyStore() throws IOException, GeneralSecurityException {
|
||||||
|
if (storePassword != null) { // Assume we have already created the store
|
||||||
|
try {
|
||||||
|
keyStore = getKeyStore(storePassword.toCharArray());
|
||||||
|
X509Certificate caCert = (X509Certificate) keyStore.getCertificate(KeyToolUtils.CA_ALIAS);
|
||||||
|
if (caCert == null) {
|
||||||
|
keyStore = null; // no CA key - probably the wrong store type.
|
||||||
|
} else {
|
||||||
|
caCert.checkValidity(new Date(System.currentTimeMillis()+DateUtils.MILLIS_PER_DAY));
|
||||||
|
}
|
||||||
|
} catch (IOException e) { // store is faulty, we need to recreate it
|
||||||
|
keyStore = null; // if cert is not valid, flag up to recreate it
|
||||||
|
if (e.getCause() instanceof UnrecoverableKeyException) {
|
||||||
|
log.warn("Could not read key store " + e.getMessage() + " cause " + e.getCause().getMessage());
|
||||||
|
} else {
|
||||||
|
log.warn("Could not open/read key store " + e.getMessage()); // message includes the file name
|
||||||
|
}
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
log.warn("Problem reading key store" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (keyStore == null) { // no existing file or not valid
|
||||||
|
storePassword = RandomStringUtils.randomAlphanumeric(20); // Alphanum to avoid issues with command-line quoting
|
||||||
|
keyPassword = storePassword; // we use same password for both
|
||||||
|
setPassword(storePassword);
|
||||||
|
log.info("Creating Proxy CA in " + CERT_PATH_ABS);
|
||||||
|
KeyToolUtils.generateProxyCA(CERT_PATH, storePassword, CERT_VALIDITY);
|
||||||
|
log.info("Created keystore in " + CERT_PATH_ABS);
|
||||||
|
keyStore = getKeyStore(storePassword.toCharArray()); // This should now work
|
||||||
|
}
|
||||||
|
final String sslDomains = getSslDomains().trim();
|
||||||
|
if (sslDomains.length() > 0) {
|
||||||
|
final String[] domains = sslDomains.split(",");
|
||||||
|
// The subject may be either a host or a domain
|
||||||
|
for(String subject : domains) {
|
||||||
|
if (isValid(subject)) {
|
||||||
|
if (!keyStore.containsAlias(subject)) {
|
||||||
|
log.info("Creating entry " + subject + " in " + CERT_PATH_ABS);
|
||||||
|
KeyToolUtils.generateHostCert(CERT_PATH, storePassword, subject, CERT_VALIDITY);
|
||||||
|
keyStore = getKeyStore(storePassword.toCharArray()); // reload to pick up new aliases
|
||||||
|
// reloading is very quick compared with creating an entry currently
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warn("Attempt to create an invalid domain certificate: " + subject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValid(String subject) {
|
||||||
|
String parts[] = subject.split("\\.");
|
||||||
|
if (!parts[0].endsWith("*")) { // not a wildcard
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return parts.length >= 3 && AbstractVerifier.acceptableCountryWildcard(subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should only be called for a specific host
|
||||||
|
KeyStore updateKeyStore(String port, String host) throws IOException, GeneralSecurityException {
|
||||||
|
synchronized(CERT_PATH) { // ensure Proxy threads cannot interfere with each other
|
||||||
|
if (!keyStore.containsAlias(host)) {
|
||||||
|
log.info(port + "Creating entry " + host + " in " + CERT_PATH_ABS);
|
||||||
|
KeyToolUtils.generateHostCert(CERT_PATH, storePassword, host, CERT_VALIDITY);
|
||||||
|
}
|
||||||
|
keyStore = getKeyStore(storePassword.toCharArray()); // reload after adding alias
|
||||||
|
}
|
||||||
|
return keyStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise the single key JMeter keystore (original behaviour)
|
||||||
|
*/
|
||||||
|
private void initJMeterKeyStore() throws IOException, GeneralSecurityException {
|
||||||
|
if (storePassword != null) { // Assume we have already created the store
|
||||||
|
try {
|
||||||
|
keyStore = getKeyStore(storePassword.toCharArray());
|
||||||
|
X509Certificate caCert = (X509Certificate) keyStore.getCertificate(JMETER_SERVER_ALIAS);
|
||||||
|
caCert.checkValidity(new Date(System.currentTimeMillis()+DateUtils.MILLIS_PER_DAY));
|
||||||
|
} catch (Exception e) { // store is faulty, we need to recreate it
|
||||||
|
keyStore = null; // if cert is not valid, flag up to recreate it
|
||||||
|
log.warn("Could not open expected file or certificate is not valid " + CERT_PATH_ABS + " " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (keyStore == null) { // no existing file or not valid
|
||||||
|
storePassword = RandomStringUtils.randomAlphanumeric(20); // Alphanum to avoid issues with command-line quoting
|
||||||
|
keyPassword = storePassword; // we use same password for both
|
||||||
|
setPassword(storePassword);
|
||||||
|
log.info("Generating standard keypair in " + CERT_PATH_ABS);
|
||||||
|
CERT_PATH.delete(); // safer to start afresh
|
||||||
|
KeyToolUtils.genkeypair(CERT_PATH, JMETER_SERVER_ALIAS, storePassword, CERT_VALIDITY, null, null);
|
||||||
|
keyStore = getKeyStore(storePassword.toCharArray()); // This should now work
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyStore getKeyStore(char[] password) throws GeneralSecurityException, IOException {
|
||||||
|
InputStream in = null;
|
||||||
|
try {
|
||||||
|
in = new BufferedInputStream(new FileInputStream(CERT_PATH));
|
||||||
|
log.debug("Opened Keystore file: " + CERT_PATH_ABS);
|
||||||
|
KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE);
|
||||||
|
ks.load(in, password);
|
||||||
|
log.debug("Loaded Keystore file: " + CERT_PATH_ABS);
|
||||||
|
return ks;
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPassword() {
|
||||||
|
return prefs.get(USER_PASSWORD_KEY, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPassword(String password) {
|
||||||
|
prefs.put(USER_PASSWORD_KEY, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the keystore for use by the Proxy
|
||||||
|
KeyStore getKeyStore() {
|
||||||
|
return keyStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getKeyPassword() {
|
||||||
|
return keyPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDynamicMode() {
|
||||||
|
return KEYSTORE_MODE == KeystoreMode.DYNAMIC_KEYSTORE;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
package org.apache.jmeter.protocol.http.proxy.gui;
|
package org.apache.jmeter.protocol.http.proxy.gui;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Cursor;
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.datatransfer.DataFlavor;
|
import java.awt.datatransfer.DataFlavor;
|
||||||
import java.awt.datatransfer.UnsupportedFlavorException;
|
import java.awt.datatransfer.UnsupportedFlavorException;
|
||||||
|
|
@ -70,6 +71,7 @@ import org.apache.jmeter.testelement.WorkBench;
|
||||||
import org.apache.jmeter.testelement.property.PropertyIterator;
|
import org.apache.jmeter.testelement.property.PropertyIterator;
|
||||||
import org.apache.jmeter.util.JMeterUtils;
|
import org.apache.jmeter.util.JMeterUtils;
|
||||||
import org.apache.jorphan.gui.GuiUtils;
|
import org.apache.jorphan.gui.GuiUtils;
|
||||||
|
import org.apache.jorphan.gui.JLabeledTextField;
|
||||||
import org.apache.jorphan.logging.LoggingManager;
|
import org.apache.jorphan.logging.LoggingManager;
|
||||||
import org.apache.log.Logger;
|
import org.apache.log.Logger;
|
||||||
|
|
||||||
|
|
@ -89,6 +91,8 @@ public class ProxyControlGui extends LogicControllerGui implements JMeterGUIComp
|
||||||
|
|
||||||
private JTextField portField;
|
private JTextField portField;
|
||||||
|
|
||||||
|
private JLabeledTextField sslDomains;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to indicate that HTTP request headers should be captured. The
|
* Used to indicate that HTTP request headers should be captured. The
|
||||||
* default is to capture the HTTP request headers, which are specific to
|
* default is to capture the HTTP request headers, which are specific to
|
||||||
|
|
@ -230,6 +234,7 @@ public class ProxyControlGui extends LogicControllerGui implements JMeterGUIComp
|
||||||
if (el instanceof ProxyControl) {
|
if (el instanceof ProxyControl) {
|
||||||
model = (ProxyControl) el;
|
model = (ProxyControl) el;
|
||||||
model.setPort(portField.getText());
|
model.setPort(portField.getText());
|
||||||
|
model.setSslDomains(sslDomains.getText());
|
||||||
setIncludeListInProxyControl(model);
|
setIncludeListInProxyControl(model);
|
||||||
setExcludeListInProxyControl(model);
|
setExcludeListInProxyControl(model);
|
||||||
model.setCaptureHttpHeaders(httpHeaders.isSelected());
|
model.setCaptureHttpHeaders(httpHeaders.isSelected());
|
||||||
|
|
@ -294,6 +299,7 @@ public class ProxyControlGui extends LogicControllerGui implements JMeterGUIComp
|
||||||
super.configure(element);
|
super.configure(element);
|
||||||
model = (ProxyControl) element;
|
model = (ProxyControl) element;
|
||||||
portField.setText(model.getPortString());
|
portField.setText(model.getPortString());
|
||||||
|
sslDomains.setText(model.getSslDomains());
|
||||||
httpHeaders.setSelected(model.getCaptureHttpHeaders());
|
httpHeaders.setSelected(model.getCaptureHttpHeaders());
|
||||||
groupingMode.setSelectedIndex(model.getGroupingMode());
|
groupingMode.setSelectedIndex(model.getGroupingMode());
|
||||||
addAssertions.setSelected(model.getAssertions());
|
addAssertions.setSelected(model.getAssertions());
|
||||||
|
|
@ -453,6 +459,10 @@ public class ProxyControlGui extends LogicControllerGui implements JMeterGUIComp
|
||||||
private void startProxy() {
|
private void startProxy() {
|
||||||
ValueReplacer replacer = GuiPackage.getInstance().getReplacer();
|
ValueReplacer replacer = GuiPackage.getInstance().getReplacer();
|
||||||
modifyTestElement(model);
|
modifyTestElement(model);
|
||||||
|
// Proxy can take some while to start up; show a wating cursor
|
||||||
|
Cursor cursor = getCursor();
|
||||||
|
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||||
|
// TODO somehow show progress
|
||||||
try {
|
try {
|
||||||
replacer.replaceValues(model);
|
replacer.replaceValues(model);
|
||||||
model.startProxy();
|
model.startProxy();
|
||||||
|
|
@ -474,6 +484,8 @@ public class ProxyControlGui extends LogicControllerGui implements JMeterGUIComp
|
||||||
JMeterUtils.getResString("proxy_daemon_error"), // $NON-NLS-1$
|
JMeterUtils.getResString("proxy_daemon_error"), // $NON-NLS-1$
|
||||||
"Error",
|
"Error",
|
||||||
JOptionPane.ERROR_MESSAGE);
|
JOptionPane.ERROR_MESSAGE);
|
||||||
|
} finally {
|
||||||
|
setCursor(cursor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -584,9 +596,13 @@ public class ProxyControlGui extends LogicControllerGui implements JMeterGUIComp
|
||||||
HorizontalPanel panel = new HorizontalPanel();
|
HorizontalPanel panel = new HorizontalPanel();
|
||||||
panel.add(label);
|
panel.add(label);
|
||||||
panel.add(portField);
|
panel.add(portField);
|
||||||
|
panel.add(Box.createHorizontalStrut(10));
|
||||||
|
|
||||||
gPane.add(panel, BorderLayout.WEST);
|
gPane.add(panel, BorderLayout.WEST);
|
||||||
gPane.add(Box.createHorizontalStrut(10));
|
|
||||||
|
sslDomains = new JLabeledTextField(JMeterUtils.getResString("proxy_domains")); // $NON-NLS-1$
|
||||||
|
sslDomains.setEnabled(ProxyControl.isDynamicMode());
|
||||||
|
gPane.add(sslDomains, BorderLayout.CENTER);
|
||||||
return gPane;
|
return gPane;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue