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