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>