Security context
MediumGHSA-vc5p-v9hr-52mj CVE-2025-68161CWE-297Published Dec 18, 2025

Apache Log4j does not verify the TLS hostname in its Socket Appender

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.0-beta9 → fixed in 2.25.3

Details

The Socket Appender in Apache Log4j Core versions 2.0-beta9 through 2.25.2 does not perform TLS hostname verification of the peer certificate, even when the [verifyHostName](https://logging.apache.org/log4j/2.x/manual/appenders/network.html#SslConfiguration-attr-verifyHostName) configuration attribute or the [log4j2.sslVerifyHostName](https://logging.apache.org/log4j/2.x/manual/systemproperties.html#log4j2.sslVerifyHostName) system property is set to true. This issue may allow a man-in-the-middle attacker to intercept or redirect log traffic under the following conditions: * The attacker is able to intercept or redirect network traffic between the client and the log receiver. * The attacker can present a server certificate issued by a certification authority trusted by the Socket Appender’s configured trust store (or by the default Java trust store if no custom trust store is configured). Users are advised to upgrade to Apache Log4j Core version 2.25.3, which addresses this issue. As an alternative mitigation, the Socket Appender may be configured to use a private or restricted trust root to limit the set of trusted certificates.

The fix

Fix host name verification in `SslSocketManager`

=?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?=· Dec 2, 2025, 09:46 AM+69933bc40056ffd
log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslSocketManagerTest.java+48 14
@@ -16,30 +16,64 @@
*/
package org.apache.logging.log4j.core.net.ssl;
-import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import javax.net.ssl.SNIHostName;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSocket;
+import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.net.SslSocketManager;
+import org.apache.logging.log4j.test.junit.UsingStatusListener;
import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.Issue;
class SslSocketManagerTest {
- @Issue("https://github.com/apache/logging-log4j2/issues/3947")
+
+ private static final String HOST_NAME = "apache.org";
+
+ private static final int HOST_PORT = 443;
+
+ private static final Layout<?> LAYOUT = PatternLayout.createDefaultLayout();
+
@Test
- void shouldNotThrowExceptionWhenConfiguringTrustStore() {
- final TrustStoreConfiguration trustStoreConfiguration = assertDoesNotThrow(() -> new TrustStoreConfiguration(
+ @Issue("https://github.com/apache/logging-log4j2/issues/3947")
+ @UsingStatusListener // Suppresses `StatusLogger` output, unless there is a failure
+ void should_not_throw_exception_when_configuration_without_KeyStore() throws Exception {
+ final TrustStoreConfiguration trustStoreConfig = new TrustStoreConfiguration(
SslKeyStoreConstants.TRUSTSTORE_LOCATION,
SslKeyStoreConstants::TRUSTSTORE_PWD,
SslKeyStoreConstants.TRUSTSTORE_TYPE,
- null));
- final SslConfiguration sslConfiguration =
- SslConfiguration.createSSLConfiguration(null, null, trustStoreConfiguration);
- assertDoesNotThrow(() -> {
- // noinspection EmptyTryBlock (try-with-resources to close `SslSocketManager`, even on failure
- try (final SslSocketManager ignored = SslSocketManager.getSocketManager(
- sslConfiguration, "localhost", 0, 0, 0, true, PatternLayout.createDefaultLayout(), 8192, null)) {
- // Do nothing
- }
- });
+ null);
+ final SslConfiguration sslConfig = SslConfiguration.createSSLConfiguration(null, null, trustStoreConfig);
+ assertThatCode(() -> {
+ // noinspection EmptyTryBlock
+ try (final SslSocketManager ignored = createSocketManager(sslConfig)) {
+ // Do nothing
+ }
+ })
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ void host_name_verification_should_take_effect() {
+ final SslConfiguration sslConfig = SslConfiguration.createSSLConfiguration(
+ null,
+ null,
+ null,
+ // Explicitly enable hostname verification
+ true);
+ try (final SslSocketManager ssm = createSocketManager(sslConfig)) {
+ final SSLSocket sslSocket = (SSLSocket) ssm.getSocket();
+ final SSLParameters sslParams = sslSocket.getSSLParameters();
+ assertThat(sslParams.getEndpointIdentificationAlgorithm()).isEqualTo("HTTPS");
+ assertThat(sslParams.getServerNames()).containsOnly(new SNIHostName(HOST_NAME));
+ }
+ }
+
+ private static SslSocketManager createSocketManager(final SslConfiguration sslConfig) {
+ return SslSocketManager.getSocketManager(
+ sslConfig, SslSocketManagerTest.HOST_NAME, HOST_PORT, 0, 0, true, LAYOUT, 8192, null);
}
}
log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/internal/InetAddressValidatorTest.java+399 0
@@ -0,0 +1,1082 @@
+/*
+ * 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.net.ssl.internal;
+
+import static org.apache.logging.log4j.core.net.ssl.internal.InetAddressValidator.isValid;
+import static org.apache.logging.log4j.core.net.ssl.internal.InetAddressValidator.isValidInet6Address;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * @see <a href="https://github.com/apache/commons-validator/blob/7c27355d86f7dc5a4a548658745b85c9f0d5b99f/src/test/java/org/apache/commons/validator/routines/InetAddressValidatorTest.java"><code>InetAddressValidatorTest</code> of Apache Commons Validator</a>
+ */
+class InetAddressValidatorTest {
+
+ /**
+ * Test obviously broken IPs.
+ */
+ @Test
+ void testBrokenInetAddresses() {
+ assertFalse(isValid("124.14.32.abc"), "IP with characters should be invalid");
+ // TODO: there is some debate as to whether leading zeros should be allowed
+ // They are ambiguous: does the leading 0 mean octal?
+ assertFalse(isValid("124.14.32.01"), "IP with leading zeroes should be invalid");
+ assertFalse(isValid("23.64.12"), "IP with three groups should be invalid");
+ assertFalse(isValid("26.34.23.77.234"), "IP with five groups should be invalid");
+ assertFalse(isValidInet6Address(""), "IP empty string should be invalid"); // empty string
+ }
+
+ /**
+ * Test valid and invalid IPs from each address class.
+ */
+ @Test
+ void testInetAddressesByClass() {
+ assertTrue(isValid("24.25.231.12"), "class A IP should be valid");
+ assertFalse(isValid("2.41.32.324"), "illegal class A IP should be invalid");
+
+ assertTrue(isValid("135.14.44.12"), "class B IP should be valid");
+ assertFalse(isValid("154.123.441.123"), "illegal class B IP should be invalid");
+
+ assertTrue(isValid("213.25.224.32"), "class C IP should be valid");
+ assertFalse(isValid("201.543.23.11"), "illegal class C IP should be invalid");
+
+ assertTrue(isValid("229.35.159.6"), "class D IP should be valid");
+ assertFalse(isValid("231.54.11.987"), "illegal class D IP should be invalid");
+
+ assertTrue(isValid("248.85.24.92"), "class E IP should be valid");
+ assertFalse(isValid("250.21.323.48"), "illegal class E IP should be invalid");
+ }
+
+ /**
+ * Test IPs that point to real, well-known hosts (without actually looking them up).
+ */
+ @Test
+ void testInetAddressesFromTheWild() {
+ assertTrue(isValid("140.211.11.130"), "www.apache.org IP should be valid");
+ assertTrue(isValid("72.14.253.103"), "www.l.google.com IP should be valid");
+ assertTrue(isValid("199.232.41.5"), "fsf.org IP should be valid");
+ assertTrue(isValid("216.35.123.87"), "appscs.ign.com IP should be valid");
+ }
+
+ /**
+ * Test IPv6 addresses.
+ * <p>
+ * These tests were ported from a <a href="https://download.dartware.com/thirdparty/test-ipv6-regex.pl">Perl script</a>.
+ * </p>
+ */
+ @Test
+ void testIPv6() {
+ // The original Perl script contained a lot of duplicate tests.
+ // I removed the duplicates I noticed, but there may be more.
+ assertFalse(isValidInet6Address(""), "IPV6 empty string should be invalid"); // empty string
+ assertTrue(isValidInet6Address("::1"), "IPV6 ::1 should be valid"); // loopback, compressed, non-routable
+ assertTrue(isValidInet6Address("::"), "IPV6 :: should be valid"); // unspecified, compressed, non-routable
+ assertTrue(isValidInet6Address("0:0:0:0:0:0:0:1"), "IPV6 0:0:0:0:0:0:0:1 should be valid"); // loopback, full
+ assertTrue(isValidInet6Address("0:0:0:0:0:0:0:0"), "IPV6 0:0:0:0:0:0:0:0 should be valid"); // unspecified, full
+ assertTrue(
+ isValidInet6Address("2001:DB8:0:0:8:800:200C:417A"),
+ "IPV6 2001:DB8:0:0:8:800:200C:417A should be valid"); // unicast, full
+ assertTrue(
+ isValidInet6Address("FF01:0:0:0:0:0:0:101"),
+ "IPV6 FF01:0:0:0:0:0:0:101 should be valid"); // multicast, full
+ assertTrue(
+ isValidInet6Address("2001:DB8::8:800:200C:417A"),
+ "IPV6 2001:DB8::8:800:200C:417A should be valid"); // unicast, compressed
+ assertTrue(isValidInet6Address("FF01::101"), "IPV6 FF01::101 should be valid"); // multicast, compressed
+ assertFalse(
+ isValidInet6Address("2001:DB8:0:0:8:800:200C:417A:221"),
+ "IPV6 2001:DB8:0:0:8:800:200C:417A:221 should be invalid"); // unicast,
+ // full
+ assertFalse(
+ isValidInet6Address("FF01::101::2"), "IPV6 FF01::101::2 should be invalid"); // multicast, compressed
+ assertTrue(isValidInet6Address("fe80::217:f2ff:fe07:ed62"), "IPV6 fe80::217:f2ff:fe07:ed62 should be valid");
+ assertTrue(
+ isValidInet6Address("2001:0000:1234:0000:0000:C1C0:ABCD:0876"),
+ "IPV6 2001:0000:1234:0000:0000:C1C0:ABCD:0876 should be valid");
+ assertTrue(
+ isValidInet6Address("3ffe:0b00:0000:0000:0001:0000:0000:000a"),
+ "IPV6 3ffe:0b00:0000:0000:0001:0000:0000:000a should be valid");
+ assertTrue(
+ isValidInet6Address("FF02:0000:0000:0000:0000:0000:0000:0001"),
+ "IPV6 FF02:0000:0000:0000:0000:0000:0000:0001 should be valid");
+ assertTrue(
+ isValidInet6Address("0000:0000:0000:0000:0000:0000:0000:0001"),
+ "IPV6 0000:0000:0000:0000:0000:0000:0000:0001 should be valid");
+ assertTrue(
+ isValidInet6Address("0000:0000:0000:0000:0000:0000:0000:0000"),
+ "IPV6 0000:0000:0000:0000:0000:0000:0000:0000 should be valid");
+ assertFalse(
+ isValidInet6Address("02001:0000:1234:0000:0000:C1C0:ABCD:0876"),
+ "IPV6 02001:0000:1234:0000:0000:C1C0:ABCD:0876 should be invalid"); // extra 0 not allowed!
+ assertFalse(
+ isValidInet6Address("2001:0000:1234:0000:00001:C1C0:ABCD:0876"),
+ "IPV6 2001:0000:1234:0000:00001:C1C0:ABCD:0876 should be invalid"); // extra 0 not allowed!
+ assertFalse(
+ isValidInet6Address("2001:0000:1234:0000:0000:C1C0:ABCD:0876 0"),
+ "IPV6 2001:0000:1234:0000:0000:C1C0:ABCD:0876 0 should be invalid"); // junk after valid address
+ assertFalse(
+ isValidInet6Address("2001:0000:1234: 0000:0000:C1C0:ABCD:0876"),
+ "IPV6 2001:0000:1234: 0000:0000:C1C0:ABCD:0876 should be invalid"); // internal space
+ assertFalse(
+ isValidInet6Address("3ffe:0b00:0000:0001:0000:0000:000a"),
+ "IPV6 3ffe:0b00:0000:0001:0000:0000:000a should be invalid"); // seven
+ // segments
+ assertFalse(
+ isValidInet6Address("FF02:0000:0000:0000:0000:0000:0000:0000:0001"),
+ "IPV6 FF02:0000:0000:0000:0000:0000:0000:0000:0001 should be invalid"); // nine segments
+ assertFalse(isValidInet6Address("3ffe:b00::1::a"), "IPV6 3ffe:b00::1::a should be invalid"); // double "::"
+ assertFalse(
+ isValidInet6Address("::1111:2222:3333:4444:5555:6666::"),
+ "IPV6 ::1111:2222:3333:4444:5555:6666:: should be invalid"); // double
+ // "::"
+ assertTrue(isValidInet6Address("2::10"), "IPV6 2::10 should be valid");
+ assertTrue(isValidInet6Address("ff02::1"), "IPV6 ff02::1 should be valid");
+ assertTrue(isValidInet6Address("fe80::"), "IPV6 fe80:: should be valid");
+ assertTrue(isValidInet6Address("2002::"), "IPV6 2002:: should be valid");
+ assertTrue(isValidInet6Address("2001:db8::"), "IPV6 2001:db8:: should be valid");
+ assertTrue(isValidInet6Address("2001:0db8:1234::"), "IPV6 2001:0db8:1234:: should be valid");
+ assertTrue(isValidInet6Address("::ffff:0:0"), "IPV6 ::ffff:0:0 should be valid");
+ assertTrue(isValidInet6Address("1:2:3:4:5:6:7:8"), "IPV6 1:2:3:4:5:6:7:8 should be valid");
+ assertTrue(isValidInet6Address("1:2:3:4:5:6::8"), "IPV6 1:2:3:4:5:6::8 should be valid");
+ assertTrue(isValidInet6Address("1:2:3:4:5::8"), "IPV6 1:2:3:4:5::8 should be valid");
+ assertTrue(isValidInet6Address("1:2:3:4::8"), "IPV6 1:2:3:4::8 should be valid");
+ assertTrue(isValidInet6Address("1:2:3::8"), "IPV6 1:2:3::8 should be valid");
+ assertTrue(isValidInet6Address("1:2::8"), "IPV6 1:2::8 should be valid");
+ assertTrue(isValidInet6Address("1::8"), "IPV6 1::8 should be valid");
+ assertTrue(isValidInet6Address("1::2:3:4:5:6:7"), "IPV6 1::2:3:4:5:6:7 should be valid");
+ assertTrue(isValidInet6Address("1::2:3:4:5:6"), "IPV6 1::2:3:4:5:6 should be valid");
+ assertTrue(isValidInet6Address("1::2:3:4:5"), "IPV6 1::2:3:4:5 should be valid");
+ assertTrue(isValidInet6Address("1::2:3:4"), "IPV6 1::2:3:4 should be valid");
+ assertTrue(isValidInet6Address("1::2:3"), "IPV6 1::2:3 should be valid");
+ assertTrue(isValidInet6Address("::2:3:4:5:6:7:8"), "IPV6 ::2:3:4:5:6:7:8 should be valid");
+ assertTrue(isValidInet6Address("::2:3:4:5:6:7"), "IPV6 ::2:3:4:5:6:7 should be valid");
+ assertTrue(isValidInet6Address("::2:3:4:5:6"), "IPV6 ::2:3:4:5:6 should be valid");
+ assertTrue(isValidInet6Address("::2:3:4:5"), "IPV6 ::2:3:4:5 should be valid");
+ assertTrue(isValidInet6Address("::2:3:4"), "IPV6 ::2:3:4 should be valid");
+ assertTrue(isValidInet6Address("::2:3"), "IPV6 ::2:3 should be valid");
+ assertTrue(isValidInet6Address("::8"), "IPV6 ::8 should be valid");
+ assertTrue(isValidInet6Address("1:2:3:4:5:6::"), "IPV6 1:2:3:4:5:6:: should be valid");
+ assertTrue(isValidInet6Address("1:2:3:4:5::"), "IPV6 1:2:3:4:5:: should be valid");
+ assertTrue(isValidInet6Address("1:2:3:4::"), "IPV6 1:2:3:4:: should be valid");
+ assertTrue(isValidInet6Address("1:2:3::"), "IPV6 1:2:3:: should be valid");
+ assertTrue(isValidInet6Address("1:2::"), "IPV6 1:2:: should be valid");
+ assertTrue(isValidInet6Address("1::"), "IPV6 1:: should be valid");
+ assertTrue(isValidInet6Address("1:2:3:4:5::7:8"), "IPV6 1:2:3:4:5::7:8 should be valid");
+ assertFalse(isValidInet6Address("1:2:3::4:5::7:8"), "IPV6 1:2:3::4:5::7:8 should be invalid"); // Double "::"
+ assertFalse(isValidInet6Address("12345::6:7:8"), "IPV6 12345::6:7:8 should be invalid");
+ assertTrue(isValidInet6Address("1:2:3:4::7:8"), "IPV6 1:2:3:4::7:8 should be valid");
+ assertTrue(isValidInet6Address("1:2:3::7:8"), "IPV6 1:2:3::7:8 should be valid");
+ assertTrue(isValidInet6Address("1:2::7:8"), "IPV6 1:2::7:8 should be valid");
+ assertTrue(isValidInet6Address("1::7:8"), "IPV6 1::7:8 should be valid");
+ // IPv4 addresses as dotted-quads
+ assertTrue(isValidInet6Address("1:2:3:4:5:6:1.2.3.4"), "IPV6 1:2:3:4:5:6:1.2.3.4 should be valid");
+ assertTrue(isValidInet6Address("1:2:3:4:5::1.2.3.4"), "IPV6 1:2:3:4:5::1.2.3.4 should be valid");
+ assertTrue(isValidInet6Address("1:2:3:4::1.2.3.4"), "IPV6 1:2:3:4::1.2.3.4 should be valid");
+ assertTrue(isValidInet6Address("1:2:3::1.2.3.4"), "IPV6 1:2:3::1.2.3.4 should be valid");
+ assertTrue(isValidInet6Address("1:2::1.2.3.4"), "IPV6 1:2::1.2.3.4 should be valid");
+ assertTrue(isValidInet6Address("1::1.2.3.4"), "IPV6 1::1.2.3.4 should be valid");
+ assertTrue(isValidInet6Address("1:2:3:4::5:1.2.3.4"), "IPV6 1:2:3:4::5:1.2.3.4 should be valid");
+ assertTrue(isValidInet6Address("1:2:3::5:1.2.3.4"), "IPV6 1:2:3::5:1.2.3.4 should be valid");
+ assertTrue(isValidInet6Address("1:2::5:1.2.3.4"), "IPV6 1:2::5:1.2.3.4 should be valid");
+ assertTrue(isValidInet6Address("1::5:1.2.3.4"), "IPV6 1::5:1.2.3.4 should be valid");
+ assertTrue(isValidInet6Address("1::5:11.22.33.44"), "IPV6 1::5:11.22.33.44 should be valid");
+ assertFalse(isValidInet6Address("1::5:400.2.3.4"), "IPV6 1::5:400.2.3.4 should be invalid");
+ assertFalse(isValidInet6Address("1::5:260.2.3.4"), "IPV6 1::5:260.2.3.4 should be invalid");
+ assertFalse(isValidInet6Address("1::5:256.2.3.4"), "IPV6 1::5:256.2.3.4 should be invalid");
+ assertFalse(isValidInet6Address("1::5:1.256.3.4"), "IPV6 1::5:1.256.3.4 should be invalid");
+ assertFalse(isValidInet6Address("1::5:1.2.256.4"), "IPV6 1::5:1.2.256.4 should be invalid");
+ assertFalse(isValidInet6Address("1::5:1.2.3.256"), "IPV6 1::5:1.2.3.256 should be invalid");
+ assertFalse(isValidInet6Address("1::5:300.2.3.4"), "IPV6 1::5:300.2.3.4 should be invalid");
+ assertFalse(isValidInet6Address("1::5:1.300.3.4"), "IPV6 1::5:1.300.3.4 should be invalid");
+ assertFalse(isValidInet6Address("1::5:1.2.300.4"), "IPV6 1::5:1.2.300.4 should be invalid");
+ assertFalse(isValidInet6Address("1::5:1.2.3.300"), "IPV6 1::5:1.2.3.300 should be invalid");
+ assertFalse(isValidInet6Address("1::5:900.2.3.4"), "IPV6 1::5:900.2.3.4 should be invalid");
+ assertFalse(isValidInet6Address("1::5:1.900.3.4"), "IPV6 1::5:1.900.3.4 should be invalid");
+ assertFalse(isValidInet6Address("1::5:1.2.900.4"), "IPV6 1::5:1.2.900.4 should be invalid");
+ assertFalse(isValidInet6Address("1::5:1.2.3.900"), "IPV6 1::5:1.2.3.900 should be invalid");
+ assertFalse(isValidInet6Address("1::5:300.300.300.300"), "IPV6 1::5:300.300.300.300 should be invalid");
+ assertFalse(isValidInet6Address("1::5:3000.30.30.30"), "IPV6 1::5:3000.30.30.30 should be invalid");
+ assertFalse(isValidInet6Address("1::400.2.3.4"), "IPV6 1::400.2.3.4 should be invalid");
+ assertFalse(isValidInet6Address("1::260.2.3.4"), "IPV6 1::260.2.3.4 should be invalid");
+ assertFalse(isValidInet6Address("1::256.2.3.4"), "IPV6 1::256.2.3.4 should be invalid");
+ assertFalse(isValidInet6Address("1::1.256.3.4"), "IPV6 1::1.256.3.4 should be invalid");
+ assertFalse(isValidInet6Address("1::1.2.256.4"), "IPV6 1::1.2.256.4 should be invalid");
+ assertFalse(isValidInet6Address("1::1.2.3.256"), "IPV6 1::1.2.3.256 should be invalid");
+ assertFalse(isValidInet6Address("1::300.2.3.4"), "IPV6 1::300.2.3.4 should be invalid");
+ assertFalse(isValidInet6Address("1::1.300.3.4"), "IPV6 1::1.300.3.4 should be invalid");
+ assertFalse(isValidInet6Address("1::1.2.300.4"), "IPV6 1::1.2.300.4 should be invalid");
+ assertFalse(isValidInet6Address("1::1.2.3.300"), "IPV6 1::1.2.3.300 should be invalid");
+ assertFalse(isValidInet6Address("1::900.2.3.4"), "IPV6 1::900.2.3.4 should be invalid");
+ assertFalse(isValidInet6Address("1::1.900.3.4"), "IPV6 1::1.900.3.4 should be invalid");
+ assertFalse(isValidInet6Address("1::1.2.900.4"), "IPV6 1::1.2.900.4 should be invalid");
+ assertFalse(isValidInet6Address("1::1.2.3.900"), "IPV6 1::1.2.3.900 should be invalid");
+ assertFalse(isValidInet6Address("1::300.300.300.300"), "IPV6 1::300.300.300.300 should be invalid");
+ assertFalse(isValidInet6Address("1::3000.30.30.30"), "IPV6 1::3000.30.30.30 should be invalid");
+ assertFalse(isValidInet6Address("::400.2.3.4"), "IPV6 ::400.2.3.4 should be invalid");
+ assertFalse(isValidInet6Address("::260.2.3.4"), "IPV6 ::260.2.3.4 should be invalid");
+ assertFalse(isValidInet6Address("::256.2.3.4"), "IPV6 ::256.2.3.4 should be invalid");
+ assertFalse(isValidInet6Address("::1.256.3.4"), "IPV6 ::1.256.3.4 should be invalid");
+ assertFalse(isValidInet6Address("::1.2.256.4"), "IPV6 ::1.2.256.4 should be invalid");
+ assertFalse(isValidInet6Address("::1.2.3.256"), "IPV6 ::1.2.3.256 should be invalid");
+ assertFalse(isValidInet6Address("::300.2.3.4"), "IPV6 ::300.2.3.4 should be invalid");
+ assertFalse(isValidInet6Address("::1.300.3.4"), "IPV6 ::1.300.3.4 should be invalid");
+ assertFalse(isValidInet6Address("::1.2.300.4"), "IPV6 ::1.2.300.4 should be invalid");
+ assertFalse(isValidInet6Address("::1.2.3.300"), "IPV6 ::1.2.3.300 should be invalid");
+ assertFalse(isValidInet6Address("::900.2.3.4"), "IPV6 ::900.2.3.4 should be invalid");
+ assertFalse(isValidInet6Address("::1.900.3.4"), "IPV6 ::1.900.3.4 should be invalid");
+ assertFalse(isValidInet6Address("::1.2.900.4"), "IPV6 ::1.2.900.4 should be invalid");
+ assertFalse(isValidInet6Address("::1.2.3.900"), "IPV6 ::1.2.3.900 should be invalid");
+ assertFalse(isValidInet6Address("::300.300.300.300"), "IPV6 ::300.300.300.300 should be invalid");
+ assertFalse(isValidInet6Address("::3000.30.30.30"), "IPV6 ::3000.30.30.30 should be invalid");
+ assertTrue(
+ isValidInet6Address("fe80::217:f2ff:254.7.237.98"), "IPV6 fe80::217:f2ff:254.7.237.98 should be valid");
+ assertTrue(isValidInet6Address("::ffff:192.168.1.26"), "IPV6 ::ffff:192.168.1.26 should be valid");
+ assertFalse(
+ isValidInet6Address("2001:1:1:1:1:1:255Z255X255Y255"),
+ "IPV6 2001:1:1:1:1:1:255Z255X255Y255 should be invalid"); // garbage
+ // instead of "."
+ // in IPv4
+ assertFalse(isValidInet6Address("::ffff:192x168.1.26"), "IPV6 ::ffff:192x168.1.26 should be invalid"); // ditto
+ assertTrue(isValidInet6Address("::ffff:192.168.1.1"), "IPV6 ::ffff:192.168.1.1 should be valid");
+ assertTrue(
+ isValidInet6Address("0:0:0:0:0:0:13.1.68.3"),
+ "IPV6 0:0:0:0:0:0:13.1.68.3 should be valid"); // IPv4-compatible IPv6 address, full,
+ // deprecated
+ assertTrue(
+ isValidInet6Address("0:0:0:0:0:FFFF:129.144.52.38"),
+ "IPV6 0:0:0:0:0:FFFF:129.144.52.38 should be valid"); // IPv4-mapped IPv6
+ // address, full
+ assertTrue(
+ isValidInet6Address("::13.1.68.3"),
+ "IPV6 ::13.1.68.3 should be valid"); // IPv4-compatible IPv6 address, compressed, deprecated
+ assertTrue(
+ isValidInet6Address("::FFFF:129.144.52.38"),
+ "IPV6 ::FFFF:129.144.52.38 should be valid"); // IPv4-mapped IPv6 address, compressed
+ assertTrue(
+ isValidInet6Address("fe80:0:0:0:204:61ff:254.157.241.86"),
+ "IPV6 fe80:0:0:0:204:61ff:254.157.241.86 should be valid");
+ assertTrue(
+ isValidInet6Address("fe80::204:61ff:254.157.241.86"),
+ "IPV6 fe80::204:61ff:254.157.241.86 should be valid");
+ assertTrue(isValidInet6Address("::ffff:12.34.56.78"), "IPV6 ::ffff:12.34.56.78 should be valid");
+ assertFalse(isValidInet6Address("::ffff:2.3.4"), "IPV6 ::ffff:2.3.4 should be invalid");
+ assertFalse(isValidInet6Address("::ffff:257.1.2.3"), "IPV6 ::ffff:257.1.2.3 should be invalid");
+ assertFalse(isValidInet6Address("1.2.3.4"), "IPV6 1.2.3.4 should be invalid");
+ assertFalse(
+ isValidInet6Address("1.2.3.4:1111:2222:3333:4444::5555"),
+ "IPV6 1.2.3.4:1111:2222:3333:4444::5555 should be invalid");
+ assertFalse(
+ isValidInet6Address("1.2.3.4:1111:2222:3333::5555"),
+ "IPV6 1.2.3.4:1111:2222:3333::5555 should be invalid");
+ assertFalse(isValidInet6Address("1.2.3.4:1111:2222::5555"), "IPV6 1.2.3.4:1111:2222::5555 should be invalid");
+ assertFalse(isValidInet6Address("1.2.3.4:1111::5555"), "IPV6 1.2.3.4:1111::5555 should be invalid");
+ assertFalse(isValidInet6Address("1.2.3.4::5555"), "IPV6 1.2.3.4::5555 should be invalid");
+ assertFalse(isValidInet6Address("1.2.3.4::"), "IPV6 1.2.3.4:: should be invalid");
+ // Testing IPv4 addresses represented as dotted-quads
+ // Leading zeroes in IPv4 addresses not allowed: some systems treat the leading "0" in ".086" as the start of an
+ // octal number
+ // Update: The BNF in RFC-3986 explicitly defines the dec-octet (for IPv4 addresses) not to have a leading zero
+ assertFalse(
+ isValidInet6Address("fe80:0000:0000:0000:0204:61ff:254.157.241.086"),
+ "IPV6 fe80:0000:0000:0000:0204:61ff:254.157.241.086 should be invalid");
+ assertTrue(
+ isValidInet6Address("::ffff:192.0.2.128"),
+ "IPV6 ::ffff:192.0.2.128 should be valid"); // but this is OK, since there's a single
+ // digit
+ assertFalse(
+ isValidInet6Address("XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4"),
+ "IPV6 XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4 should be invalid");
+ assertFalse(
+ isValidInet6Address("1111:2222:3333:4444:5555:6666:00.00.00.00"),
+ "IPV6 1111:2222:3333:4444:5555:6666:00.00.00.00 should be invalid");
+ assertFalse(
+ isValidInet6Address("1111:2222:3333:4444:5555:6666:000.000.000.000"),
+ "IPV6 1111:2222:3333:4444:5555:6666:000.000.000.000 should be invalid");
+ assertFalse(
+ isValidInet6Address("1111:2222:3333:4444:5555:6666:256.256.256.256"),
+ "IPV6 1111:2222:3333:4444:5555:6666:256.256.256.256 should be invalid");
+ assertTrue(
+ isValidInet6Address("fe80:0000:0000:0000:0204:61ff:fe9d:f156"),
+ "IPV6 fe80:0000:0000:0000:0204:61ff:fe9d:f156 should be valid");
+ assertTrue(
+ isValidInet6Address("fe80:0:0:0:204:61ff:fe9d:f156"),
+ "IPV6 fe80:0:0:0:204:61ff:fe9d:f156 should be valid");
+ assertTrue(isValidInet6Address("fe80::204:61ff:fe9d:f156"), "IPV6 fe80::204:61ff:fe9d:f156 should be valid");
+ assertFalse(isValidInet6Address(":"), "IPV6 : should be invalid");
+ assertTrue(isValidInet6Address("::ffff:c000:280"), "IPV6 ::ffff:c000:280 should be valid");
+ assertFalse(
+ isValidInet6Address("1111:2222:3333:4444::5555:"), "IPV6 1111:2222:3333:4444::5555: should be invalid");
+ assertFalse(isValidInet6Address("1111:2222:3333::5555:"), "IPV6 1111:2222:3333::5555: should be invalid");
+ assertFalse(isValidInet6Address("1111:2222::5555:"), "IPV6 1111:2222::5555: should be invalid");
+ assertFalse(isValidInet6Address("1111::5555:"), "IPV6 1111::5555: should be invalid");
+ assertFalse(isValidInet6Address("::5555:"), "IPV6 ::5555: should be invalid");
+ assertFalse(isValidInet6Address(":::"), "IPV6 ::: should be invalid");
+ assertFalse(isValidInet6Address("1111:"), "IPV6 1111: should be invalid");
+ assertFalse(
+ isValidInet6Address(":1111:2222:3333:4444::5555"), "IPV6 :1111:2222:3333:4444::5555 should be invalid");
+ assertFalse(isValidInet6Address(":1111:2222:3333::5555"), "IPV6 :1111:2222:3333::5555 should be invalid");
+ assertFalse(isValidInet6Address(":1111:2222::5555"), "IPV6 :1111:2222::5555 should be invalid");
+ assertFalse(isValidInet6Address(":1111::5555"), "IPV6 :1111::5555 should be invalid");
+ assertFalse(isValidInet6Address(":::5555"), "IPV6 :::5555 should be invalid");
+ assertTrue(
+ isValidInet6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
+ "IPV6 2001:0db8:85a3:0000:0000:8a2e:0370:7334 should be valid");
+ assertTrue(
+ isValidInet6Address("2001:db8:85a3:0:0:8a2e:370:7334"),
+ "IPV6 2001:db8:85a3:0:0:8a2e:370:7334 should be valid");
+ assertTrue(
+ isValidInet6Address("2001:db8:85a3::8a2e:370:7334"),
+ "IPV6 2001:db8:85a3::8a2e:370:7334 should be valid");
+ assertTrue(
+ isValidInet6Address("2001:0db8:0000:0000:0000:0000:1428:57ab"),
+ "IPV6 2001:0db8:0000:0000:0000:0000:1428:57ab should be valid");
+ assertTrue(
+ isValidInet6Address("2001:0db8:0000:0000:0000::1428:57ab"),
+ "IPV6 2001:0db8:0000:0000:0000::1428:57ab should be valid");
+ assertTrue(
+ isValidInet6Address("2001:0db8:0:0:0:0:1428:57ab"), "IPV6 2001:0db8:0:0:0:0:1428:57ab should be valid");
+ assertTrue(isValidInet6Address("2001:0db8:0:0::1428:57ab"), "IPV6 2001:0db8:0:0::1428:57ab should be valid");
+ assertTrue(isValidInet6Address("2001:0db8::1428:57ab"), "IPV6 2001:0db8::1428:57ab should be valid");
+ assertTrue(isValidInet6Address("2001:db8::1428:57ab"), "IPV6 2001:db8::1428:57ab should be valid");
+ assertTrue(isValidInet6Address("::ffff:0c22:384e"), "IPV6 ::ffff:0c22:384e should be valid");
+ assertTrue(
+ isValidInet6Address("2001:0db8:1234:0000:0000:0000:0000:0000"),
+ "IPV6 2001:0db8:1234:0000:0000:0000:0000:0000 should be valid");
+ assertTrue(
+ isValidInet6Address("2001:0db8:1234:ffff:ffff:ffff:ffff:ffff"),
+ "IPV6 2001:0db8:1234:ffff:ffff:ffff:ffff:ffff should be valid");
+ assertTrue(isValidInet6Address("2001:db8:a::123"), "IPV6 2001:db8:a::123 should be valid");
+ assertFalse(isValidInet6Address("123"), "IPV6 123 should be invalid");
+ assertFalse(isValidInet6Address("ldkfj"), "IPV6 ldkfj should be invalid");
+ assertFalse(isValidInet6Address("2001::FFD3::57ab"), "IPV6 2001::FFD3::57ab should be invalid");
+ assertFalse(
+ isValidInet6Address("2001:db8:85a3::8a2e:37023:7334"),
+ "IPV6 2001:db8:85a3::8a2e:37023:7334 should be invalid");
+ assertFalse(
+ isValidInet6Address("2001:db8:85a3::8a2e:370k:7334"),
+ "IPV6 2001:db8:85a3::8a2e:370k:7334 should be invalid");
+ assertFalse(isValidInet6Address("1:2:3:4:5:6:7:8:9"), "IPV6 1:2:3:4:5:6:7:8:9 should be invalid");
+ assertFalse(isValidInet6Address("1::2::3"), "IPV6 1::2::3 should be invalid");
+ assertFalse(isValidInet6Address("1:::3:4:5"), "IPV6 1:::3:4:5 should be invalid");
+ assertFalse(isValidInet6Address("1:2:3::4:5:6:7:8:9"), "IPV6 1:2:3::4:5:6:7:8:9 should be invalid");
+ assertTrue(
+ isValidInet6Address("1111:2222:3333:4444:5555:6666:7777:8888"),
+ "IPV6 1111:2222:3333:4444:5555:6666:7777:8888 should be valid");
+ assertTrue(
+ isValidInet6Address("1111:2222:3333:4444:5555:6666:7777::"),
+ "IPV6 1111:2222:3333:4444:5555:6666:7777:: should be valid");
+ assertTrue(
+ isValidInet6Address("1111:2222:3333:4444:5555:6666::"),
+ "IPV6 1111:2222:3333:4444:5555:6666:: should be valid");
+ assertTrue(
+ isValidInet6Address("1111:2222:3333:4444:5555::"), "IPV6 1111:2222:3333:4444:5555:: should be valid");
+ assertTrue(isValidInet6Address("1111:2222:3333:4444::"), "IPV6 1111:2222:3333:4444:: should be valid");
+ assertTrue(isValidInet6Address("1111:2222:3333::"), "IPV6 1111:2222:3333:: should be valid");
+ assertTrue(isValidInet6Address("1111:2222::"), "IPV6 1111:2222:: should be valid");
+ assertTrue(isValidInet6Address("1111::"), "IPV6 1111:: should be valid");
+ assertTrue(
+ isValidInet6Address("1111:2222:3333:4444:5555:6666::8888"),
+ "IPV6 1111:2222:3333:4444:5555:6666::8888 should be valid");
+ assertTrue(
+ isValidInet6Address("1111:2222:3333:4444:5555::8888"),
+ "IPV6 1111:2222:3333:4444:5555::8888 should be valid");
+ assertTrue(isValidInet6Address("1111:2222:3333:4444::8888"), "IPV6 1111:2222:3333:4444::8888 should be valid");
… diff truncated
log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java+47 14
@@ -30,19 +30,22 @@
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import javax.net.ssl.SNIHostName;
import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
+import org.apache.logging.log4j.core.net.ssl.internal.InetAddressValidator;
import org.apache.logging.log4j.util.Strings;
-/**
- *
- */
public class SslSocketManager extends TcpSocketManager {
+
public static final int DEFAULT_PORT = 6514;
+
private static final SslSocketManagerFactory FACTORY = new SslSocketManagerFactory();
+
private final SslConfiguration sslConfig;
/**
@@ -285,10 +288,7 @@ private static String createSslConfigurationId(final SslConfiguration sslConfig)
@Override
protected Socket createSocket(final InetSocketAddress socketAddress) throws IOException {
- final SSLSocketFactory socketFactory = createSslSocketFactory(sslConfig);
- final Socket newSocket = socketFactory.createSocket();
- newSocket.connect(socketAddress, getConnectTimeoutMillis());
- return newSocket;
+ return createSocket(getHost(), socketAddress, getConnectTimeoutMillis(), sslConfig, getSocketOptions());
}
private static SSLSocketFactory createSslSocketFactory(final SslConfiguration sslConf) {
@@ -333,32 +333,65 @@ Socket createSocket(final SslFactoryData data) throws IOException {
for (InetSocketAddress socketAddress : socketAddresses) {
try {
return SslSocketManager.createSocket(
- socketAddress, data.connectTimeoutMillis, data.sslConfiguration, data.socketOptions);
+ data.host,
+ socketAddress,
+ data.connectTimeoutMillis,
+ data.sslConfiguration,
+ data.socketOptions);
} catch (IOException ex) {
- ioe = ex;
+ final String message = String.format(
+ "failed create a socket to `%s:%s` that is resolved to address `%s`",
+ data.host, data.port, socketAddress);
+ final IOException newEx = new IOException(message, ex);
+ if (ioe == null) {
+ ioe = newEx;
+ } else {
+ ioe.addSuppressed(newEx);
+ }
}
}
throw new IOException(errorMessage(data, socketAddresses), ioe);
}
}
- static Socket createSocket(
+ private static Socket createSocket(
+ final String hostName,
final InetSocketAddress socketAddress,
final int connectTimeoutMillis,
final SslConfiguration sslConfiguration,
final SocketOptions socketOptions)
throws IOException {
+
+ // Create the `SSLSocket`
final SSLSocketFactory socketFactory = createSslSocketFactory(sslConfiguration);
final SSLSocket socket = (SSLSocket) socketFactory.createSocket();
+
+ // Apply socket options before `connect()`
if (socketOptions != null) {
- // Not sure which options must be applied before or after the connect() call.
socketOptions.apply(socket);
}
+
+ // Connect the socket
socket.connect(socketAddress, connectTimeoutMillis);
- if (socketOptions != null) {
- // Not sure which options must be applied before or after the connect() call.
- socketOptions.apply(socket);
+
+ // Verify the host name
+ if (sslConfiguration.isVerifyHostName()) {
+ // Allowed endpoint identification algorithms: HTTPS and LDAPS.
+ // https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#endpoint-identification-algorithms
+ final SSLParameters sslParameters = socket.getSSLParameters();
+ sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
+ // Literal IPv4 and IPv6 addresses are not permitted in "HostName".
+ // https://www.rfc-editor.org/rfc/rfc6066.html#section-3
+ if (!InetAddressValidator.isValid(hostName)) {
+ sslParameters.setServerNames(Collections.singletonList(new SNIHostName(hostName)));
+ }
+ socket.setSSLParameters(sslParameters);
}
+
+ // Force the handshake right after `connect()` instead of waiting for read/write to trigger it indirectly at a
+ // later stage
+ socket.startHandshake();
+
return socket;
}
}
log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfiguration.java+8 4
@@ -132,8 +132,8 @@ private static SSLContext createSslContext(
@Nullable final TrustStoreConfiguration trustStoreConfig) {
try {
final SSLContext sslContext = SSLContext.getInstance(protocol);
- final KeyManager[] keyManagers = loadKeyManagers(keyStoreConfig);
- final TrustManager[] trustManagers = loadTrustManagers(trustStoreConfig);
+ @Nullable final KeyManager[] keyManagers = loadKeyManagers(keyStoreConfig);
+ @Nullable final TrustManager[] trustManagers = loadTrustManagers(trustStoreConfig);
sslContext.init(keyManagers, trustManagers, null);
return sslContext;
} catch (final Exception error) {
@@ -144,9 +144,11 @@ private static SSLContext createSslContext(
}
}
+ @Nullable
+ @NullUnmarked
private static KeyManager[] loadKeyManagers(@Nullable final KeyStoreConfiguration config) throws Exception {
if (config == null) {
- return new KeyManager[0];
+ return null;
}
final KeyManagerFactory factory = KeyManagerFactory.getInstance(config.getKeyManagerFactoryAlgorithm());
final char[] password = config.getPasswordAsCharArray();
@@ -158,9 +160,11 @@ private static KeyManager[] loadKeyManagers(@Nullable final KeyStoreConfiguratio
return factory.getKeyManagers();
}
+ @Nullable
+ @NullUnmarked
private static TrustManager[] loadTrustManagers(@Nullable final TrustStoreConfiguration config) throws Exception {
if (config == null) {
- return new TrustManager[0];
+ return null;
}
final TrustManagerFactory factory = TrustManagerFactory.getInstance(config.getTrustManagerFactoryAlgorithm());
factory.init(config.getKeyStore());
log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/internal/InetAddressValidator.java+171 0
@@ -0,0 +1,171 @@
+/*
+ * 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.net.ssl.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.logging.log4j.util.Strings;
+
+/**
+ * {@link java.net.InetAddress} literal validator.
+ *
+ * @implNote
+ * IP address validation is hard.
+ * IPv6 validation is copied from Apache Commons Validator.
+ * This is an internal class with a very limited usage, and should stay that way.
+ * This class should be replaced with {@code java.net.InetAddress#ofLiteral(String)} introduced in Java 22.
+ *
+ * @see <a href="https://github.com/apache/commons-validator/blob/7c27355d86f7dc5a4a548658745b85c9f0d5b99f/src/main/java/org/apache/commons/validator/routines/InetAddressValidator.java"><code>InetAddressValidator</code> of Apache Commons Validator</a>
+ */
+public final class InetAddressValidator {
+
+ private static final Pattern IPV4_PATTERN = Pattern.compile("^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$");
+
+ /** Max number of hex groups (separated by {@code :}) in an IPV6 address */
+ private static final int IPV6_MAX_HEX_GROUPS = 8;
+
+ /** Max hex digits in each IPv6 group */
+ private static final int IPV6_MAX_HEX_DIGITS_PER_GROUP = 4;
+
+ private static final Pattern DIGITS_PATTERN = Pattern.compile("\\d{1,3}");
+
+ private static final Pattern ID_CHECK_PATTERN = Pattern.compile("[^\\s/%]+");
+
+ private InetAddressValidator() {}
+
+ /**
+ * {@return {@code true} if the provided string is a valid IPv4 or IPv6 address, {@code false} otherwise}.
+ * Verification is performed without any name resolution.
+ *
+ * @param inetAddress the string to validate
+ */
+ public static boolean isValid(final String inetAddress) {
+ return isValidInet4Address(inetAddress) || isValidInet6Address(inetAddress);
+ }
+
+ static boolean isValidInet4Address(final String inet4Address) {
+ final Matcher m = IPV4_PATTERN.matcher(inet4Address);
+ if (!m.matches()) {
+ return false;
+ }
+ for (int i = 1; i <= 4; i++) {
+ final String g = m.group(i);
+ if (g.length() > 1 && g.startsWith("0")) {
+ return false;
+ }
+ final int n;
+ try {
+ n = Integer.parseInt(g);
+ } catch (final NumberFormatException ignored) {
+ return false;
+ }
+ if (n > 0xFF) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static boolean isValidInet6Address(String inet6Address) {
+ String[] parts;
+ // remove prefix size. This will appear after the zone id (if any)
+ parts = inet6Address.split("/", -1);
+ if (parts.length > 2) {
+ return false; // can only have one prefix specifier
+ }
+ if (parts.length == 2) {
+ if (!DIGITS_PATTERN.matcher(parts[1]).matches()) {
+ return false; // not a valid number
+ }
+ final int bits = Integer.parseInt(parts[1]); // cannot fail because of RE check
+ if (bits < 0 || bits > 128) {
+ return false; // out of range
+ }
+ }
+ // remove zone-id
+ parts = parts[0].split("%", -1);
+ // The id syntax is implementation independent, but it presumably cannot allow:
+ // whitespace, '/' or '%'
+ if (parts.length > 2
+ || parts.length == 2 && !ID_CHECK_PATTERN.matcher(parts[1]).matches()) {
+ return false; // invalid id
+ }
+ inet6Address = parts[0];
+ final boolean containsCompressedZeroes = inet6Address.contains("::");
+ if (containsCompressedZeroes && inet6Address.indexOf("::") != inet6Address.lastIndexOf("::")) {
+ return false;
+ }
+ final boolean startsWithCompressed = inet6Address.startsWith("::");
+ final boolean endsWithCompressed = inet6Address.endsWith("::");
+ final boolean endsWithSep = inet6Address.endsWith(":");
+ if (inet6Address.startsWith(":") && !startsWithCompressed || endsWithSep && !endsWithCompressed) {
+ return false;
+ }
+ String[] octets = inet6Address.split(":");
+ if (containsCompressedZeroes) {
+ final List<String> octetList = new ArrayList<>(Arrays.asList(octets));
+ if (endsWithCompressed) {
+ // String.split() drops ending empty segments
+ octetList.add("");
+ } else if (startsWithCompressed && !octetList.isEmpty()) {
+ octetList.remove(0);
+ }
+ octets = octetList.toArray(new String[0]);
+ }
+ if (octets.length > IPV6_MAX_HEX_GROUPS) {
+ return false;
+ }
+ int validOctets = 0;
+ int emptyOctets = 0; // consecutive empty chunks
+ for (int index = 0; index < octets.length; index++) {
+ final String octet = octets[index];
+ if (Strings.isBlank(octet)) {
+ emptyOctets++;
+ if (emptyOctets > 1) {
+ return false;
+ }
+ } else {
+ emptyOctets = 0;
+ // Is last chunk an IPv4 address?
+ if (index == octets.length - 1 && octet.contains(".")) {
+ if (!isValidInet4Address(octet)) {
+ return false;
+ }
+ validOctets += 2;
+ continue;
+ }
+ if (octet.length() > IPV6_MAX_HEX_DIGITS_PER_GROUP) {
+ return false;
+ }
+ int octetInt = 0;
+ try {
+ octetInt = Integer.parseInt(octet, 16);
+ } catch (final NumberFormatException e) {
+ return false;
+ }
+ if (octetInt < 0 || octetInt > 0xffff) {
+ return false;
+ }
+ }
+ validOctets++;
+ }
+ return validOctets <= IPV6_MAX_HEX_GROUPS && (validOctets >= IPV6_MAX_HEX_GROUPS || containsCompressedZeroes);
+ }
+}
.../logging/log4j/core/net/SslSocketManager.java | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java+11 1
@@ -383,7 +383,17 @@ private static Socket createSocket(
// Literal IPv4 and IPv6 addresses are not permitted in "HostName".
// https://www.rfc-editor.org/rfc/rfc6066.html#section-3
if (!InetAddressValidator.isValid(hostName)) {
- sslParameters.setServerNames(Collections.singletonList(new SNIHostName(hostName)));
+ // `SNIHostName::new` validates host names using `IDN.toASCII(hostName, IDN.USE_STD3_ASCII_RULES)`.
+ // Instead of failing, simply skip host names causing `SNIHostName::new` failures.
+ SNIHostName serverName = null;
+ try {
+ serverName = new SNIHostName(hostName);
+ } catch (IllegalArgumentException ignored) {
+ // Do nothing
+ }
+ if (serverName != null) {
+ sslParameters.setServerNames(Collections.singletonList(serverName));
+ }
}
socket.setSSLParameters(sslParameters);
}
.../antora/modules/ROOT/pages/manual/appenders/network.adoc | 3 +++
1 file changed, 3 insertions(+)
src/site/antora/modules/ROOT/pages/manual/appenders/network.adoc+3 0
@@ -67,6 +67,9 @@ https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuid
If `true`, the host name in X509 certificate will be compared to the requested host name.
In the case of a mismatch, the connection will fail.
+Note that SSL/TLS does not allow IP literals for host name verification – see https://www.rfc-editor.org/rfc/rfc6066.html#section-3[RFC 6066].
+Host name verification will only be effective if the provided host name is not an IP literal and a valid host name per https://www.rfc-editor.org/rfc/rfc1035.html[RFC 1035].
+
See also
xref:manual/systemproperties.adoc#log4j2.sslVerifyHostName[`log4j2.sslVerifyHostName`].
|===
.../6666_fix_SslSocketAppender_verifyHostName.xml | 12 ++++++++++++
1 file changed, 12 insertions(+)
create mode 100644 src/changelog/.2.x.x/6666_fix_SslSocketAppender_verifyHostName.xml
src/changelog/.2.x.x/6666_fix_SslSocketAppender_verifyHostName.xml+12 0
@@ -0,0 +1,12 @@
+<?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">
+ <issue id="6666" link="https://github.com/apache/logging-log4j2/pull/6666"/>
+ <description format="asciidoc">
+ Fix incorrect handling of the host name verification (i.e., `verifyHostName`) in `SslSocketAppender`
+ </description>
+</entry>
.../net/{ssl => }/SslSocketManagerTest.java | 56 +-
.../internal/InetAddressValidatorTest.java | 1082 -----------------
.../log4j/core/net/SslSocketManager.java | 56 +-
.../ssl/internal/InetAddressValidator.java | 171 ---
4 files changed, 95 insertions(+), 1270 deletions(-)
rename log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/{ssl => }/SslSocketManagerTest.java (59%)
delete mode 100644 log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/internal/InetAddressValidatorTest.java
delete mode 100644 log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/internal/InetAddressValidator.java
More files changed — see the full commit.

Fix host name verification in `SSLSocketManager` (#4002)

=?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?=· Dec 15, 2025, 12:05 PM+238633b93748497
log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/SslSocketManagerTest.java+131 0
@@ -0,0 +1,131 @@
+/*
+ * 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.net;
+
+import static org.apache.logging.log4j.core.net.SslSocketManager.createSniHostName;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import javax.net.ssl.SNIHostName;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSocket;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
+import org.apache.logging.log4j.core.net.ssl.SslKeyStoreConstants;
+import org.apache.logging.log4j.core.net.ssl.TrustStoreConfiguration;
+import org.apache.logging.log4j.test.junit.UsingStatusListener;
+import org.apache.logging.log4j.util.Strings;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junitpioneer.jupiter.Issue;
+
+class SslSocketManagerTest {
+
+ private static final String HOST_NAME = "apache.org";
+
+ private static final int HOST_PORT = 443;
+
+ private static final Layout<?> LAYOUT = PatternLayout.createDefaultLayout();
+
+ @Test
+ @Issue("https://github.com/apache/logging-log4j2/issues/3947")
+ @UsingStatusListener // Suppresses `StatusLogger` output, unless there is a failure
+ void should_not_throw_exception_when_configuration_without_KeyStore() throws Exception {
+ final TrustStoreConfiguration trustStoreConfig = new TrustStoreConfiguration(
+ SslKeyStoreConstants.TRUSTSTORE_LOCATION,
+ SslKeyStoreConstants::TRUSTSTORE_PWD,
+ SslKeyStoreConstants.TRUSTSTORE_TYPE,
+ null);
+ final SslConfiguration sslConfig = SslConfiguration.createSSLConfiguration(null, null, trustStoreConfig);
+ assertThatCode(() -> {
+ // noinspection EmptyTryBlock
+ try (final SslSocketManager ignored = createSocketManager(sslConfig)) {
+ // Do nothing
+ }
+ })
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ void host_name_verification_should_take_effect() {
+ final SslConfiguration sslConfig = SslConfiguration.createSSLConfiguration(
+ null,
+ null,
+ null,
+ // Explicitly enable hostname verification
+ true);
+ try (final SslSocketManager ssm = createSocketManager(sslConfig)) {
+ final SSLSocket sslSocket = (SSLSocket) ssm.getSocket();
+ final SSLParameters sslParams = sslSocket.getSSLParameters();
+ assertThat(sslParams.getEndpointIdentificationAlgorithm()).isEqualTo("HTTPS");
+ assertThat(sslParams.getServerNames()).containsOnly(new SNIHostName(HOST_NAME));
+ }
+ }
+
+ private static SslSocketManager createSocketManager(final SslConfiguration sslConfig) {
+ return SslSocketManager.getSocketManager(
+ sslConfig, SslSocketManagerTest.HOST_NAME, HOST_PORT, 0, 0, true, LAYOUT, 8192, null);
+ }
+
+ static String[] hostNamesAllowedForSniServerName() {
+ return new String[] {
+ "apache.org",
+ "a.b",
+ "sub.domain.co.uk",
+ "xn--bcher-kva.example", // valid IDN/punycode
+ "my-host-123.example",
+ "a1234567890.b1234567890.c1234567890", // numeric/alpha labels
+ "EXAMPLE.COM", // case-insensitive
+ "service--node.example", // double hyphens allowed
+ Strings.repeat("l", 63) + ".com", // 63-char label, valid
+ "a.b.c.d.e.f.g.h.i.j", // many labels, short each
+ };
+ }
+
+ @ParameterizedTest
+ @MethodSource("hostNamesAllowedForSniServerName")
+ void createSniHostName_should_work_with_allowed_input(String hostName) {
+ assertThat(createSniHostName(hostName)).isNotNull();
+ }
+
+ static String[] hostNamesNotAllowedForSniServerName() {
+ return new String[] {
+ // Invalid because IP literals not allowed
+ "192.168.1.1", // IPv4 literal
+ "2001:db8::1", // IPv6 literal
+ "[2001:db8::1]", // IPv6 URL literal form
+ // Invalid because illegal characters
+ "exa_mple.com", // underscore not allowed
+ "host name.com", // space not allowed
+ "example!.com", // symbol not allowed
+ // Invalid because label syntax violations
+ "-example.com", // leading hyphen
+ "example-.com", // trailing hyphen
+ ".example.com", // leading dot / empty label
+ // Invalid because label too long
+ Strings.repeat("l", 64) + ".com",
+ };
+ }
+
+ @ParameterizedTest
+ @MethodSource("hostNamesNotAllowedForSniServerName")
+ void createSniHostName_should_fail_with_not_allowed_input(String hostName) {
+ assertThat(createSniHostName(hostName)).isNull();
+ }
+}
log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslSocketManagerTest.java+0 45
@@ -1,45 +0,0 @@
-/*
- * 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.net.ssl;
-
-import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
-
-import org.apache.logging.log4j.core.layout.PatternLayout;
-import org.apache.logging.log4j.core.net.SslSocketManager;
-import org.junit.jupiter.api.Test;
-import org.junitpioneer.jupiter.Issue;
-
-class SslSocketManagerTest {
- @Issue("https://github.com/apache/logging-log4j2/issues/3947")
- @Test
- void shouldNotThrowExceptionWhenConfiguringTrustStore() {
- final TrustStoreConfiguration trustStoreConfiguration = assertDoesNotThrow(() -> new TrustStoreConfiguration(
- SslKeyStoreConstants.TRUSTSTORE_LOCATION,
- SslKeyStoreConstants::TRUSTSTORE_PWD,
- SslKeyStoreConstants.TRUSTSTORE_TYPE,
- null));
- final SslConfiguration sslConfiguration =
- SslConfiguration.createSSLConfiguration(null, null, trustStoreConfiguration);
- assertDoesNotThrow(() -> {
- // noinspection EmptyTryBlock (try-with-resources to close `SslSocketManager`, even on failure
- try (final SslSocketManager ignored = SslSocketManager.getSocketManager(
- sslConfiguration, "localhost", 0, 0, 0, true, PatternLayout.createDefaultLayout(), 8192, null)) {
- // Do nothing
- }
- });
- }
-}
log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java+84 14
@@ -30,19 +30,22 @@
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import javax.net.ssl.SNIHostName;
import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
import org.apache.logging.log4j.util.Strings;
+import org.jspecify.annotations.Nullable;
-/**
- *
- */
public class SslSocketManager extends TcpSocketManager {
+
public static final int DEFAULT_PORT = 6514;
+
private static final SslSocketManagerFactory FACTORY = new SslSocketManagerFactory();
+
private final SslConfiguration sslConfig;
/**
@@ -285,10 +288,7 @@ private static String createSslConfigurationId(final SslConfiguration sslConfig)
@Override
protected Socket createSocket(final InetSocketAddress socketAddress) throws IOException {
- final SSLSocketFactory socketFactory = createSslSocketFactory(sslConfig);
- final Socket newSocket = socketFactory.createSocket();
- newSocket.connect(socketAddress, getConnectTimeoutMillis());
- return newSocket;
+ return createSocket(getHost(), socketAddress, getConnectTimeoutMillis(), sslConfig, getSocketOptions());
}
private static SSLSocketFactory createSslSocketFactory(final SslConfiguration sslConf) {
@@ -333,32 +333,102 @@ Socket createSocket(final SslFactoryData data) throws IOException {
for (InetSocketAddress socketAddress : socketAddresses) {
try {
return SslSocketManager.createSocket(
- socketAddress, data.connectTimeoutMillis, data.sslConfiguration, data.socketOptions);
+ data.host,
+ socketAddress,
+ data.connectTimeoutMillis,
+ data.sslConfiguration,
+ data.socketOptions);
} catch (IOException ex) {
- ioe = ex;
+ final String message = String.format(
+ "failed create a socket to `%s:%s` that is resolved to address `%s`",
+ data.host, data.port, socketAddress);
+ final IOException newEx = new IOException(message, ex);
+ if (ioe == null) {
+ ioe = newEx;
+ } else {
+ ioe.addSuppressed(newEx);
+ }
}
}
throw new IOException(errorMessage(data, socketAddresses), ioe);
}
}
- static Socket createSocket(
+ private static Socket createSocket(
+ final String hostName,
final InetSocketAddress socketAddress,
final int connectTimeoutMillis,
final SslConfiguration sslConfiguration,
final SocketOptions socketOptions)
throws IOException {
+
+ // Create the `SSLSocket`
final SSLSocketFactory socketFactory = createSslSocketFactory(sslConfiguration);
final SSLSocket socket = (SSLSocket) socketFactory.createSocket();
+
+ // Apply socket options before `connect()`
if (socketOptions != null) {
- // Not sure which options must be applied before or after the connect() call.
socketOptions.apply(socket);
}
+
+ // Connect the socket
socket.connect(socketAddress, connectTimeoutMillis);
- if (socketOptions != null) {
- // Not sure which options must be applied before or after the connect() call.
- socketOptions.apply(socket);
+
+ // Verify the host name
+ if (sslConfiguration.isVerifyHostName()) {
+ // Allowed endpoint identification algorithms: HTTPS and LDAPS.
+ // https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#endpoint-identification-algorithms
+ final SSLParameters sslParameters = socket.getSSLParameters();
+ sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
+ final SNIHostName serverName = createSniHostName(hostName);
+ if (serverName != null) {
+ sslParameters.setServerNames(Collections.singletonList(serverName));
+ }
+ socket.setSSLParameters(sslParameters);
}
+
+ // Force the handshake right after `connect()` instead of waiting for read/write to trigger it indirectly at a
+ // later stage
+ socket.startHandshake();
+
return socket;
}
+
+ /**
+ * {@return an {@link SNIHostName} instance if the provided host name is not an IP literal (RFC 6066), and constitutes a valid host name (RFC 1035); null otherwise}
+ *
+ * @param hostName a host name
+ *
+ * @see <a href="https://www.rfc-editor.org/rfc/rfc6066.html#section-3">Literal IPv4 and IPv6 addresses are not permitted in "HostName" (RFC 6066)</a>
+ * @see <a href="https://www.rfc-editor.org/rfc/rfc1035.html">Domain Names - Implementation and Specification (RFC 1035)</a>
+ */
+ @Nullable
+ static SNIHostName createSniHostName(String hostName) {
+ // The actual check should be
+ //
+ // !isIPv4(h) && !isIPv6(h) && isValidHostName(h)
+ //
+ // Though we translate this into
+ //
+ // !h.matches("\d+[.]\d+[.]\d+[.]\d+") && new SNIServerName(h)
+ //
+ // This simplification is possible because
+ //
+ // - The `\d+[.]\d+[.]\d+[.]\d+` is sufficient to eliminate IPv4 addresses.
+ // Any sequence of four numeric labels (e.g., `1234.2345.3456.4567`) is not a valid host name.
+ // Hence, false positives are not a problem, they would be eliminated by `isValidHostName()` anyway.
+ //
+ // - `SNIServerName::new` throws an exception on invalid host names.
+ // This check is performed using `IDN.toASCII(hostName, IDN.USE_STD3_ASCII_RULES)`.
+ // IPv6 literals don't qualify as a valid host name by `IDN::toASCII`.
+ // This assumption on `IDN` is unlikely to change in the foreseeable future.
+ if (!hostName.matches("\\d+[.]\\d+[.]\\d+[.]\\d+")) {
+ try {
+ return new SNIHostName(hostName);
+ } catch (IllegalArgumentException ignored) {
+ // Do nothing
+ }
+ }
+ return null;
+ }
}
log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfiguration.java+8 4
@@ -132,8 +132,8 @@ private static SSLContext createSslContext(
@Nullable final TrustStoreConfiguration trustStoreConfig) {
try {
final SSLContext sslContext = SSLContext.getInstance(protocol);
- final KeyManager[] keyManagers = loadKeyManagers(keyStoreConfig);
- final TrustManager[] trustManagers = loadTrustManagers(trustStoreConfig);
+ @Nullable final KeyManager[] keyManagers = loadKeyManagers(keyStoreConfig);
+ @Nullable final TrustManager[] trustManagers = loadTrustManagers(trustStoreConfig);
sslContext.init(keyManagers, trustManagers, null);
return sslContext;
} catch (final Exception error) {
@@ -144,9 +144,11 @@ private static SSLContext createSslContext(
}
}
+ @Nullable
+ @NullUnmarked
private static KeyManager[] loadKeyManagers(@Nullable final KeyStoreConfiguration config) throws Exception {
if (config == null) {
- return new KeyManager[0];
+ return null;
}
final KeyManagerFactory factory = KeyManagerFactory.getInstance(config.getKeyManagerFactoryAlgorithm());
final char[] password = config.getPasswordAsCharArray();
@@ -158,9 +160,11 @@ private static KeyManager[] loadKeyManagers(@Nullable final KeyStoreConfiguratio
return factory.getKeyManagers();
}
+ @Nullable
+ @NullUnmarked
private static TrustManager[] loadTrustManagers(@Nullable final TrustStoreConfiguration config) throws Exception {
if (config == null) {
- return new TrustManager[0];
+ return null;
}
final TrustManagerFactory factory = TrustManagerFactory.getInstance(config.getTrustManagerFactoryAlgorithm());
factory.init(config.getKeyStore());
src/changelog/.2.x.x/4002_fix_SslSocketAppender_verifyHostName.xml+12 0
@@ -0,0 +1,12 @@
+<?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">
+ <issue id="4002" link="https://github.com/apache/logging-log4j2/pull/4002"/>
+ <description format="asciidoc">
+ Fix incorrect handling of the host name verification (i.e., `verifyHostName`) in `SslSocketManager`, which is used by Socket Appender when SSL/TLS is enabled
+ </description>
+</entry>
src/site/antora/modules/ROOT/pages/manual/appenders/network.adoc+3 0
@@ -67,6 +67,9 @@ https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuid
If `true`, the host name in X509 certificate will be compared to the requested host name.
In the case of a mismatch, the connection will fail.
+Note that SSL/TLS does not allow IP literals for host name verification – see https://www.rfc-editor.org/rfc/rfc6066.html#section-3[RFC 6066].
+Host name verification will only be effective if the provided host name is not an IP literal and a valid host name per https://www.rfc-editor.org/rfc/rfc1035.html[RFC 1035].
+
See also
xref:manual/systemproperties.adoc#log4j2.sslVerifyHostName[`log4j2.sslVerifyHostName`].
|===

References