Security context
MediumGHSA-6hg6-v5c8-fphq CVE-2026-34477CWE-297Published Apr 10, 2026

Apache Log4j Core: `verifyHostName` attribute silently ignored in TLS configuration

Research this vulnerability

Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.

Affected versions

2.12.0 → fixed in 2.25.43.0.0-alpha1 and later (no fix listed)

Details

The fix for CVE-2025-68161 was incomplete: it addressed hostname verification only when enabled via the [`log4j2.sslVerifyHostName`](https://logging.apache.org/log4j/2.x/manual/systemproperties.html#log4j2.sslVerifyHostName) system property, but not when configured through the [`verifyHostName`](https://logging.apache.org/log4j/2.x/manual/appenders/network.html#SslConfiguration-attr-verifyHostName) attribute of the `<Ssl>` element. Although the `verifyHostName` configuration attribute was introduced in Log4j Core 2.12.0, it was silently ignored in all versions through 2.25.3, leaving TLS connections vulnerable to interception regardless of the configured value. A network-based attacker may be able to perform a man-in-the-middle attack when all of the following conditions are met: * An SMTP, Socket, or Syslog appender is in use. * TLS is configured via a nested <Ssl> element. * The attacker can present a certificate issued by a CA trusted by the appender's configured trust store, or by the default Java trust store if none is configured. This issue does not affect users of the HTTP appender, which uses a separate [`verifyHostname`](https://logging.apache.org/log4j/2.x/manual/appenders/network.html#HttpAppender-attr-verifyHostName) attribute that was not subject to this bug and verifies host names by default. Users are advised to upgrade to Apache Log4j Core 2.25.4, which corrects this issue.

The fix

Align `SslConfiguration` factory method usage with

"Piotr P. Karwasz"· Jan 21, 2026, 01:12 PM+624129ab3a4cfe4
log4j-core-test/pom.xml+17 0
@@ -64,8 +64,19 @@
<!-- Additional version of LMAX Disruptor to test -->
<disruptor4.version>4.0.0</disruptor4.version>
<json-unit.version>2.40.1</json-unit.version>
+ <bouncycastle.version>1.83</bouncycastle.version>
</properties>
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcpkix-jdk18on</artifactId>
+ <version>${bouncycastle.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
<dependencies>
<dependency>
@@ -149,6 +160,12 @@
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcpkix-jdk18on</artifactId>
+ <scope>test</scope>
+ </dependency>
+
<!-- Other -->
<dependency>
<groupId>commons-codec</groupId>
log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/LineReadingTcpServer.java+12 6
@@ -58,6 +58,8 @@ final class LineReadingTcpServer implements AutoCloseable {
private volatile boolean running;
+ private InetAddress bindAddress = InetAddress.getLoopbackAddress();
+
private ServerSocket serverSocket;
private Socket clientSocket;
@@ -74,6 +76,11 @@ final class LineReadingTcpServer implements AutoCloseable {
this.serverSocketFactory = serverSocketFactory;
}
+ // For testing purposes
+ void setBindAddress(final InetAddress bindAddress) {
+ this.bindAddress = bindAddress;
+ }
+
synchronized void start(final String name, final int port) throws IOException {
if (!running) {
running = true;
@@ -83,8 +90,7 @@ synchronized void start(final String name, final int port) throws IOException {
}
private ServerSocket createServerSocket(final int port) throws IOException {
- final ServerSocket serverSocket =
- serverSocketFactory.createServerSocket(port, 1, InetAddress.getLoopbackAddress());
+ final ServerSocket serverSocket = serverSocketFactory.createServerSocket(port, 1, bindAddress);
serverSocket.setReuseAddress(true);
serverSocket.setSoTimeout(0); // Zero indicates `accept()` will block indefinitely
await("server socket binding")
@@ -104,12 +110,12 @@ private Thread createReaderThread(final String name) {
}
private void acceptClients() {
- try {
- while (running) {
+ while (running) {
+ try {
acceptClient();
+ } catch (final Exception error) {
+ LOGGER.error("failed accepting client connections", error);
}
- } catch (final Exception error) {
- LOGGER.error("failed accepting client connections", error);
}
}
log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/TlsSocketAppenderTest.java+322 0
@@ -0,0 +1,322 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.appender;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.stream.Stream;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.net.SslSocketManager;
+import org.apache.logging.log4j.test.TestProperties;
+import org.apache.logging.log4j.test.junit.UsingTestProperties;
+import org.jspecify.annotations.Nullable;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+@UsingTestProperties
+class TlsSocketAppenderTest {
+
+ // Test DNS names and IP addresses
+ private static final String TARGET_HOSTNAME = "log4j.localhost";
+ private static final String TARGET_IP = "::1";
+ private static final String ATTACKER_HOSTNAME = "not-log4j.localhost";
+ private static final String ATTACKER_IP = "127.0.0.1";
+
+ // Test PKI material
+ private static final KeyPair CA_KEY_PAIR = X509Certificates.generateKeyPair();
+ private static final KeyPair SERVER_KEY_PAIR = X509Certificates.generateKeyPair();
+ private static final KeyPair CLIENT_KEY_PAIR = X509Certificates.generateKeyPair();
+
+ private static final X509Certificate CA_CERT;
+
+ private static final X509Certificate TARGET_CERT1;
+ private static final X509Certificate TARGET_CERT2;
+ private static final X509Certificate TARGET_CERT3;
+
+ private static final X509Certificate ATTACKER_CERT1;
+ private static final X509Certificate ATTACKER_CERT2;
+ private static final X509Certificate ATTACKER_CERT3;
+
+ /** Client certificate used for mutual TLS (mTLS) scenarios. */
+ private static final X509Certificate CLIENT_CERT;
+
+ static {
+ try {
+ CA_CERT = X509Certificates.generateCACertificate(CA_KEY_PAIR);
+ PrivateKey caPrivateKey = CA_KEY_PAIR.getPrivate();
+
+ // Certificates with CN only
+ TARGET_CERT1 = X509Certificates.generateServerCertificate(
+ SERVER_KEY_PAIR, caPrivateKey, "CN=" + TARGET_HOSTNAME, null, null);
+ ATTACKER_CERT1 = X509Certificates.generateServerCertificate(
+ SERVER_KEY_PAIR, caPrivateKey, "CN=" + ATTACKER_HOSTNAME, null, null);
+
+ // Certificates with SAN (DNS)
+ TARGET_CERT2 = X509Certificates.generateServerCertificate(
+ SERVER_KEY_PAIR, caPrivateKey, "CN=Test Server", TARGET_HOSTNAME, null);
+ ATTACKER_CERT2 = X509Certificates.generateServerCertificate(
+ SERVER_KEY_PAIR, caPrivateKey, "CN=Test Attacker Server", ATTACKER_HOSTNAME, null);
+
+ // Certificates with SAN (IP)
+ TARGET_CERT3 = X509Certificates.generateServerCertificate(
+ SERVER_KEY_PAIR, caPrivateKey, "CN=Test Server", null, TARGET_IP);
+ ATTACKER_CERT3 = X509Certificates.generateServerCertificate(
+ SERVER_KEY_PAIR, caPrivateKey, "CN=Test Attacker Server", null, ATTACKER_IP);
+
+ CLIENT_CERT = X509Certificates.generateClientCertificate(CLIENT_KEY_PAIR, caPrivateKey, "CN=Test Client");
+
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ // Store parameters
+
+ private static final String KEYSTORE_TYPE = "PKCS12";
+ private static final char[] KEYSTORE_PWD = "aKeyStoreSecret".toCharArray();
+ private static final String TRUSTSTORE_TYPE = "PKCS12";
+ private static final char[] TRUSTSTORE_PWD = "aTrustStoreSecret".toCharArray();
+
+ @TempDir
+ private static Path certPath;
+
+ static Stream<Arguments> connectionAlwaysSucceedsWithoutHostnameVerification() {
+ return Stream.of(
+ Arguments.of(TARGET_HOSTNAME, ATTACKER_CERT1),
+ Arguments.of(TARGET_HOSTNAME, ATTACKER_CERT2),
+ Arguments.of(TARGET_IP, ATTACKER_CERT3));
+ }
+
+ static Stream<Arguments> connectionSucceedsOnHostNameMatch() {
+ return Stream.of(
+ // No client certificate
+ Arguments.of(TARGET_HOSTNAME, TARGET_CERT1, null),
+ Arguments.of(TARGET_HOSTNAME, TARGET_CERT2, null),
+ Arguments.of(TARGET_IP, TARGET_CERT3, null),
+
+ // These tests ensure that connections to the attacher fail because of hostname mismatch,
+ // not because of other TLS issues.
+ Arguments.of(ATTACKER_HOSTNAME, ATTACKER_CERT1, null),
+ Arguments.of(ATTACKER_HOSTNAME, ATTACKER_CERT2, null),
+ Arguments.of(ATTACKER_IP, ATTACKER_CERT3, null),
+
+ // Mutual TLS
+ Arguments.of(TARGET_HOSTNAME, TARGET_CERT1, CLIENT_CERT),
+ Arguments.of(TARGET_HOSTNAME, TARGET_CERT2, CLIENT_CERT),
+ Arguments.of(TARGET_IP, TARGET_CERT3, CLIENT_CERT));
+ }
+
+ static Stream<Arguments> connectionFailsOnHostNameMismatch() {
+ return Stream.of(
+ Arguments.of(TARGET_HOSTNAME, ATTACKER_CERT1),
+ Arguments.of(TARGET_HOSTNAME, ATTACKER_CERT2),
+ Arguments.of(TARGET_IP, ATTACKER_CERT3));
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void connectionAlwaysSucceedsWithoutHostnameVerification(
+ String hostName, X509Certificate serverCertificate, TestProperties props) throws Exception {
+
+ TestTlsMaterial tls = createTlsMaterial(hostName, serverCertificate, null);
+ applyClientTlsProperties(props, tls);
+ props.setProperty("ssl.verifyHostname", "false");
+
+ try (LineReadingTcpServer server = createTlsServer(hostName, tls.serverSslContext, false)) {
+ props.setProperty("server.host", hostName);
+ props.setProperty("server.port", server.getServerSocket().getLocalPort());
+
+ try (LoggerContext ctx = createLoggerContext()) {
+ Logger logger = ctx.getLogger(TlsSocketAppenderTest.class);
+
+ String expected = "Test message for host " + hostName;
+ logger.info(expected);
+
+ assertThat(server.pollLines(1)).containsExactly(expected);
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void connectionSucceedsOnHostNameMatch(
+ String hostName,
+ X509Certificate serverCertificate,
+ @Nullable X509Certificate clientCertificate,
+ TestProperties props)
+ throws Exception {
+
+ TestTlsMaterial tls = createTlsMaterial(hostName, serverCertificate, clientCertificate);
+ applyClientTlsProperties(props, tls);
+
+ try (LineReadingTcpServer server = createTlsServer(hostName, tls.serverSslContext, clientCertificate != null)) {
+ props.setProperty("server.host", hostName);
+ props.setProperty("server.port", server.getServerSocket().getLocalPort());
+
+ try (LoggerContext ctx = createLoggerContext()) {
+ Logger logger = ctx.getLogger(TlsSocketAppenderTest.class);
+
+ String expected = "Test message for host " + hostName;
+ logger.info(expected);
+
+ assertThat(server.pollLines(1)).containsExactly(expected);
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void connectionFailsOnHostNameMismatch(String hostName, X509Certificate serverCertificate, TestProperties props)
+ throws Exception {
+
+ // No mTLS needed; we only care about hostname verification failure.
+ TestTlsMaterial tls = createTlsMaterial(hostName, serverCertificate, null);
+ applyClientTlsProperties(props, tls);
+
+ try (LineReadingTcpServer server = createTlsServer(hostName, tls.serverSslContext, false)) {
+ props.setProperty("server.host", hostName);
+ props.setProperty("server.port", server.getServerSocket().getLocalPort());
+
+ try (LoggerContext ctx = createLoggerContext()) {
+ assertSocketAppenderNotConnected(ctx, hostName);
+ }
+ }
+ }
+
+ private static TestTlsMaterial createTlsMaterial(
+ String hostName, X509Certificate serverCertificate, @Nullable X509Certificate clientCertificate)
+ throws Exception {
+
+ // Client keystore: only populated when we test mutual TLS.
+ String clientKeystore = generateKeystore(
+ hostName + "-client", clientCertificate, clientCertificate != null ? CLIENT_KEY_PAIR : null);
+
+ String serverKeystore = generateKeystore(hostName + "-server", serverCertificate, SERVER_KEY_PAIR);
+
+ String truststore = generateTruststore(hostName);
+
+ SSLContext serverSslContext = SslContexts.createSslContext(
+ KEYSTORE_TYPE, serverKeystore, KEYSTORE_PWD, TRUSTSTORE_TYPE, truststore, TRUSTSTORE_PWD);
+
+ return new TestTlsMaterial(clientKeystore, truststore, serverSslContext);
+ }
+
+ private static void applyClientTlsProperties(TestProperties props, TestTlsMaterial tls) {
+ props.setProperty("keystore.location", tls.clientKeystoreLocation);
+ props.setProperty("keystore.password", new String(KEYSTORE_PWD));
+ props.setProperty("keystore.type", KEYSTORE_TYPE);
+
+ props.setProperty("truststore.location", tls.truststoreLocation);
+ props.setProperty("truststore.password", new String(TRUSTSTORE_PWD));
+ props.setProperty("truststore.type", TRUSTSTORE_TYPE);
+ }
+
+ private static LineReadingTcpServer createTlsServer(String hostName, SSLContext sslContext, boolean needClientAuth)
+ throws Exception {
+
+ LineReadingTcpServer server = new LineReadingTcpServer(sslContext.getServerSocketFactory());
+
+ // Bind to all interfaces to allow testing with different host names.
+ server.setBindAddress(null);
+
+ server.start("TlsSocketAppenderTest-" + hostName, 0);
+
+ SSLServerSocket socket = (SSLServerSocket) server.getServerSocket();
+ socket.setNeedClientAuth(needClientAuth);
+
+ return server;
+ }
+
+ private static LoggerContext createLoggerContext() throws Exception {
+ URL configLocation = TlsSocketAppenderTest.class.getResource("/TlsSocketAppenderTest/log4j2.xml");
+ assertThat(configLocation).isNotNull();
+
+ LoggerContext ctx = new LoggerContext("TlsSocketAppenderTest", null, configLocation.toURI());
+ ctx.start();
+ return ctx;
+ }
+
+ private static void assertSocketAppenderNotConnected(LoggerContext ctx, String hostName) {
+ SocketAppender appender = ctx.getConfiguration().getAppender("SOCKET-" + hostName);
+ assertThat(appender).isNotNull();
+ assertThat(appender.getManager()).isInstanceOf(SslSocketManager.class);
+
+ SslSocketManager manager = (SslSocketManager) appender.getManager();
+ Socket socket = manager.getSocket();
+
+ if (socket != null) {
+ assertThat(socket.isConnected()).isFalse();
+ }
+ }
+
+ private static String generateTruststore(String alias) throws Exception {
+ KeyStore trustStore = KeyStore.getInstance(TRUSTSTORE_TYPE);
+ trustStore.load(null, null);
+ trustStore.setCertificateEntry(alias, CA_CERT);
+
+ Path file = certPath.resolve(alias + "-truststore.p12");
+ try (OutputStream out = Files.newOutputStream(file)) {
+ trustStore.store(out, TRUSTSTORE_PWD);
+ }
+ return file.toAbsolutePath().toString();
+ }
+
+ private static String generateKeystore(
+ String alias, @Nullable X509Certificate certificate, @Nullable KeyPair keyPair) throws Exception {
+
+ KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
+ keyStore.load(null, null);
+
+ if (certificate != null && keyPair != null) {
+ keyStore.setKeyEntry(
+ alias, keyPair.getPrivate(), KEYSTORE_PWD, new X509Certificate[] {certificate, CA_CERT});
+ }
+
+ Path file = certPath.resolve(alias + "-keystore.p12");
+ try (OutputStream out = Files.newOutputStream(file)) {
+ keyStore.store(out, KEYSTORE_PWD);
+ }
+ return file.toAbsolutePath().toString();
+ }
+
+ private static class TestTlsMaterial {
+
+ private final @Nullable String clientKeystoreLocation;
+ private final String truststoreLocation;
+ private final SSLContext serverSslContext;
+
+ private TestTlsMaterial(String clientKeystoreLocation, String truststoreLocation, SSLContext serverSslContext) {
+ this.clientKeystoreLocation = clientKeystoreLocation;
+ this.truststoreLocation = truststoreLocation;
+ this.serverSslContext = serverSslContext;
+ }
+ }
+}
log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/X509Certificates.java+193 0
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.appender;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.cert.CertIOException;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * Utility class to generate X.509 certificates for testing purposes.
+ */
+final class X509Certificates {
+
+ private static final String CA_DN = "CN=Test CA";
+ private static final long MINUTE_IN_MILLIS = 60_000L;
+ private static final long YEAR_IN_MILLIS = 365L * 24 * 60 * MINUTE_IN_MILLIS;
+
+ private static final KeyPairGenerator RSA_GENERATOR;
+ private static final Random RANDOM = new Random();
+
+ static {
+ try {
+ RSA_GENERATOR = KeyPairGenerator.getInstance("RSA");
+ RSA_GENERATOR.initialize(2048);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static KeyPair generateKeyPair() {
+ return RSA_GENERATOR.generateKeyPair();
+ }
+
+ static X509Certificate generateCACertificate(KeyPair keyPair) throws Exception {
+ JcaX509v3CertificateBuilder builder = getCertificateBuilder(keyPair.getPublic(), CA_DN, true);
+ addKeyUsageExtension(builder, KeyUsage.keyCertSign);
+ return buildCertificate(builder, keyPair.getPrivate());
+ }
+
+ /**
+ * Create and sign a server X.509 certificate for tests.
+ *
+ * <p>The produced certificate complies with {@code sun.security.validator.EndEntityChecker}.</p>
+ *
+ * @param keyPair the subject key pair
+ * @param caKey the private key of the issuing CA used to sign the certificate
+ * @param subjectDn the subject distinguished name for the certificate (for example {@code CN=example.com})
+ * @param dnsAltSubject optional DNS Subject Alternative Name; pass {@code null} to omit
+ * @param ipAltSubject optional IP Subject Alternative Name; pass {@code null} to omit
+ * @return a signed X.509 server certificate
+ * @throws Exception if certificate creation or signing fails
+ */
+ static X509Certificate generateServerCertificate(
+ KeyPair keyPair,
+ PrivateKey caKey,
+ String subjectDn,
+ @Nullable String dnsAltSubject,
+ @Nullable String ipAltSubject)
+ throws Exception {
+ JcaX509v3CertificateBuilder builder = getCertificateBuilder(keyPair.getPublic(), subjectDn, false);
+ // The required key usage for the server certificate depends on the key exchange algorithm:
+ // - keyEncipherment for RSA key exchange (deprecated)
+ // - digitalSignature for ephemeral Diffie-Hellman key exchange (DHE or ECDHE)
+ // - keyAgreement for static Diffie-Hellman key exchange (DH or ECDH)
+ addKeyUsageExtension(builder, KeyUsage.digitalSignature | KeyUsage.keyAgreement);
+ addExtendedKeyUsageExtension(builder, KeyPurposeId.id_kp_serverAuth);
+ addSubjectAlternativeName(builder, dnsAltSubject, ipAltSubject);
+ return buildCertificate(builder, caKey);
+ }
+
+ /**
+ * Create and sign a client X.509 certificate for tests.
+ *
+ * <p>The produced certificate complies with {@code sun.security.validator.EndEntityChecker}.</p>
+ *
+ * @param keyPair the subject key pair
+ * @param caKey the private key of the issuing CA used to sign the certificate
+ * @param subjectDn the subject distinguished name for the certificate (for example {@code CN=example.com})
+ * @return a signed X.509 server certificate
+ * @throws Exception if certificate creation or signing fails
+ */
+ static X509Certificate generateClientCertificate(KeyPair keyPair, PrivateKey caKey, String subjectDn)
+ throws Exception {
+ JcaX509v3CertificateBuilder builder = getCertificateBuilder(keyPair.getPublic(), subjectDn, false);
+ // The required key usage for the client certificate
+ addKeyUsageExtension(builder, KeyUsage.digitalSignature);
+ addExtendedKeyUsageExtension(builder, KeyPurposeId.id_kp_clientAuth);
+ return buildCertificate(builder, caKey);
+ }
+
+ private static JcaX509v3CertificateBuilder getCertificateBuilder(
+ PublicKey subjectPub, String subjectDn, boolean isCa) throws CertIOException {
+ long now = System.currentTimeMillis();
+ Date notBefore = new Date(now - MINUTE_IN_MILLIS);
+ Date notAfter = new Date(now + YEAR_IN_MILLIS);
+ BigInteger serial = BigInteger.valueOf(RANDOM.nextLong());
+
+ X500Name issuer = new X500Name(CA_DN);
+ X500Name subject = new X500Name(subjectDn);
+
+ JcaX509v3CertificateBuilder builder =
+ new JcaX509v3CertificateBuilder(issuer, serial, notBefore, notAfter, subject, subjectPub);
+
+ // Basic Constraints
+ builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(isCa));
+
+ return builder;
+ }
+
+ private static void addKeyUsageExtension(JcaX509v3CertificateBuilder builder, int keyUsage) throws CertIOException {
+ builder.addExtension(Extension.keyUsage, true, new KeyUsage(keyUsage));
+ }
+
+ private static void addExtendedKeyUsageExtension(JcaX509v3CertificateBuilder builder, KeyPurposeId kp)
+ throws CertIOException {
+ builder.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(kp));
+ }
+
+ private static GeneralName getIpAddressGeneralName(String ipAltSubject) {
+ return new GeneralName(GeneralName.iPAddress, ipAltSubject);
+ }
+
+ private static GeneralName getDnsGeneralName(String dnsAltSubject) {
+ return new GeneralName(GeneralName.dNSName, dnsAltSubject);
+ }
+
+ private static void addSubjectAlternativeName(
+ JcaX509v3CertificateBuilder builder, @Nullable String dnsAltSubject, @Nullable String ipAltSubject)
+ throws CertIOException {
+ if (ipAltSubject != null || dnsAltSubject != null) {
+ List<GeneralName> names = new ArrayList<>();
+ if (dnsAltSubject != null) {
+ names.add(getDnsGeneralName(dnsAltSubject));
+ }
+ if (ipAltSubject != null) {
+ names.add(getIpAddressGeneralName(ipAltSubject));
+ }
+ GeneralName[] gna = names.toArray(new GeneralName[0]);
+ builder.addExtension(Extension.subjectAlternativeName, false, new GeneralNames(gna));
+ }
+ }
+
+ private static X509Certificate buildCertificate(JcaX509v3CertificateBuilder builder, PrivateKey signerKey)
+ throws OperatorCreationException, CertificateException {
+ ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(signerKey);
+
+ X509CertificateHolder holder = builder.build(signer);
+
+ return new JcaX509CertificateConverter().getCertificate(holder);
+ }
+
+ private X509Certificates() {
+ // private constructor to prevent instantiation
+ }
+}
log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationTest.java+21 0
@@ -16,6 +16,7 @@
*/
package org.apache.logging.log4j.core.net.ssl;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -25,6 +26,11 @@
import java.net.UnknownHostException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
+import org.apache.logging.log4j.core.config.DefaultConfiguration;
+import org.apache.logging.log4j.core.config.Node;
+import org.apache.logging.log4j.core.config.plugins.util.PluginBuilder;
+import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
+import org.apache.logging.log4j.core.config.plugins.util.PluginType;
import org.apache.logging.log4j.test.junit.UsingStatusListener;
import org.junit.jupiter.api.Test;
@@ -138,4 +144,19 @@ void loadKeyStoreWithoutPassword() throws StoreConfigurationException {
final SSLSocketFactory factory = sslConf.getSslContext().getSocketFactory();
assertNotNull(factory);
}
+
+ @Test
+ void verifyHostNameFromXml() {
+ PluginManager pluginManager = new PluginManager(Node.CATEGORY);
+ pluginManager.collectPlugins();
+ PluginType<?> pluginType = pluginManager.getPluginType("Ssl");
+ assertThat(pluginType).isNotNull();
+ Node ssl = new Node(null, pluginType.getElementName(), pluginType);
+ ssl.getAttributes().put("verifyHostName", "true");
+ PluginBuilder builder = new PluginBuilder(pluginType);
+ SslConfiguration sslConfiguration = (SslConfiguration) builder.withConfigurationNode(ssl)
+ .withConfiguration(new DefaultConfiguration())
+ .build();
+ assertThat(sslConfiguration.isVerifyHostName()).isTrue();
+ }
}
log4j-core-test/src/test/resources/TlsSocketAppenderTest/log4j2.xml+42 0
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<Configuration xmlns="https://logging.apache.org/xml/ns"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-config-2.xsd">
+ <Appenders>
+ <Socket name="SOCKET-${test:server.host}"
+ host="${test:server.host}"
+ port="${test:server.port}"
+ protocol="SSL">
+ <Ssl verifyHostName="${test:ssl.verifyHostname:-true}">
+ <KeyStore location="${test:keystore.location}"
+ password="${test:keystore.password}"
+ type="${test:keystore.type}"/>
+ <TrustStore location="${test:truststore.location}"
+ password="${test:truststore.password}"
+ type="${test:truststore.type}"/>
+ </Ssl>
+ <PatternLayout pattern="%m%n"/>
+ </Socket>
+ </Appenders>
+ <Loggers>
+ <Root level="INFO">
+ <AppenderRef ref="SOCKET-${test:server.host}"/>
+ </Root>
+ </Loggers>
+</Configuration>
log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfiguration.java+6 6
@@ -178,15 +178,14 @@ private static TrustManager[] loadTrustManagers(@Nullable final TrustStoreConfig
* @param keyStoreConfig The KeyStoreConfiguration.
* @param trustStoreConfig The TrustStoreConfiguration.
* @return a new SslConfiguration
+ * @deprecated Since 2.26.0, use {@link #createSSLConfiguration(String, KeyStoreConfiguration, TrustStoreConfiguration, boolean)} instead.
*/
+ @Deprecated
@NullUnmarked
- @PluginFactory
public static SslConfiguration createSSLConfiguration(
- // @formatter:off
- @PluginAttribute("protocol") final String protocol,
- @PluginElement("KeyStore") final KeyStoreConfiguration keyStoreConfig,
- @PluginElement("TrustStore") final TrustStoreConfiguration trustStoreConfig) {
- // @formatter:on
+ final String protocol,
+ final KeyStoreConfiguration keyStoreConfig,
+ final TrustStoreConfiguration trustStoreConfig) {
return new SslConfiguration(protocol, false, keyStoreConfig, trustStoreConfig);
}
@@ -201,6 +200,7 @@ public static SslConfiguration createSSLConfiguration(
* @since 2.12
*/
@NullUnmarked
+ @PluginFactory
public static SslConfiguration createSSLConfiguration(
// @formatter:off
@PluginAttribute("protocol") final String protocol,
src/changelog/.2.x.x/ssl-connection.xml+11 0
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns="https://logging.apache.org/xml/ns"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="
+ https://logging.apache.org/xml/ns
+ https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
+ type="fixed">
+ <description format="asciidoc">
+ Align `SslConfiguration` factory method usage with Log4j 2.12+ API.
+ </description>
+</entry>
.../.2.x.x/{ssl-connection.xml => 4061_ssl-connection.xml} | 2 ++
1 file changed, 2 insertions(+)
rename src/changelog/.2.x.x/{ssl-connection.xml => 4061_ssl-connection.xml} (73%)
More files changed — see the full commit.

References