This is an automated email from the ASF dual-hosted git repository.

szetszwo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ratis.git


The following commit(s) were added to refs/heads/master by this push:
     new ee40e300c RATIS-2537. Support configurable gRPC TLS provider and 
cipher suites (#1462)
ee40e300c is described below

commit ee40e300ca91db36bd90988dbe48e477ea6a4f9c
Author: Haonan <[email protected]>
AuthorDate: Fri May 22 12:00:49 2026 +0800

    RATIS-2537. Support configurable gRPC TLS provider and cipher suites (#1462)
---
 .../java/org/apache/ratis/security/TlsConf.java    | 86 +++++++++++++++++---
 .../java/org/apache/ratis/grpc/GrpcTlsConfig.java  | 93 +++++++++++++++++++++-
 .../main/java/org/apache/ratis/grpc/GrpcUtil.java  | 67 +++++++++++++++-
 .../org/apache/ratis/grpc/TestGrpcTlsConfig.java   | 65 +++++++++++++++
 4 files changed, 299 insertions(+), 12 deletions(-)

diff --git a/ratis-common/src/main/java/org/apache/ratis/security/TlsConf.java 
b/ratis-common/src/main/java/org/apache/ratis/security/TlsConf.java
index 30cf67c83..b44212459 100644
--- a/ratis-common/src/main/java/org/apache/ratis/security/TlsConf.java
+++ b/ratis-common/src/main/java/org/apache/ratis/security/TlsConf.java
@@ -17,6 +17,7 @@
  */
 package org.apache.ratis.security;
 
+import org.apache.ratis.thirdparty.io.netty.handler.ssl.SslProvider;
 import org.apache.ratis.util.JavaUtils;
 import org.apache.ratis.util.Preconditions;
 
@@ -25,8 +26,11 @@ import javax.net.ssl.TrustManager;
 import java.io.File;
 import java.security.PrivateKey;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -170,16 +174,22 @@ public class TlsConf {
   private final KeyManagerConf keyManager;
   private final TrustManagerConf trustManager;
   private final boolean mutualTls;
-
-  private TlsConf(String name, KeyManagerConf keyManager, TrustManagerConf 
trustManager, boolean mutualTls) {
-    this.name = JavaUtils.getClassSimpleName(getClass()) + 
COUNT.getAndIncrement() + (name == null? "": "-" + name);
-    this.keyManager = keyManager;
-    this.trustManager = trustManager;
-    this.mutualTls = mutualTls;
-  }
+  private final SslProvider sslProvider;
+  private final String jsseProviderName;
+  private final List<String> protocols;
+  private final List<String> cipherSuites;
 
   protected TlsConf(Builder b) {
-    this(b.buildName(), b.buildKeyManagerConf(), b.buildTrustManagerConf(), 
b.isMutualTls());
+    final String buildName = b.buildName();
+    this.name = JavaUtils.getClassSimpleName(getClass()) + 
COUNT.getAndIncrement()
+        + (buildName == null? "": "-" + buildName);
+    this.keyManager = b.buildKeyManagerConf();
+    this.trustManager = b.buildTrustManagerConf();
+    this.mutualTls = b.isMutualTls();
+    this.sslProvider = b.sslProvider;
+    this.jsseProviderName = b.jsseProviderName;
+    this.protocols = copy(b.protocols);
+    this.cipherSuites = copy(b.cipherSuites);
   }
 
   /** @return the key manager configuration. */
@@ -197,6 +207,22 @@ public class TlsConf {
     return mutualTls;
   }
 
+  public SslProvider getSslProvider() {
+    return sslProvider;
+  }
+
+  public String getJsseProviderName() {
+    return jsseProviderName;
+  }
+
+  public List<String> getProtocols() {
+    return copy(protocols);
+  }
+
+  public List<String> getCipherSuites() {
+    return copy(cipherSuites);
+  }
+
   @Override
   public String toString() {
     return name;
@@ -206,6 +232,14 @@ public class TlsConf {
     return new Builder();
   }
 
+  private static List<String> copy(List<String> values) {
+    return values != null ? Collections.unmodifiableList(new 
ArrayList<>(values)) : null;
+  }
+
+  private static List<String> copy(String[] values) {
+    return values != null ? copy(Arrays.asList(values)) : null;
+  }
+
   /** For building {@link TlsConf}. */
   public static class Builder {
     private String name;
@@ -215,6 +249,10 @@ public class TlsConf {
     private boolean mutualTls;
     private KeyManager keyManager;
     private TrustManager trustManager;
+    private SslProvider sslProvider;
+    private String jsseProviderName;
+    private List<String> protocols;
+    private List<String> cipherSuites;
 
     public Builder setName(String name) {
       this.name = name;
@@ -251,6 +289,36 @@ public class TlsConf {
       return this;
     }
 
+    public Builder setSslProvider(SslProvider sslProvider) {
+      this.sslProvider = sslProvider;
+      return this;
+    }
+
+    public Builder setJsseProviderName(String jsseProviderName) {
+      this.jsseProviderName = jsseProviderName;
+      return this;
+    }
+
+    public Builder setProtocols(String... protocols) {
+      this.protocols = copy(protocols);
+      return this;
+    }
+
+    public Builder setProtocols(List<String> protocols) {
+      this.protocols = copy(protocols);
+      return this;
+    }
+
+    public Builder setCipherSuites(String... cipherSuites) {
+      this.cipherSuites = copy(cipherSuites);
+      return this;
+    }
+
+    public Builder setCipherSuites(List<String> cipherSuites) {
+      this.cipherSuites = copy(cipherSuites);
+      return this;
+    }
+
     private boolean isMutualTls() {
       return mutualTls;
     }
@@ -285,4 +353,4 @@ public class TlsConf {
       return new TlsConf(this);
     }
   }
-}
\ No newline at end of file
+}
diff --git a/ratis-grpc/src/main/java/org/apache/ratis/grpc/GrpcTlsConfig.java 
b/ratis-grpc/src/main/java/org/apache/ratis/grpc/GrpcTlsConfig.java
index ff540c3cc..c6b8dce59 100644
--- a/ratis-grpc/src/main/java/org/apache/ratis/grpc/GrpcTlsConfig.java
+++ b/ratis-grpc/src/main/java/org/apache/ratis/grpc/GrpcTlsConfig.java
@@ -18,6 +18,7 @@
 package org.apache.ratis.grpc;
 
 import org.apache.ratis.security.TlsConf;
+import org.apache.ratis.thirdparty.io.netty.handler.ssl.SslProvider;
 
 import javax.net.ssl.KeyManager;
 import javax.net.ssl.TrustManager;
@@ -131,4 +132,94 @@ public class GrpcTlsConfig extends TlsConf {
   private static Builder newBuilder(KeyManager keyManager, TrustManager 
trustManager, boolean mTlsEnabled) {
     return 
newBuilder().setMutualTls(mTlsEnabled).setKeyManager(keyManager).setTrustManager(trustManager);
   }
-}
\ No newline at end of file
+
+  public static Builder newBuilder() {
+    return new Builder();
+  }
+
+  /** For building {@link GrpcTlsConfig}. */
+  public static class Builder extends TlsConf.Builder {
+    @Override
+    public Builder setName(String name) {
+      super.setName(name);
+      return this;
+    }
+
+    @Override
+    public Builder setTrustCertificates(CertificatesConf trustCertificates) {
+      super.setTrustCertificates(trustCertificates);
+      return this;
+    }
+
+    @Override
+    public Builder setPrivateKey(PrivateKeyConf privateKey) {
+      super.setPrivateKey(privateKey);
+      return this;
+    }
+
+    @Override
+    public Builder setKeyCertificates(CertificatesConf keyCertificates) {
+      super.setKeyCertificates(keyCertificates);
+      return this;
+    }
+
+    @Override
+    public Builder setKeyManager(KeyManager keyManager) {
+      super.setKeyManager(keyManager);
+      return this;
+    }
+
+    @Override
+    public Builder setTrustManager(TrustManager trustManager) {
+      super.setTrustManager(trustManager);
+      return this;
+    }
+
+    @Override
+    public Builder setMutualTls(boolean mutualTls) {
+      super.setMutualTls(mutualTls);
+      return this;
+    }
+
+    @Override
+    public Builder setSslProvider(SslProvider sslProvider) {
+      super.setSslProvider(sslProvider);
+      return this;
+    }
+
+    @Override
+    public Builder setJsseProviderName(String jsseProviderName) {
+      super.setJsseProviderName(jsseProviderName);
+      return this;
+    }
+
+    @Override
+    public Builder setProtocols(String... protocols) {
+      super.setProtocols(protocols);
+      return this;
+    }
+
+    @Override
+    public Builder setProtocols(List<String> protocols) {
+      super.setProtocols(protocols);
+      return this;
+    }
+
+    @Override
+    public Builder setCipherSuites(String... cipherSuites) {
+      super.setCipherSuites(cipherSuites);
+      return this;
+    }
+
+    @Override
+    public Builder setCipherSuites(List<String> cipherSuites) {
+      super.setCipherSuites(cipherSuites);
+      return this;
+    }
+
+    @Override
+    public GrpcTlsConfig build() {
+      return new GrpcTlsConfig(this, false);
+    }
+  }
+}
diff --git a/ratis-grpc/src/main/java/org/apache/ratis/grpc/GrpcUtil.java 
b/ratis-grpc/src/main/java/org/apache/ratis/grpc/GrpcUtil.java
index df076875b..36b8e9d65 100644
--- a/ratis-grpc/src/main/java/org/apache/ratis/grpc/GrpcUtil.java
+++ b/ratis-grpc/src/main/java/org/apache/ratis/grpc/GrpcUtil.java
@@ -20,6 +20,7 @@ package org.apache.ratis.grpc;
 import java.util.function.Consumer;
 import org.apache.ratis.protocol.exceptions.ServerNotReadyException;
 import org.apache.ratis.protocol.exceptions.TimeoutIOException;
+import org.apache.ratis.security.TlsConf;
 import org.apache.ratis.security.TlsConf.TrustManagerConf;
 import org.apache.ratis.security.TlsConf.CertificatesConf;
 import org.apache.ratis.security.TlsConf.PrivateKeyConf;
@@ -30,9 +31,12 @@ import org.apache.ratis.thirdparty.io.grpc.Status;
 import org.apache.ratis.thirdparty.io.grpc.StatusRuntimeException;
 import org.apache.ratis.thirdparty.io.grpc.netty.GrpcSslContexts;
 import org.apache.ratis.thirdparty.io.grpc.stub.StreamObserver;
+import 
org.apache.ratis.thirdparty.io.netty.handler.ssl.ApplicationProtocolConfig;
 import org.apache.ratis.thirdparty.io.netty.handler.ssl.ClientAuth;
+import 
org.apache.ratis.thirdparty.io.netty.handler.ssl.IdentityCipherSuiteFilter;
 import org.apache.ratis.thirdparty.io.netty.handler.ssl.SslContext;
 import org.apache.ratis.thirdparty.io.netty.handler.ssl.SslContextBuilder;
+import org.apache.ratis.thirdparty.io.netty.handler.ssl.SslProvider;
 import org.apache.ratis.util.IOUtils;
 import org.apache.ratis.util.JavaUtils;
 import org.apache.ratis.util.LogUtils;
@@ -45,6 +49,9 @@ import javax.net.ssl.KeyManager;
 import javax.net.ssl.SSLException;
 import javax.net.ssl.TrustManager;
 import java.io.IOException;
+import java.security.Provider;
+import java.security.Security;
+import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
@@ -64,6 +71,12 @@ public interface GrpcUtil {
   Metadata.Key<String> HEARTBEAT =
       Metadata.Key.of("heartbeat", Metadata.ASCII_STRING_MARSHALLER);
 
+  ApplicationProtocolConfig ALPN =
+      new ApplicationProtocolConfig(ApplicationProtocolConfig.Protocol.ALPN,
+          ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
+          ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
+          "h2");
+
   static StatusRuntimeException wrapException(Throwable t) {
     return wrapException(t, -1);
   }
@@ -306,6 +319,56 @@ public interface GrpcUtil {
     }
   }
 
+  static SslContextBuilder configureSslContextBuilder(
+      SslContextBuilder b, TlsConf tlsConf, SslProvider defaultSslProvider) {
+    final Provider jsseProvider = getJsseProvider(tlsConf);
+    if (jsseProvider != null) {
+      b = configureJsseProvider(b, jsseProvider);
+    } else {
+      final SslProvider sslProvider = tlsConf.getSslProvider() != null
+          ? tlsConf.getSslProvider() : defaultSslProvider;
+      if (sslProvider == SslProvider.OPENSSL_REFCNT) {
+        b = GrpcSslContexts.configure(b, 
OPENSSL).sslProvider(SslProvider.OPENSSL_REFCNT);
+      } else {
+        b = sslProvider != null ? GrpcSslContexts.configure(b, sslProvider) : 
GrpcSslContexts.configure(b);
+      }
+    }
+    final List<String> protocols = tlsConf.getProtocols();
+    if (protocols != null && !protocols.isEmpty()) {
+      b.protocols(protocols.toArray(new String[0]));
+    }
+    final List<String> cipherSuites = tlsConf.getCipherSuites();
+    if (cipherSuites != null && !cipherSuites.isEmpty()) {
+      b.ciphers(cipherSuites, IdentityCipherSuiteFilter.INSTANCE);
+    }
+    return b;
+  }
+
+  static SslContextBuilder configureJsseProvider(SslContextBuilder b, Provider 
provider) {
+    try {
+      return GrpcSslContexts.configure(b, provider);
+    } catch (IllegalArgumentException e) {
+      if (!String.valueOf(e.getMessage()).contains("Unknown provider")) {
+        throw e;
+      }
+      return b.sslProvider(SslProvider.JDK)
+          .applicationProtocolConfig(ALPN)
+          .sslContextProvider(provider);
+    }
+  }
+
+  static Provider getJsseProvider(TlsConf tlsConf) {
+    final String providerName = tlsConf.getJsseProviderName();
+    if (providerName == null || providerName.trim().isEmpty()) {
+      return null;
+    }
+    final Provider namedProvider = Security.getProvider(providerName.trim());
+    if (namedProvider == null) {
+      throw new IllegalArgumentException("JSSE provider not found: " + 
providerName);
+    }
+    return namedProvider;
+  }
+
   static SslContext buildSslContextForServer(GrpcTlsConfig tlsConf) {
     if (tlsConf == null) {
       return null;
@@ -315,7 +378,7 @@ public interface GrpcUtil {
       b.clientAuth(ClientAuth.REQUIRE);
       setTrustManager(b, tlsConf.getTrustManager());
     }
-    b = GrpcSslContexts.configure(b, OPENSSL);
+    b = configureSslContextBuilder(b, tlsConf, OPENSSL);
     try {
       return b.build();
     } catch (Exception e) {
@@ -328,7 +391,7 @@ public interface GrpcUtil {
       return null;
     }
 
-    final SslContextBuilder b = GrpcSslContexts.forClient();
+    final SslContextBuilder b = 
configureSslContextBuilder(SslContextBuilder.forClient(), tlsConf, null);
     setTrustManager(b, tlsConf.getTrustManager());
     if (tlsConf.getMtlsEnabled()) {
       setKeyManager(b, tlsConf.getKeyManager());
diff --git 
a/ratis-grpc/src/test/java/org/apache/ratis/grpc/TestGrpcTlsConfig.java 
b/ratis-grpc/src/test/java/org/apache/ratis/grpc/TestGrpcTlsConfig.java
new file mode 100644
index 000000000..d3c6be8e2
--- /dev/null
+++ b/ratis-grpc/src/test/java/org/apache/ratis/grpc/TestGrpcTlsConfig.java
@@ -0,0 +1,65 @@
+/*
+ * 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.ratis.grpc;
+
+import org.apache.ratis.thirdparty.io.netty.handler.ssl.SslContextBuilder;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import javax.net.ssl.SSLException;
+import java.security.Provider;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class TestGrpcTlsConfig {
+  @Test
+  public void testGrpcTlsConfigCopiesLists() {
+    final List<String> protocols = new 
ArrayList<>(Collections.singletonList("TLSv1.2"));
+    final List<String> cipherSuites = new 
ArrayList<>(Collections.singletonList("TLS_AES_128_GCM_SHA256"));
+
+    final GrpcTlsConfig conf = GrpcTlsConfig.newBuilder()
+        .setProtocols(protocols)
+        .setCipherSuites(cipherSuites)
+        .build();
+
+    protocols.add("TLSv1.3");
+    cipherSuites.add("TLS_AES_256_GCM_SHA384");
+
+    Assertions.assertEquals(Collections.singletonList("TLSv1.2"), 
conf.getProtocols());
+    
Assertions.assertEquals(Collections.singletonList("TLS_AES_128_GCM_SHA256"), 
conf.getCipherSuites());
+    Assertions.assertThrows(UnsupportedOperationException.class, () -> 
conf.getProtocols().add("TLSv1.3"));
+    Assertions.assertThrows(UnsupportedOperationException.class,
+        () -> conf.getCipherSuites().add("TLS_AES_256_GCM_SHA384"));
+  }
+
+  @Test
+  public void testUnknownJsseProviderUsesGenericJdkConfiguration() {
+    final SslContextBuilder builder = GrpcUtil.configureJsseProvider(
+        SslContextBuilder.forClient(), new TestProvider());
+    Assertions.assertThrows(SSLException.class, builder::build);
+  }
+
+  private static class TestProvider extends Provider {
+    private static final long serialVersionUID = 1L;
+
+    TestProvider() {
+      super("TestJSSE", 1.0, "Test JSSE provider");
+    }
+  }
+}

Reply via email to