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

alexstocks pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/dubbo-go-samples.git


The following commit(s) were added to refs/heads/main by this push:
     new d1ae103e feat: add TLS support for Java-Go interoperability (#996)
d1ae103e is described below

commit d1ae103ead94abacd4e10c1b1eab1058601d0bdd
Author: CAICAII <[email protected]>
AuthorDate: Sat Dec 20 23:03:22 2025 +0800

    feat: add TLS support for Java-Go interoperability (#996)
    
    * feat: add TLS support for Java-Go interoperability
---
 README.md                                          |   2 +-
 README_CN.md                                       |   2 +-
 build/Makefile                                     |   7 +-
 .../tls/tests/integration/tls_test.go              |  35 ++-
 start_integrate_test.sh                            |   1 +
 tls/README.md                                      | 101 +++++++-
 tls/README_CN.md                                   |  99 +++++++-
 tls/{client => go-client}/cmd/main.go              |   5 +-
 tls/{server => go-server}/cmd/main.go              |   6 +-
 tls/java-client/pom.xml                            | 110 +++++++++
 .../dubbo/samples/tls/consumer/GreetClient.java    |  24 ++
 .../samples/tls/consumer/GreetClientImpl.java      |  66 +++++
 .../samples/tls/consumer/TlsTriProtoConsumer.java  |  72 ++++++
 tls/java-server/pom.xml                            | 118 +++++++++
 .../src/main/java/greet/GreetService.java          |  30 +++
 .../samples/tls/provider/GreetServiceImpl.java     |  51 ++++
 .../tls/provider/Pkcs1ToPkcs8KeyConverter.java     | 272 +++++++++++++++++++++
 .../dubbo/samples/tls/provider/TlsTriProvider.java | 107 ++++++++
 tls/pom.xml                                        |  54 ++++
 19 files changed, 1117 insertions(+), 45 deletions(-)

diff --git a/README.md b/README.md
index 48d46225..f8ac7ef8 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,7 @@ A collection of runnable Dubbo-go examples covering 
configuration, registries, o
 * `streaming`: Streaming RPC example, also includes Go–Java interoperability 
when both use streaming.
 * `task`: Task scheduling and execution example.
 * `timeout`: Demonstrates timeout handling in Dubbo-go.
-* `tls`: Demonstrates how to use TLS (based on X.509 certificates) in Dubbo-go 
to enable encrypted communication and/or mutual authentication between client 
and server.
+* `tls`: Demonstrates how to use TLS (based on X.509 certificates) in Dubbo-go 
to enable encrypted communication and/or mutual authentication between client 
and server, also includes Go–Java interoperability.
 * `transaction/seata-go`: Distributed transaction example using `seata-go`.
 
 ### compatibility (legacy Dubbo-go samples)
diff --git a/README_CN.md b/README_CN.md
index 47c6bd9b..57fc157c 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -43,7 +43,7 @@
 * `streaming`:流式 RPC 调用示例,并包含了Dubbo-go与Dubbo-java同时使用流式传输的互操作示例。
 * `task`:任务调度与执行示例。
 * `timeout`:Dubbo-go 超时处理示例。
-* `tls`:演示如何在 Dubbo-go 中使用 TLS(基于 X.509 证书),实现客户端与服务端之间的加密通信和/或双向认证。
+* `tls`:演示如何在 Dubbo-go 中使用 TLS(基于 X.509 证书),实现客户端与服务端之间的加密通信和/或双向认证,同时包含 Go 与 
Java 的互操作示例。
 * `transaction/seata-go`:基于 `seata-go` 的分布式事务示例。
 
 ### compatibility(旧版 Dubbo-go 示例)
diff --git a/build/Makefile b/build/Makefile
index d06bdcff..6817b543 100644
--- a/build/Makefile
+++ b/build/Makefile
@@ -132,7 +132,9 @@ start: export DUBBO_GO_CONFIG_PATH ?= 
$(PROJECT_DIR)/go-server/conf/dubbogo.yml
 start: build
        $(info   >  Starting application $(PROJECT_NAME), output is redirected 
to $(LOG_FILE))
        @ls $(OUT_DIR)/$(PROJECT_NAME)$(EXT_NAME)
-       @-$(OUT_DIR)/$(PROJECT_NAME)$(EXT_NAME) > $(LOG_FILE) 2>&1 & echo $$! > 
$(PID)
+       @-command -v lsof >/dev/null 2>&1 && lsof -P -sTCP:LISTEN -tiTCP:20000 
-iTCP:20001 -iTCP:20002 -iTCP:4318 -iTCP:50051 | xargs -r kill -9 || true
+       @sleep 1
+       @-cd $(PROJECT_DIR) && $(OUT_DIR)/$(PROJECT_NAME)$(EXT_NAME) > 
$(LOG_FILE) 2>&1 & echo $$! > $(PID)
        @cat $(PID) | sed "/^/s/^/  \>  PID: /"
 
 ## start: print application log (for server)
@@ -145,7 +147,7 @@ print-server-log:
 .PHONY: run
 run: build
        $(info   >  Running application $(PROJECT_NAME), output is redirected 
to $(LOG_FILE))
-       @-$(OUT_DIR)/$(PROJECT_NAME)$(EXT_NAME) 2>&1 | tee $(LOG_FILE)
+       @-cd $(PROJECT_DIR) && $(OUT_DIR)/$(PROJECT_NAME)$(EXT_NAME) 2>&1 | tee 
$(LOG_FILE)
 
 ## stop: Stop running the application (for server)
 .PHONY: stop
@@ -153,6 +155,7 @@ stop:
        $(info   >  Stopping the application $(PROJECT_NAME))
        @cat $(PID) | sed "/^/s/^/  \>  Killing PID: /"
        @-kill `cat $(PID)` 2>/dev/null || true
+       @-command -v lsof >/dev/null 2>&1 && lsof -P -sTCP:LISTEN -tiTCP:20000 
-iTCP:20001 -iTCP:20002 -iTCP:4318 -iTCP:50051 | xargs -r kill -9 || true
 
 ## integration: Run integration test for this application
 .PHONY: integration
diff --git a/tls/client/cmd/main.go 
b/integrate_test/tls/tests/integration/tls_test.go
similarity index 61%
copy from tls/client/cmd/main.go
copy to integrate_test/tls/tests/integration/tls_test.go
index 9bea27a1..cdc266cc 100644
--- a/tls/client/cmd/main.go
+++ b/integrate_test/tls/tests/integration/tls_test.go
@@ -13,44 +13,57 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package main
+package integration
 
 import (
        "context"
+       "path/filepath"
+       "runtime"
+       "testing"
 )
 
 import (
        "dubbo.apache.org/dubbo-go/v3/client"
        _ "dubbo.apache.org/dubbo-go/v3/imports"
        "dubbo.apache.org/dubbo-go/v3/tls"
-
-       "github.com/dubbogo/gost/log/logger"
 )
 
 import (
-       "github.com/apache/dubbo-go-samples/tls/proto"
+       greet "github.com/apache/dubbo-go-samples/tls/proto"
 )
 
-func main() {
+// buildCertPath returns an absolute path to a file inside tls/x509.
+func buildCertPath(name string) string {
+       _, file, _, _ := runtime.Caller(0)
+       base := filepath.Dir(file)                 // 
.../integrate_test/tls/tests/integration
+       root := filepath.Join(base, "../../../..") // repo root
+       return filepath.Join(root, "tls", "x509", name)
+}
+
+func TestTLSGreet(t *testing.T) {
        cli, err := client.NewClient(
                client.WithClientURL("127.0.0.1:20000"),
+               client.WithClientProtocolTriple(),
                client.WithClientTLSOption(
-                       tls.WithCACertFile("../../x509/server_ca_cert.pem"),
+                       tls.WithCACertFile(buildCertPath("server_ca_cert.pem")),
                        tls.WithServerName("dubbogo.test.example.com"),
                ),
        )
        if err != nil {
-               panic(err)
+               t.Fatalf("new client: %v", err)
        }
 
        svc, err := greet.NewGreetService(cli)
        if err != nil {
-               panic(err)
+               t.Fatalf("new service: %v", err)
        }
 
-       resp, err := svc.Greet(context.Background(), &greet.GreetRequest{Name: 
"hello world"})
+       resp, err := svc.Greet(context.Background(), &greet.GreetRequest{Name: 
"hello tls"})
        if err != nil {
-               logger.Error(err)
+               t.Fatalf("greet call failed: %v", err)
+       }
+
+       if resp == nil || resp.Greeting != "hello tls" {
+               t.Fatalf("unexpected response: %#v", resp)
        }
-       logger.Infof("Greet response: %s", resp.Greeting)
 }
diff --git a/start_integrate_test.sh b/start_integrate_test.sh
index d113e3c0..d0bfd985 100755
--- a/start_integrate_test.sh
+++ b/start_integrate_test.sh
@@ -74,6 +74,7 @@ array+=("rpc/triple/pb-json")
 array+=("rpc/multi-protocols")
 
 # tls
+array+=("tls")
 #array+=("compatibility/tls/dubbo")# 
tls.LoadX509KeyPair(certs{../../../x509/server1_cert.pem}, 
privateKey{../../../x509/server1_key.pem}) = err:open 
../../../x509/server1_cert.pem: no such file or directory
 #array+=("compatibility/tls/triple")# 
tls.LoadX509KeyPair(certs{../../../x509/server1_cert.pem}, 
privateKey{../../../x509/server1_key.pem}) = err:open 
../../../x509/server1_cert.pem: no such file or directory
 #array+=("compatibility/tls/grpc")# 
tls.LoadX509KeyPair(certs{../../../x509/server1_cert.pem}, 
privateKey{../../../x509/server1_key.pem}) = err:open 
../../../x509/server1_cert.pem: no such file or directory
diff --git a/tls/README.md b/tls/README.md
index 07307c7b..7bec9175 100644
--- a/tls/README.md
+++ b/tls/README.md
@@ -2,18 +2,21 @@
 
 ## Description
 
-This example demonstrates how to use TLS (based on X.509 certificates) in 
Dubbo-Go to enable encrypted communication and/or mutual authentication between 
a client and a server. The example includes a simple `greet` service, client 
and server sample programs, and scripts for generating test certificates using 
X.509.
+This example demonstrates how to use TLS (based on X.509 certificates) in 
Dubbo-Go and Dubbo-Java to enable encrypted communication and/or mutual 
authentication between a client and a server. More importantly, this example 
showcases the **cross-language interoperability between Dubbo-Go and 
Dubbo-Java**—through the Triple protocol and Protobuf serialization, a Go 
client can seamlessly call a Java server, and a Java client can call a Go 
server. The example includes client and server sampl [...]
 
 ## Directory Structure
 
-* **client/**: Client example program
-* **server/**: Server example program
+* **go-client/**: Go client example program
+* **go-server/**: Go server example program
+* **java-server/**: Java server example program (Dubbo-Java provider)
+* **java-client/**: Java client example program (Dubbo-Java consumer)
 * **proto/**: Proto file and generated code for the `greet` service
 * **x509/**: Scripts and example certificates for generating/storing test 
certificates
 
 ## Prerequisites
 
-* Go (recommended version 1.18+)
+* Go (recommended version 1.18+) for running Go clients and servers
+* Java Development Kit (JDK 8+) and Maven (3.6.0+), required only when running 
Java servers or clients
 * On Windows, it is recommended to use Git Bash or WSL to run the certificate 
generation script (`x509/create.sh` uses OpenSSL).
 
 ## Generating Test Certificates
@@ -35,28 +38,100 @@ This example demonstrates how to use TLS (based on X.509 
certificates) in Dubbo-
 
 ## Running the Example
 
-### 1. Start the Server
+### Option 1: Go Server + Go Client
 
-In the root project directory, run the following command to start the server:
+#### 1. Start the Go Server
+
+In the tls directory, run the following command to start the Go server:
+
+```bash
+go run ./go-server/cmd  
+```
+
+The server will load the server certificates and CA from the `x509/` directory 
and listen on the address specified in the configuration. If you need to 
customize the configuration, please modify the `server` program or the relevant 
parts in the source code.
+
+#### 2. Start the Go Client
+
+In another terminal in the tls directory, run the following command to start 
the Go client:
+
+```bash
+go run ./go-client/cmd  
+```
+
+The client will use the certificates from the `x509/` directory to establish a 
TLS connection with the server and invoke the `greet` service.
+
+### Option 2: Java Server + Go Client (Dubbo-Go ↔ Dubbo-Java Interoperability)
+
+This demonstrates **interoperability between Dubbo-Go and Dubbo-Java** using 
the Triple protocol over TLS.
+
+#### 1. Start the Java Server (Dubbo-Java Provider)
+
+In the tls directory, navigate to the `java-server` subdirectory and start the 
Maven project:
+
+```bash
+cd ./java-server
+mvn clean compile
+mvn exec:java 
-Dexec.mainClass="org.apache.dubbo.samples.tls.provider.TlsTriProvider"
+```
+
+The Java server will start on port 20000 using TLS with the certificates from 
`x509/server2_cert.pem` and `x509/server2_key.pem`.
+
+#### 2. Start the Go Client
+
+In another terminal, in the tls directory, run the Go client:
 
 ```bash
-go run ./tls/server/cmd  
+go run ./client/cmd
+```
+
+The Go client will connect to the Java server via TLS and invoke the `greet` 
service. You should see output like:
+
 ```
+Greet response: hello world from Java provider
+```
+
+### Option 3: Go Server + Java Client
 
-The server will load the server certificates and CA from the `x509/` directory 
and listen on the address specified in the configuration. To customize this, 
modify the `server` program or the source code as needed.
+This option demonstrates the reverse interoperability—a Java client calling a 
Go server.
 
-### 2. Start the Client
+#### 1. Start the Go Server
 
-In another terminal, run the following command to start the client:
+In the tls directory, run the following command to start the Go server:
 
 ```bash
-go run ./tls/client/cmd  
+go run ./go-server/cmd
 ```
 
-The client will use the client certificates and CA from the `x509/` directory 
to establish a TLS connection with the server and invoke the `greet` service.
+The Go server will start on port 20000 with TLS encryption enabled.
+
+#### 2. Start the Java Client
+
+In another terminal, in the tls directory, navigate to the `java-client` 
subdirectory and start the Maven project:
+
+```bash
+cd ./java-client
+mvn clean compile
+mvn exec:java 
-Dexec.mainClass="org.apache.dubbo.samples.tls.consumer.TlsTriProtoConsumer"
+# To customize the target host and TLS authority (SNI), add: 
-Dtls.host=127.0.0.1 -Dtls.authority=dubbogo.test.example.com
+```
+
+The Java client will connect to the Go server via TLS and invoke the `greet` 
service. You should see output like:
+
+```
+Greet response: hello world
+```
+
+## Dubbo-Go and Dubbo-Java Interoperability
+
+This example demonstrates **cross-language interoperability** between Dubbo-Go 
and Dubbo-Java frameworks:
+
+* **Protocol**: Both use the Triple protocol (based on gRPC/HTTP2)
+* **Serialization**: Protobuf ensures language-agnostic data exchange
+* **TLS/SSL**: Both support X.509 certificate-based encryption and 
authentication
+* **Service Interface**: Defined in `proto/greet.proto`, code is generated for 
both Go and Java
 
 ## Notes
 
-* The certificate paths and whether mutual authentication is enabled depend on 
the files loaded by the example programs. Please check `tls/server/cmd/main.go` 
and `tls/client/cmd/main.go` to understand the specific behavior and available 
command-line parameters.
+* The certificate paths and whether mutual authentication is enabled depend on 
the files loaded by the example programs. Please check 
`tls/go-server/cmd/main.go` and `tls/go-client/cmd/main.go` to understand the 
specific behavior and available command-line parameters.
 * On Windows, running the `create.sh` script may require WSL/Git Bash or 
manually running OpenSSL commands.
 * This example is intended for educational and testing purposes only. The 
example certificates should not be used in a production environment.
diff --git a/tls/README_CN.md b/tls/README_CN.md
index ab1c27dc..f696bfd9 100644
--- a/tls/README_CN.md
+++ b/tls/README_CN.md
@@ -2,18 +2,21 @@
 
 ## 说明
 
-本示例演示了如何在 Dubbo-Go 中使用 TLS(基于 X.509 证书)实现客户端与服务端之间的加密通信和/或双向认证。示例包括一个简单的 
`greet` 服务、客户端与服务端的示例程序,以及用于生成测试证书的 X.509 脚本和示例证书。
+本示例演示了如何在 Dubbo-Go 和 Dubbo-Java 中使用 TLS(基于 X.509 
证书)实现客户端与服务端之间的加密通信和/或双向认证。更重要的是,本示例展示了 **Dubbo-Go 与 Dubbo-Java 的跨语言互通能力**— Go 
客户端可以无缝调用 Java 服务端,Java 客户端也可以调用 Go 服务端。示例包括 Go 和 Java 
的客户端与服务端的示例程序,以及用于生成测试证书的 X.509 脚本和示例证书。
 
 ## 目录结构
 
-* **client/**: 客户端示例程序
-* **server/**: 服务端示例程序
+* **go-client/**: Go 客户端示例程序
+* **go-server/**: Go 服务端示例程序
+* **java-server/**: Java 服务端示例程序(Dubbo-Java 提供者)
+* **java-client/**: Java 客户端示例程序(Dubbo-Java 消费者)
 * **proto/**: `greet` 服务的 Proto 文件及生成的代码
 * **x509/**: 生成和存放测试证书的脚本和示例证书
 
 ## 先决条件
 
-* Go 语言 (建议使用 1.18+ 版本)
+* Go 语言 (建议使用 1.18+ 版本),用于运行 Go 客户端和服务端
+* Java 开发环境 (JDK 8+) 和 Maven (3.6.0+),仅当运行 Java 服务端或客户端时需要
 * 在 Windows 系统中,建议使用 Git Bash 或 WSL 来运行证书生成脚本(`x509/create.sh` 使用 OpenSSL)。
 
 ## 生成测试证书
@@ -35,28 +38,100 @@
 
 ## 如何运行
 
-### 1. 启动服务端
+### 方式一:Go 服务端 + Go 客户端
 
-在项目根目录下执行以下命令来启动服务端:
+#### 1. 启动 Go 服务端
+
+在 tls 目录下执行以下命令来启动 Go 服务端:
 
 ```bash
-go run ./tls/server/cmd  
+go run ./go-server/cmd  
 ```
 
 服务端会加载 `x509/` 目录下的服务器证书和 CA,默认监听配置中指定的地址。如果需要自定义配置,请修改 `server` 程序或源代码中的相关内容。
 
-### 2. 启动客户端
+#### 2. 启动 Go 客户端
+
+在另一个终端于 tls 目录下执行以下命令来启动 Go 客户端:
+
+```bash
+go run ./go-client/cmd  
+```
+
+客户端会使用 `x509/` 目录下的证书与服务端建立 TLS 连接,并调用 `greet` 服务。
+
+### 方式二:Java 服务端 + Go 客户端(Dubbo-Go ↔ Dubbo-Java 互通)
+
+本方式展示了 **Dubbo-Go 与 Dubbo-Java 通过 Triple 协议和 TLS 进行互通**。
+
+#### 1. 启动 Java 服务端(Dubbo-Java 提供者)
+
+在 tls 目录下,进入 `java-server` 子目录并启动 Maven 项目:
+
+```bash
+cd ./java-server
+mvn clean compile
+mvn exec:java 
-Dexec.mainClass="org.apache.dubbo.samples.tls.provider.TlsTriProvider"
+```
+
+Java 服务端将在 20000 端口启动,使用 `x509/server2_cert.pem` 和 `x509/server2_key.pem` 证书进行 
TLS 加密。
+
+#### 2. 启动 Go 客户端
+
+在另一个终端,在 tls 目录下运行 Go 客户端:
+
+```bash
+go run ./client/cmd
+```
+
+Go 客户端将通过 TLS 连接到 Java 服务端并调用 `greet` 服务。你应该会看到类似如下的输出:
+
+```
+Greet response: Hello hello world from Java provider
+```
+
+### 方式三:Go 服务端 + Java 客户端
 
-在另一个终端执行以下命令来启动客户端:
+本方式展示了反向的互通——Java 客户端调用 Go 服务端。
+
+#### 1. 启动 Go 服务端
+
+在 tls 目录下执行以下命令来启动 Go 服务端:
 
 ```bash
-go run ./tls/client/cmd  
+go run ./go-server/cmd
 ```
 
-客户端会使用 `x509/` 目录下的客户端证书和 CA 与服务端建立 TLS 连接,并调用 `greet` 服务。
+Go 服务端将在 20000 端口启动,使用 TLS 证书进行加密。
+
+#### 2. 启动 Java 客户端
+
+在另一个终端,在 tls 目录下进入 `java-client` 子目录并启动 Maven 项目:
+
+```bash
+cd ./java-client
+mvn clean compile
+mvn exec:java 
-Dexec.mainClass="org.apache.dubbo.samples.tls.consumer.TlsTriProtoConsumer"
+# 如需自定义连接地址与 SNI(证书主机名),可加:-Dtls.host=127.0.0.1 
-Dtls.authority=dubbogo.test.example.com
+```
+
+Java 客户端将通过 TLS 连接到 Go 服务端并调用 `greet` 服务。你应该会看到类似如下的输出:
+
+```
+Greet response: hello world
+```
+
+## Dubbo-Go 与 Dubbo-Java 互通性
+
+本示例展示了 **Dubbo-Go 和 Dubbo-Java 框架之间的跨语言互通**:
+
+* **协议**:双方都使用 Triple 协议(基于 gRPC/HTTP2)
+* **序列化**:Protobuf 确保语言无关的数据交换
+* **TLS/SSL**:双方都支持基于 X.509 证书的加密和认证
+* **服务接口**:在 `proto/greet.proto` 中定义,为 Go 和 Java 分别生成代码
 
 ## 注意事项
 
-* 证书路径和是否启用双向认证的设置取决于示例程序中加载的文件。请查看 `tls/server/cmd/main.go` 和 
`tls/client/cmd/main.go` 以了解具体行为和可用的命令行参数。
+* 证书路径和是否启用双向认证的设置取决于示例程序中加载的文件。请查看 `tls/go-server/cmd/main.go` 和 
`tls/go-client/cmd/main.go` 以了解具体行为和可用的命令行参数。
 * 在 Windows 环境下运行 `create.sh` 脚本时,可能需要 WSL/Git Bash,或手动执行 OpenSSL 命令。
 * 本示例用于教学和测试目的,示例证书不应在生产环境中使用。
diff --git a/tls/client/cmd/main.go b/tls/go-client/cmd/main.go
similarity index 91%
rename from tls/client/cmd/main.go
rename to tls/go-client/cmd/main.go
index 9bea27a1..44babc8d 100644
--- a/tls/client/cmd/main.go
+++ b/tls/go-client/cmd/main.go
@@ -28,14 +28,15 @@ import (
 )
 
 import (
-       "github.com/apache/dubbo-go-samples/tls/proto"
+       greet "github.com/apache/dubbo-go-samples/tls/proto"
 )
 
 func main() {
        cli, err := client.NewClient(
                client.WithClientURL("127.0.0.1:20000"),
+               client.WithClientProtocolTriple(),
                client.WithClientTLSOption(
-                       tls.WithCACertFile("../../x509/server_ca_cert.pem"),
+                       tls.WithCACertFile("x509/server_ca_cert.pem"),
                        tls.WithServerName("dubbogo.test.example.com"),
                ),
        )
diff --git a/tls/server/cmd/main.go b/tls/go-server/cmd/main.go
similarity index 91%
rename from tls/server/cmd/main.go
rename to tls/go-server/cmd/main.go
index e3cff9cb..0028dae3 100644
--- a/tls/server/cmd/main.go
+++ b/tls/go-server/cmd/main.go
@@ -29,7 +29,7 @@ import (
 )
 
 import (
-       "github.com/apache/dubbo-go-samples/tls/proto"
+       greet "github.com/apache/dubbo-go-samples/tls/proto"
 )
 
 type GreetTripleServer struct {
@@ -47,8 +47,8 @@ func main() {
                        protocol.WithTriple(),
                ),
                server.WithServerTLSOption(
-                       tls.WithCertFile("../../x509/server2_cert.pem"),
-                       tls.WithKeyFile("../../x509/server2_key.pem"),
+                       tls.WithCertFile("x509/server2_cert.pem"),
+                       tls.WithKeyFile("x509/server2_key.pem"),
                        tls.WithServerName("dubbogo.test.example.com"),
                ),
        )
diff --git a/tls/java-client/pom.xml b/tls/java-client/pom.xml
new file mode 100644
index 00000000..b3a5d91e
--- /dev/null
+++ b/tls/java-client/pom.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.dubbo.samples</groupId>
+        <artifactId>java-tri-ssl</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>java-tri-ssl-consumer</artifactId>
+    <name>Java Triple TLS Consumer</name>
+
+    <properties>
+        <grpc.version>1.68.0</grpc.version>
+    </properties>
+
+    <dependencies>
+        <!-- Client builds and runs standalone; generate protobuf/grpc stubs 
locally -->
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java</artifactId>
+            <version>3.21.7</version>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-stub</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-protobuf</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.annotation</groupId>
+            <artifactId>javax.annotation-api</artifactId>
+            <version>1.3.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo</artifactId>
+        </dependency>
+        <!-- Use grpc-netty-shaded to avoid Netty version conflicts -->
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-netty-shaded</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-slf4j-impl</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <extensions>
+            <extension>
+                <groupId>kr.motd.maven</groupId>
+                <artifactId>os-maven-plugin</artifactId>
+                <version>1.7.1</version>
+            </extension>
+        </extensions>
+        <plugins>
+            <plugin>
+                <groupId>org.xolstice.maven.plugins</groupId>
+                <artifactId>protobuf-maven-plugin</artifactId>
+                <version>0.6.1</version>
+                <configuration>
+                    
<protocArtifact>com.google.protobuf:protoc:3.21.7:exe:${os.detected.classifier}</protocArtifact>
+                    
<protoSourceRoot>${project.basedir}/../proto</protoSourceRoot>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>compile</goal>
+                            <goal>compile-custom</goal>
+                        </goals>
+                        <configuration>
+                            <pluginId>grpc-java</pluginId>
+                            
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.6.4</version>
+                <configuration>
+                    <executable>true</executable>
+                    
<mainClass>org.apache.dubbo.samples.tls.consumer.TlsTriProtoConsumer</mainClass>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                        <configuration>
+                            <classifier>exec</classifier>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git 
a/tls/java-client/src/main/java/org/apache/dubbo/samples/tls/consumer/GreetClient.java
 
b/tls/java-client/src/main/java/org/apache/dubbo/samples/tls/consumer/GreetClient.java
new file mode 100644
index 00000000..71d38592
--- /dev/null
+++ 
b/tls/java-client/src/main/java/org/apache/dubbo/samples/tls/consumer/GreetClient.java
@@ -0,0 +1,24 @@
+/*
+ * 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.dubbo.samples.tls.consumer;
+
+import greet.Greet;
+
+public interface GreetClient {
+    Greet.GreetResponse greet(Greet.GreetRequest request);
+}
diff --git 
a/tls/java-client/src/main/java/org/apache/dubbo/samples/tls/consumer/GreetClientImpl.java
 
b/tls/java-client/src/main/java/org/apache/dubbo/samples/tls/consumer/GreetClientImpl.java
new file mode 100644
index 00000000..bfb1ece9
--- /dev/null
+++ 
b/tls/java-client/src/main/java/org/apache/dubbo/samples/tls/consumer/GreetClientImpl.java
@@ -0,0 +1,66 @@
+/*
+ * 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.dubbo.samples.tls.consumer;
+
+import greet.Greet;
+import greet.GreetServiceGrpc;
+import io.grpc.ManagedChannel;
+import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
+import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
+import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext;
+
+import javax.net.ssl.SSLException;
+import java.io.File;
+
+public class GreetClientImpl implements GreetClient {
+    private final ManagedChannel channel;           // gRPC connection channel
+    private final GreetServiceGrpc.GreetServiceBlockingStub stub;  // 
Synchronous RPC stub
+
+    /**
+     * Initialize TLS encrypted connection
+     * @param targetHost Server hostname or IP address to connect to
+     * @param port Server port number
+     * @param authorityHost Hostname for TLS certificate verification (SNI)
+     * @param caCertPath CA certificate path for server verification
+     */
+    public GreetClientImpl(String targetHost, int port, String authorityHost, 
String caCertPath) throws SSLException {
+        // Configure SSL context and load CA certificate for server 
verification
+        SslContext sslContext = GrpcSslContexts.forClient()
+                .trustManager(new File(caCertPath))
+                .build();
+
+        // Create gRPC channel with TLS encryption enabled
+        this.channel = NettyChannelBuilder.forAddress(targetHost, port)
+                .sslContext(sslContext)
+                .overrideAuthority(authorityHost)  // SNI: hostname used 
during certificate verification
+                .build();
+        
+        // Create synchronous RPC service stub
+        this.stub = GreetServiceGrpc.newBlockingStub(channel);
+    }
+
+    @Override
+    public Greet.GreetResponse greet(Greet.GreetRequest request) {
+        // Call remote service through TLS encrypted channel
+        return stub.greet(request);
+    }
+
+    public void shutdown() {
+        channel.shutdown();  // Close connection and release resources
+    }
+}
diff --git 
a/tls/java-client/src/main/java/org/apache/dubbo/samples/tls/consumer/TlsTriProtoConsumer.java
 
b/tls/java-client/src/main/java/org/apache/dubbo/samples/tls/consumer/TlsTriProtoConsumer.java
new file mode 100644
index 00000000..3536bf3e
--- /dev/null
+++ 
b/tls/java-client/src/main/java/org/apache/dubbo/samples/tls/consumer/TlsTriProtoConsumer.java
@@ -0,0 +1,72 @@
+/*
+ * 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.dubbo.samples.tls.consumer;
+
+import greet.Greet;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.RegistryConfig;
+import org.apache.dubbo.config.SslConfig;
+import org.apache.dubbo.config.bootstrap.DubboBootstrap;
+
+import javax.net.ssl.SSLException;
+
+public class TlsTriProtoConsumer {
+    public static void main(String[] args) throws SSLException {
+        String caCertPath;
+        if (args.length > 0) {
+            caCertPath = args[0];
+        } else {
+            String root = System.getProperty("user.dir");
+            caCertPath = root + "/../x509/server_ca_cert.pem";
+        }
+        java.io.File caFile = new java.io.File(caCertPath);
+        if (!caFile.isAbsolute()) {
+            caFile = caFile.getAbsoluteFile();
+        }
+        if (!caFile.exists()) {
+            throw new IllegalArgumentException("CA cert not found: " + caFile);
+        }
+        caCertPath = caFile.getPath();
+
+        String host = System.getProperty("tls.host", "127.0.0.1");
+        String authority = System.getProperty("tls.authority", 
"dubbogo.test.example.com");
+        int port = Integer.getInteger("tls.port", 20000);
+
+        // Dubbo lifecycle and TLS config
+        SslConfig ssl = new SslConfig();
+        ssl.setClientTrustCertCollectionPath(caCertPath);
+
+        DubboBootstrap bootstrap = DubboBootstrap.getInstance();
+        bootstrap.application(new 
ApplicationConfig("java-tri-ssl-proto-consumer"))
+                .registry(new RegistryConfig(RegistryConfig.NO_AVAILABLE))
+                .ssl(ssl)
+                .start();
+
+        // Bridge client using gRPC stub under the hood
+        GreetClientImpl client = new GreetClientImpl(host, port, authority, 
caCertPath);
+        try {
+            Greet.GreetResponse resp = client.greet(
+                    Greet.GreetRequest.newBuilder().setName("hello 
world").build()
+            );
+            System.out.println("Greet response: " + resp.getGreeting());
+        } finally {
+            client.shutdown();
+            bootstrap.destroy();
+        }
+    }
+}
diff --git a/tls/java-server/pom.xml b/tls/java-server/pom.xml
new file mode 100644
index 00000000..52fbf4da
--- /dev/null
+++ b/tls/java-server/pom.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.dubbo.samples</groupId>
+        <artifactId>java-tri-ssl</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>java-tri-ssl-provider</artifactId>
+    <name>Java Triple TLS Provider</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java</artifactId>
+            <version>3.21.7</version>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-stub</artifactId>
+            <version>1.53.0</version>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-protobuf</artifactId>
+            <version>1.53.0</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.annotation</groupId>
+            <artifactId>javax.annotation-api</artifactId>
+            <version>1.3.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-tcnative-boringssl-static</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-slf4j-impl</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <extensions>
+            <extension>
+                <groupId>kr.motd.maven</groupId>
+                <artifactId>os-maven-plugin</artifactId>
+                <version>1.7.1</version>
+            </extension>
+        </extensions>
+        <plugins>
+            <plugin>
+                <groupId>org.xolstice.maven.plugins</groupId>
+                <artifactId>protobuf-maven-plugin</artifactId>
+                <version>0.6.1</version>
+                <configuration>
+                    
<protocArtifact>com.google.protobuf:protoc:3.21.7:exe:${os.detected.classifier}</protocArtifact>
+                    
<protoSourceRoot>${project.basedir}/../proto</protoSourceRoot>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>compile</goal>
+                            <goal>compile-custom</goal>
+                        </goals>
+                        <configuration>
+                            <pluginId>grpc-java</pluginId>
+                            
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.53.0:exe:${os.detected.classifier}</pluginArtifact>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.6.4</version>
+                <configuration>
+                    <executable>true</executable>
+                    
<mainClass>org.apache.dubbo.samples.tls.provider.TlsTriProvider</mainClass>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                        <configuration>
+                            <classifier>exec</classifier>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <version>3.0.0</version>
+                <configuration>
+                    
<mainClass>org.apache.dubbo.samples.tls.provider.TlsTriProvider</mainClass>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/tls/java-server/src/main/java/greet/GreetService.java 
b/tls/java-server/src/main/java/greet/GreetService.java
new file mode 100644
index 00000000..9bc9fcfc
--- /dev/null
+++ b/tls/java-server/src/main/java/greet/GreetService.java
@@ -0,0 +1,30 @@
+/*
+ * 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 greet;
+
+/**
+ * Dubbo service interface for GreetService.
+ * Standard Dubbo RPC interface (not gRPC streaming).
+ * Located in greet package to match proto package.
+ */
+public interface GreetService {
+    /**
+     * Greet a user - synchronous Dubbo RPC method.
+     */
+    Greet.GreetResponse greet(Greet.GreetRequest request);
+}
diff --git 
a/tls/java-server/src/main/java/org/apache/dubbo/samples/tls/provider/GreetServiceImpl.java
 
b/tls/java-server/src/main/java/org/apache/dubbo/samples/tls/provider/GreetServiceImpl.java
new file mode 100644
index 00000000..3fc7dc67
--- /dev/null
+++ 
b/tls/java-server/src/main/java/org/apache/dubbo/samples/tls/provider/GreetServiceImpl.java
@@ -0,0 +1,51 @@
+/*
+ * 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.dubbo.samples.tls.provider;
+
+import greet.Greet;
+import greet.GreetService;
+import greet.GreetServiceGrpc;
+import io.grpc.stub.StreamObserver;
+
+public class GreetServiceImpl extends GreetServiceGrpc.GreetServiceImplBase 
implements GreetService {
+    
+    /**
+     * Dubbo RPC method - synchronous
+     */
+    @Override
+    public Greet.GreetResponse greet(Greet.GreetRequest request) {
+        return Greet.GreetResponse.newBuilder()
+                .setGreeting(request.getName())
+                .build();
+    }
+    
+    /**
+     * gRPC method - asynchronous (from GreetServiceImplBase)
+     */
+    @Override
+    public void greet(Greet.GreetRequest request, 
StreamObserver<Greet.GreetResponse> responseObserver) {
+        try {
+            Greet.GreetResponse response = greet(request);  // Delegate to 
sync method
+            responseObserver.onNext(response);
+            responseObserver.onCompleted();
+        } catch (Exception e) {
+            // Send error to client to prevent hanging
+            responseObserver.onError(e);
+        }
+    }
+}
diff --git 
a/tls/java-server/src/main/java/org/apache/dubbo/samples/tls/provider/Pkcs1ToPkcs8KeyConverter.java
 
b/tls/java-server/src/main/java/org/apache/dubbo/samples/tls/provider/Pkcs1ToPkcs8KeyConverter.java
new file mode 100644
index 00000000..beb25da8
--- /dev/null
+++ 
b/tls/java-server/src/main/java/org/apache/dubbo/samples/tls/provider/Pkcs1ToPkcs8KeyConverter.java
@@ -0,0 +1,272 @@
+/*
+ * 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.dubbo.samples.tls.provider;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.attribute.PosixFilePermission;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Base64;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Converts PKCS#1 format private keys (-----BEGIN RSA PRIVATE KEY-----)
+ * to PKCS#8 format (-----BEGIN PRIVATE KEY-----) that Java/Netty can parse.
+ * 
+ * Uses openssl pkcs8 command internally via system process.
+ * Implements secure temporary file handling with proper cleanup and 
permissions.
+ */
+public class Pkcs1ToPkcs8KeyConverter {
+    
+    // Track temporary files for cleanup
+    private static final List<File> tempFiles = new ArrayList<>();
+    
+    // Install shutdown hook once
+    static {
+        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+            cleanupTempFiles();
+        }));
+    }
+
+    /**
+     * Convert PKCS#1 private key to PKCS#8 format
+     * @param pkcs1KeyPath path to PKCS#1 PEM file
+     * @return PKCS#8 PEM content as string
+     * @throws Exception if conversion fails
+     */
+    public static String convertPkcs1ToPkcs8(String pkcs1KeyPath) throws 
Exception {
+        // Validate input path
+        if (pkcs1KeyPath == null || pkcs1KeyPath.trim().isEmpty()) {
+            throw new IllegalArgumentException("Key path cannot be null or 
empty");
+        }
+        
+        // Verify input file exists and is readable
+        File inputFile = new File(pkcs1KeyPath);
+        if (!inputFile.exists()) {
+            throw new FileNotFoundException("Private key file not found: " + 
pkcs1KeyPath);
+        }
+        if (!inputFile.canRead()) {
+            throw new IOException("Cannot read private key file: " + 
pkcs1KeyPath);
+        }
+        
+        // Call openssl to convert PKCS#1 to PKCS#8
+        ProcessBuilder pb = new ProcessBuilder(
+                "openssl", "pkcs8",
+                "-topk8",
+                "-inform", "PEM",
+                "-outform", "PEM",
+                "-in", pkcs1KeyPath,
+                "-nocrypt"
+        );
+        
+        Process process = null;
+        try {
+            process = pb.start();
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to execute openssl command. 
Ensure OpenSSL is installed and in PATH.", e);
+        }
+        
+        final Process finalProcess = process;
+        final StringBuilder output = new StringBuilder();
+        final StringBuilder errorOutput = new StringBuilder();
+        
+        try {
+            // Read both stdout and stderr concurrently to prevent deadlock
+            Thread outputThread = new Thread(() -> {
+                try (BufferedReader reader = new BufferedReader(new 
InputStreamReader(finalProcess.getInputStream()))) {
+                    String line;
+                    while ((line = reader.readLine()) != null) {
+                        synchronized (output) {
+                            output.append(line).append("\n");
+                        }
+                    }
+                } catch (IOException e) {
+                    System.err.println("[TLS WARNING] Error reading process 
output: " + e.getMessage());
+                }
+            }, "openssl-output-reader");
+            
+            Thread errorThread = new Thread(() -> {
+                try (BufferedReader reader = new BufferedReader(new 
InputStreamReader(finalProcess.getErrorStream()))) {
+                    String line;
+                    while ((line = reader.readLine()) != null) {
+                        synchronized (errorOutput) {
+                            errorOutput.append(line).append("\n");
+                        }
+                    }
+                } catch (IOException e) {
+                    System.err.println("[TLS WARNING] Error reading process 
error stream: " + e.getMessage());
+                }
+            }, "openssl-error-reader");
+            
+            outputThread.start();
+            errorThread.start();
+            
+            // Wait for process to complete
+            int exitCode = process.waitFor();
+            
+            // Wait for stream readers to finish
+            outputThread.join(5000); // 5 second timeout
+            errorThread.join(5000);
+            
+            if (exitCode != 0) {
+                throw new RuntimeException("OpenSSL PKCS#1 to PKCS#8 
conversion failed (exit code " + exitCode + "): " + errorOutput.toString());
+            }
+            
+            if (output.length() == 0) {
+                throw new RuntimeException("OpenSSL produced no output. Error: 
" + errorOutput.toString());
+            }
+            
+            return output.toString();
+            
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("OpenSSL process was interrupted", e);
+        } finally {
+            // Ensure process is destroyed to free resources
+            if (process != null) {
+                process.destroy();
+                // Give it a moment to terminate gracefully
+                try {
+                    if (!process.waitFor(1000, 
java.util.concurrent.TimeUnit.MILLISECONDS)) {
+                        process.destroyForcibly();
+                    }
+                } catch (InterruptedException e) {
+                    process.destroyForcibly();
+                    Thread.currentThread().interrupt();
+                }
+            }
+        }
+    }
+    
+    /**
+     * Check if a PEM file is in PKCS#1 format
+     * @param pemPath path to PEM file
+     * @return true if it contains "BEGIN RSA PRIVATE KEY"
+     * @throws IOException if file read fails
+     */
+    public static boolean isPkcs1Format(String pemPath) throws IOException {
+        try (BufferedReader reader = new BufferedReader(new 
FileReader(pemPath))) {
+            String firstLine = reader.readLine();
+            return firstLine != null && firstLine.contains("BEGIN RSA PRIVATE 
KEY");
+        }
+    }
+    
+    /**
+     * Write PKCS#8 PEM content to a temporary file with secure permissions
+     * @param pkcs8Content PEM content
+     * @return path to temporary file
+     * @throws IOException if file creation fails
+     */
+    public static String writeTempPkcs8File(String pkcs8Content) throws 
IOException {
+        File temp = File.createTempFile("pkcs8_key_", ".pem");
+        
+        // Set restrictive permissions (owner read/write only) on Unix-like 
systems
+        try {
+            Set<PosixFilePermission> perms = new HashSet<>();
+            perms.add(PosixFilePermission.OWNER_READ);
+            perms.add(PosixFilePermission.OWNER_WRITE);
+            Files.setPosixFilePermissions(temp.toPath(), perms);
+            System.out.println("[TLS] Set secure permissions (600) on 
temporary key file");
+        } catch (UnsupportedOperationException e) {
+            // Windows or non-POSIX system - file permissions not supported
+            System.out.println("[TLS WARNING] Cannot set POSIX permissions on 
this system. Temporary key file may be accessible to other users.");
+        }
+        
+        // Register for cleanup on exit
+        temp.deleteOnExit();
+        synchronized (tempFiles) {
+            tempFiles.add(temp);
+        }
+        
+        try (FileWriter writer = new FileWriter(temp)) {
+            writer.write(pkcs8Content);
+        }
+        
+        return temp.getAbsolutePath();
+    }
+    
+    /**
+     * Clean up all temporary key files securely
+     */
+    public static void cleanupTempFiles() {
+        synchronized (tempFiles) {
+            for (File file : tempFiles) {
+                if (file.exists()) {
+                    try {
+                        // Overwrite file content before deletion for security
+                        secureDelete(file);
+                        System.out.println("[TLS] Securely deleted temporary 
key file: " + file.getAbsolutePath());
+                    } catch (Exception e) {
+                        System.err.println("[TLS WARNING] Failed to securely 
delete temp file: " + file.getAbsolutePath() + ", " + e.getMessage());
+                        // Attempt regular deletion as fallback
+                        file.delete();
+                    }
+                }
+            }
+            tempFiles.clear();
+        }
+    }
+    
+    /**
+     * Securely delete a file by overwriting its content before deletion
+     * @param file file to delete
+     * @throws IOException if deletion fails
+     */
+    private static void secureDelete(File file) throws IOException {
+        if (file.exists()) {
+            long length = file.length();
+            try (RandomAccessFile raf = new RandomAccessFile(file, "rws")) {
+                // Overwrite with zeros
+                raf.seek(0);
+                for (long i = 0; i < length; i++) {
+                    raf.write(0);
+                }
+            }
+            // Delete the file
+            if (!file.delete()) {
+                throw new IOException("Failed to delete file: " + 
file.getAbsolutePath());
+            }
+        }
+    }
+    
+    /**
+     * Load and convert PKCS#1 key if necessary
+     * @param keyPath path to private key file
+     * @return path to PKCS#8 format key (may be temporary file)
+     * @throws Exception if conversion fails
+     */
+    public static String loadAndConvertKey(String keyPath) throws Exception {
+        if (!isPkcs1Format(keyPath)) {
+            // Already PKCS#8 or other format, use as is
+            return keyPath;
+        }
+        
+        System.out.println("[TLS] Detected PKCS#1 format key, converting to 
PKCS#8...");
+        String pkcs8Content = convertPkcs1ToPkcs8(keyPath);
+        String tempPath = writeTempPkcs8File(pkcs8Content);
+        System.out.println("[TLS] Converted key written to temporary file: " + 
tempPath);
+        System.out.println("[TLS] Temporary key file will be securely deleted 
on JVM shutdown");
+        
+        return tempPath;
+    }
+}
diff --git 
a/tls/java-server/src/main/java/org/apache/dubbo/samples/tls/provider/TlsTriProvider.java
 
b/tls/java-server/src/main/java/org/apache/dubbo/samples/tls/provider/TlsTriProvider.java
new file mode 100644
index 00000000..7c85033b
--- /dev/null
+++ 
b/tls/java-server/src/main/java/org/apache/dubbo/samples/tls/provider/TlsTriProvider.java
@@ -0,0 +1,107 @@
+/*
+ * 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.dubbo.samples.tls.provider;
+
+import greet.GreetServiceGrpc;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.ProtocolConfig;
+import org.apache.dubbo.config.ServiceConfig;
+import org.apache.dubbo.config.SslConfig;
+import org.apache.dubbo.config.bootstrap.DubboBootstrap;
+
+import java.util.concurrent.CountDownLatch;
+
+public class TlsTriProvider {
+    public static void main(String[] args) throws Exception {
+        // Allow overriding cert paths via args: certChain, privateKey, 
[trustChain]
+        SslConfig ssl = new SslConfig();
+        String privateKeyPath;
+        
+        if (args.length > 0) {
+            if (args.length < 2 || args.length > 3) {
+                System.out.println("USAGE: TlsTriProvider certChainFilePath 
privateKeyFilePath [trustCertCollectionFilePath]");
+                System.exit(1);
+            }
+            ssl.setServerKeyCertChainPath(args[0]);
+            privateKeyPath = args[1];
+            if (args.length == 3) {
+                ssl.setServerTrustCertCollectionPath(args[2]);
+            }
+        } else {
+            // default relative to this module: reuse go sample certs
+            String root = System.getProperty("user.dir");
+            // user.dir is typically .../java-tri-ssl/provider
+            String base = root + "/../x509";
+            ssl.setServerKeyCertChainPath(base + "/server2_cert.pem");
+            privateKeyPath = base + "/server2_key.pem";
+        }
+        
+        // Convert PKCS#1 to PKCS#8 if necessary with proper error handling
+        try {
+            privateKeyPath = 
Pkcs1ToPkcs8KeyConverter.loadAndConvertKey(privateKeyPath);
+            ssl.setServerPrivateKeyPath(privateKeyPath);
+        } catch (Exception e) {
+            System.err.println("[TLS ERROR] Failed to load or convert private 
key: " + e.getMessage());
+            System.err.println("Please ensure:");
+            System.err.println("  1. The private key file exists and is 
readable");
+            System.err.println("  2. The key is in valid PKCS#1 or PKCS#8 
format");
+            System.err.println("  3. OpenSSL is installed and available in 
PATH (required for PKCS#1 conversion)");
+            throw new RuntimeException("TLS configuration failed", e);
+        }
+
+        ProtocolConfig protocol = new ProtocolConfig();
+        protocol.setName("tri");
+        protocol.setPort(20000);
+        protocol.setSslEnabled(true);
+
+        // Use the Dubbo interface from greet package (matches proto 
package.service)
+        ServiceConfig<greet.GreetService> service = new ServiceConfig<>();
+        service.setInterface(greet.GreetService.class);
+        service.setRef(new GreetServiceImpl());
+        service.setRegister(false); // no registry, direct export
+
+        DubboBootstrap bootstrap = DubboBootstrap.getInstance();
+        bootstrap.application(new ApplicationConfig("java-tri-ssl-provider"))
+                .protocol(protocol)
+                .ssl(ssl)
+                .service(service)
+                .start();
+
+        // Add graceful shutdown hook
+        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+            System.out.println("[TLS] Shutting down Java triple TLS 
provider...");
+            try {
+                bootstrap.stop();
+                System.out.println("[TLS] Dubbo provider stopped gracefully");
+            } catch (Exception e) {
+                System.err.println("[TLS] Error during shutdown: " + 
e.getMessage());
+            }
+        }));
+
+        System.out.println("Java triple TLS provider started on 
tri://0.0.0.0:20000");
+        System.out.println("Press Ctrl+C to stop the server");
+        
+        // Keep the application running and handle interruptions gracefully
+        try {
+            Thread.currentThread().join();
+        } catch (InterruptedException e) {
+            System.out.println("[TLS] Server interrupted, shutting down...");
+            Thread.currentThread().interrupt();
+        }
+    }
+}
diff --git a/tls/pom.xml b/tls/pom.xml
new file mode 100644
index 00000000..ae1f4f1e
--- /dev/null
+++ b/tls/pom.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.apache.dubbo.samples</groupId>
+    <artifactId>java-tri-ssl</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+    <name>Dubbo Java Triple TLS Samples</name>
+
+    <modules>
+        <module>java-server</module>
+        <module>java-client</module>
+    </modules>
+
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <dubbo.version>3.3.1</dubbo.version>
+        <log4j2.version>2.20.0</log4j2.version>
+        <netty.tcnative.version>2.0.65.Final</netty.tcnative.version>
+        <netty.version>4.1.100.Final</netty.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo-bom</artifactId>
+                <version>${dubbo.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            
+            <dependency>
+                <groupId>org.apache.logging.log4j</groupId>
+                <artifactId>log4j-slf4j-impl</artifactId>
+                <version>${log4j2.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.netty</groupId>
+                <artifactId>netty-tcnative-boringssl-static</artifactId>
+                <version>${netty.tcnative.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.netty</groupId>
+                <artifactId>netty-all</artifactId>
+                <version>${netty.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>

Reply via email to