Java tutorial
/* * 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 jproxy; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.UnrecoverableKeyException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.prefs.Preferences; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.digest.DigestUtils; 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.config.Arguments; import org.apache.jmeter.config.ConfigElement; import org.apache.jmeter.config.ConfigTestElement; import org.apache.jmeter.control.GenericController; import org.apache.jmeter.control.TransactionController; import org.apache.jmeter.control.gui.LogicControllerGui; import org.apache.jmeter.control.gui.TransactionControllerGui; import org.apache.jmeter.engine.util.ValueReplacer; import org.apache.jmeter.exceptions.IllegalUserActionException; import org.apache.jmeter.functions.InvalidVariableException; import org.apache.jmeter.gui.GuiPackage; import org.apache.jmeter.gui.tree.JMeterTreeModel; import org.apache.jmeter.gui.tree.JMeterTreeNode; import org.apache.jmeter.protocol.http.control.AuthManager; import org.apache.jmeter.protocol.http.control.Authorization; import org.apache.jmeter.protocol.http.control.Header; import org.apache.jmeter.protocol.http.control.HeaderManager; import org.apache.jmeter.protocol.http.control.RecordingController; import org.apache.jmeter.protocol.http.gui.AuthPanel; import org.apache.jmeter.protocol.http.gui.HeaderPanel; import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult; import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; import org.apache.jmeter.protocol.http.sampler.HTTPSamplerFactory; import org.apache.jmeter.protocol.http.util.HTTPConstants; import org.apache.jmeter.protocol.http.util.accesslog.LogFilter; import org.apache.jmeter.samplers.SampleEvent; import org.apache.jmeter.samplers.SampleListener; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.testelement.TestElement; import org.apache.jmeter.testelement.TestPlan; import org.apache.jmeter.testelement.TestStateListener; import org.apache.jmeter.testelement.WorkBench; import org.apache.jmeter.testelement.property.BooleanProperty; import org.apache.jmeter.testelement.property.CollectionProperty; import org.apache.jmeter.testelement.property.IntegerProperty; import org.apache.jmeter.testelement.property.JMeterProperty; import org.apache.jmeter.testelement.property.PropertyIterator; import org.apache.jmeter.testelement.property.StringProperty; import org.apache.jmeter.testelement.property.TestElementProperty; import org.apache.jmeter.threads.AbstractThreadGroup; import org.apache.jmeter.timers.Timer; import org.apache.jmeter.util.JMeterUtils; import org.apache.jorphan.exec.KeyToolUtils; import org.apache.jorphan.logging.LoggingManager; import org.apache.jorphan.util.JOrphanUtils; import org.apache.log.Logger; import org.apache.log.format.RawFormatter; import org.apache.log.output.io.WriterTarget; import org.apache.oro.text.MalformedCachePatternException; import org.apache.oro.text.regex.Pattern; import org.apache.oro.text.regex.Perl5Compiler; //For unit tests, @see TestProxyControl /** * Class handles storing of generated samples, etc */ public class ProxyControl extends GenericController { private static final Logger log = LoggingManager.getLoggerForClass(); private static final String TARGET_CATEGORY = "REQUEST_LOGGING"; private static final Logger logToFile = LoggingManager.getLoggerFor(TARGET_CATEGORY); private static int logFilePrefix = 0; private static String logSessionDir = ""; private static final long serialVersionUID = 240L; // private static final String ASSERTION_GUI = AssertionGui.class.getName(); private static final String TRANSACTION_CONTROLLER_GUI = TransactionControllerGui.class.getName(); private static final String LOGIC_CONTROLLER_GUI = LogicControllerGui.class.getName(); private static final String HEADER_PANEL = HeaderPanel.class.getName(); private static final String AUTH_PANEL = AuthPanel.class.getName(); private static final String AUTH_MANAGER = AuthManager.class.getName(); public static final int DEFAULT_PORT = 8888; // and as a string public static final String DEFAULT_PORT_S = Integer.toString(DEFAULT_PORT);// Used by GUI //+ JMX file attributes 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 INCLUDE_LIST = "ProxyControlGui.include_list"; // $NON-NLS-1$ private static final String CAPTURE_HTTP_HEADERS = "ProxyControlGui.capture_http_headers"; // $NON-NLS-1$ private static final String ADD_ASSERTIONS = "ProxyControlGui.add_assertion"; // $NON-NLS-1$ private static final String GROUPING_MODE = "ProxyControlGui.grouping_mode"; // $NON-NLS-1$ private static final String SAMPLER_TYPE_NAME = "ProxyControlGui.sampler_type_name"; // $NON-NLS-1$ private static final String SAMPLER_REDIRECT_AUTOMATICALLY = "ProxyControlGui.sampler_redirect_automatically"; // $NON-NLS-1$ private static final String SAMPLER_FOLLOW_REDIRECTS = "ProxyControlGui.sampler_follow_redirects"; // $NON-NLS-1$ private static final String USE_KEEPALIVE = "ProxyControlGui.use_keepalive"; // $NON-NLS-1$ private static final String SAMPLER_DOWNLOAD_IMAGES = "ProxyControlGui.sampler_download_images"; // $NON-NLS-1$ private static final String REGEX_MATCH = "ProxyControlGui.regex_match"; // $NON-NLS-1$ private static final String CONTENT_TYPE_EXCLUDE = "ProxyControlGui.content_type_exclude"; // $NON-NLS-1$ private static final String CONTENT_TYPE_INCLUDE = "ProxyControlGui.content_type_include"; // $NON-NLS-1$ private static final String NOTIFY_CHILD_SAMPLER_LISTENERS_FILTERED = "ProxyControlGui.notify_child_sl_filtered"; // $NON-NLS-1$ private static final String BASIC_AUTH = "Basic"; // $NON-NLS-1$ private static final String DIGEST_AUTH = "Digest"; // $NON-NLS-1$ //- JMX file attributes // Must agree with the order of entries in the drop-down // created in ProxyControlGui.createGroupingPanel() //private static final int GROUPING_NO_GROUPS = 0; private static final int GROUPING_ADD_SEPARATORS = 1; private static final int GROUPING_IN_SIMPLE_CONTROLLERS = 2; private static final int GROUPING_STORE_FIRST_ONLY = 3; private static final int GROUPING_IN_TRANSACTION_CONTROLLERS = 4; // Original numeric order (we now use strings) private static final String SAMPLER_TYPE_HTTP_SAMPLER_JAVA = "0"; private static final String SAMPLER_TYPE_HTTP_SAMPLER_HC3_1 = "1"; private static final String SAMPLER_TYPE_HTTP_SAMPLER_HC4 = "2"; private static final long sampleGap = JMeterUtils.getPropDefault("proxy.pause", 5000); // $NON-NLS-1$ // Detect if user has pressed a new link // for ssl connection private static final String KEYSTORE_TYPE = JMeterUtils.getPropDefault("proxy.cert.type", "JKS"); // $NON-NLS-1$ $NON-NLS-2$ // 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 PREFERENCES = 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$ // If this is defined, it is assumed to be the alias of a user-supplied certificate; overrides dynamic mode static final String CERT_ALIAS = JMeterUtils.getProperty("proxy.cert.alias"); // $NON-NLS-1$ public enum KeystoreMode { USER_KEYSTORE, // user-provided keystore JMETER_KEYSTORE, // keystore generated by JMeter; single entry DYNAMIC_KEYSTORE, // keystore generated by JMeter; dynamic entries NONE // cannot use keystore } static final KeystoreMode KEYSTORE_MODE; static { if (CERT_ALIAS != null) { KEYSTORE_MODE = KeystoreMode.USER_KEYSTORE; log.info("HTTP(S) Test Script Recorder will use the keystore '" + CERT_PATH_ABS + "' with the alias: '" + CERT_ALIAS + "'"); } else { if (!KeyToolUtils.haveKeytool()) { KEYSTORE_MODE = KeystoreMode.NONE; } else if (USE_DYNAMIC_KEYS) { KEYSTORE_MODE = KeystoreMode.DYNAMIC_KEYSTORE; log.info( "HTTP(S) Test Script Recorder SSL Proxy will use keys that support embedded 3rd party resources in file " + CERT_PATH_ABS); } else { KEYSTORE_MODE = KeystoreMode.JMETER_KEYSTORE; log.warn( "HTTP(S) Test Script Recorder SSL Proxy will use keys that may not work for embedded resources in file " + CERT_PATH_ABS); } } } // Whether to use the redirect disabling feature (can be switched off if it does not work) private static final boolean ATTEMPT_REDIRECT_DISABLING = JMeterUtils.getPropDefault("proxy.redirect.disabling", true); // $NON-NLS-1$ // Although this field is mutable, it is only accessed within the synchronized method deliverSampler() private static String LAST_REDIRECT = null; /* * TODO this assumes that the redirected response will always immediately follow the original response. * This may not always be true. * Is there a better way to do this? */ private transient Daemon server; private long lastTime = 0;// When was the last sample seen? private transient KeyStore keyStore; private volatile boolean addAssertions = false; private volatile int groupingMode = 0; private volatile boolean samplerRedirectAutomatically = false; private volatile boolean samplerFollowRedirects = false; private volatile boolean useKeepAlive = false; private volatile boolean samplerDownloadImages = false; private volatile boolean notifyChildSamplerListenersOfFilteredSamples = true; private volatile boolean regexMatch = false;// Should we match using regexes? /** * Tree node where the samples should be stored. * <p> * This property is not persistent. */ private JMeterTreeNode target; private String storePassword; private String keyPassword; public ProxyControl() { setPort(DEFAULT_PORT); setExcludeList(new HashSet<String>()); setIncludeList(new HashSet<String>()); setCaptureHttpHeaders(true); // maintain original behaviour } public static void setSessionDir(String dirName) { logSessionDir = dirName; new File(logSessionDir).mkdir(); } public void setPort(int port) { this.setProperty(new IntegerProperty(PORT, port)); } public void setPort(String port) { setProperty(PORT, port); } public void setSslDomains(String domains) { setProperty(DOMAINS, domains, ""); } public String getSslDomains() { return getPropertyAsString(DOMAINS, ""); } public void setCaptureHttpHeaders(boolean capture) { setProperty(new BooleanProperty(CAPTURE_HTTP_HEADERS, capture)); } public void setGroupingMode(int grouping) { this.groupingMode = grouping; setProperty(new IntegerProperty(GROUPING_MODE, grouping)); } public void setAssertions(boolean b) { addAssertions = b; setProperty(new BooleanProperty(ADD_ASSERTIONS, b)); } @Deprecated public void setSamplerTypeName(int samplerTypeName) { setProperty(new IntegerProperty(SAMPLER_TYPE_NAME, samplerTypeName)); } public void setSamplerTypeName(String samplerTypeName) { setProperty(new StringProperty(SAMPLER_TYPE_NAME, samplerTypeName)); } public void setSamplerRedirectAutomatically(boolean b) { samplerRedirectAutomatically = b; setProperty(new BooleanProperty(SAMPLER_REDIRECT_AUTOMATICALLY, b)); } public void setSamplerFollowRedirects(boolean b) { samplerFollowRedirects = b; setProperty(new BooleanProperty(SAMPLER_FOLLOW_REDIRECTS, b)); } /** * @param b flag whether keep alive should be used */ public void setUseKeepAlive(boolean b) { useKeepAlive = b; setProperty(new BooleanProperty(USE_KEEPALIVE, b)); } public void setSamplerDownloadImages(boolean b) { samplerDownloadImages = b; setProperty(new BooleanProperty(SAMPLER_DOWNLOAD_IMAGES, b)); } public void setNotifyChildSamplerListenerOfFilteredSamplers(boolean b) { notifyChildSamplerListenersOfFilteredSamples = b; setProperty(new BooleanProperty(NOTIFY_CHILD_SAMPLER_LISTENERS_FILTERED, b)); } public void setIncludeList(Collection<String> list) { setProperty(new CollectionProperty(INCLUDE_LIST, new HashSet<>(list))); } public void setExcludeList(Collection<String> list) { setProperty(new CollectionProperty(EXCLUDE_LIST, new HashSet<>(list))); } /** * @param b flag whether regex matching should be used */ public void setRegexMatch(boolean b) { regexMatch = b; setProperty(new BooleanProperty(REGEX_MATCH, b)); } public void setContentTypeExclude(String contentTypeExclude) { setProperty(new StringProperty(CONTENT_TYPE_EXCLUDE, contentTypeExclude)); } public void setContentTypeInclude(String contentTypeInclude) { setProperty(new StringProperty(CONTENT_TYPE_INCLUDE, contentTypeInclude)); } public boolean getAssertions() { return getPropertyAsBoolean(ADD_ASSERTIONS); } public int getGroupingMode() { return getPropertyAsInt(GROUPING_MODE); } public int getPort() { return getPropertyAsInt(PORT); } public String getPortString() { return getPropertyAsString(PORT); } public int getDefaultPort() { return DEFAULT_PORT; } public boolean getCaptureHttpHeaders() { return getPropertyAsBoolean(CAPTURE_HTTP_HEADERS); } public String getSamplerTypeName() { // Convert the old numeric types - just in case someone wants to reload the workbench String type = getPropertyAsString(SAMPLER_TYPE_NAME); if (SAMPLER_TYPE_HTTP_SAMPLER_JAVA.equals(type)) { type = HTTPSamplerFactory.IMPL_JAVA; } else if (SAMPLER_TYPE_HTTP_SAMPLER_HC3_1.equals(type)) { type = HTTPSamplerFactory.IMPL_HTTP_CLIENT3_1; } else if (SAMPLER_TYPE_HTTP_SAMPLER_HC4.equals(type)) { type = HTTPSamplerFactory.IMPL_HTTP_CLIENT4; } return type; } public boolean getSamplerRedirectAutomatically() { return getPropertyAsBoolean(SAMPLER_REDIRECT_AUTOMATICALLY, false); } public boolean getSamplerFollowRedirects() { return getPropertyAsBoolean(SAMPLER_FOLLOW_REDIRECTS, true); } public boolean getUseKeepalive() { return getPropertyAsBoolean(USE_KEEPALIVE, true); } public boolean getSamplerDownloadImages() { return getPropertyAsBoolean(SAMPLER_DOWNLOAD_IMAGES, false); } public boolean getNotifyChildSamplerListenerOfFilteredSamplers() { return getPropertyAsBoolean(NOTIFY_CHILD_SAMPLER_LISTENERS_FILTERED, true); } public boolean getRegexMatch() { return getPropertyAsBoolean(REGEX_MATCH, false); } public String getContentTypeExclude() { return getPropertyAsString(CONTENT_TYPE_EXCLUDE); } public String getContentTypeInclude() { return getPropertyAsString(CONTENT_TYPE_INCLUDE); } public void addConfigElement(ConfigElement config) { // NOOP } 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(); try { server = new Daemon(getPort(), this); server.start(); // Disable GUI interaction //GuiPackage.getInstance().register(server); } catch (IOException e) { log.error("Could not create Proxy daemon", e); throw e; } } public void addExcludedPattern(String pattern) { getExcludePatterns().addItem(pattern); } public CollectionProperty getExcludePatterns() { return (CollectionProperty) getProperty(EXCLUDE_LIST); } public void addIncludedPattern(String pattern) { getIncludePatterns().addItem(pattern); } public CollectionProperty getIncludePatterns() { return (CollectionProperty) getProperty(INCLUDE_LIST); } public void clearExcludedPatterns() { getExcludePatterns().clear(); } public void clearIncludedPatterns() { getIncludePatterns().clear(); } /** * @return the target controller node */ public JMeterTreeNode getTarget() { return target; } /** * Sets the target node where the samples generated by the proxy have to be * stored. * * @param target target node to store generated samples */ public void setTarget(JMeterTreeNode target) { this.target = target; } public synchronized void logRawdata(final HttpRequestHdr request, final HTTPSamplerBase sampler, final SampleResult result) { if (sampler != null) { if (filterContentType(result) && filterUrl(sampler)) { // set logging for HTTP(S) row data to separate file // create folder for session if (logSessionDir.equals("")) { logSessionDir = "session_" + new Date().getTime(); new File(logSessionDir).mkdir(); } try { logFilePrefix++; FileWriter requestWriter = new FileWriter( logSessionDir + File.separator + "request_" + logFilePrefix + ".txt"); WriterTarget logTarget = new WriterTarget(requestWriter, new RawFormatter()); LoggingManager.setTarget(logTarget, TARGET_CATEGORY); logToFile.info(new String(request.getRawRequestData())); FileWriter responsetWriter = new FileWriter( logSessionDir + File.separator + "response_" + logFilePrefix + ".txt"); WriterTarget logTargetResponse = new WriterTarget(responsetWriter, new RawFormatter()); LoggingManager.setTarget(logTargetResponse, TARGET_CATEGORY); logToFile.info(new String(result.getResponseHeaders())); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } ; } } /** * Receives the recorded sampler from the proxy server for placing in the * test tree; this is skipped if the sampler is null (e.g. for recording SSL errors) * Always sends the result to any registered sample listeners. * * @param sampler the sampler, may be null * @param subConfigs the configuration elements to be added (e.g. header namager) * @param result the sample result, not null * TODO param serverResponse to be added to allow saving of the * server's response while recording. */ public synchronized void deliverSampler(final HTTPSamplerBase sampler, final TestElement[] subConfigs, final SampleResult result) { // boolean notifySampleListeners = true; boolean notifySampleListeners = false; if (sampler != null) { if (ATTEMPT_REDIRECT_DISABLING && (samplerRedirectAutomatically || samplerFollowRedirects)) { if (result instanceof HTTPSampleResult) { final HTTPSampleResult httpSampleResult = (HTTPSampleResult) result; final String urlAsString = httpSampleResult.getUrlAsString(); if (urlAsString.equals(LAST_REDIRECT)) { // the url matches the last redirect sampler.setEnabled(false); sampler.setComment("Detected a redirect from the previous sample"); } else { // this is not the result of a redirect LAST_REDIRECT = null; // so break the chain } if (httpSampleResult.isRedirect()) { // Save Location so resulting sample can be disabled if (LAST_REDIRECT == null) { sampler.setComment("Detected the start of a redirect chain"); } LAST_REDIRECT = httpSampleResult.getRedirectLocation(); } else { LAST_REDIRECT = null; } } } if (filterContentType(result) && filterUrl(sampler)) { JMeterTreeNode myTarget = findTargetControllerNode(); @SuppressWarnings("unchecked") // OK, because find only returns correct element types Collection<ConfigTestElement> defaultConfigurations = (Collection<ConfigTestElement>) findApplicableElements( myTarget, ConfigTestElement.class, false); @SuppressWarnings("unchecked") // OK, because find only returns correct element types Collection<Arguments> userDefinedVariables = (Collection<Arguments>) findApplicableElements( myTarget, Arguments.class, true); removeValuesFromSampler(sampler, defaultConfigurations); replaceValues(sampler, subConfigs, userDefinedVariables); sampler.setAutoRedirects(samplerRedirectAutomatically); sampler.setFollowRedirects(samplerFollowRedirects); sampler.setUseKeepAlive(useKeepAlive); sampler.setImageParser(samplerDownloadImages); Authorization authorization = createAuthorization(subConfigs, sampler); if (authorization != null) { setAuthorization(authorization, myTarget); } placeSampler(sampler, subConfigs, myTarget); } else { if (log.isDebugEnabled()) { log.debug("Sample excluded based on url or content-type: " + result.getUrlAsString() + " - " + result.getContentType()); } notifySampleListeners = notifyChildSamplerListenersOfFilteredSamples; result.setSampleLabel("[" + result.getSampleLabel() + "]"); } } if (notifySampleListeners) { // SampleEvent is not passed JMeterVariables, because they don't make sense for Proxy Recording notifySampleListeners(new SampleEvent(result, "WorkBench")); // TODO - is this the correct threadgroup name? } else { log.debug("Sample not delivered to Child Sampler Listener based on url or content-type: " + result.getUrlAsString() + " - " + result.getContentType()); } } /** * Detect Header manager in subConfigs, * Find(if any) Authorization header * Construct Authentication object * Removes Authorization if present * * @param subConfigs {@link TestElement}[] * @param sampler {@link HTTPSamplerBase} * @return {@link Authorization} */ private Authorization createAuthorization(final TestElement[] subConfigs, HTTPSamplerBase sampler) { Header authHeader = null; Authorization authorization = null; // Iterate over subconfig elements searching for HeaderManager for (TestElement te : subConfigs) { if (te instanceof HeaderManager) { @SuppressWarnings("unchecked") // headers should only contain the correct classes List<TestElementProperty> headers = (ArrayList<TestElementProperty>) ((HeaderManager) te) .getHeaders().getObjectValue(); for (Iterator<?> iterator = headers.iterator(); iterator.hasNext();) { TestElementProperty tep = (TestElementProperty) iterator.next(); if (tep.getName().equals(HTTPConstants.HEADER_AUTHORIZATION)) { //Construct Authorization object from HEADER_AUTHORIZATION authHeader = (Header) tep.getObjectValue(); String[] authHeaderContent = authHeader.getValue().split(" ");//$NON-NLS-1$ String authType = null; String authCredentialsBase64 = null; if (authHeaderContent.length >= 2) { authType = authHeaderContent[0]; authCredentialsBase64 = authHeaderContent[1]; authorization = new Authorization(); try { authorization.setURL(sampler.getUrl().toExternalForm()); } catch (MalformedURLException e) { log.error("Error filling url on authorization, message:" + e.getMessage(), e); authorization.setURL("${AUTH_BASE_URL}");//$NON-NLS-1$ } // if HEADER_AUTHORIZATION contains "Basic" // then set Mechanism.BASIC_DIGEST, otherwise Mechanism.KERBEROS authorization.setMechanism(authType.equals(BASIC_AUTH) || authType.equals(DIGEST_AUTH) ? AuthManager.Mechanism.BASIC_DIGEST : AuthManager.Mechanism.KERBEROS); if (BASIC_AUTH.equals(authType)) { String authCred = new String(Base64.decodeBase64(authCredentialsBase64)); String[] loginPassword = authCred.split(":"); //$NON-NLS-1$ authorization.setUser(loginPassword[0]); authorization.setPass(loginPassword[1]); } else { // Digest or Kerberos authorization.setUser("${AUTH_LOGIN}");//$NON-NLS-1$ authorization.setPass("${AUTH_PASSWORD}");//$NON-NLS-1$ } } // remove HEADER_AUTHORIZATION from HeaderManager // because it's useless after creating Authorization object iterator.remove(); } } } } return authorization; } public void stopProxy() { if (server != null) { server.stopServer(); //GuiPackage.getInstance().unregister(server); try { server.join(1000); // wait for server to stop } catch (InterruptedException e) { //NOOP } notifyTestListenersOfEnd(); server = null; } } public String[] getCertificateDetails() { if (isDynamicMode()) { try { X509Certificate caCert = (X509Certificate) keyStore.getCertificate(KeyToolUtils.getRootCAalias()); if (caCert == null) { return new String[] { "Could not find certificate" }; } return new String[] { caCert.getSubjectX500Principal().toString(), "Fingerprint(SHA1): " + JOrphanUtils.baToHexString(DigestUtils.sha1(caCert.getEncoded()), ' '), "Created: " + caCert.getNotBefore().toString() }; } catch (GeneralSecurityException e) { log.error("Problem reading root CA from keystore", e); return new String[] { "Problem with root certificate", e.getMessage() }; } } return null; // should not happen } // Package protected to allow test case access boolean filterUrl(HTTPSamplerBase sampler) { String domain = sampler.getDomain(); if (domain == null || domain.length() == 0) { return false; } String url = generateMatchUrl(sampler); CollectionProperty includePatterns = getIncludePatterns(); if (includePatterns.size() > 0) { if (!matchesPatterns(url, includePatterns)) { return false; } } CollectionProperty excludePatterns = getExcludePatterns(); if (excludePatterns.size() > 0) { if (matchesPatterns(url, excludePatterns)) { return false; } } return true; } // Package protected to allow test case access /** * Filter the response based on the content type. * If no include nor exclude filter is specified, the result will be included * * @param result the sample result to check * @return <code>true</code> means result will be kept */ boolean filterContentType(SampleResult result) { String includeExp = getContentTypeInclude(); String excludeExp = getContentTypeExclude(); // If no expressions are specified, we let the sample pass if ((includeExp == null || includeExp.length() == 0) && (excludeExp == null || excludeExp.length() == 0)) { return true; } // Check that we have a content type String sampleContentType = result.getContentType(); if (sampleContentType == null || sampleContentType.length() == 0) { if (log.isDebugEnabled()) { log.debug("No Content-type found for : " + result.getUrlAsString()); } return true; } if (log.isDebugEnabled()) { log.debug("Content-type to filter : " + sampleContentType); } // Check if the include pattern is matched boolean matched = testPattern(includeExp, sampleContentType, true); if (!matched) { return false; } // Check if the exclude pattern is matched matched = testPattern(excludeExp, sampleContentType, false); if (!matched) { return false; } return true; } /** * Returns true if matching pattern was different from expectedToMatch * @param expression Expression to match * @param sampleContentType * @return boolean true if Matching expression */ private boolean testPattern(String expression, String sampleContentType, boolean expectedToMatch) { if (expression != null && expression.length() > 0) { if (log.isDebugEnabled()) { log.debug("Testing Expression : " + expression + " on sampleContentType:" + sampleContentType + ", expected to match:" + expectedToMatch); } Pattern pattern = null; try { pattern = JMeterUtils.getPatternCache().getPattern(expression, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.SINGLELINE_MASK); if (JMeterUtils.getMatcher().contains(sampleContentType, pattern) != expectedToMatch) { return false; } } catch (MalformedCachePatternException e) { log.warn("Skipped invalid content pattern: " + expression, e); } } return true; } /** * Find if there is any AuthManager in JMeterTreeModel * If there is no one, create and add it to tree * Add authorization object to AuthManager * @param authorization {@link Authorization} * @param target {@link JMeterTreeNode} */ private void setAuthorization(Authorization authorization, JMeterTreeNode target) { JMeterTreeModel jmeterTreeModel = GuiPackage.getInstance().getTreeModel(); List<JMeterTreeNode> authManagerNodes = jmeterTreeModel.getNodesOfType(AuthManager.class); if (authManagerNodes.size() == 0) { try { log.debug("Creating HTTP Authentication manager for authorization:" + authorization); AuthManager authManager = newAuthorizationManager(authorization); jmeterTreeModel.addComponent(authManager, target); } catch (IllegalUserActionException e) { log.error("Failed to add Authorization Manager to target node:" + target.getName(), e); } } else { AuthManager authManager = (AuthManager) authManagerNodes.get(0).getTestElement(); authManager.addAuth(authorization); } } /** * Helper method to add a Response Assertion * Called from AWT Event thread */ private void addAssertion(JMeterTreeModel model, JMeterTreeNode node) throws IllegalUserActionException { // ResponseAssertion ra = new ResponseAssertion(); // ra.setProperty(TestElement.GUI_CLASS, ASSERTION_GUI); // ra.setName(JMeterUtils.getResString("assertion_title")); // $NON-NLS-1$ // ra.setTestFieldResponseData(); // model.addComponent(ra, node); } /** * Construct AuthManager * @param authorization * @return AuthManager * @throws IllegalUserActionException */ private AuthManager newAuthorizationManager(Authorization authorization) throws IllegalUserActionException { AuthManager authManager = new AuthManager(); authManager.setProperty(TestElement.GUI_CLASS, AUTH_PANEL); authManager.setProperty(TestElement.TEST_CLASS, AUTH_MANAGER); authManager.setName("HTTP Authorization Manager"); authManager.addAuth(authorization); return authManager; } /** * Helper method to add a Divider * Called from Application Thread that needs to update GUI (JMeterTreeModel) */ private void addDivider(final JMeterTreeModel model, final JMeterTreeNode node) { final GenericController sc = new GenericController(); sc.setProperty(TestElement.GUI_CLASS, LOGIC_CONTROLLER_GUI); sc.setName("-------------------"); // $NON-NLS-1$ safelyAddComponent(model, node, sc); } private void safelyAddComponent(final JMeterTreeModel model, final JMeterTreeNode node, final GenericController controller) { // JMeterUtils.runSafe(true, new Runnable() { // @Override // public void run() { // try { // model.addComponent(controller, node); // } catch (IllegalUserActionException e) { // log.error("Program error", e); // throw new Error(e); // } // } // }); } /** * Helper method to add a Simple Controller to contain the samplers. * Called from Application Thread that needs to update GUI (JMeterTreeModel) * @param model * Test component tree model * @param node * Node in the tree where we will add the Controller * @param name * A name for the Controller * @throws InvocationTargetException * @throws InterruptedException */ private void addSimpleController(final JMeterTreeModel model, final JMeterTreeNode node, String name) throws InterruptedException, InvocationTargetException { final GenericController sc = new GenericController(); sc.setProperty(TestElement.GUI_CLASS, LOGIC_CONTROLLER_GUI); sc.setName(name); safelyAddComponent(model, node, sc); } /** * Helper method to add a Transaction Controller to contain the samplers. * Called from Application Thread that needs to update GUI (JMeterTreeModel) * @param model * Test component tree model * @param node * Node in the tree where we will add the Controller * @param name * A name for the Controller * @throws InvocationTargetException * @throws InterruptedException */ private void addTransactionController(final JMeterTreeModel model, final JMeterTreeNode node, String name) throws InterruptedException, InvocationTargetException { final TransactionController sc = new TransactionController(); sc.setIncludeTimers(false); sc.setProperty(TestElement.GUI_CLASS, TRANSACTION_CONTROLLER_GUI); sc.setName(name); safelyAddComponent(model, node, sc); } /** * Helper method to replicate any timers found within the Proxy Controller * into the provided sampler, while replacing any occurrences of string _T_ * in the timer's configuration with the provided deltaT. * Called from AWT Event thread * @param model * Test component tree model * @param node * Sampler node in where we will add the timers * @param deltaT * Time interval from the previous request */ private void addTimers(JMeterTreeModel model, JMeterTreeNode node, long deltaT) { TestPlan variables = new TestPlan(); variables.addParameter("T", Long.toString(deltaT)); // $NON-NLS-1$ ValueReplacer replacer = new ValueReplacer(variables); JMeterTreeNode mySelf = model.getNodeOf(this); Enumeration<JMeterTreeNode> children = mySelf.children(); while (children.hasMoreElements()) { JMeterTreeNode templateNode = children.nextElement(); if (templateNode.isEnabled()) { TestElement template = templateNode.getTestElement(); if (template instanceof Timer) { TestElement timer = (TestElement) template.clone(); try { replacer.undoReverseReplace(timer); model.addComponent(timer, node); } catch (InvalidVariableException | IllegalUserActionException e) { // Not 100% sure, but I believe this can't happen, so // I'll log and throw an error: log.error("Program error", e); throw new Error(e); } } } } } /** * Finds the first enabled node of a given type in the tree. * * @param type * class of the node to be found * * @return the first node of the given type in the test component tree, or * <code>null</code> if none was found. */ private JMeterTreeNode findFirstNodeOfType(Class<?> type) { JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel(); List<JMeterTreeNode> nodes = treeModel.getNodesOfType(type); for (JMeterTreeNode node : nodes) { if (node.isEnabled()) { return node; } } return null; } /** * Finds the controller where samplers have to be stored, that is: * <ul> * <li>The controller specified by the <code>target</code> property. * <li>If none was specified, the first RecordingController in the tree. * <li>If none is found, the first AbstractThreadGroup in the tree. * <li>If none is found, the Workspace. * </ul> * * @return the tree node for the controller where the proxy must store the * generated samplers. */ public JMeterTreeNode findTargetControllerNode() { JMeterTreeNode myTarget = getTarget(); if (myTarget != null) { return myTarget; } myTarget = findFirstNodeOfType(RecordingController.class); if (myTarget != null) { return myTarget; } myTarget = findFirstNodeOfType(AbstractThreadGroup.class); if (myTarget != null) { return myTarget; } myTarget = findFirstNodeOfType(WorkBench.class); if (myTarget != null) { return myTarget; } log.error("Program error: test script recording target not found."); return null; } /** * Finds all configuration objects of the given class applicable to the * recorded samplers, that is: * <ul> * <li>All such elements directly within the HTTP(S) Test Script Recorder (these have * the highest priority). * <li>All such elements directly within the target controller (higher * priority) or directly within any containing controller (lower priority), * including the Test Plan itself (lowest priority). * </ul> * * @param myTarget * tree node for the recording target controller. * @param myClass * Class of the elements to be found. * @param ascending * true if returned elements should be ordered in ascending * priority, false if they should be in descending priority. * * @return a collection of applicable objects of the given class. */ // TODO - could be converted to generic class? private Collection<?> findApplicableElements(JMeterTreeNode myTarget, Class<? extends TestElement> myClass, boolean ascending) { JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel(); LinkedList<TestElement> elements = new LinkedList<>(); // Look for elements directly within the HTTP proxy: Enumeration<?> kids = treeModel.getNodeOf(this).children(); while (kids.hasMoreElements()) { JMeterTreeNode subNode = (JMeterTreeNode) kids.nextElement(); if (subNode.isEnabled()) { TestElement element = (TestElement) subNode.getUserObject(); if (myClass.isInstance(element)) { if (ascending) { elements.addFirst(element); } else { elements.add(element); } } } } // Look for arguments elements in the target controller or higher up: for (JMeterTreeNode controller = myTarget; controller != null; controller = (JMeterTreeNode) controller .getParent()) { kids = controller.children(); while (kids.hasMoreElements()) { JMeterTreeNode subNode = (JMeterTreeNode) kids.nextElement(); if (subNode.isEnabled()) { TestElement element = (TestElement) subNode.getUserObject(); if (myClass.isInstance(element)) { log.debug("Applicable: " + element.getName()); if (ascending) { elements.addFirst(element); } else { elements.add(element); } } // Special case for the TestPlan's Arguments sub-element: if (element instanceof TestPlan) { TestPlan tp = (TestPlan) element; Arguments args = tp.getArguments(); if (myClass.isInstance(args)) { if (ascending) { elements.addFirst(args); } else { elements.add(args); } } } } } } return elements; } private void placeSampler(final HTTPSamplerBase sampler, final TestElement[] subConfigs, JMeterTreeNode myTarget) { try { final JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel(); boolean firstInBatch = false; long now = System.currentTimeMillis(); long deltaT = now - lastTime; int cachedGroupingMode = groupingMode; if (deltaT > sampleGap) { if (!myTarget.isLeaf() && cachedGroupingMode == GROUPING_ADD_SEPARATORS) { addDivider(treeModel, myTarget); } if (cachedGroupingMode == GROUPING_IN_SIMPLE_CONTROLLERS) { addSimpleController(treeModel, myTarget, sampler.getName()); } if (cachedGroupingMode == GROUPING_IN_TRANSACTION_CONTROLLERS) { addTransactionController(treeModel, myTarget, sampler.getName()); } firstInBatch = true;// Remember this was first in its batch } if (lastTime == 0) { deltaT = 0; // Decent value for timers } lastTime = now; if (cachedGroupingMode == GROUPING_STORE_FIRST_ONLY) { if (!firstInBatch) { return; // Huh! don't store this one! } // If we're not storing subsequent samplers, we'll need the // first sampler to do all the work...: sampler.setFollowRedirects(true); sampler.setImageParser(true); } if (cachedGroupingMode == GROUPING_IN_SIMPLE_CONTROLLERS || cachedGroupingMode == GROUPING_IN_TRANSACTION_CONTROLLERS) { // Find the last controller in the target to store the // sampler there: for (int i = myTarget.getChildCount() - 1; i >= 0; i--) { JMeterTreeNode c = (JMeterTreeNode) myTarget.getChildAt(i); if (c.getTestElement() instanceof GenericController) { myTarget = c; break; } } } final long deltaTFinal = deltaT; final boolean firstInBatchFinal = firstInBatch; final JMeterTreeNode myTargetFinal = myTarget; // JMeterUtils.runSafe(true, new Runnable() { // @Override // public void run() { // try { // final JMeterTreeNode newNode = treeModel.addComponent(sampler, myTargetFinal); // if (firstInBatchFinal) { // if (addAssertions) { // addAssertion(treeModel, newNode); // } // addTimers(treeModel, newNode, deltaTFinal); // } // // for (int i = 0; subConfigs != null && i < subConfigs.length; i++) { // if (subConfigs[i] instanceof HeaderManager) { // final TestElement headerManager = subConfigs[i]; // headerManager.setProperty(TestElement.GUI_CLASS, HEADER_PANEL); // treeModel.addComponent(headerManager, newNode); // } // } // } catch (IllegalUserActionException e) { // JMeterUtils.reportErrorToUser(e.getMessage()); // } // } // }); } catch (Exception e) { JMeterUtils.reportErrorToUser(e.getMessage()); } } /** * Remove from the sampler all values which match the one provided by the * first configuration in the given collection which provides a value for * that property. * * @param sampler * Sampler to remove values from. * @param configurations * ConfigTestElements in descending priority. */ private void removeValuesFromSampler(HTTPSamplerBase sampler, Collection<ConfigTestElement> configurations) { for (PropertyIterator props = sampler.propertyIterator(); props.hasNext();) { JMeterProperty prop = props.next(); String name = prop.getName(); String value = prop.getStringValue(); // There's a few properties which are excluded from this processing: if (name.equals(TestElement.ENABLED) || name.equals(TestElement.GUI_CLASS) || name.equals(TestElement.NAME) || name.equals(TestElement.TEST_CLASS)) { continue; // go on with next property. } for (ConfigTestElement config : configurations) { String configValue = config.getPropertyAsString(name); if (configValue != null && configValue.length() > 0) { if (configValue.equals(value)) { sampler.setProperty(name, ""); // $NON-NLS-1$ } // Property was found in a config element. Whether or not // it matched the value in the sampler, we're done with // this property -- don't look at lower-priority configs: break; } } } } private String generateMatchUrl(HTTPSamplerBase sampler) { StringBuilder buf = new StringBuilder(sampler.getDomain()); buf.append(':'); // $NON-NLS-1$ buf.append(sampler.getPort()); buf.append(sampler.getPath()); if (sampler.getQueryString().length() > 0) { buf.append('?'); // $NON-NLS-1$ buf.append(sampler.getQueryString()); } return buf.toString(); } private boolean matchesPatterns(String url, CollectionProperty patterns) { //for (JMeterProperty jMeterProperty : patterns) { //String item = jMeterProperty.getStringValue(); PropertyIterator i = patterns.iterator(); while (i.hasNext()) { String item = i.next().getStringValue(); try { Pattern pattern = JMeterUtils.getPatternCache().getPattern(item, Perl5Compiler.READ_ONLY_MASK | Perl5Compiler.SINGLELINE_MASK); if (JMeterUtils.getMatcher().matches(url, pattern)) { return true; } } catch (MalformedCachePatternException e) { log.warn("Skipped invalid pattern: " + item, e); } } return false; } /** * Scan all test elements passed in for values matching the value of any of * the variables in any of the variable-holding elements in the collection. * * @param sampler * A TestElement to replace values on * @param configs * More TestElements to replace values on * @param variables * Collection of Arguments to use to do the replacement, ordered * by ascending priority. */ private void replaceValues(TestElement sampler, TestElement[] configs, Collection<Arguments> variables) { // Build the replacer from all the variables in the collection: ValueReplacer replacer = new ValueReplacer(); for (Arguments variable : variables) { final Map<String, String> map = variable.getArgumentsAsMap(); for (Iterator<String> vals = map.values().iterator(); vals.hasNext();) { final Object next = vals.next(); if ("".equals(next)) {// Drop any empty values (Bug 45199) vals.remove(); } } replacer.addVariables(map); } try { boolean cachedRegexpMatch = regexMatch; replacer.reverseReplace(sampler, cachedRegexpMatch); for (TestElement config : configs) { if (config != null) { replacer.reverseReplace(config, cachedRegexpMatch); } } } catch (InvalidVariableException e) { log.warn("Invalid variables included for replacement into recorded " + "sample", e); } } /** * This will notify sample listeners directly within the Proxy of the * sampling that just occurred -- so that we have a means to record the * server's responses as we go. * * @param event * sampling event to be delivered */ private void notifySampleListeners(SampleEvent event) { JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel(); JMeterTreeNode myNode = treeModel.getNodeOf(this); Enumeration<JMeterTreeNode> kids = myNode.children(); while (kids.hasMoreElements()) { JMeterTreeNode subNode = kids.nextElement(); if (subNode.isEnabled()) { TestElement testElement = subNode.getTestElement(); if (testElement instanceof SampleListener) { ((SampleListener) testElement).sampleOccurred(event); } } } } /** * This will notify test listeners directly within the Proxy that the 'test' * (here meaning the proxy recording) has started. */ private void notifyTestListenersOfStart() { JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel(); JMeterTreeNode myNode = treeModel.getNodeOf(this); Enumeration<JMeterTreeNode> kids = myNode.children(); while (kids.hasMoreElements()) { JMeterTreeNode subNode = kids.nextElement(); if (subNode.isEnabled()) { TestElement testElement = subNode.getTestElement(); if (testElement instanceof TestStateListener) { ((TestStateListener) testElement).testStarted(); } } } } /** * This will notify test listeners directly within the Proxy that the 'test' * (here meaning the proxy recording) has ended. */ private void notifyTestListenersOfEnd() { JMeterTreeModel treeModel = GuiPackage.getInstance().getTreeModel(); JMeterTreeNode myNode = treeModel.getNodeOf(this); Enumeration<JMeterTreeNode> kids = myNode.children(); while (kids.hasMoreElements()) { JMeterTreeNode subNode = kids.nextElement(); if (subNode.isEnabled()) { TestElement testElement = subNode.getTestElement(); if (testElement instanceof TestStateListener) { // TL - TE ((TestStateListener) testElement).testEnded(); } } } } @Override public boolean canRemove() { 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("HTTP(S) Test Script Recorder will use the keystore '" + CERT_PATH_ABS + "' with the alias: '" + CERT_ALIAS + "'"); initUserKeyStore(); break; case NONE: throw new IOException("Cannot find keytool application and no keystore was provided"); 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()); for (String alias : KeyToolUtils.getCAaliases()) { X509Certificate caCert = (X509Certificate) keyStore.getCertificate(alias); if (caCert == null) { keyStore = null; // no CA key - probably the wrong store type. break; // cannot continue } else { caCert.checkValidity(new Date(System.currentTimeMillis() + DateUtils.MILLIS_PER_DAY)); log.info("Valid alias found for " + alias); } } } 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) { keyStore = null; // if cert is not valid, flag up to recreate it 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("\\."); return !parts[0].endsWith("*") // not a wildcard || 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); if (!CERT_PATH.delete()) { // safer to start afresh log.warn("Could not delete " + CERT_PATH.getAbsolutePath() + ", this could create issues, stop jmeter, ensure file is deleted and restart again"); } 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 PREFERENCES.get(USER_PASSWORD_KEY, null); } private void setPassword(String password) { PREFERENCES.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; } }