Skip to content

Commit 0354de5

Browse files
committed
grpc-native: Add TLS tests
Signed-off-by: Johannes Zottele <[email protected]>
1 parent 00d573c commit 0354de5

File tree

5 files changed

+180
-87
lines changed

5 files changed

+180
-87
lines changed

grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ public enum class TlsClientAuth {
4444
/**
4545
* Clients are requested to present their identity, but clients without identities are
4646
* permitted.
47+
* Also, if the client certificate is provided but cannot be verified,
48+
* the client is permitted.
4749
*/
4850
OPTIONAL,
4951

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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+
}

grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbTlsTest.kt

Lines changed: 0 additions & 86 deletions
This file was deleted.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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
6+
7+
import kotlinx.rpc.grpc.StatusCode
8+
import kotlinx.rpc.grpc.StatusException
9+
import kotlinx.rpc.grpc.statusCode
10+
import kotlin.test.assertContains
11+
import kotlin.test.assertEquals
12+
import kotlin.test.assertFailsWith
13+
14+
15+
fun assertGrpcFailure(statusCode: StatusCode, message: String? = null, block: () -> Unit) {
16+
val exc = assertFailsWith<StatusException>(message) { block() }
17+
assertEquals(statusCode, exc.getStatus().statusCode)
18+
if (message != null) {
19+
assertContains(message, exc.getStatus().getDescription() ?: "")
20+
}
21+
}

grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,6 @@ private class TlsCredentialsOptionsBuilder {
187187

188188
private fun TlsClientAuth.toRaw(): grpc_ssl_client_certificate_request_type = when (this) {
189189
TlsClientAuth.NONE -> grpc_ssl_client_certificate_request_type.GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE
190-
TlsClientAuth.OPTIONAL -> grpc_ssl_client_certificate_request_type.GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_AND_VERIFY
190+
TlsClientAuth.OPTIONAL -> grpc_ssl_client_certificate_request_type.GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_BUT_DONT_VERIFY
191191
TlsClientAuth.REQUIRE -> grpc_ssl_client_certificate_request_type.GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY
192192
}

0 commit comments

Comments
 (0)