1+ /*
2+ * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+ */
4+
5+ package kotlinx.rpc.grpc.test.proto
6+
7+ import hello.HelloRequest
8+ import hello.HelloService
9+ import hello.invoke
10+ import kotlinx.coroutines.test.runTest
11+ import kotlinx.rpc.RpcServer
12+ import kotlinx.rpc.grpc.GrpcClient
13+ import kotlinx.rpc.grpc.StatusCode
14+ import kotlinx.rpc.grpc.TlsChannelCredentials
15+ import kotlinx.rpc.grpc.TlsClientAuth
16+ import kotlinx.rpc.grpc.TlsServerCredentials
17+ import kotlinx.rpc.grpc.test.CA_PEM
18+ import kotlinx.rpc.grpc.test.CLIENT_CERT_PEM
19+ import kotlinx.rpc.grpc.test.CLIENT_KEY_PEM
20+ import kotlinx.rpc.grpc.test.EchoRequest
21+ import kotlinx.rpc.grpc.test.EchoService
22+ import kotlinx.rpc.grpc.test.EchoServiceImpl
23+ import kotlinx.rpc.grpc.test.SERVER_CERT_PEM
24+ import kotlinx.rpc.grpc.test.SERVER_KEY_PEM
25+ import kotlinx.rpc.grpc.test.assertGrpcFailure
26+ import kotlinx.rpc.grpc.test.invoke
27+ import kotlinx.rpc.registerService
28+ import kotlinx.rpc.withService
29+ import kotlin.test.Test
30+ import kotlin.test.assertEquals
31+
32+ class GrpcTlsTest : GrpcProtoTest () {
33+
34+ override fun RpcServer.registerServices () {
35+ registerService<EchoService > { EchoServiceImpl () }
36+ }
37+
38+ @Test
39+ fun `test client side TLS with default credentials - should succeed` () = runTest {
40+ // uses default client TLS credentials
41+ // TODO: Use a test server controlled by us (KRPC-215)
42+ val grpcClient = GrpcClient (" grpcb.in" , 9001 )
43+ val service = grpcClient.withService<HelloService >()
44+ val request = HelloRequest {
45+ greeting = " world"
46+ }
47+ val result = service.SayHello (request)
48+
49+ assertEquals(" hello world" , result.reply)
50+
51+ grpcClient.shutdown()
52+ grpcClient.awaitTermination()
53+ }
54+
55+ @Test
56+ fun `test TLS with valid certificates - should succeed` () {
57+ val serverTls = TlsServerCredentials (SERVER_CERT_PEM , SERVER_KEY_PEM )
58+ val clientTls = TlsChannelCredentials { trustManager(SERVER_CERT_PEM ) }
59+
60+ runGrpcTest(serverTls, clientTls, overrideAuthority = " foo.test.google.fr" , test = ::defaultUnaryTest)
61+ }
62+
63+ @Test
64+ fun `test mTLS with valid certificates - should succeed` () = runTest {
65+ val serverTls = TlsServerCredentials (SERVER_CERT_PEM , SERVER_KEY_PEM ) {
66+ trustManager(CA_PEM )
67+ clientAuth(TlsClientAuth .REQUIRE )
68+ }
69+ val clientTls = TlsChannelCredentials {
70+ keyManager(CLIENT_CERT_PEM , CLIENT_KEY_PEM )
71+ trustManager(CA_PEM )
72+ }
73+
74+ runGrpcTest(serverTls, clientTls, overrideAuthority = " foo.test.google.fr" , test = ::defaultUnaryTest)
75+ }
76+
77+ @Test
78+ fun `test mTLS with clientAuth optional - should succeed` () = runTest {
79+ // the server uses a trustManager that does not know about the client certificate,
80+ // so the client can authentication cannot be verified.
81+ // but as the clientAuth is optional, the connection will succeed.
82+ val caCertWithoutClient = SERVER_CERT_PEM
83+ val serverTls = TlsServerCredentials (SERVER_CERT_PEM , SERVER_KEY_PEM ) {
84+ trustManager(caCertWithoutClient)
85+ // clientAuth is optional, so a client without a certificate can connect
86+ clientAuth(TlsClientAuth .OPTIONAL )
87+ }
88+ val clientTls = TlsChannelCredentials {
89+ keyManager(CLIENT_CERT_PEM , CLIENT_KEY_PEM )
90+ trustManager(CA_PEM )
91+ }
92+
93+ runGrpcTest(serverTls, clientTls, overrideAuthority = " foo.test.google.fr" , test = ::defaultUnaryTest)
94+ }
95+
96+ @Test
97+ fun `test mTLS with clientAuth required - should fail` () = runTest {
98+ val serverTls = TlsServerCredentials (SERVER_CERT_PEM , SERVER_KEY_PEM ) {
99+ trustManager(CA_PEM )
100+ // client must authenticate
101+ clientAuth(TlsClientAuth .REQUIRE )
102+ }
103+ // client does NOT provide keyManager, only trusts CA
104+ val clientTls = TlsChannelCredentials {
105+ trustManager(CA_PEM )
106+ }
107+
108+ assertGrpcFailure(StatusCode .UNAVAILABLE ) {
109+ runGrpcTest(serverTls, clientTls, overrideAuthority = " foo.test.google.fr" , test = ::defaultUnaryTest)
110+ }
111+ }
112+
113+ @Test
114+ fun `test TLS with no client trustManager - should fail` () = runTest {
115+ val serverTls = TlsServerCredentials (SERVER_CERT_PEM , SERVER_KEY_PEM )
116+ // client credential doesn't contain a trustManager, so server authentication will fail
117+ val clientTls = TlsChannelCredentials {}
118+ assertGrpcFailure(StatusCode .UNAVAILABLE ) {
119+ runGrpcTest(serverTls, clientTls, overrideAuthority = " foo.test.google.fr" , test = ::defaultUnaryTest)
120+ }
121+ }
122+
123+ @Test
124+ fun `test TLS with invalid authority - should fail` () = runTest {
125+ val serverTls = TlsServerCredentials (SERVER_CERT_PEM , SERVER_KEY_PEM )
126+ val clientTls = TlsChannelCredentials { trustManager(CA_PEM ) }
127+ // the authority does not match the certificate
128+ assertGrpcFailure(StatusCode .UNAVAILABLE ) {
129+ runGrpcTest(serverTls, clientTls, overrideAuthority = " invalid.host.name" , test = ::defaultUnaryTest)
130+ }
131+ }
132+
133+ @Test
134+ fun `test TLS server with plaintext client - should fail` () = runTest {
135+ val serverTls = TlsServerCredentials (SERVER_CERT_PEM , SERVER_KEY_PEM )
136+ assertGrpcFailure(StatusCode .UNAVAILABLE ) {
137+ runGrpcTest(serverCreds = serverTls, overrideAuthority = " foo.test.google.fr" , test = ::defaultUnaryTest)
138+ }
139+ }
140+
141+ @Test
142+ fun `test TLS client with plaintext server - should fail` () = runTest {
143+ val clientTls = TlsChannelCredentials { trustManager(CA_PEM ) }
144+ assertGrpcFailure(StatusCode .UNAVAILABLE ) {
145+ runGrpcTest(clientCreds = clientTls, overrideAuthority = " foo.test.google.fr" , test = ::defaultUnaryTest)
146+ }
147+ }
148+
149+ }
150+
151+ private suspend fun defaultUnaryTest (client : GrpcClient ) {
152+ val service = client.withService<EchoService >()
153+ val request = EchoRequest { message = " Echo" }
154+ val response = service.UnaryEcho (request)
155+ assertEquals(" Echo" , response.message)
156+ }
0 commit comments