diff --git a/geode-core/src/distributedTest/java/org/apache/geode/cache/ssl/P2PServerOnlyTLSWithAuthDUnitTest.java b/geode-core/src/distributedTest/java/org/apache/geode/cache/ssl/P2PServerOnlyTLSWithAuthDUnitTest.java
new file mode 100644
index 000000000000..0424c6bd7998
--- /dev/null
+++ b/geode-core/src/distributedTest/java/org/apache/geode/cache/ssl/P2PServerOnlyTLSWithAuthDUnitTest.java
@@ -0,0 +1,382 @@
+/*
+ * 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.geode.cache.ssl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.Properties;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.cache.RegionShortcut;
+import org.apache.geode.security.GemFireSecurityException;
+import org.apache.geode.test.dunit.IgnoredException;
+import org.apache.geode.test.dunit.rules.ClusterStartupRule;
+import org.apache.geode.test.dunit.rules.MemberVM;
+import org.apache.geode.test.junit.categories.SecurityTest;
+import org.apache.geode.test.junit.rules.ServerOnlyTLSTestFixture;
+
+/**
+ * Distributed tests for Peer-to-Peer (P2P) Cache Topology using Server-only TLS with
+ * Application-Layer Authentication (Approach 3).
+ *
+ *
+ * This test demonstrates that in a P2P cache configuration (where all members are peers, no
+ * client/server distinction), Approach 3 works correctly:
+ *
+ *
TLS Encryption: All peer-to-peer connections use TLS for transport
+ * encryption
+ *
No Certificate Authentication: Peers do NOT exchange certificates during
+ * TLS handshake (ssl-require-authentication=false)
+ *
Application-Layer Authentication: Peers authenticate using
+ * username/password via SecurityManager
+ *
Authorization: SecurityManager enforces CLUSTER:MANAGE permission for peer
+ * join
+ *
+ *
+ *
+ * Key Difference from Client/Server: In P2P topology, all members are equal peers
+ * that communicate directly. Each peer presents a server certificate for TLS encryption, but
+ * authentication happens at the application layer using credentials validated by SecurityManager.
+ *
+ *
+ * This approach solves the public CA clientAuth EKU sunset problem for P2P topologies by:
+ *
+ *
Eliminating the need for client certificates entirely
+ *
Maintaining full TLS encryption for all transport
+ *
Using existing authentication infrastructure (LDAP, database, tokens)
+ *
+ *
+ * @see ServerOnlyTLSWithAuthDUnitTest for client/server topology tests
+ */
+@Category({SecurityTest.class})
+public class P2PServerOnlyTLSWithAuthDUnitTest {
+
+ private static final String REGION_NAME = "testRegion";
+
+ @Rule
+ public ClusterStartupRule cluster = new ClusterStartupRule();
+
+ private ServerOnlyTLSTestFixture fixture;
+
+ @Before
+ public void setUp() throws Exception {
+ fixture = new ServerOnlyTLSTestFixture();
+
+ // Add ignored exceptions for SSL-related cleanup warnings
+ IgnoredException.addIgnoredException("javax.net.ssl.SSLException");
+ IgnoredException.addIgnoredException("java.io.IOException");
+ IgnoredException.addIgnoredException("Authentication failed");
+ IgnoredException.addIgnoredException("Security check failed");
+ }
+
+ /**
+ * Test basic P2P cluster formation with server-only TLS and application-layer authentication.
+ *
+ *
+ * Verifies:
+ *
+ *
Locator and servers present TLS certificates (server cert from public or private CA)
+ *
Peers do NOT present client certificates during TLS handshake
+ *
All peers authenticate using username/password
+ *
SecurityManager validates credentials and requires CLUSTER:MANAGE permission
+ *
Cluster forms successfully with encrypted peer-to-peer communication
+ *
+ */
+ @Test
+ public void testP2PClusterFormationWithServerOnlyTLSAndAppAuth() throws Exception {
+ // Create certificate stores using fixture
+ // All peers use the same certificate for TLS (server cert)
+ CertStores clusterStores = fixture.createClusterStores();
+
+ // Configure locator with:
+ // - Server-only TLS (ssl-require-authentication=false)
+ // - Security manager for application-layer authentication
+ // - Peer authentication credentials
+ Properties locatorProps = clusterStores.propertiesWith("all", false, false);
+ fixture.addSecurityManagerConfig(locatorProps);
+ fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+
+ // Start locator - it will authenticate itself when joining
+ MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+ int locatorPort = locator.getPort();
+
+ // Configure first server with same setup
+ Properties server1Props = clusterStores.propertiesWith("all", false, false);
+ fixture.addSecurityManagerConfig(server1Props);
+ fixture.addPeerAuthProperties(server1Props, "cluster", "cluster");
+
+ // Start first server - it joins via application-layer auth, not certificate auth
+ MemberVM server1 = cluster.startServerVM(1, server1Props, locatorPort);
+
+ // Verify server1 successfully joined the cluster
+ server1.invoke(() -> {
+ assertThat(ClusterStartupRule.getCache()).isNotNull();
+ assertThat(ClusterStartupRule.getCache().getDistributedSystem().getAllOtherMembers())
+ .hasSize(1); // locator
+ });
+
+ // Configure second server with same setup
+ Properties server2Props = clusterStores.propertiesWith("all", false, false);
+ fixture.addSecurityManagerConfig(server2Props);
+ fixture.addPeerAuthProperties(server2Props, "cluster", "cluster");
+
+ // Start second server
+ MemberVM server2 = cluster.startServerVM(2, server2Props, locatorPort);
+
+ // Verify server2 successfully joined and sees all peers
+ server2.invoke(() -> {
+ assertThat(ClusterStartupRule.getCache()).isNotNull();
+ assertThat(ClusterStartupRule.getCache().getDistributedSystem().getAllOtherMembers())
+ .hasSize(2); // locator + server1
+ });
+
+ // Verify all peers see each other (peer-to-peer mesh formed)
+ server1.invoke(() -> {
+ assertThat(ClusterStartupRule.getCache().getDistributedSystem().getAllOtherMembers())
+ .hasSize(2); // locator + server2
+ });
+ }
+
+ /**
+ * Test P2P data replication across encrypted peer connections without certificate authentication.
+ *
+ *
+ * Verifies:
+ *
+ *
Data replicates across peers using TLS-encrypted connections
+ *
No certificate authentication is used (application credentials only)
+ */
+ @Test
+ public void testP2PPeerRejectedWithoutClusterManagePermission() throws Exception {
+ CertStores clusterStores = fixture.createClusterStores();
+
+ // Configure and start locator
+ Properties locatorProps = clusterStores.propertiesWith("all", false, false);
+ fixture.addSecurityManagerConfig(locatorProps);
+ fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+ MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+ int locatorPort = locator.getPort();
+
+ // Try to start server with user that has valid credentials but no CLUSTER:MANAGE
+ // SimpleSecurityManager allows authentication when username == password
+ // but "data" user does NOT have CLUSTER:MANAGE permission
+ Properties serverProps = clusterStores.propertiesWith("all", false, false);
+ fixture.addSecurityManagerConfig(serverProps);
+ fixture.addPeerAuthProperties(serverProps, "data", "data"); // Valid creds, insufficient perms
+
+ // Server should fail to join due to authorization failure
+ // Note: Root cause is SecurityException, not GemFireSecurityException
+ assertThatThrownBy(() -> cluster.startServerVM(1, serverProps, locatorPort))
+ .hasRootCauseInstanceOf(SecurityException.class)
+ .hasStackTraceContaining("not authorized for CLUSTER:MANAGE");
+ }
+
+ /**
+ * Test that peer with no credentials cannot join P2P cluster.
+ *
+ *
+ * Verifies:
+ *
+ *
TLS handshake succeeds (encryption established)
+ *
Application-layer authentication fails (no credentials provided)
+ *
Peer is rejected
+ *
+ */
+ @Test
+ public void testP2PPeerRejectedWithNoCredentials() throws Exception {
+ CertStores clusterStores = fixture.createClusterStores();
+
+ // Configure and start locator
+ Properties locatorProps = clusterStores.propertiesWith("all", false, false);
+ fixture.addSecurityManagerConfig(locatorProps);
+ fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+ MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+ int locatorPort = locator.getPort();
+
+ // Try to start server WITHOUT any authentication credentials
+ Properties serverProps = clusterStores.propertiesWith("all", false, false);
+ fixture.addSecurityManagerConfig(serverProps);
+ // NO peer auth properties added - missing credentials
+
+ // Server should fail to join due to missing credentials
+ assertThatThrownBy(() -> cluster.startServerVM(1, serverProps, locatorPort))
+ .hasRootCauseInstanceOf(GemFireSecurityException.class);
+ }
+
+ /**
+ * Test multiple peers joining with different valid credentials.
+ *
+ *
+ * Verifies that the cluster supports heterogeneous peer credentials as long as all have
+ * CLUSTER:MANAGE permission. This demonstrates flexibility in credential management where
+ * different services/teams can use different credentials.
+ */
+ @Test
+ public void testMultiplePeersWithDifferentCredentials() throws Exception {
+ CertStores clusterStores = fixture.createClusterStores();
+
+ // Start locator with "cluster" credentials
+ Properties locatorProps = clusterStores.propertiesWith("all", false, false);
+ fixture.addSecurityManagerConfig(locatorProps);
+ fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+ MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+ int locatorPort = locator.getPort();
+
+ // Start server1 with "clusterManage" credentials
+ // SimpleSecurityManager grants CLUSTER:MANAGE to "cluster" and "clusterManage" users
+ Properties server1Props = clusterStores.propertiesWith("all", false, false);
+ fixture.addSecurityManagerConfig(server1Props);
+ fixture.addPeerAuthProperties(server1Props, "clusterManage", "clusterManage");
+ MemberVM server1 = cluster.startServerVM(1, server1Props, locatorPort);
+
+ // Start server2 with "cluster" credentials (same as locator)
+ Properties server2Props = clusterStores.propertiesWith("all", false, false);
+ fixture.addSecurityManagerConfig(server2Props);
+ fixture.addPeerAuthProperties(server2Props, "cluster", "cluster");
+ MemberVM server2 = cluster.startServerVM(2, server2Props, locatorPort);
+
+ // Verify all peers joined successfully
+ server1.invoke(() -> {
+ assertThat(ClusterStartupRule.getCache().getDistributedSystem().getAllOtherMembers())
+ .hasSize(2); // locator + server2
+ });
+
+ server2.invoke(() -> {
+ assertThat(ClusterStartupRule.getCache().getDistributedSystem().getAllOtherMembers())
+ .hasSize(2); // locator + server1
+ });
+ }
+}
diff --git a/geode-core/src/distributedTest/java/org/apache/geode/cache/ssl/ServerOnlyTLSWithAuthDUnitTest.java b/geode-core/src/distributedTest/java/org/apache/geode/cache/ssl/ServerOnlyTLSWithAuthDUnitTest.java
new file mode 100644
index 000000000000..04305b0a0f32
--- /dev/null
+++ b/geode-core/src/distributedTest/java/org/apache/geode/cache/ssl/ServerOnlyTLSWithAuthDUnitTest.java
@@ -0,0 +1,513 @@
+/*
+ * 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.geode.cache.ssl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.RegionShortcut;
+import org.apache.geode.cache.client.ClientCache;
+import org.apache.geode.cache.client.ClientCacheFactory;
+import org.apache.geode.cache.client.ClientRegionShortcut;
+import org.apache.geode.examples.SimpleSecurityManager;
+import org.apache.geode.test.dunit.IgnoredException;
+import org.apache.geode.test.dunit.rules.ClientVM;
+import org.apache.geode.test.dunit.rules.ClusterStartupRule;
+import org.apache.geode.test.dunit.rules.MemberVM;
+import org.apache.geode.test.junit.categories.SecurityTest;
+import org.apache.geode.test.junit.rules.ServerOnlyTLSTestFixture;
+
+/**
+ * Distributed tests for Server-only TLS with Alternative Client Authentication.
+ *
+ *
+ * These tests verify that:
+ *
+ *
Servers present certificates and clients verify them
+ *
Clients do NOT present certificates (ssl-require-authentication=false)
+ *
All transport is TLS-encrypted in both directions
+ *
Clients authenticate using application-layer credentials (username/password or tokens)
+ *
Authorization is enforced through SecurityManager
+ *
+ */
+@Category({SecurityTest.class})
+public class ServerOnlyTLSWithAuthDUnitTest {
+
+ private static final String REGION_NAME = "testRegion";
+
+ @Rule
+ public ClusterStartupRule cluster = new ClusterStartupRule();
+
+ private ServerOnlyTLSTestFixture fixture;
+
+ @Before
+ public void setUp() throws Exception {
+ fixture = new ServerOnlyTLSTestFixture();
+
+ // Add ignored exceptions for SSL-related cleanup warnings
+ IgnoredException.addIgnoredException("javax.net.ssl.SSLException");
+ IgnoredException.addIgnoredException("java.io.IOException");
+ }
+
+ /**
+ * Test basic client connection with TLS transport encryption and username/password
+ * authentication.
+ *
+ *
+ * Verifies:
+ *
+ *
Server presents certificate, client verifies it
+ *
Client does NOT present certificate
+ *
Client authenticates with valid username/password
+ *
Transport is encrypted
+ *
+ */
+ @Test
+ public void testBasicConnectionWithUsernamePassword() throws Exception {
+ // Create certificates and stores using fixture
+ // Note: Use createClusterStores() for both locator and server peer SSL communication
+ CertStores clusterStores = fixture.createClusterStores();
+ CertStores clientStores = fixture.createClientStores();
+
+ // Configure locator with server-only TLS (require-authentication=false) and security manager
+ Properties locatorProps = clusterStores.propertiesWith("all", false, false);
+ fixture.addSecurityManagerConfig(locatorProps);
+ fixture.addPeerAuthProperties(locatorProps, "cluster", "cluster");
+
+ // Start locator
+ MemberVM locator = cluster.startLocatorVM(0, locatorProps);
+ int locatorPort = locator.getPort();
+
+ // Configure server with server-only TLS and security manager
+ Properties serverProps = clusterStores.propertiesWith("all", false, false);
+ fixture.addSecurityManagerConfig(serverProps);
+ fixture.addPeerAuthProperties(serverProps, "cluster", "cluster");
+ serverProps.setProperty("locators", "localhost[" + locatorPort + "]");
+
+ // Start server
+ MemberVM server = cluster.startServerVM(1, serverProps, locatorPort);
+
+ // Create region on server
+ server.invoke(() -> {
+ ClusterStartupRule.getCache()
+ .createRegionFactory(RegionShortcut.REPLICATE)
+ .create(REGION_NAME);
+ });
+
+ // Configure client with server-only TLS (truststore only, no keystore)
+ Properties clientSSLProps = clientStores.propertiesWith("all", false, false);
+
+ // Add authentication properties (username/password)
+ // SimpleSecurityManager accepts username when username == password
+ Properties clientAuthProps = fixture.createClientAuthProperties("data", "data");
+ clientSSLProps.putAll(clientAuthProps);
+
+ // Connect client
+ ClientVM client = cluster.startClientVM(2, c -> c
+ .withProperties(clientSSLProps)
+ .withLocatorConnection(locatorPort));
+
+ // Verify client can perform operations
+ client.invoke(() -> {
+ ClientCache clientCache = ClusterStartupRule.getClientCache();
+ assertThat(clientCache).isNotNull();
+
+ Region