Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2541,7 +2541,9 @@ private void configureHttpClient(HttpClient client, ClientConnector connector) {
client.setDestinationIdleTimeout(idleTimeout);
}
client.setIdleTimeout(idleTimeout);
addConnectionLogging(client);
if (LowLevelDebugLog.isEnabled()) {
addConnectionLogging(client);
}
}

private static void addConnectionLogging(HttpClient client) {
Expand Down Expand Up @@ -2589,6 +2591,9 @@ public void handshakeFailed(Event event, Throwable failure) {
}

private static void logAlpnLine(String message) {
if (!LowLevelDebugLog.isEnabled()) {
return;
}
try {
Path parent = ALPN_DEBUG_LOG_PATH.getParent();
if (parent != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package com.blazemeter.jmeter.http2.core;

import static com.blazemeter.jmeter.http2.core.LowLevelDebugLog.lowLevelDebug;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;
import java.security.KeyStore;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.CRL;
import java.security.cert.X509Certificate;
import java.util.Collection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509KeyManager;
import org.apache.jmeter.util.JsseSSLManager;
Expand All @@ -22,9 +27,21 @@ public class JMeterJettySslContextFactory extends SslContextFactory.Client {

public JMeterJettySslContextFactory() {
setTrustAll(true);
setValidatePeerCerts(false);
String keyStorePath = System.getProperty("javax.net.ssl.keyStore");
if (keyStorePath != null && !keyStorePath.isEmpty()) {
setKeyStorePath("file://" + keyStorePath);
if (SslStorePathResolver.isFileBasedStoreLocation(keyStorePath)) {
String jettyKeyStoreUri = SslStorePathResolver.toJettyFileUri(keyStorePath);
String keyStoreType = SslStorePathResolver.resolveKeyStoreType(keyStorePath);
lowLevelDebug(
"SSL keyStore path resolved: javax.net.ssl.keyStore='{}' -> jettyUri='{}'",
keyStorePath, jettyKeyStoreUri);
lowLevelDebug(
"SSL keyStore type resolved: javax.net.ssl.keyStoreType='{}' -> jettyType='{}'",
System.getProperty("javax.net.ssl.keyStoreType"), keyStoreType);
setKeyStorePath(jettyKeyStoreUri);
setKeyStoreType(keyStoreType);
}
keys = getKeyStore((JsseSSLManager) SSLManager.getInstance());
/*
we need to set password after getting keystore since getKeystore may ask the user for the
Expand All @@ -37,7 +54,18 @@ public JMeterJettySslContextFactory() {

String truststore = System.getProperty("javax.net.ssl.trustStore");
if (truststore != null && !truststore.isEmpty()) {
setTrustStorePath("file://" + truststore);
if (SslStorePathResolver.isFileBasedStoreLocation(truststore)) {
String jettyTrustStoreUri = SslStorePathResolver.toJettyFileUri(truststore);
String trustStoreType = SslStorePathResolver.resolveTrustStoreType(truststore);
lowLevelDebug(
"SSL trustStore path resolved: javax.net.ssl.trustStore='{}' -> jettyUri='{}'",
truststore, jettyTrustStoreUri);
lowLevelDebug(
"SSL trustStore type resolved: javax.net.ssl.trustStoreType='{}' -> jettyType='{}'",
System.getProperty("javax.net.ssl.trustStoreType"), trustStoreType);
setTrustStorePath(jettyTrustStoreUri);
setTrustStoreType(trustStoreType);
}
getTrustStore((JsseSSLManager) SSLManager.getInstance());
/*
we need to set password after getting truststore since getTrustStore may ask the user for the
Expand Down Expand Up @@ -77,6 +105,18 @@ protected void checkTrustAll() {
protected void checkEndPointIdentificationAlgorithm() {
}

// JMeter HTTP uses CustomX509TrustManager which does not validate server certificates.
// Jetty still runs PKIX when a keyStore is configured unless trust managers are overridden.
@Override
protected TrustManager[] getTrustManagers(KeyStore trustStore,
Collection<? extends CRL> crls) throws Exception {
if (isTrustAll()) {
lowLevelDebug("SSL trust managers: using TRUST_ALL_CERTS (JMeter HTTP parity)");
return TRUST_ALL_CERTS;
}
return super.getTrustManagers(trustStore, crls);
}

// Overwritten to provide jmeter SSLManager configured keyManagers
@Override
protected KeyManager[] getKeyManagers(KeyStore keyStore) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.blazemeter.jmeter.http2.core;

import java.io.File;
import java.util.Locale;

/**
* Converts {@code javax.net.ssl.*} store paths to Jetty {@code file:} URIs.
*/
public final class SslStorePathResolver {

/** JSSE / JMeter sentinel for non-file keystores (e.g. PKCS#11). */
public static final String NON_FILE_KEYSTORE_LOCATION = "NONE";

private static final String KEY_STORE_TYPE_PROPERTY = "javax.net.ssl.keyStoreType";
private static final String TRUST_STORE_TYPE_PROPERTY = "javax.net.ssl.trustStoreType";
private static final String PKCS12 = "pkcs12";
private static final String JKS = "JKS";

private SslStorePathResolver() {
// Utility class.
}

/**
* Whether the property points at a filesystem keystore (not PKCS#11 {@code NONE}).
*/
public static boolean isFileBasedStoreLocation(final String storePath) {
return storePath != null
&& !storePath.isEmpty()
&& !NON_FILE_KEYSTORE_LOCATION.equalsIgnoreCase(storePath);
}

/**
* Same as JMeter ({@code new File(path)}), then {@link File#toURI()} for Jetty.
*
* @param storePath filesystem path from {@code javax.net.ssl.keyStore} or trustStore
* @return Jetty-compatible {@code file:} URI
*/
public static String toJettyFileUri(final String storePath) {
return new File(storePath).getAbsoluteFile().toURI().toString();
}

/**
* Resolves the Jetty key store type from {@link #KEY_STORE_TYPE_PROPERTY}, matching
* {@link org.apache.jmeter.util.SSLManager} when unset (.p12 → PKCS12, else JKS).
*
* @param keyStorePath filesystem path from {@code javax.net.ssl.keyStore}
* @return type string for {@link org.eclipse.jetty.util.ssl.SslContextFactory#setKeyStoreType}
*/
public static String resolveKeyStoreType(final String keyStorePath) {
return resolveStoreType(KEY_STORE_TYPE_PROPERTY, keyStorePath, false);
}

/**
* Resolves the Jetty trust store type from {@link #TRUST_STORE_TYPE_PROPERTY}, or by file
* extension when unset (.p12 / .pfx → PKCS12, else JKS).
*
* @param trustStorePath filesystem path from {@code javax.net.ssl.trustStore}
* @return type string for {@link org.eclipse.jetty.util.ssl.SslContextFactory#setTrustStoreType}
*/
public static String resolveTrustStoreType(final String trustStorePath) {
return resolveStoreType(TRUST_STORE_TYPE_PROPERTY, trustStorePath, true);
}

private static String resolveStoreType(final String typeProperty, final String storePath,
final boolean inferPfxAsPkcs12) {
String explicit = System.getProperty(typeProperty);
if (explicit != null && !explicit.isEmpty()) {
return explicit;
}
String lower = storePath.toLowerCase(Locale.ENGLISH);
if (lower.endsWith(".p12") || (inferPfxAsPkcs12 && lower.endsWith(".pfx"))) {
return PKCS12;
}
return JKS;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
import org.apache.jmeter.protocol.http.util.HTTPFileArg;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.util.SSLManager;
import org.assertj.core.api.JUnitSoftAssertions;
import org.eclipse.jetty.client.Request;
import org.eclipse.jetty.client.ContentResponse;
Expand Down Expand Up @@ -1391,6 +1392,7 @@ public void shouldGetSuccessResponseWhenServerRequiresClientCertAndOneIsConfigur
} finally {
System.setProperty(keyStorePropertyName, "");
System.setProperty(keyStorePasswordPropertyName, "");
SSLManager.reset();
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.blazemeter.jmeter.http2.core;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;

import com.blazemeter.jmeter.http2.HTTP2TestBase;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.cert.CRL;
import java.util.Collection;
import javax.net.ssl.TrustManager;
import org.apache.jmeter.util.SSLManager;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.After;
import org.junit.Test;

public class JMeterJettySslContextFactoryTest extends HTTP2TestBase {

private String previousKeyStore;
private String previousKeyStorePassword;
private Path tempKeystore;

@After
public void restoreSystemProperties() throws IOException {
restoreProperty("javax.net.ssl.keyStore", previousKeyStore);
restoreProperty("javax.net.ssl.keyStorePassword", previousKeyStorePassword);
SSLManager.reset();
if (tempKeystore != null) {
Files.deleteIfExists(tempKeystore);
tempKeystore = null;
}
}

@Test
public void shouldConstructWithFileBasedKeyStorePath() throws IOException {
tempKeystore = Files.createTempFile("jmeter-jetty-ssl", ".p12");
try (InputStream in = getClass().getResourceAsStream("keystore.p12")) {
if (in == null) {
throw new IllegalStateException("classpath resource keystore.p12 not found");
}
in.transferTo(Files.newOutputStream(tempKeystore));
}

previousKeyStore = System.getProperty("javax.net.ssl.keyStore");
previousKeyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword");

SSLManager.reset();
System.setProperty("javax.net.ssl.keyStore", tempKeystore.toString());
System.setProperty("javax.net.ssl.keyStorePassword", ServerBuilder.KEYSTORE_PASSWORD);

assertThatCode(JMeterJettySslContextFactory::new).doesNotThrowAnyException();
}

@Test
public void shouldUseTrustAllManagersWhenKeyStoreIsConfigured() throws Exception {
tempKeystore = Files.createTempFile("jmeter-jetty-ssl-trust", ".p12");
try (InputStream in = getClass().getResourceAsStream("keystore.p12")) {
if (in == null) {
throw new IllegalStateException("classpath resource keystore.p12 not found");
}
in.transferTo(Files.newOutputStream(tempKeystore));
}

previousKeyStore = System.getProperty("javax.net.ssl.keyStore");
previousKeyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword");

SSLManager.reset();
System.setProperty("javax.net.ssl.keyStore", tempKeystore.toString());
System.setProperty("javax.net.ssl.keyStorePassword", ServerBuilder.KEYSTORE_PASSWORD);

TestableJMeterJettySslContextFactory factory = new TestableJMeterJettySslContextFactory();
TrustManager[] trustManagers = factory.getTrustManagersForTest(null, null);

assertThat(trustManagers).isSameAs(SslContextFactory.TRUST_ALL_CERTS);
}

@Test
public void shouldConstructWhenKeyStoreIsPkcs11None() {
previousKeyStore = System.getProperty("javax.net.ssl.keyStore");
previousKeyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword");

SSLManager.reset();
System.setProperty("javax.net.ssl.keyStore",
SslStorePathResolver.NON_FILE_KEYSTORE_LOCATION);

assertThatCode(JMeterJettySslContextFactory::new).doesNotThrowAnyException();
}

private static final class TestableJMeterJettySslContextFactory
extends JMeterJettySslContextFactory {

TrustManager[] getTrustManagersForTest(java.security.KeyStore trustStore,
Collection<? extends CRL> crls) throws Exception {
return getTrustManagers(trustStore, crls);
}
}

private static void restoreProperty(String key, String value) {
if (value == null) {
System.clearProperty(key);
} else {
System.setProperty(key, value);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ public class ServerBuilder {
private final TeardownableServer server = new TeardownableServer();
private final HttpConfiguration httpsConfig = new HttpConfiguration();
private boolean withSSL;
private String serverKeyStorePathOverride;
private String serverKeyStoreTypeOverride;
private HTTP2ServerConnectionFactory http2ConnectionFactory;
private HttpConnectionFactory http1ConnectionFactory;
private HTTP2CServerConnectionFactory http2cConnectionFactory;
Expand Down Expand Up @@ -139,6 +141,15 @@ public ServerBuilder withSSL() {
return this;
}

/**
* Uses a filesystem keystore for the server TLS certificate (e.g. an untrusted JKS in tests).
*/
public ServerBuilder withServerKeyStorePath(String keyStorePath, String keyStoreType) {
this.serverKeyStorePathOverride = keyStorePath;
this.serverKeyStoreTypeOverride = keyStoreType;
return this;
}

public ServerBuilder withALPN() {
this.ALPN = true;
return this;
Expand Down Expand Up @@ -244,7 +255,13 @@ private List<ConnectionFactory> buildConnectionFactories() {

private SslContextFactory.Server buildServerSslContextFactory() {
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(getKeyStorePathAsUriPathInSSLContextFactoryFormat());
if (serverKeyStorePathOverride != null) {
sslContextFactory.setKeyStorePath(
SslStorePathResolver.toJettyFileUri(serverKeyStorePathOverride));
sslContextFactory.setKeyStoreType(serverKeyStoreTypeOverride);
} else {
sslContextFactory.setKeyStorePath(getKeyStorePathAsUriPathInSSLContextFactoryFormat());
}
sslContextFactory.setKeyStorePassword(KEYSTORE_PASSWORD);
return sslContextFactory;
}
Expand Down
Loading