From 178c5b39fc4faabe8fb6d07c5a9924c9055baeab Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Fri, 5 Sep 2025 17:56:13 +0200 Subject: [PATCH 01/12] grpc-native: Add default TLS support Signed-off-by: Johannes Zottele --- .../rpc/grpc/test/proto/GrpcbInTlsTest.kt | 35 ++++++++ .../src/commonTest/proto/grpcb_in_hello.proto | 22 +++++ .../kotlinx/rpc/grpc/ManagedChannel.native.kt | 7 +- .../rpc/grpc/internal/NativeManagedChannel.kt | 26 +++++- .../protobuf/internal/ProtobufException.kt | 2 +- .../test/runners/BoxTestGenerated.java | 74 ++++++++-------- .../test/runners/DiagnosticTestGenerated.java | 86 ++++++++++--------- 7 files changed, 168 insertions(+), 84 deletions(-) create mode 100644 grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt create mode 100644 grpc/grpc-core/src/commonTest/proto/grpcb_in_hello.proto diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt new file mode 100644 index 000000000..af76d6d58 --- /dev/null +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.test.proto + +import hello.HelloRequest +import hello.HelloService +import hello.invoke +import kotlinx.coroutines.test.runTest +import kotlinx.rpc.grpc.GrpcClient +import kotlinx.rpc.withService +import kotlin.test.Test + + +class GrpcbInTlsTest { + + val grpcClient = GrpcClient("grpcb.in", 9001) { +// usePlaintext() + } + + @Test + fun testTlsCall() = runTest { + val service = grpcClient.withService() + + val request = HelloRequest { + greeting = "Postman" + } + val result = service.SayHello(request) + + println(result.reply) + } + + +} \ No newline at end of file diff --git a/grpc/grpc-core/src/commonTest/proto/grpcb_in_hello.proto b/grpc/grpc-core/src/commonTest/proto/grpcb_in_hello.proto new file mode 100644 index 000000000..084fa2875 --- /dev/null +++ b/grpc/grpc-core/src/commonTest/proto/grpcb_in_hello.proto @@ -0,0 +1,22 @@ +syntax = "proto2"; + +// grpc://grpcb.in:9000 (without TLS) +// grpc://grpcb.in:9001 (with TLS) + + +package hello; + +service HelloService { + rpc SayHello(HelloRequest) returns (HelloResponse); + rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse); + rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse); + rpc BidiHello(stream HelloRequest) returns (stream HelloResponse); +} + +message HelloRequest { + optional string greeting = 1; +} + +message HelloResponse { + required string reply = 1; +} \ No newline at end of file diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt index 766ffbb2b..d78596642 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt @@ -9,6 +9,7 @@ package kotlinx.rpc.grpc import kotlinx.rpc.grpc.internal.GrpcChannel import kotlinx.rpc.grpc.internal.GrpcChannelCredentials import kotlinx.rpc.grpc.internal.GrpcInsecureChannelCredentials +import kotlinx.rpc.grpc.internal.GrpcSslChannelCredentials import kotlinx.rpc.grpc.internal.NativeManagedChannel import kotlinx.rpc.grpc.internal.internalError @@ -29,17 +30,17 @@ public actual abstract class ManagedChannelBuilder> internal class NativeManagedChannelBuilder( private val target: String, ) : ManagedChannelBuilder() { - private var credentials: GrpcChannelCredentials? = null + private var credentials: Lazy = lazy { GrpcSslChannelCredentials() } override fun usePlaintext(): NativeManagedChannelBuilder { - credentials = GrpcInsecureChannelCredentials() + credentials = lazy { GrpcInsecureChannelCredentials() } return this } fun buildChannel(): NativeManagedChannel { return NativeManagedChannel( target, - credentials = credentials ?: error("No credentials set"), + credentials = credentials.value, ) } diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt index 7d465f4d0..22f9e7e40 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt @@ -11,6 +11,7 @@ import cnames.structs.grpc_channel_credentials import kotlinx.atomicfu.atomic import kotlinx.cinterop.CPointer import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.memScoped import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob @@ -27,13 +28,14 @@ import libkgrpc.grpc_channel_credentials_release import libkgrpc.grpc_channel_destroy import libkgrpc.grpc_insecure_credentials_create import libkgrpc.grpc_slice_unref +import libkgrpc.grpc_ssl_credentials_create_ex import kotlin.coroutines.cancellation.CancellationException import kotlin.experimental.ExperimentalNativeApi import kotlin.native.ref.createCleaner import kotlin.time.Duration /** - * Wrapper for [cnames.structs.grpc_channel_credentials]. + * Wrapper for [grpc_channel_credentials]. */ internal sealed class GrpcChannelCredentials( internal val raw: CPointer, @@ -47,8 +49,28 @@ internal sealed class GrpcChannelCredentials( * Insecure credentials. */ internal class GrpcInsecureChannelCredentials() : - GrpcChannelCredentials(grpc_insecure_credentials_create() ?: error("Failed to create channel credentials")) + GrpcChannelCredentials( + grpc_insecure_credentials_create() ?: error("grpc_insecure_credentials_create() returned null") + ) +/** + * SSL credentials. + */ +internal class GrpcSslChannelCredentials( + raw: CPointer, +) : GrpcChannelCredentials(raw) { + + constructor() : this( + memScoped { + grpc_ssl_credentials_create_ex( + null, + null, + null, + null + ) ?: error("grpc_ssl_credentials_create() returned null") + } + ) +} /** * Native implementation of [ManagedChannel]. diff --git a/protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/ProtobufException.kt b/protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/ProtobufException.kt index 39c27a42f..24ca908a8 100644 --- a/protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/ProtobufException.kt +++ b/protobuf/protobuf-core/src/commonMain/kotlin/kotlinx/rpc/protobuf/internal/ProtobufException.kt @@ -13,7 +13,7 @@ public class ProtobufDecodingException : ProtobufException { public constructor(message: String, cause: Throwable? = null) : super(message, cause) public companion object Companion { - internal fun missingRequiredField(messageName: String, fieldName: String) = + public fun missingRequiredField(messageName: String, fieldName: String): ProtobufDecodingException = ProtobufDecodingException("Message '$messageName' is missing a required field: $fieldName") internal fun negativeSize() = ProtobufDecodingException( diff --git a/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/BoxTestGenerated.java b/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/BoxTestGenerated.java index e29c721ec..d68a29b72 100644 --- a/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/BoxTestGenerated.java +++ b/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/BoxTestGenerated.java @@ -7,51 +7,53 @@ package kotlinx.rpc.codegen.test.runners; import com.intellij.testFramework.TestDataPath; -import org.jetbrains.kotlin.test.util.KtTestUtil; import org.jetbrains.kotlin.test.TargetBackend; import org.jetbrains.kotlin.test.TestMetadata; +import org.jetbrains.kotlin.test.util.KtTestUtil; import org.junit.jupiter.api.Test; import java.io.File; import java.util.regex.Pattern; -/** This class is generated by {@link kotlinx.rpc.codegen.test.GenerateTestsKt}. DO NOT MODIFY MANUALLY */ +/** + * This class is generated by {@link kotlinx.rpc.codegen.test.GenerateTestsKt}. DO NOT MODIFY MANUALLY + */ @SuppressWarnings("all") @TestMetadata("src/testData/box") @TestDataPath("$PROJECT_ROOT") public class BoxTestGenerated extends AbstractBoxTest { - @Test - public void testAllFilesPresentInBox() { - KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("src/testData/box"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); - } - - @Test - @TestMetadata("customParameterTypes.kt") - public void testCustomParameterTypes() { - runTest("src/testData/box/customParameterTypes.kt"); - } - - @Test - @TestMetadata("flowParameter.kt") - public void testFlowParameter() { - runTest("src/testData/box/flowParameter.kt"); - } - - @Test - @TestMetadata("grpc.kt") - public void testGrpc() { - runTest("src/testData/box/grpc.kt"); - } - - @Test - @TestMetadata("multiModule.kt") - public void testMultiModule() { - runTest("src/testData/box/multiModule.kt"); - } - - @Test - @TestMetadata("simple.kt") - public void testSimple() { - runTest("src/testData/box/simple.kt"); - } + @Test + public void testAllFilesPresentInBox() { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("src/testData/box"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @Test + @TestMetadata("customParameterTypes.kt") + public void testCustomParameterTypes() { + runTest("src/testData/box/customParameterTypes.kt"); + } + + @Test + @TestMetadata("flowParameter.kt") + public void testFlowParameter() { + runTest("src/testData/box/flowParameter.kt"); + } + + @Test + @TestMetadata("grpc.kt") + public void testGrpc() { + runTest("src/testData/box/grpc.kt"); + } + + @Test + @TestMetadata("multiModule.kt") + public void testMultiModule() { + runTest("src/testData/box/multiModule.kt"); + } + + @Test + @TestMetadata("simple.kt") + public void testSimple() { + runTest("src/testData/box/simple.kt"); + } } diff --git a/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/DiagnosticTestGenerated.java b/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/DiagnosticTestGenerated.java index 3bc448f56..724b13026 100644 --- a/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/DiagnosticTestGenerated.java +++ b/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/DiagnosticTestGenerated.java @@ -7,57 +7,59 @@ package kotlinx.rpc.codegen.test.runners; import com.intellij.testFramework.TestDataPath; -import org.jetbrains.kotlin.test.util.KtTestUtil; import org.jetbrains.kotlin.test.TargetBackend; import org.jetbrains.kotlin.test.TestMetadata; +import org.jetbrains.kotlin.test.util.KtTestUtil; import org.junit.jupiter.api.Test; import java.io.File; import java.util.regex.Pattern; -/** This class is generated by {@link kotlinx.rpc.codegen.test.GenerateTestsKt}. DO NOT MODIFY MANUALLY */ +/** + * This class is generated by {@link kotlinx.rpc.codegen.test.GenerateTestsKt}. DO NOT MODIFY MANUALLY + */ @SuppressWarnings("all") @TestMetadata("src/testData/diagnostics") @TestDataPath("$PROJECT_ROOT") public class DiagnosticTestGenerated extends AbstractDiagnosticTest { - @Test - public void testAllFilesPresentInDiagnostics() { - KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("src/testData/diagnostics"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); - } - - @Test - @TestMetadata("checkedAnnotation.kt") - public void testCheckedAnnotation() { - runTest("src/testData/diagnostics/checkedAnnotation.kt"); - } - - @Test - @TestMetadata("grpc.kt") - public void testGrpc() { - runTest("src/testData/diagnostics/grpc.kt"); - } - - @Test - @TestMetadata("rpcChecked.kt") - public void testRpcChecked() { - runTest("src/testData/diagnostics/rpcChecked.kt"); - } - - @Test - @TestMetadata("rpcService.kt") - public void testRpcService() { - runTest("src/testData/diagnostics/rpcService.kt"); - } - - @Test - @TestMetadata("strictMode.kt") - public void testStrictMode() { - runTest("src/testData/diagnostics/strictMode.kt"); - } - - @Test - @TestMetadata("withCodec.kt") - public void testWithCodec() { - runTest("src/testData/diagnostics/withCodec.kt"); - } + @Test + public void testAllFilesPresentInDiagnostics() { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("src/testData/diagnostics"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @Test + @TestMetadata("checkedAnnotation.kt") + public void testCheckedAnnotation() { + runTest("src/testData/diagnostics/checkedAnnotation.kt"); + } + + @Test + @TestMetadata("grpc.kt") + public void testGrpc() { + runTest("src/testData/diagnostics/grpc.kt"); + } + + @Test + @TestMetadata("rpcChecked.kt") + public void testRpcChecked() { + runTest("src/testData/diagnostics/rpcChecked.kt"); + } + + @Test + @TestMetadata("rpcService.kt") + public void testRpcService() { + runTest("src/testData/diagnostics/rpcService.kt"); + } + + @Test + @TestMetadata("strictMode.kt") + public void testStrictMode() { + runTest("src/testData/diagnostics/strictMode.kt"); + } + + @Test + @TestMetadata("withCodec.kt") + public void testWithCodec() { + runTest("src/testData/diagnostics/withCodec.kt"); + } } From 9318ec7eb42fbdb37bf639ca875d48961077e389 Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Mon, 8 Sep 2025 09:51:17 +0200 Subject: [PATCH 02/12] grpc-native: Refactor channel credentials Signed-off-by: Johannes Zottele --- .../kotlin/kotlinx/rpc/grpc/credentials.kt | 17 +++++++ .../rpc/grpc/test/proto/GrpcbInTlsTest.kt | 3 ++ .../kotlinx/rpc/grpc/ManagedChannel.jvm.kt | 4 +- .../kotlinx/rpc/grpc/credentials.jvm.kt | 19 ++++++++ .../kotlinx/rpc/grpc/ManagedChannel.native.kt | 7 +-- .../kotlinx/rpc/grpc/credentials.native.kt | 48 +++++++++++++++++++ .../rpc/grpc/internal/NativeManagedChannel.kt | 45 +---------------- .../kotlin/kotlinx/rpc/grpc/internal/utils.kt | 2 +- 8 files changed, 95 insertions(+), 50 deletions(-) create mode 100644 grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt create mode 100644 grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt create mode 100644 grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt new file mode 100644 index 000000000..8b5607377 --- /dev/null +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +public expect abstract class ChannelCredentials + +public expect class InsecureChannelCredentials : ChannelCredentials + +public expect class TlsChannelCredentials : ChannelCredentials + + +public expect fun InsecureChannelCredentials(): ChannelCredentials +public expect fun TlsChannelCredentials(): ChannelCredentials \ No newline at end of file diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt index af76d6d58..9af65669d 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt @@ -9,6 +9,7 @@ import hello.HelloService import hello.invoke import kotlinx.coroutines.test.runTest import kotlinx.rpc.grpc.GrpcClient +import kotlinx.rpc.grpc.TlsChannelCredentials import kotlinx.rpc.withService import kotlin.test.Test @@ -23,6 +24,8 @@ class GrpcbInTlsTest { fun testTlsCall() = runTest { val service = grpcClient.withService() + TlsChannelCredentials.create() + val request = HelloRequest { greeting = "Postman" } diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.jvm.kt index 559007568..988d60563 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.jvm.kt @@ -28,7 +28,9 @@ internal actual fun ManagedChannelBuilder(hostname: String, port: Int): ManagedC return io.grpc.ManagedChannelBuilder.forAddress(hostname, port) } -internal actual fun ManagedChannelBuilder(target: String): ManagedChannelBuilder<*> { +internal actual fun ManagedChannelBuilder( + target: String, +): ManagedChannelBuilder<*> { return io.grpc.ManagedChannelBuilder.forTarget(target) } diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt new file mode 100644 index 000000000..a3f6016c8 --- /dev/null +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc + +public actual typealias ChannelCredentials = io.grpc.ChannelCredentials + +public actual typealias TlsChannelCredentials = io.grpc.TlsChannelCredentials + +public actual typealias InsecureChannelCredentials = io.grpc.InsecureChannelCredentials + +public actual fun InsecureChannelCredentials.create(): ChannelCredentials { + return io.grpc.InsecureChannelCredentials.create() +} + +public actual fun TlsChannelCredentials.create(): ChannelCredentials { + return io.grpc.TlsChannelCredentials.create() +} \ No newline at end of file diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt index d78596642..849e45762 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt @@ -7,9 +7,6 @@ package kotlinx.rpc.grpc import kotlinx.rpc.grpc.internal.GrpcChannel -import kotlinx.rpc.grpc.internal.GrpcChannelCredentials -import kotlinx.rpc.grpc.internal.GrpcInsecureChannelCredentials -import kotlinx.rpc.grpc.internal.GrpcSslChannelCredentials import kotlinx.rpc.grpc.internal.NativeManagedChannel import kotlinx.rpc.grpc.internal.internalError @@ -30,10 +27,10 @@ public actual abstract class ManagedChannelBuilder> internal class NativeManagedChannelBuilder( private val target: String, ) : ManagedChannelBuilder() { - private var credentials: Lazy = lazy { GrpcSslChannelCredentials() } + private var credentials: Lazy = lazy { TlsChannelCredentials.create() } override fun usePlaintext(): NativeManagedChannelBuilder { - credentials = lazy { GrpcInsecureChannelCredentials() } + credentials = lazy { InsecureChannelCredentials.create() } return this } diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt new file mode 100644 index 000000000..b0717a98e --- /dev/null +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class) + +package kotlinx.rpc.grpc + +import cnames.structs.grpc_channel_credentials +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.ExperimentalForeignApi +import libkgrpc.grpc_channel_credentials_release +import libkgrpc.grpc_insecure_credentials_create +import libkgrpc.grpc_tls_credentials_create +import libkgrpc.grpc_tls_credentials_options_create +import kotlin.experimental.ExperimentalNativeApi +import kotlin.native.ref.createCleaner + +public actual abstract class ChannelCredentials internal constructor( + internal val raw: CPointer, +) { + internal val rawCleaner = createCleaner(raw) { + grpc_channel_credentials_release(it) + } +} + +public actual class InsecureChannelCredentials internal constructor( + raw: CPointer, +) : ChannelCredentials(raw) + +public actual class TlsChannelCredentials internal constructor( + raw: CPointer, +) : ChannelCredentials(raw) + + +public actual fun InsecureChannelCredentials.Companion.create(): ChannelCredentials { + return InsecureChannelCredentials( + grpc_insecure_credentials_create() ?: error("grpc_insecure_credentials_create() returned null") + ) +} + +public actual fun TlsChannelCredentials.Companion.create(): ChannelCredentials { + val raw = grpc_tls_credentials_options_create()?.let { + grpc_tls_credentials_create(it) + } ?: error("Failed to create TLS credentials") + + return TlsChannelCredentials(raw) +} \ No newline at end of file diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt index 22f9e7e40..d726aae43 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt @@ -7,16 +7,15 @@ package kotlinx.rpc.grpc.internal import cnames.structs.grpc_channel -import cnames.structs.grpc_channel_credentials import kotlinx.atomicfu.atomic import kotlinx.cinterop.CPointer import kotlinx.cinterop.ExperimentalForeignApi -import kotlinx.cinterop.memScoped import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.rpc.grpc.ChannelCredentials import kotlinx.rpc.grpc.ManagedChannel import kotlinx.rpc.grpc.ManagedChannelPlatform import libkgrpc.GPR_CLOCK_REALTIME @@ -24,53 +23,13 @@ import libkgrpc.GRPC_PROPAGATE_DEFAULTS import libkgrpc.gpr_inf_future import libkgrpc.grpc_channel_create import libkgrpc.grpc_channel_create_call -import libkgrpc.grpc_channel_credentials_release import libkgrpc.grpc_channel_destroy -import libkgrpc.grpc_insecure_credentials_create import libkgrpc.grpc_slice_unref -import libkgrpc.grpc_ssl_credentials_create_ex import kotlin.coroutines.cancellation.CancellationException import kotlin.experimental.ExperimentalNativeApi import kotlin.native.ref.createCleaner import kotlin.time.Duration -/** - * Wrapper for [grpc_channel_credentials]. - */ -internal sealed class GrpcChannelCredentials( - internal val raw: CPointer, -) { - val rawCleaner = createCleaner(raw) { - grpc_channel_credentials_release(it) - } -} - -/** - * Insecure credentials. - */ -internal class GrpcInsecureChannelCredentials() : - GrpcChannelCredentials( - grpc_insecure_credentials_create() ?: error("grpc_insecure_credentials_create() returned null") - ) - -/** - * SSL credentials. - */ -internal class GrpcSslChannelCredentials( - raw: CPointer, -) : GrpcChannelCredentials(raw) { - - constructor() : this( - memScoped { - grpc_ssl_credentials_create_ex( - null, - null, - null, - null - ) ?: error("grpc_ssl_credentials_create() returned null") - } - ) -} /** * Native implementation of [ManagedChannel]. @@ -81,7 +40,7 @@ internal class GrpcSslChannelCredentials( internal class NativeManagedChannel( target: String, // we must store them, otherwise the credentials are getting released - credentials: GrpcChannelCredentials, + credentials: ChannelCredentials, ) : ManagedChannel, ManagedChannelPlatform() { // a reference to make sure the grpc_init() was called. (it is released after shutdown) diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/utils.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/utils.kt index 83b807652..28f7d8136 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/utils.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/utils.kt @@ -43,7 +43,7 @@ import libkgrpc.grpc_slice_unref import libkgrpc.grpc_status_code import platform.posix.memcpy -internal fun internalError(message: String) { +internal fun internalError(message: String): Nothing { error("Unexpected internal error: $message. Please, report the issue here: https://github.com/Kotlin/kotlinx-rpc/issues/new?template=bug_report.md") } From 0b3124f57b38566f5d40bf5f8ea2d8e32097fd0a Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Mon, 8 Sep 2025 16:50:22 +0200 Subject: [PATCH 03/12] grpc-native: Working custom TLS credentials Signed-off-by: Johannes Zottele --- .../kotlin/kotlinx/rpc/grpc/GrpcClient.kt | 15 +- .../kotlin/kotlinx/rpc/grpc/GrpcServer.kt | 8 +- .../kotlin/kotlinx/rpc/grpc/ManagedChannel.kt | 12 +- .../kotlin/kotlinx/rpc/grpc/Server.kt | 2 +- .../kotlin/kotlinx/rpc/grpc/credentials.kt | 20 ++- .../rpc/grpc/test/proto/GrpcbInTlsTest.kt | 150 +++++++++++++++++- .../kotlinx/rpc/grpc/ManagedChannel.jvm.kt | 9 +- .../kotlin/kotlinx/rpc/grpc/Server.jvm.kt | 3 +- .../kotlinx/rpc/grpc/credentials.jvm.kt | 53 ++++++- .../kotlinx/rpc/grpc/ManagedChannel.native.kt | 18 ++- .../kotlin/kotlinx/rpc/grpc/Server.native.kt | 12 +- .../kotlinx/rpc/grpc/credentials.native.kt | 125 ++++++++++++++- .../kotlinx/rpc/grpc/internal/NativeServer.kt | 22 +-- 13 files changed, 386 insertions(+), 63 deletions(-) diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcClient.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcClient.kt index 32b891486..7c184e0f8 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcClient.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcClient.kt @@ -13,7 +13,14 @@ import kotlinx.rpc.grpc.codec.ThrowingMessageCodecResolver import kotlinx.rpc.grpc.codec.plus import kotlinx.rpc.grpc.descriptor.GrpcServiceDelegate import kotlinx.rpc.grpc.descriptor.GrpcServiceDescriptor -import kotlinx.rpc.grpc.internal.* +import kotlinx.rpc.grpc.internal.GrpcDefaultCallOptions +import kotlinx.rpc.grpc.internal.MethodDescriptor +import kotlinx.rpc.grpc.internal.MethodType +import kotlinx.rpc.grpc.internal.bidirectionalStreamingRpc +import kotlinx.rpc.grpc.internal.clientStreamingRpc +import kotlinx.rpc.grpc.internal.serverStreamingRpc +import kotlinx.rpc.grpc.internal.type +import kotlinx.rpc.grpc.internal.unaryRpc import kotlinx.rpc.internal.utils.map.RpcInternalConcurrentHashMap import kotlin.time.Duration @@ -124,10 +131,11 @@ public class GrpcClient internal constructor( public fun GrpcClient( hostname: String, port: Int, + credentials: ChannelCredentials? = null, messageCodecResolver: MessageCodecResolver = EmptyMessageCodecResolver, configure: ManagedChannelBuilder<*>.() -> Unit = {}, ): GrpcClient { - val channel = ManagedChannelBuilder(hostname, port).apply(configure).buildChannel() + val channel = ManagedChannelBuilder(hostname, port, credentials).apply(configure).buildChannel() return GrpcClient(channel, messageCodecResolver) } @@ -136,9 +144,10 @@ public fun GrpcClient( */ public fun GrpcClient( target: String, + credentials: ChannelCredentials? = null, messageCodecResolver: MessageCodecResolver = EmptyMessageCodecResolver, configure: ManagedChannelBuilder<*>.() -> Unit = {}, ): GrpcClient { - val channel = ManagedChannelBuilder(target).apply(configure).buildChannel() + val channel = ManagedChannelBuilder(target, credentials).apply(configure).buildChannel() return GrpcClient(channel, messageCodecResolver) } diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcServer.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcServer.kt index eb1e3abb7..1a73e9c74 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcServer.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcServer.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.job import kotlinx.rpc.RpcServer import kotlinx.rpc.descriptor.RpcCallable import kotlinx.rpc.descriptor.flowInvokator @@ -49,6 +48,7 @@ private typealias ResponseServer = Any */ public class GrpcServer internal constructor( override val port: Int = 8080, + credentials: ServerCredentials? = null, messageCodecResolver: MessageCodecResolver = EmptyMessageCodecResolver, parentContext: CoroutineContext = EmptyCoroutineContext, configure: ServerBuilder<*>.() -> Unit, @@ -61,7 +61,7 @@ public class GrpcServer internal constructor( private var isBuilt = false private lateinit var internalServer: Server - private val serverBuilder: ServerBuilder<*> = ServerBuilder(port).apply(configure) + private val serverBuilder: ServerBuilder<*> = ServerBuilder(port, credentials).apply(configure) private val registry: MutableHandlerRegistry by lazy { MutableHandlerRegistry().apply { serverBuilder.fallbackHandlerRegistry(this) } } @@ -192,10 +192,12 @@ public class GrpcServer internal constructor( */ public fun GrpcServer( port: Int, + credentials: ServerCredentials? = null, messageCodecResolver: MessageCodecResolver = EmptyMessageCodecResolver, parentContext: CoroutineContext = EmptyCoroutineContext, configure: ServerBuilder<*>.() -> Unit = {}, builder: RpcServer.() -> Unit = {}, ): GrpcServer { - return GrpcServer(port, messageCodecResolver, parentContext, configure).apply(builder).apply { build() } + return GrpcServer(port, credentials, messageCodecResolver, parentContext, configure).apply(builder) + .apply { build() } } diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt index 64f6ee4aa..7722110f4 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt @@ -71,7 +71,15 @@ public expect abstract class ManagedChannelBuilder> public fun usePlaintext(): T } -internal expect fun ManagedChannelBuilder(hostname: String, port: Int): ManagedChannelBuilder<*> -internal expect fun ManagedChannelBuilder(target: String): ManagedChannelBuilder<*> +internal expect fun ManagedChannelBuilder( + hostname: String, + port: Int, + credentials: ChannelCredentials? = null, +): ManagedChannelBuilder<*> + +internal expect fun ManagedChannelBuilder( + target: String, + credentials: ChannelCredentials? = null, +): ManagedChannelBuilder<*> internal expect fun ManagedChannelBuilder<*>.buildChannel(): ManagedChannel diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/Server.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/Server.kt index c35b6dfb9..90e4b317b 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/Server.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/Server.kt @@ -31,7 +31,7 @@ public expect abstract class ServerBuilder> { public abstract fun fallbackHandlerRegistry(registry: HandlerRegistry?): T } -internal expect fun ServerBuilder(port: Int): ServerBuilder<*> +internal expect fun ServerBuilder(port: Int, credentials: ServerCredentials? = null): ServerBuilder<*> /** * Server for listening for and dispatching incoming calls. diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt index 8b5607377..62f941239 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt @@ -7,11 +7,27 @@ package kotlinx.rpc.grpc/* */ public expect abstract class ChannelCredentials +public expect abstract class ServerCredentials public expect class InsecureChannelCredentials : ChannelCredentials +public expect class InsecureServerCredentials : ServerCredentials public expect class TlsChannelCredentials : ChannelCredentials +public expect class TlsServerCredentials : ServerCredentials +public expect fun TlsChannelCredentials(): ChannelCredentials +public expect fun TlsServerCredentials(certChain: String, privateKey: String): ServerCredentials -public expect fun InsecureChannelCredentials(): ChannelCredentials -public expect fun TlsChannelCredentials(): ChannelCredentials \ No newline at end of file +public interface TlsChannelCredentialsBuilder { + public fun trustManager(rootCertsPem: String): TlsChannelCredentialsBuilder + public fun build(): ChannelCredentials +} + +public interface TlsServerCredentialsBuilder { + public fun keyManager(certChainPem: String, privateKeyPem: String): TlsServerCredentialsBuilder + public fun build(): ServerCredentials +} + + +public expect fun TlsChannelCredentialsBuilder(): TlsChannelCredentialsBuilder +public expect fun TlsServerCredentialsBuilder(): TlsServerCredentialsBuilder diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt index 9af65669d..9dc7e10b2 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt @@ -9,23 +9,26 @@ import hello.HelloService import hello.invoke import kotlinx.coroutines.test.runTest import kotlinx.rpc.grpc.GrpcClient -import kotlinx.rpc.grpc.TlsChannelCredentials +import kotlinx.rpc.grpc.GrpcServer +import kotlinx.rpc.grpc.TlsChannelCredentialsBuilder +import kotlinx.rpc.grpc.TlsServerCredentialsBuilder +import kotlinx.rpc.grpc.test.EchoRequest +import kotlinx.rpc.grpc.test.EchoService +import kotlinx.rpc.grpc.test.EchoServiceImpl +import kotlinx.rpc.grpc.test.invoke +import kotlinx.rpc.registerService import kotlinx.rpc.withService import kotlin.test.Test +private const val PORT = 50051 class GrpcbInTlsTest { - val grpcClient = GrpcClient("grpcb.in", 9001) { -// usePlaintext() - } @Test fun testTlsCall() = runTest { + val grpcClient = GrpcClient("grpcb.in", 9001) val service = grpcClient.withService() - - TlsChannelCredentials.create() - val request = HelloRequest { greeting = "Postman" } @@ -35,4 +38,137 @@ class GrpcbInTlsTest { } + @Test + fun testLocalTls() = runTest { + val serverTls = TlsServerCredentialsBuilder() + .keyManager(SERVER_CERT_PEM, SERVER_KEY_PEM) + .build() + + val grpcServer = GrpcServer( + PORT, + credentials = serverTls, + builder = { + registerService { EchoServiceImpl() } + }) + grpcServer.start() + + val clientTls = TlsChannelCredentialsBuilder() + .trustManager(SERVER_CERT_PEM) + .build() + + val grpcClient = GrpcClient( + "localhost", PORT, + credentials = clientTls, + ) {} + + val service = grpcClient.withService() + val request = EchoRequest { + message = "Postman" + } + + try { + service.UnaryEcho(request) + } catch (t: Throwable) { + println("[DEBUG_LOG] TLS test failed: ${t::class.simpleName}: ${t.message}") + t.printStackTrace() + throw t + } finally { + grpcServer.shutdown() + grpcServer.awaitTermination() + grpcClient.shutdown() + grpcClient.awaitTermination() + } + } + + + private val SERVER_CERT_PEM = """ + -----BEGIN CERTIFICATE----- + MIIFfTCCA2WgAwIBAgIUOfRdPPo6IDmMJKBumLdSe59ldxEwDQYJKoZIhvcNAQEL + BQAwTjELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5 + MQwwCgYDVQQKDANEZXYxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yNTA5MDgxMzIx + MTNaFw0yNjA5MDgxMzIxMTNaME4xCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVTdGF0 + ZTENMAsGA1UEBwwEQ2l0eTEMMAoGA1UECgwDRGV2MRIwEAYDVQQDDAlsb2NhbGhv + c3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDCFj6DIg8qZm26Qque + 6kyVADZgJrBgL19HNgLmDpxixMlnSIOjWMb2IpAcT7Ln6PytEevoyPFubO0uXcRT + jyf3xIaXWjj299w0TsvGYF6rZhUApKIYAKiamIjZLid0+VaKJzGzoIDFAU0W1gsp + x9sz0YkP0OHuMcOjnkxIbqgL/lZOg1JDVf6hJrAi6CE0Iar03/R6cj+GNsf6W3RF + MSUh9MWzDhgVY5DcVLf44V9s8hOiHu8p46aVirHti72pGbfYFNfD23Xmv14QX9pY + qtuaxDWcMk44K48kf+7ztC3jDbzJkloy0oFEWSmeUwVNYltkrG3Z8753y6ZcHBBc + dw+lq3Fd0x3/9gSKs3w2zjzetym6JrKC5wzItuAqe5rJibAjB3aPhjtKKHmVXzSC + mjuRWtxk/yL7V8yPA6FelwBLgVXaZAUL/IsabkHOerJsS7tZbM4/1baZIbfiVhIJ + wwXHri5zQPTiX/tWZGwjic0ZlxVhF0D2qnILwhXdQyze3K1aoV3T8+i2QIpiLmWQ + jCmqzoM55r/5E2KO/dDbfB8Qtw/rd08wOUcl9+bphetw1vyka4SGSZBwpGy6AUvC + RAGA+5Hk0eqHW6Zqqr7MLzeLd1OlixVLhKgSGRDp7kdRlhPeY9kGrEGU/hXrfVKI + YxbdL5JvK838jfNgKeurWYL8OQIDAQABo1MwUTAdBgNVHQ4EFgQUtOl1Hhw8PeHN + hXC5h4Q9mD0oFdwwHwYDVR0jBBgwFoAUtOl1Hhw8PeHNhXC5h4Q9mD0oFdwwDwYD + VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAp4jaSnXHxTBzSopi8tOa + utxTV45Cz7dhUCfvgPPVsYURgg4MZMLJ908awcU0Wge0DbnwGQx+BQnsh8YCbroG + LVPzbhl4xUmroDfr4XX8lVWmfhBOSC8K1ZR1s6udnHYkfzzZ4w5oBBfVJGsbHrNu + KdYsHvSMpnneRBkMHwjHggcWpJX/Fg+c1lg1WP938qoCgPFZAgVEry5OdgMK0bmj + OUMrM1WKYq/CMi6t0+iwX7V+sRe8+gD+clIJXdg2j9bN+kHXw5FnVBLvvdzED1tL + FG5M+Uq10bomxpLbsUYX6+7ZIkCLvkhMovSYI/M/TcNzy8i41CxS+UibtObJ7XRZ + 512650ySuBKRSwfk8MCp7LOL+nosxcLHtqMmZjlQlziJFDHlxkDlNxsjCSnK0Fxk + djAbq16F9t/C2HIVHK+zKhPGqVVfoeM9ksmngAIOUK05pz8665gjo61748WW+rqf + f/WKiQtu28AjskPNY3CS2vnl3jbSqMrFsi1hPWV2dQRgzl5xI2qR+03sQHaaQUTc + d/whE7t85PqmsVED2vmD9dGy6vTnd3nH8j8DEVaBO/y9bJbpyLZuivCuKxZX+GYg + Mk3Ms7t3rsdPbpiiHK6lcfntwVxThBBSbSTqDvUb4GYryzdQ5B5ib4nEc/aI/msU + ubFotuU6gIPvra9MI/NUSpA= + -----END CERTIFICATE----- + """.trimIndent() + + private val SERVER_KEY_PEM = """ + -----BEGIN PRIVATE KEY----- + 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 + paLEjX+8kznUisDokSzJ7iHoZgmN + -----END PRIVATE KEY----- + """.trimIndent() + } \ No newline at end of file diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.jvm.kt index 988d60563..350b5e550 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.jvm.kt @@ -24,13 +24,20 @@ internal actual fun ManagedChannelBuilder<*>.buildChannel(): ManagedChannel { return build().toKotlin() } -internal actual fun ManagedChannelBuilder(hostname: String, port: Int): ManagedChannelBuilder<*> { +internal actual fun ManagedChannelBuilder( + hostname: String, + port: Int, + credentials: ChannelCredentials?, +): ManagedChannelBuilder<*> { + if (credentials != null) return io.grpc.Grpc.newChannelBuilderForAddress(hostname, port, credentials) return io.grpc.ManagedChannelBuilder.forAddress(hostname, port) } internal actual fun ManagedChannelBuilder( target: String, + credentials: ChannelCredentials?, ): ManagedChannelBuilder<*> { + if (credentials != null) return io.grpc.Grpc.newChannelBuilder(target, credentials) return io.grpc.ManagedChannelBuilder.forTarget(target) } diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/Server.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/Server.jvm.kt index cf6fce68a..ba84ffecd 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/Server.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/Server.jvm.kt @@ -14,7 +14,8 @@ import kotlin.time.Duration */ public actual typealias ServerBuilder = io.grpc.ServerBuilder -internal actual fun ServerBuilder(port: Int): ServerBuilder<*> { +internal actual fun ServerBuilder(port: Int, credentials: ServerCredentials?): ServerBuilder<*> { + if (credentials != null) return io.grpc.Grpc.newServerBuilderForPort(port, credentials) return io.grpc.ServerBuilder.forPort(port) } diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt index a3f6016c8..5bae83496 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt @@ -6,14 +6,55 @@ package kotlinx.rpc.grpc public actual typealias ChannelCredentials = io.grpc.ChannelCredentials -public actual typealias TlsChannelCredentials = io.grpc.TlsChannelCredentials +public actual typealias ServerCredentials = io.grpc.ServerCredentials + +// we need a wrapper for InsecureChannelCredentials as our constructor would conflict with the private +// java constructor. public actual typealias InsecureChannelCredentials = io.grpc.InsecureChannelCredentials +public actual typealias InsecureServerCredentials = io.grpc.InsecureServerCredentials + +public actual typealias TlsServerCredentials = io.grpc.TlsServerCredentials +public actual typealias TlsChannelCredentials = io.grpc.TlsChannelCredentials -public actual fun InsecureChannelCredentials.create(): ChannelCredentials { - return io.grpc.InsecureChannelCredentials.create() +public actual fun TlsChannelCredentials(): ChannelCredentials { + return TlsChannelCredentialsBuilder().build() } -public actual fun TlsChannelCredentials.create(): ChannelCredentials { - return io.grpc.TlsChannelCredentials.create() -} \ No newline at end of file +public actual fun TlsServerCredentials( + certChain: String, + privateKey: String, +): ServerCredentials { + return TlsServerCredentialsBuilder().keyManager(certChain, privateKey).build() +} + +public actual fun TlsServerCredentialsBuilder(): TlsServerCredentialsBuilder = object : TlsServerCredentialsBuilder { + private var sb = TlsServerCredentials.newBuilder() + + override fun keyManager( + certChainPem: String, + privateKeyPem: String, + ): TlsServerCredentialsBuilder { + sb.keyManager(certChainPem.byteInputStream(), privateKeyPem.byteInputStream()) + return this + } + + override fun build(): ServerCredentials { + return sb.build() + } +} + +public actual fun TlsChannelCredentialsBuilder(): TlsChannelCredentialsBuilder = object : TlsChannelCredentialsBuilder { + private var cb = TlsChannelCredentials.newBuilder() + + override fun trustManager(rootCertsPem: String): TlsChannelCredentialsBuilder { + cb.trustManager(rootCertsPem.byteInputStream()) + return this + } + + override fun build(): ChannelCredentials { + return cb.build() + } +} + + diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt index 849e45762..9e6bc7b46 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt @@ -26,11 +26,11 @@ public actual abstract class ManagedChannelBuilder> internal class NativeManagedChannelBuilder( private val target: String, + private var credentials: Lazy, ) : ManagedChannelBuilder() { - private var credentials: Lazy = lazy { TlsChannelCredentials.create() } override fun usePlaintext(): NativeManagedChannelBuilder { - credentials = lazy { InsecureChannelCredentials.create() } + credentials = lazy { InsecureChannelCredentials() } return this } @@ -48,12 +48,18 @@ internal actual fun ManagedChannelBuilder<*>.buildChannel(): ManagedChannel { return buildChannel() } -internal actual fun ManagedChannelBuilder(hostname: String, port: Int): ManagedChannelBuilder<*> { - return NativeManagedChannelBuilder(target = "$hostname:$port") +internal actual fun ManagedChannelBuilder( + hostname: String, + port: Int, + credentials: ChannelCredentials?, +): ManagedChannelBuilder<*> { + val credentials = if (credentials == null) lazy { TlsChannelCredentials() } else lazy { credentials } + return NativeManagedChannelBuilder(target = "$hostname:$port", credentials) } -internal actual fun ManagedChannelBuilder(target: String): ManagedChannelBuilder<*> { - return NativeManagedChannelBuilder(target) +internal actual fun ManagedChannelBuilder(target: String, credentials: ChannelCredentials?): ManagedChannelBuilder<*> { + val credentials = if (credentials == null) lazy { TlsChannelCredentials() } else lazy { credentials } + return NativeManagedChannelBuilder(target, credentials) } diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/Server.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/Server.native.kt index 5e8f53cba..476214555 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/Server.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/Server.native.kt @@ -4,7 +4,7 @@ package kotlinx.rpc.grpc -import kotlinx.rpc.grpc.internal.GrpcInsecureServerCredentials + import kotlinx.rpc.grpc.internal.NativeServer import kotlinx.rpc.grpc.internal.ServerMethodDefinition @@ -21,10 +21,8 @@ public actual abstract class ServerBuilder> { private class NativeServerBuilder( val port: Int, + val credentials: ServerCredentials, ) : ServerBuilder() { - - // TODO: Add actual credentials - private val credentials = GrpcInsecureServerCredentials() private val services = mutableListOf() private var fallbackRegistry: HandlerRegistry = DefaultFallbackRegistry @@ -44,8 +42,8 @@ private class NativeServerBuilder( } -internal actual fun ServerBuilder(port: Int): ServerBuilder<*> { - return NativeServerBuilder(port) +internal actual fun ServerBuilder(port: Int, credentials: ServerCredentials?): ServerBuilder<*> { + return NativeServerBuilder(port, credentials ?: InsecureServerCredentials()) } internal actual fun Server(builder: ServerBuilder<*>): Server { @@ -64,4 +62,4 @@ private object DefaultFallbackRegistry : HandlerRegistry() { return null } -} +} \ No newline at end of file diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt index b0717a98e..1903270f6 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt @@ -7,42 +7,159 @@ package kotlinx.rpc.grpc import cnames.structs.grpc_channel_credentials +import cnames.structs.grpc_server_credentials +import cnames.structs.grpc_tls_credentials_options import kotlinx.cinterop.CPointer import kotlinx.cinterop.ExperimentalForeignApi import libkgrpc.grpc_channel_credentials_release import libkgrpc.grpc_insecure_credentials_create +import libkgrpc.grpc_insecure_server_credentials_create +import libkgrpc.grpc_server_credentials_release +import libkgrpc.grpc_tls_certificate_provider_release +import libkgrpc.grpc_tls_certificate_provider_static_data_create import libkgrpc.grpc_tls_credentials_create import libkgrpc.grpc_tls_credentials_options_create +import libkgrpc.grpc_tls_credentials_options_destroy +import libkgrpc.grpc_tls_credentials_options_set_certificate_provider +import libkgrpc.grpc_tls_credentials_options_watch_identity_key_cert_pairs +import libkgrpc.grpc_tls_credentials_options_watch_root_certs +import libkgrpc.grpc_tls_identity_pairs_add_pair +import libkgrpc.grpc_tls_identity_pairs_create +import libkgrpc.grpc_tls_server_credentials_create import kotlin.experimental.ExperimentalNativeApi import kotlin.native.ref.createCleaner public actual abstract class ChannelCredentials internal constructor( internal val raw: CPointer, ) { + @Suppress("unused") internal val rawCleaner = createCleaner(raw) { grpc_channel_credentials_release(it) } } +public actual abstract class ServerCredentials internal constructor( + internal val raw: CPointer, +) { + @Suppress("unused") + internal val rawCleaner = createCleaner(raw) { + grpc_server_credentials_release(it) + } +} + public actual class InsecureChannelCredentials internal constructor( raw: CPointer, ) : ChannelCredentials(raw) +public actual class InsecureServerCredentials internal constructor( + raw: CPointer, +) : ServerCredentials(raw) + public actual class TlsChannelCredentials internal constructor( raw: CPointer, ) : ChannelCredentials(raw) +public actual class TlsServerCredentials( + raw: CPointer, +) : ServerCredentials(raw) + -public actual fun InsecureChannelCredentials.Companion.create(): ChannelCredentials { +public fun InsecureChannelCredentials(): ChannelCredentials { return InsecureChannelCredentials( grpc_insecure_credentials_create() ?: error("grpc_insecure_credentials_create() returned null") ) } -public actual fun TlsChannelCredentials.Companion.create(): ChannelCredentials { +public fun InsecureServerCredentials(): ServerCredentials { + return InsecureServerCredentials( + grpc_insecure_server_credentials_create() ?: error("grpc_insecure_server_credentials_create() returned null") + ) +} + +public actual fun TlsChannelCredentials(): ChannelCredentials { val raw = grpc_tls_credentials_options_create()?.let { grpc_tls_credentials_create(it) } ?: error("Failed to create TLS credentials") - return TlsChannelCredentials(raw) -} \ No newline at end of file +} + +public actual fun TlsServerCredentials( + certChain: String, + privateKey: String, +): ServerCredentials { + return TlsServerCredentialsBuilder().keyManager(certChain, privateKey).build() +} + +public actual fun TlsChannelCredentialsBuilder(): TlsChannelCredentialsBuilder = object : TlsChannelCredentialsBuilder { + var optionsBuilder = TlsCredentialsOptionsBuilder() + + override fun trustManager(rootCertsPem: String): TlsChannelCredentialsBuilder { + optionsBuilder.trustManager(rootCertsPem) + return this + } + + override fun build(): ChannelCredentials { + val opts = optionsBuilder.build() + val creds = grpc_tls_credentials_create(opts) + ?: run { + grpc_tls_credentials_options_destroy(opts); + error("TLS channel credential creation failed") + } + return TlsChannelCredentials(creds) + } +} + +public actual fun TlsServerCredentialsBuilder(): TlsServerCredentialsBuilder = object : TlsServerCredentialsBuilder { + var optionsBuilder = TlsCredentialsOptionsBuilder() + + override fun keyManager(certChainPem: String, privateKeyPem: String): TlsServerCredentialsBuilder { + optionsBuilder.keyManager(certChainPem, privateKeyPem) + return this + } + + override fun build(): TlsServerCredentials { + val opts = optionsBuilder.build() + val creds = grpc_tls_server_credentials_create(opts) + ?: run { + grpc_tls_credentials_options_destroy(opts); + error("TLS server credential creation failed") + } + return TlsServerCredentials(creds) + } +} + + +private class TlsCredentialsOptionsBuilder { + private var roots: String? = null + private var cert: String? = null + private var key: String? = null + + fun trustManager(rootCertsPem: String) = apply { roots = rootCertsPem } + fun keyManager(certChainPem: String, privateKeyPem: String) = apply { + cert = certChainPem; key = privateKeyPem + } + + fun build(): CPointer { + val opts = grpc_tls_credentials_options_create() ?: error("alloc opts failed") + + val pairs = if (cert != null && key != null) { + val p = grpc_tls_identity_pairs_create() ?: error("pairs alloc failed") + grpc_tls_identity_pairs_add_pair(p, key, cert); + p + } else null + + if (roots != null || pairs != null) { + val provider = grpc_tls_certificate_provider_static_data_create( + roots, pairs + ) ?: error("provider alloc failed") + grpc_tls_credentials_options_set_certificate_provider(opts, provider) + grpc_tls_certificate_provider_release(provider) + } + + + if (pairs != null) grpc_tls_credentials_options_watch_identity_key_cert_pairs(opts) + if (roots != null) grpc_tls_credentials_options_watch_root_certs(opts) + + return opts + } +} diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeServer.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeServer.kt index 39ede8291..a5b26084f 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeServer.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeServer.kt @@ -7,7 +7,6 @@ package kotlinx.rpc.grpc.internal import cnames.structs.grpc_server -import cnames.structs.grpc_server_credentials import kotlinx.atomicfu.atomic import kotlinx.cinterop.COpaquePointer import kotlinx.cinterop.CPointer @@ -20,12 +19,11 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.withTimeoutOrNull import kotlinx.rpc.grpc.HandlerRegistry import kotlinx.rpc.grpc.Server +import kotlinx.rpc.grpc.ServerCredentials import kotlinx.rpc.grpc.ServerServiceDefinition -import libkgrpc.grpc_insecure_server_credentials_create import libkgrpc.grpc_server_add_http2_port import libkgrpc.grpc_server_cancel_all_calls import libkgrpc.grpc_server_create -import libkgrpc.grpc_server_credentials_release import libkgrpc.grpc_server_destroy import libkgrpc.grpc_server_register_completion_queue import libkgrpc.grpc_server_register_method @@ -37,29 +35,13 @@ import libkgrpc.kgrpc_registered_call_allocation import libkgrpc.kgrpc_server_set_batch_method_allocator import libkgrpc.kgrpc_server_set_register_method_allocator import kotlin.experimental.ExperimentalNativeApi -import kotlin.native.ref.createCleaner import kotlin.time.Duration -/** - * Wrapper for [grpc_server_credentials]. - */ -internal sealed class GrpcServerCredentials( - internal val raw: CPointer, -) { - val rawCleaner = createCleaner(raw) { - grpc_server_credentials_release(it) - } -} - -internal class GrpcInsecureServerCredentials : - GrpcServerCredentials(grpc_insecure_server_credentials_create() ?: error("Failed to create server credentials")) - - internal class NativeServer( override val port: Int, // we must reference them, otherwise the credentials are getting garbage collected @Suppress("Redundant") - private val credentials: GrpcServerCredentials, + private val credentials: ServerCredentials, services: List, val fallbackRegistry: HandlerRegistry, ) : Server { From ba97b645351fe607b92cb4c79214d900bb73ba60 Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Mon, 8 Sep 2025 18:31:24 +0200 Subject: [PATCH 04/12] grpc-native: Working mTLS on JVM Signed-off-by: Johannes Zottele --- .../kotlin/kotlinx/rpc/grpc/ManagedChannel.kt | 2 + .../kotlin/kotlinx/rpc/grpc/credentials.kt | 46 ++- .../kotlinx/rpc/grpc/internal/GrpcChannel.kt | 2 +- .../rpc/grpc/test/proto/GrpcbInTlsTest.kt | 270 ++++++++++++------ .../kotlinx/rpc/grpc/credentials.jvm.kt | 67 +++-- .../kotlinx/rpc/grpc/ManagedChannel.native.kt | 10 + .../kotlinx/rpc/grpc/credentials.native.kt | 87 ++++-- .../rpc/grpc/internal/GrpcChannel.native.kt | 2 +- .../rpc/grpc/internal/NativeManagedChannel.kt | 10 +- 9 files changed, 339 insertions(+), 157 deletions(-) diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt index 7722110f4..6414533b7 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt @@ -69,6 +69,8 @@ public interface ManagedChannel { */ public expect abstract class ManagedChannelBuilder> { public fun usePlaintext(): T + + public abstract fun overrideAuthority(authority: String): T } internal expect fun ManagedChannelBuilder( diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt index 62f941239..a0e560372 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt @@ -15,19 +15,53 @@ public expect class InsecureServerCredentials : ServerCredentials public expect class TlsChannelCredentials : ChannelCredentials public expect class TlsServerCredentials : ServerCredentials -public expect fun TlsChannelCredentials(): ChannelCredentials -public expect fun TlsServerCredentials(certChain: String, privateKey: String): ServerCredentials +public fun TlsChannelCredentials(configure: TlsChannelCredentialsBuilder.() -> Unit = {}): ChannelCredentials { + val builder = TlsChannelCredentialsBuilder() + builder.configure() + return builder.build() +} + +public fun TlsServerCredentials( + certChain: String, + privateKey: String, + configure: TlsServerCredentialsBuilder.() -> Unit = {}, +): ServerCredentials { + val builder = TlsServerCredentialsBuilder(certChain, privateKey) + builder.configure() + return builder.build() +} public interface TlsChannelCredentialsBuilder { public fun trustManager(rootCertsPem: String): TlsChannelCredentialsBuilder + public fun keyManager(certChainPem: String, privateKeyPem: String): TlsChannelCredentialsBuilder public fun build(): ChannelCredentials } +public enum class TlsClientAuth { + /** Clients will not present any identity. */ + NONE, + + /** + * Clients are requested to present their identity, but clients without identities are + * permitted. + */ + OPTIONAL, + + /** + * Clients are requested to present their identity, and are required to provide a valid + * identity. + */ + REQUIRE +} + public interface TlsServerCredentialsBuilder { - public fun keyManager(certChainPem: String, privateKeyPem: String): TlsServerCredentialsBuilder + public fun trustManager(rootCertsPem: String): TlsServerCredentialsBuilder + public fun clientAuth(clientAuth: TlsClientAuth): TlsServerCredentialsBuilder public fun build(): ServerCredentials } - -public expect fun TlsChannelCredentialsBuilder(): TlsChannelCredentialsBuilder -public expect fun TlsServerCredentialsBuilder(): TlsServerCredentialsBuilder +internal expect fun TlsChannelCredentialsBuilder(): TlsChannelCredentialsBuilder +internal expect fun TlsServerCredentialsBuilder( + certChain: String, + privateKey: String, +): TlsServerCredentialsBuilder diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/GrpcChannel.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/GrpcChannel.kt index 165f41733..cbffa0b1b 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/GrpcChannel.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/internal/GrpcChannel.kt @@ -13,5 +13,5 @@ public expect abstract class GrpcChannel { callOptions: GrpcCallOptions, ): ClientCall - public abstract fun authority(): String + public abstract fun authority(): String? } diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt index 9dc7e10b2..d5bf60c2d 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt @@ -10,8 +10,9 @@ import hello.invoke import kotlinx.coroutines.test.runTest import kotlinx.rpc.grpc.GrpcClient import kotlinx.rpc.grpc.GrpcServer -import kotlinx.rpc.grpc.TlsChannelCredentialsBuilder -import kotlinx.rpc.grpc.TlsServerCredentialsBuilder +import kotlinx.rpc.grpc.TlsChannelCredentials +import kotlinx.rpc.grpc.TlsClientAuth +import kotlinx.rpc.grpc.TlsServerCredentials import kotlinx.rpc.grpc.test.EchoRequest import kotlinx.rpc.grpc.test.EchoService import kotlinx.rpc.grpc.test.EchoServiceImpl @@ -27,6 +28,7 @@ class GrpcbInTlsTest { @Test fun testTlsCall() = runTest { + // uses default TLS credentials val grpcClient = GrpcClient("grpcb.in", 9001) val service = grpcClient.withService() val request = HelloRequest { @@ -35,14 +37,16 @@ class GrpcbInTlsTest { val result = service.SayHello(request) println(result.reply) + + // Ensure we don't leak the client channel between tests + grpcClient.shutdown() + grpcClient.awaitTermination() } @Test - fun testLocalTls() = runTest { - val serverTls = TlsServerCredentialsBuilder() - .keyManager(SERVER_CERT_PEM, SERVER_KEY_PEM) - .build() + fun testCustomTls() = runTest { + val serverTls = TlsServerCredentials(SERVER_CERT_PEM, SERVER_KEY_PEM) val grpcServer = GrpcServer( PORT, @@ -52,9 +56,9 @@ class GrpcbInTlsTest { }) grpcServer.start() - val clientTls = TlsChannelCredentialsBuilder() - .trustManager(SERVER_CERT_PEM) - .build() + val clientTls = TlsChannelCredentials { + trustManager(SERVER_CERT_PEM) + } val grpcClient = GrpcClient( "localhost", PORT, @@ -80,94 +84,182 @@ class GrpcbInTlsTest { } } + @Test + fun testCustomMTls() = runTest { + val serverTls = TlsServerCredentials(SERVER_CERT_PEM, SERVER_KEY_PEM) { + trustManager(CA_PEM) + clientAuth(TlsClientAuth.REQUIRE) + } + + val grpcServer = GrpcServer( + PORT, + credentials = serverTls, + builder = { + registerService { EchoServiceImpl() } + }) + grpcServer.start() + + val clientTls = TlsChannelCredentials { + keyManager(CLIENT_CERT_PEM, CLIENT_KEY_PEM) + trustManager(CA_PEM) + } + + val grpcClient = GrpcClient( + "localhost", PORT, + credentials = clientTls, + ) { + overrideAuthority("foo.test.google.fr") + } + + val service = grpcClient.withService() + val request = EchoRequest { + message = "Postman" + } + + try { + service.UnaryEcho(request) + } catch (t: Throwable) { + println("[DEBUG_LOG] TLS test failed: ${t::class.simpleName}: ${t.message}") + t.printStackTrace() + throw t + } finally { + grpcServer.shutdown() + grpcServer.awaitTermination() + grpcClient.shutdown() + grpcClient.awaitTermination() + } + } + + private val CA_PEM = """ + -----BEGIN CERTIFICATE----- + MIIDWjCCAkKgAwIBAgIUWrP0VvHcy+LP6UuYNtiL9gBhD5owDQYJKoZIhvcNAQEL + BQAwVjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM + GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAwwGdGVzdGNhMB4XDTIw + MDMxNzE4NTk1MVoXDTMwMDMxNTE4NTk1MVowVjELMAkGA1UEBhMCQVUxEzARBgNV + BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0 + ZDEPMA0GA1UEAwwGdGVzdGNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC + AQEAsGL0oXflF0LzoM+Bh+qUU9yhqzw2w8OOX5mu/iNCyUOBrqaHi7mGHx73GD01 + diNzCzvlcQqdNIH6NQSL7DTpBjca66jYT9u73vZe2MDrr1nVbuLvfu9850cdxiUO + Inv5xf8+sTHG0C+a+VAvMhsLiRjsq+lXKRJyk5zkbbsETybqpxoJ+K7CoSy3yc/k + QIY3TipwEtwkKP4hzyo6KiGd/DPexie4nBUInN3bS1BUeNZ5zeaIC2eg3bkeeW7c + qT55b+Yen6CxY0TEkzBK6AKt/WUialKMgT0wbTxRZO7kUCH3Sq6e/wXeFdJ+HvdV + LPlAg5TnMaNpRdQih/8nRFpsdwIDAQABoyAwHjAMBgNVHRMEBTADAQH/MA4GA1Ud + DwEB/wQEAwICBDANBgkqhkiG9w0BAQsFAAOCAQEAkTrKZjBrJXHps/HrjNCFPb5a + THuGPCSsepe1wkKdSp1h4HGRpLoCgcLysCJ5hZhRpHkRihhef+rFHEe60UePQO3S + CVTtdJB4CYWpcNyXOdqefrbJW5QNljxgi6Fhvs7JJkBqdXIkWXtFk2eRgOIP2Eo9 + /OHQHlYnwZFrk6sp4wPyR+A95S0toZBcyDVz7u+hOW0pGK3wviOe9lvRgj/H3Pwt + bewb0l+MhRig0/DVHamyVxrDRbqInU1/GTNCwcZkXKYFWSf92U+kIcTth24Q1gcw + eZiLl5FfrWokUNytFElXob0V0a5/kbhiLc3yWmvWqHTpqCALbVyF+rKJo2f5Kw== + -----END CERTIFICATE----- + """.trimIndent() private val SERVER_CERT_PEM = """ -----BEGIN CERTIFICATE----- - MIIFfTCCA2WgAwIBAgIUOfRdPPo6IDmMJKBumLdSe59ldxEwDQYJKoZIhvcNAQEL - BQAwTjELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5 - MQwwCgYDVQQKDANEZXYxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yNTA5MDgxMzIx - MTNaFw0yNjA5MDgxMzIxMTNaME4xCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVTdGF0 - ZTENMAsGA1UEBwwEQ2l0eTEMMAoGA1UECgwDRGV2MRIwEAYDVQQDDAlsb2NhbGhv - c3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDCFj6DIg8qZm26Qque - 6kyVADZgJrBgL19HNgLmDpxixMlnSIOjWMb2IpAcT7Ln6PytEevoyPFubO0uXcRT - jyf3xIaXWjj299w0TsvGYF6rZhUApKIYAKiamIjZLid0+VaKJzGzoIDFAU0W1gsp - x9sz0YkP0OHuMcOjnkxIbqgL/lZOg1JDVf6hJrAi6CE0Iar03/R6cj+GNsf6W3RF - MSUh9MWzDhgVY5DcVLf44V9s8hOiHu8p46aVirHti72pGbfYFNfD23Xmv14QX9pY - qtuaxDWcMk44K48kf+7ztC3jDbzJkloy0oFEWSmeUwVNYltkrG3Z8753y6ZcHBBc - dw+lq3Fd0x3/9gSKs3w2zjzetym6JrKC5wzItuAqe5rJibAjB3aPhjtKKHmVXzSC - mjuRWtxk/yL7V8yPA6FelwBLgVXaZAUL/IsabkHOerJsS7tZbM4/1baZIbfiVhIJ - wwXHri5zQPTiX/tWZGwjic0ZlxVhF0D2qnILwhXdQyze3K1aoV3T8+i2QIpiLmWQ - jCmqzoM55r/5E2KO/dDbfB8Qtw/rd08wOUcl9+bphetw1vyka4SGSZBwpGy6AUvC - RAGA+5Hk0eqHW6Zqqr7MLzeLd1OlixVLhKgSGRDp7kdRlhPeY9kGrEGU/hXrfVKI - YxbdL5JvK838jfNgKeurWYL8OQIDAQABo1MwUTAdBgNVHQ4EFgQUtOl1Hhw8PeHN - hXC5h4Q9mD0oFdwwHwYDVR0jBBgwFoAUtOl1Hhw8PeHNhXC5h4Q9mD0oFdwwDwYD - VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAp4jaSnXHxTBzSopi8tOa - utxTV45Cz7dhUCfvgPPVsYURgg4MZMLJ908awcU0Wge0DbnwGQx+BQnsh8YCbroG - LVPzbhl4xUmroDfr4XX8lVWmfhBOSC8K1ZR1s6udnHYkfzzZ4w5oBBfVJGsbHrNu - KdYsHvSMpnneRBkMHwjHggcWpJX/Fg+c1lg1WP938qoCgPFZAgVEry5OdgMK0bmj - OUMrM1WKYq/CMi6t0+iwX7V+sRe8+gD+clIJXdg2j9bN+kHXw5FnVBLvvdzED1tL - FG5M+Uq10bomxpLbsUYX6+7ZIkCLvkhMovSYI/M/TcNzy8i41CxS+UibtObJ7XRZ - 512650ySuBKRSwfk8MCp7LOL+nosxcLHtqMmZjlQlziJFDHlxkDlNxsjCSnK0Fxk - djAbq16F9t/C2HIVHK+zKhPGqVVfoeM9ksmngAIOUK05pz8665gjo61748WW+rqf - f/WKiQtu28AjskPNY3CS2vnl3jbSqMrFsi1hPWV2dQRgzl5xI2qR+03sQHaaQUTc - d/whE7t85PqmsVED2vmD9dGy6vTnd3nH8j8DEVaBO/y9bJbpyLZuivCuKxZX+GYg - Mk3Ms7t3rsdPbpiiHK6lcfntwVxThBBSbSTqDvUb4GYryzdQ5B5ib4nEc/aI/msU - ubFotuU6gIPvra9MI/NUSpA= + MIIDtDCCApygAwIBAgIUbJfTREJ6k6/+oInWhV1O1j3ZT0IwDQYJKoZIhvcNAQEL + BQAwVjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM + GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAwwGdGVzdGNhMB4XDTIw + MDMxODAzMTA0MloXDTMwMDMxNjAzMTA0MlowZTELMAkGA1UEBhMCVVMxETAPBgNV + BAgMCElsbGlub2lzMRAwDgYDVQQHDAdDaGljYWdvMRUwEwYDVQQKDAxFeGFtcGxl + LCBDby4xGjAYBgNVBAMMESoudGVzdC5nb29nbGUuY29tMIIBIjANBgkqhkiG9w0B + AQEFAAOCAQ8AMIIBCgKCAQEA5xOONxJJ8b8Qauvob5/7dPYZfIcd+uhAWL2ZlTPz + Qvu4oF0QI4iYgP5iGgry9zEtCM+YQS8UhiAlPlqa6ANxgiBSEyMHH/xE8lo/+caY + GeACqy640Jpl/JocFGo3xd1L8DCawjlaj6eu7T7T/tpAV2qq13b5710eNRbCAfFe + 8yALiGQemx0IYhlZXNbIGWLBNhBhvVjJh7UvOqpADk4xtl8o5j0xgMIRg6WJGK6c + 6ffSIg4eP1XmovNYZ9LLEJG68tF0Q/yIN43B4dt1oq4jzSdCbG4F1EiykT2TmwPV + YDi8tml6DfOCDGnit8svnMEmBv/fcPd31GSbXjF8M+KGGQIDAQABo2swaTAJBgNV + HRMEAjAAMAsGA1UdDwQEAwIF4DBPBgNVHREESDBGghAqLnRlc3QuZ29vZ2xlLmZy + ghh3YXRlcnpvb2kudGVzdC5nb29nbGUuYmWCEioudGVzdC55b3V0dWJlLmNvbYcE + wKgBAzANBgkqhkiG9w0BAQsFAAOCAQEAS8hDQA8PSgipgAml7Q3/djwQ644ghWQv + C2Kb+r30RCY1EyKNhnQnIIh/OUbBZvh0M0iYsy6xqXgfDhCB93AA6j0i5cS8fkhH + Jl4RK0tSkGQ3YNY4NzXwQP/vmUgfkw8VBAZ4Y4GKxppdATjffIW+srbAmdDruIRM + wPeikgOoRrXf0LA1fi4TqxARzeRwenQpayNfGHTvVF9aJkl8HoaMunTAdG5pIVcr + 9GKi/gEMpXUJbbVv3U5frX1Wo4CFo+rZWJ/LyCMeb0jciNLxSdMwj/E/ZuExlyeZ + gc9ctPjSMvgSyXEKv6Vwobleeg88V2ZgzenziORoWj4KszG/lbQZvg== -----END CERTIFICATE----- """.trimIndent() private val SERVER_KEY_PEM = """ -----BEGIN PRIVATE KEY----- - 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 - paLEjX+8kznUisDokSzJ7iHoZgmN + MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDnE443EknxvxBq + 6+hvn/t09hl8hx366EBYvZmVM/NC+7igXRAjiJiA/mIaCvL3MS0Iz5hBLxSGICU+ + WproA3GCIFITIwcf/ETyWj/5xpgZ4AKrLrjQmmX8mhwUajfF3UvwMJrCOVqPp67t + PtP+2kBXaqrXdvnvXR41FsIB8V7zIAuIZB6bHQhiGVlc1sgZYsE2EGG9WMmHtS86 + qkAOTjG2XyjmPTGAwhGDpYkYrpzp99IiDh4/Veai81hn0ssQkbry0XRD/Ig3jcHh + 23WiriPNJ0JsbgXUSLKRPZObA9VgOLy2aXoN84IMaeK3yy+cwSYG/99w93fUZJte + MXwz4oYZAgMBAAECggEBAIVn2Ncai+4xbH0OLWckabwgyJ4IM9rDc0LIU368O1kU + koais8qP9dujAWgfoh3sGh/YGgKn96VnsZjKHlyMgF+r4TaDJn3k2rlAOWcurGlj + 1qaVlsV4HiEzp7pxiDmHhWvp4672Bb6iBG+bsjCUOEk/n9o9KhZzIBluRhtxCmw5 + nw4Do7z00PTvN81260uPWSc04IrytvZUiAIx/5qxD72bij2xJ8t/I9GI8g4FtoVB + 8pB6S/hJX1PZhh9VlU6Yk+TOfOVnbebG4W5138LkB835eqk3Zz0qsbc2euoi8Hxi + y1VGwQEmMQ63jXz4c6g+X55ifvUK9Jpn5E8pq+pMd7ECgYEA93lYq+Cr54K4ey5t + sWMa+ye5RqxjzgXj2Kqr55jb54VWG7wp2iGbg8FMlkQwzTJwebzDyCSatguEZLuB + gRGroRnsUOy9vBvhKPOch9bfKIl6qOgzMJB267fBVWx5ybnRbWN/I7RvMQf3k+9y + biCIVnxDLEEYyx7z85/5qxsXg/MCgYEA7wmWKtCTn032Hy9P8OL49T0X6Z8FlkDC + Rk42ygrc/MUbugq9RGUxcCxoImOG9JXUpEtUe31YDm2j+/nbvrjl6/bP2qWs0V7l + dTJl6dABP51pCw8+l4cWgBBX08Lkeen812AAFNrjmDCjX6rHjWHLJcpS18fnRRkP + V1d/AHWX7MMCgYEA6Gsw2guhp0Zf2GCcaNK5DlQab8OL4Hwrpttzo4kuTlwtqNKp + Q9H4al9qfF4Cr1TFya98+EVYf8yFRM3NLNjZpe3gwYf2EerlJj7VLcahw0KKzoN1 + QBENfwgPLRk5sDkx9VhSmcfl/diLroZdpAwtv3vo4nEoxeuGFbKTGx3Qkf0CgYEA + xyR+dcb05Ygm3w4klHQTowQ10s1H80iaUcZBgQuR1ghEtDbUPZHsoR5t1xCB02ys + DgAwLv1bChIvxvH/L6KM8ovZ2LekBX4AviWxoBxJnfz/EVau98B0b1auRN6eSC83 + FRuGldlSOW1z/nSh8ViizSYE5H5HX1qkXEippvFRE88CgYB3Bfu3YQY60ITWIShv + nNkdcbTT9eoP9suaRJjw92Ln+7ZpALYlQMKUZmJ/5uBmLs4RFwUTQruLOPL4yLTH + awADWUzs3IRr1fwn9E+zM8JVyKCnUEM3w4N5UZskGO2klashAd30hWO+knRv/y0r + uGIYs9Ek7YXlXIRVrzMwcsrt1w== + -----END PRIVATE KEY----- + """.trimIndent() + + private val CLIENT_CERT_PEM = """ + -----BEGIN CERTIFICATE----- + MIIDNzCCAh8CFGyX00RCepOv/qCJ1oVdTtY92U83MA0GCSqGSIb3DQEBCwUAMFYx + CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl + cm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMMBnRlc3RjYTAeFw0yMDAzMTgw + MTA2MTBaFw0zMDAzMTYwMTA2MTBaMFoxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApT + b21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEzAR + BgNVBAMMCnRlc3RjbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB + AQCyqYRp+DXVp72NFbQH8hdhTZLycZXOlJhmMsrJmrjn2p7pI/8mTZ/0FC+SGWBG + ZV+ELiHrmCX5zfaILr9Iuw7Ghr3Vzoefi8r62rLupVPNi/qdqyjWk2dECHC9Z3+A + g3KzKTyerXWjKcvyKVmM0ZxE0RXhDW/RoQbqZsU2GKg1B2rhUU8KN0gVmKn0rJHO + xzRVSYeYLYp5Yn7KrtPJcKyo9aVuEr7dGANzpyF6lg/nYBWc+9SGwkoLdFvKvABY + JMyrbNhHUQfv0fzaZ0P86dfTENrDxzALrzGnqcx3KTrwJjkZ/aSr1tyD0/tXvukR + FiPxWBJhjHQ70GqTFQY19RbhAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFXCewK8 + cWT+zWxXyGFnouFSBzTi0BMBJRrhsiNoiQxkqityJHWFExiQZie+7CA+EabXCQUB + +JwMSWM29j3mSw10DTfmC3rhheQqGxy304BZyUpdpvI2dt3p/mcsE7O+p4sQrSep + gijiDssKAfxTAmUM93N6+Q8yJK5immxlbeYfijoBvmkzyB/B+qNRPsx0n7aFGnfv + oWfkW296iPhWLiwknpC3xB6oK3vRbK4Zj1OaGb0grK7VN8EyhBix2xVF61i4dzCK + kMIpl7CUpw1Mb2z8q3F2bHBS7iF7g1Ccn5VGcO+aJ+6PWydaeqJ6VEBF0Nwv9woe + mL5AluNRLaqjZvE= + -----END CERTIFICATE----- + """.trimIndent() + + private val CLIENT_KEY_PEM = """ + -----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCyqYRp+DXVp72N + FbQH8hdhTZLycZXOlJhmMsrJmrjn2p7pI/8mTZ/0FC+SGWBGZV+ELiHrmCX5zfaI + Lr9Iuw7Ghr3Vzoefi8r62rLupVPNi/qdqyjWk2dECHC9Z3+Ag3KzKTyerXWjKcvy + KVmM0ZxE0RXhDW/RoQbqZsU2GKg1B2rhUU8KN0gVmKn0rJHOxzRVSYeYLYp5Yn7K + rtPJcKyo9aVuEr7dGANzpyF6lg/nYBWc+9SGwkoLdFvKvABYJMyrbNhHUQfv0fza + Z0P86dfTENrDxzALrzGnqcx3KTrwJjkZ/aSr1tyD0/tXvukRFiPxWBJhjHQ70GqT + FQY19RbhAgMBAAECggEAIL8JUhL4awyvpWhQ8xPgTSlWwbEn8BE0TacJnCILuhNM + BRdf8LlRk/8PKQwVpVF3TFbYSMI+U6b4hMVssfv3HVQc/083dHq+3XOwUCVlUstR + SAzTE2E5EDMr1stdh0SQhV4Nilfos9s5Uk1Z6IGSztoz1GgOErIc/mGPy/aA/hbr + fRWHvTp35+MbCJSvZuOeevX2iLs0dNzqdk6DiOWIH/BVGirVPtO6ykrkuTj1FWiN + hyZ3MBChShlNH2poNX46ntOc7nEus0qteOgxBK8lummFEtlehCA7hd/8xuvYlP0k + 7aN684LCRDajmAGpoZO57NSDYQhAFGZeUZ93SMFucQKBgQDe7GGkzZFEiv91u1q9 + lgMy1h5dZjIZKgQaOarPC6wCQMUdqCf6cSLsAPr4T8EDoWsnY7dSnrTZ6YCIFL1T + idg8M3BQXipICCJkFORS76pKKZ0wMn3/NgkSepsmNct91WHr6okvx4tOaoRCtdzU + g7jt4Mr3sfLCiZtqTQyySdMUEwKBgQDNK+ZFKL0XhkWZP+PGKjWG8LWpPiK3d78/ + wYBFXzSTGlkr6FvRmYtZeNwXWRYLB4UxZ9At4hbJVEdi/2dITOz/sehVDyCAjjs3 + gycsc3UJqiZbcw5XKhI5TWBuWxkKENdbMSayogVbp2aSYoRblH764//t0ACmbfTW + KUQRQPB/uwKBgQC5QjjjfPL8w4cJkGoYpFKELO2PMR7xSrmeEc6hwlFwjeNCgjy3 + JM6g0y++rIj7O2qRkY0IXFxvvF3UuWedxTCu1xC/uYHp2ti506LsScB7YZoAM/YB + 4iYn9Tx6xLoYGP0H0iGwU2SyBlNkHT8oXU+SYP5MWtYkVbeS3/VtNWz1gQKBgQCA + 6Nk4kN0mH7YxEKRzSOfyzeDF4oV7kuB2FYUbkTL+TirC3K58JiYY5Egc31trOKFm + Jlz1xz0b6DkmKWTiV3r9OPHKJ8P7IeJxAZWmZzCdDuwkv0i+WW+z0zsIe3JjEavN + 3zb6O7R0HtziksWoqMeTqZeO+wa9iw6vVKQw1wWEqwKBgFHfahFs0DZ5cUTpGpBt + F/AQG7ukgipB6N6AkB9kDbgCs1FLgd199MQrEncug5hfpq8QerbyMatmA+GXoGMb + 7vztKEH85yzp4n02FNL6H7xL4VVILvyZHdolmiORJ4qT2hZnl8pEQ2TYuF4RlHUd + nSwXX+2o0J/nF85fm4AwWKAc -----END PRIVATE KEY----- """.trimIndent() diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt index 5bae83496..2e5e247fc 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt @@ -8,53 +8,66 @@ public actual typealias ChannelCredentials = io.grpc.ChannelCredentials public actual typealias ServerCredentials = io.grpc.ServerCredentials - // we need a wrapper for InsecureChannelCredentials as our constructor would conflict with the private // java constructor. public actual typealias InsecureChannelCredentials = io.grpc.InsecureChannelCredentials public actual typealias InsecureServerCredentials = io.grpc.InsecureServerCredentials -public actual typealias TlsServerCredentials = io.grpc.TlsServerCredentials public actual typealias TlsChannelCredentials = io.grpc.TlsChannelCredentials +public actual typealias TlsServerCredentials = io.grpc.TlsServerCredentials -public actual fun TlsChannelCredentials(): ChannelCredentials { - return TlsChannelCredentialsBuilder().build() -} +internal actual fun TlsChannelCredentialsBuilder(): TlsChannelCredentialsBuilder = + object : TlsChannelCredentialsBuilder { + private var cb = TlsChannelCredentials.newBuilder() + + + override fun trustManager(rootCertsPem: String): TlsChannelCredentialsBuilder { + cb.trustManager(rootCertsPem.byteInputStream()) + return this + } -public actual fun TlsServerCredentials( + override fun keyManager( + certChainPem: String, + privateKeyPem: String, + ): TlsChannelCredentialsBuilder { + cb.keyManager(certChainPem.byteInputStream(), privateKeyPem.byteInputStream()) + return this + } + + override fun build(): ChannelCredentials { + return cb.build() + } + } + +internal actual fun TlsServerCredentialsBuilder( certChain: String, privateKey: String, -): ServerCredentials { - return TlsServerCredentialsBuilder().keyManager(certChain, privateKey).build() -} - -public actual fun TlsServerCredentialsBuilder(): TlsServerCredentialsBuilder = object : TlsServerCredentialsBuilder { +): TlsServerCredentialsBuilder = object : TlsServerCredentialsBuilder { private var sb = TlsServerCredentials.newBuilder() - override fun keyManager( - certChainPem: String, - privateKeyPem: String, - ): TlsServerCredentialsBuilder { - sb.keyManager(certChainPem.byteInputStream(), privateKeyPem.byteInputStream()) + override fun trustManager(rootCertsPem: String): TlsServerCredentialsBuilder { + sb.trustManager(rootCertsPem.byteInputStream()) return this } - override fun build(): ServerCredentials { - return sb.build() + override fun clientAuth(clientAuth: TlsClientAuth): TlsServerCredentialsBuilder { + sb.clientAuth(clientAuth.toJava()) + return this } -} - -public actual fun TlsChannelCredentialsBuilder(): TlsChannelCredentialsBuilder = object : TlsChannelCredentialsBuilder { - private var cb = TlsChannelCredentials.newBuilder() - override fun trustManager(rootCertsPem: String): TlsChannelCredentialsBuilder { - cb.trustManager(rootCertsPem.byteInputStream()) - return this + init { + sb.keyManager(certChain.byteInputStream(), privateKey.byteInputStream()) } - override fun build(): ChannelCredentials { - return cb.build() + override fun build(): ServerCredentials { + return sb.build() } } +private fun TlsClientAuth.toJava(): io.grpc.TlsServerCredentials.ClientAuth = when (this) { + TlsClientAuth.NONE -> io.grpc.TlsServerCredentials.ClientAuth.NONE + TlsClientAuth.OPTIONAL -> io.grpc.TlsServerCredentials.ClientAuth.OPTIONAL + TlsClientAuth.REQUIRE -> io.grpc.TlsServerCredentials.ClientAuth.REQUIRE +} + diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt index 9e6bc7b46..42162f6b4 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt @@ -22,6 +22,8 @@ public actual abstract class ManagedChannelBuilder> public actual open fun usePlaintext(): T { error("Builder does not support usePlaintext()") } + + public actual abstract fun overrideAuthority(authority: String): T } internal class NativeManagedChannelBuilder( @@ -29,14 +31,22 @@ internal class NativeManagedChannelBuilder( private var credentials: Lazy, ) : ManagedChannelBuilder() { + private var authority: String? = null + override fun usePlaintext(): NativeManagedChannelBuilder { credentials = lazy { InsecureChannelCredentials() } return this } + override fun overrideAuthority(authority: String): NativeManagedChannelBuilder { + this.authority = authority + return this + } + fun buildChannel(): NativeManagedChannel { return NativeManagedChannel( target, + authority = authority, credentials = credentials.value, ) } diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt index 1903270f6..85079f39b 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt @@ -15,11 +15,13 @@ import libkgrpc.grpc_channel_credentials_release import libkgrpc.grpc_insecure_credentials_create import libkgrpc.grpc_insecure_server_credentials_create import libkgrpc.grpc_server_credentials_release +import libkgrpc.grpc_ssl_client_certificate_request_type import libkgrpc.grpc_tls_certificate_provider_release import libkgrpc.grpc_tls_certificate_provider_static_data_create import libkgrpc.grpc_tls_credentials_create import libkgrpc.grpc_tls_credentials_options_create import libkgrpc.grpc_tls_credentials_options_destroy +import libkgrpc.grpc_tls_credentials_options_set_cert_request_type import libkgrpc.grpc_tls_credentials_options_set_certificate_provider import libkgrpc.grpc_tls_credentials_options_watch_identity_key_cert_pairs import libkgrpc.grpc_tls_credentials_options_watch_root_certs @@ -76,44 +78,51 @@ public fun InsecureServerCredentials(): ServerCredentials { ) } -public actual fun TlsChannelCredentials(): ChannelCredentials { - val raw = grpc_tls_credentials_options_create()?.let { - grpc_tls_credentials_create(it) - } ?: error("Failed to create TLS credentials") - return TlsChannelCredentials(raw) -} +internal actual fun TlsChannelCredentialsBuilder(): TlsChannelCredentialsBuilder = + object : TlsChannelCredentialsBuilder { + var optionsBuilder = TlsCredentialsOptionsBuilder() + + override fun trustManager(rootCertsPem: String): TlsChannelCredentialsBuilder { + optionsBuilder.trustManager(rootCertsPem) + return this + } + + override fun keyManager( + certChainPem: String, + privateKeyPem: String, + ): TlsChannelCredentialsBuilder { + optionsBuilder.keyManager(certChainPem, privateKeyPem) + return this + } + + override fun build(): ChannelCredentials { + val opts = optionsBuilder.build() + val creds = grpc_tls_credentials_create(opts) + ?: run { + grpc_tls_credentials_options_destroy(opts); + error("TLS channel credential creation failed") + } + return TlsChannelCredentials(creds) + } + } -public actual fun TlsServerCredentials( +internal actual fun TlsServerCredentialsBuilder( certChain: String, privateKey: String, -): ServerCredentials { - return TlsServerCredentialsBuilder().keyManager(certChain, privateKey).build() -} - -public actual fun TlsChannelCredentialsBuilder(): TlsChannelCredentialsBuilder = object : TlsChannelCredentialsBuilder { +): TlsServerCredentialsBuilder = object : TlsServerCredentialsBuilder { var optionsBuilder = TlsCredentialsOptionsBuilder() - override fun trustManager(rootCertsPem: String): TlsChannelCredentialsBuilder { - optionsBuilder.trustManager(rootCertsPem) - return this + init { + optionsBuilder.keyManager(certChain, privateKey) } - override fun build(): ChannelCredentials { - val opts = optionsBuilder.build() - val creds = grpc_tls_credentials_create(opts) - ?: run { - grpc_tls_credentials_options_destroy(opts); - error("TLS channel credential creation failed") - } - return TlsChannelCredentials(creds) + override fun trustManager(rootCertsPem: String): TlsServerCredentialsBuilder { + optionsBuilder.trustManager(rootCertsPem) + return this } -} - -public actual fun TlsServerCredentialsBuilder(): TlsServerCredentialsBuilder = object : TlsServerCredentialsBuilder { - var optionsBuilder = TlsCredentialsOptionsBuilder() - override fun keyManager(certChainPem: String, privateKeyPem: String): TlsServerCredentialsBuilder { - optionsBuilder.keyManager(certChainPem, privateKeyPem) + override fun clientAuth(clientAuth: TlsClientAuth): TlsServerCredentialsBuilder { + optionsBuilder.clientAuth(clientAuth) return this } @@ -134,11 +143,20 @@ private class TlsCredentialsOptionsBuilder { private var cert: String? = null private var key: String? = null - fun trustManager(rootCertsPem: String) = apply { roots = rootCertsPem } + private var clientAuth: TlsClientAuth? = null + + fun trustManager(rootCertsPem: String) { + roots = rootCertsPem + } + fun keyManager(certChainPem: String, privateKeyPem: String) = apply { cert = certChainPem; key = privateKeyPem } + fun clientAuth(clientAuth: TlsClientAuth) { + this.clientAuth = clientAuth + } + fun build(): CPointer { val opts = grpc_tls_credentials_options_create() ?: error("alloc opts failed") @@ -160,6 +178,15 @@ private class TlsCredentialsOptionsBuilder { if (pairs != null) grpc_tls_credentials_options_watch_identity_key_cert_pairs(opts) if (roots != null) grpc_tls_credentials_options_watch_root_certs(opts) + val clientAuth = clientAuth + if (clientAuth != null) grpc_tls_credentials_options_set_cert_request_type(opts, clientAuth.toRaw()) + return opts } } + +private fun TlsClientAuth.toRaw(): grpc_ssl_client_certificate_request_type = when (this) { + TlsClientAuth.NONE -> grpc_ssl_client_certificate_request_type.GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE + TlsClientAuth.OPTIONAL -> grpc_ssl_client_certificate_request_type.GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_AND_VERIFY + TlsClientAuth.REQUIRE -> grpc_ssl_client_certificate_request_type.GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY +} \ No newline at end of file diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/GrpcChannel.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/GrpcChannel.native.kt index 3a1e78d32..74a1d5ca8 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/GrpcChannel.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/GrpcChannel.native.kt @@ -13,5 +13,5 @@ public actual abstract class GrpcChannel { callOptions: GrpcCallOptions, ): ClientCall - public actual abstract fun authority(): String + public actual abstract fun authority(): String? } diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt index d726aae43..9693ef018 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt @@ -39,6 +39,7 @@ import kotlin.time.Duration */ internal class NativeManagedChannel( target: String, + val authority: String?, // we must store them, otherwise the credentials are getting released credentials: ChannelCredentials, ) : ManagedChannel, ManagedChannelPlatform() { @@ -122,26 +123,29 @@ internal class NativeManagedChannel( // to construct a valid HTTP/2 path, we must prepend the name with a slash. // the user does not do this to align it with the java implementation. val methodNameSlice = "/$methodFullName".toGrpcSlice() + val authoritySlice = authority?.toGrpcSlice() + val rawCall = grpc_channel_create_call( channel = raw, parent_call = null, propagation_mask = GRPC_PROPAGATE_DEFAULTS, completion_queue = cq.raw, method = methodNameSlice, - host = null, + host = authoritySlice, deadline = gpr_inf_future(GPR_CLOCK_REALTIME), reserved = null ) ?: error("Failed to create call") grpc_slice_unref(methodNameSlice) + authoritySlice?.let { grpc_slice_unref(it) } return NativeClientCall( cq, rawCall, methodDescriptor, callJob ) } - override fun authority(): String { - TODO("Not yet implemented") + override fun authority(): String? { + return authority } } From 00d573ca4fe69eccf6dd140da8e5f35becd6d066 Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Tue, 9 Sep 2025 11:40:47 +0200 Subject: [PATCH 05/12] grpc-native: Add overrideAuthority on native Signed-off-by: Johannes Zottele --- .../{proto/GrpcbInTlsTest.kt => certs.kt} | 142 +----------------- .../rpc/grpc/test/proto/GrpcProtoTest.kt | 38 +++-- .../rpc/grpc/test/proto/GrpcbTlsTest.kt | 86 +++++++++++ .../rpc/grpc/internal/NativeManagedChannel.kt | 29 +++- .../rpc/grpc/internal/serverCallTags.kt | 3 - 5 files changed, 145 insertions(+), 153 deletions(-) rename grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/{proto/GrpcbInTlsTest.kt => certs.kt} (67%) create mode 100644 grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbTlsTest.kt diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/certs.kt similarity index 67% rename from grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt rename to grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/certs.kt index d5bf60c2d..e1540f5cc 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbInTlsTest.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/certs.kt @@ -2,135 +2,11 @@ * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ -package kotlinx.rpc.grpc.test.proto +package kotlinx.rpc.grpc.test -import hello.HelloRequest -import hello.HelloService -import hello.invoke -import kotlinx.coroutines.test.runTest -import kotlinx.rpc.grpc.GrpcClient -import kotlinx.rpc.grpc.GrpcServer -import kotlinx.rpc.grpc.TlsChannelCredentials -import kotlinx.rpc.grpc.TlsClientAuth -import kotlinx.rpc.grpc.TlsServerCredentials -import kotlinx.rpc.grpc.test.EchoRequest -import kotlinx.rpc.grpc.test.EchoService -import kotlinx.rpc.grpc.test.EchoServiceImpl -import kotlinx.rpc.grpc.test.invoke -import kotlinx.rpc.registerService -import kotlinx.rpc.withService -import kotlin.test.Test +// Certs are taken from grpc-java/testing/src/main/resources/certs -private const val PORT = 50051 - -class GrpcbInTlsTest { - - - @Test - fun testTlsCall() = runTest { - // uses default TLS credentials - val grpcClient = GrpcClient("grpcb.in", 9001) - val service = grpcClient.withService() - val request = HelloRequest { - greeting = "Postman" - } - val result = service.SayHello(request) - - println(result.reply) - - // Ensure we don't leak the client channel between tests - grpcClient.shutdown() - grpcClient.awaitTermination() - } - - - @Test - fun testCustomTls() = runTest { - val serverTls = TlsServerCredentials(SERVER_CERT_PEM, SERVER_KEY_PEM) - - val grpcServer = GrpcServer( - PORT, - credentials = serverTls, - builder = { - registerService { EchoServiceImpl() } - }) - grpcServer.start() - - val clientTls = TlsChannelCredentials { - trustManager(SERVER_CERT_PEM) - } - - val grpcClient = GrpcClient( - "localhost", PORT, - credentials = clientTls, - ) {} - - val service = grpcClient.withService() - val request = EchoRequest { - message = "Postman" - } - - try { - service.UnaryEcho(request) - } catch (t: Throwable) { - println("[DEBUG_LOG] TLS test failed: ${t::class.simpleName}: ${t.message}") - t.printStackTrace() - throw t - } finally { - grpcServer.shutdown() - grpcServer.awaitTermination() - grpcClient.shutdown() - grpcClient.awaitTermination() - } - } - - @Test - fun testCustomMTls() = runTest { - val serverTls = TlsServerCredentials(SERVER_CERT_PEM, SERVER_KEY_PEM) { - trustManager(CA_PEM) - clientAuth(TlsClientAuth.REQUIRE) - } - - val grpcServer = GrpcServer( - PORT, - credentials = serverTls, - builder = { - registerService { EchoServiceImpl() } - }) - grpcServer.start() - - val clientTls = TlsChannelCredentials { - keyManager(CLIENT_CERT_PEM, CLIENT_KEY_PEM) - trustManager(CA_PEM) - } - - val grpcClient = GrpcClient( - "localhost", PORT, - credentials = clientTls, - ) { - overrideAuthority("foo.test.google.fr") - } - - val service = grpcClient.withService() - val request = EchoRequest { - message = "Postman" - } - - try { - service.UnaryEcho(request) - } catch (t: Throwable) { - println("[DEBUG_LOG] TLS test failed: ${t::class.simpleName}: ${t.message}") - t.printStackTrace() - throw t - } finally { - grpcServer.shutdown() - grpcServer.awaitTermination() - grpcClient.shutdown() - grpcClient.awaitTermination() - } - } - - private val CA_PEM = """ +val CA_PEM = """ -----BEGIN CERTIFICATE----- MIIDWjCCAkKgAwIBAgIUWrP0VvHcy+LP6UuYNtiL9gBhD5owDQYJKoZIhvcNAQEL BQAwVjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM @@ -153,7 +29,7 @@ class GrpcbInTlsTest { -----END CERTIFICATE----- """.trimIndent() - private val SERVER_CERT_PEM = """ +val SERVER_CERT_PEM = """ -----BEGIN CERTIFICATE----- MIIDtDCCApygAwIBAgIUbJfTREJ6k6/+oInWhV1O1j3ZT0IwDQYJKoZIhvcNAQEL BQAwVjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM @@ -178,7 +54,7 @@ class GrpcbInTlsTest { -----END CERTIFICATE----- """.trimIndent() - private val SERVER_KEY_PEM = """ +val SERVER_KEY_PEM = """ -----BEGIN PRIVATE KEY----- MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDnE443EknxvxBq 6+hvn/t09hl8hx366EBYvZmVM/NC+7igXRAjiJiA/mIaCvL3MS0Iz5hBLxSGICU+ @@ -209,7 +85,7 @@ class GrpcbInTlsTest { -----END PRIVATE KEY----- """.trimIndent() - private val CLIENT_CERT_PEM = """ +val CLIENT_CERT_PEM = """ -----BEGIN CERTIFICATE----- MIIDNzCCAh8CFGyX00RCepOv/qCJ1oVdTtY92U83MA0GCSqGSIb3DQEBCwUAMFYx CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl @@ -232,7 +108,7 @@ class GrpcbInTlsTest { -----END CERTIFICATE----- """.trimIndent() - private val CLIENT_KEY_PEM = """ +val CLIENT_KEY_PEM = """ -----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCyqYRp+DXVp72N FbQH8hdhTZLycZXOlJhmMsrJmrjn2p7pI/8mTZ/0FC+SGWBGZV+ELiHrmCX5zfaI @@ -261,6 +137,4 @@ class GrpcbInTlsTest { 7vztKEH85yzp4n02FNL6H7xL4VVILvyZHdolmiORJ4qT2hZnl8pEQ2TYuF4RlHUd nSwXX+2o0J/nF85fm4AwWKAc -----END PRIVATE KEY----- - """.trimIndent() - -} \ No newline at end of file + """.trimIndent() \ No newline at end of file diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcProtoTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcProtoTest.kt index 735f6f4f7..07afe3c1f 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcProtoTest.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcProtoTest.kt @@ -8,30 +8,46 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.test.runTest import kotlinx.rpc.RpcServer +import kotlinx.rpc.grpc.ChannelCredentials import kotlinx.rpc.grpc.GrpcClient import kotlinx.rpc.grpc.GrpcServer +import kotlinx.rpc.grpc.ServerCredentials abstract class GrpcProtoTest { private val serverMutex = Mutex() abstract fun RpcServer.registerServices() - protected fun runGrpcTest(test: suspend (GrpcClient) -> Unit) = runTest { + protected fun runGrpcTest( + serverCreds: ServerCredentials? = null, + clientCreds: ChannelCredentials? = null, + overrideAuthority: String? = null, + test: suspend (GrpcClient) -> Unit, + ) = runTest { serverMutex.withLock { - val grpcClient = GrpcClient("localhost", PORT) { - usePlaintext() + val grpcClient = GrpcClient("localhost", PORT, credentials = clientCreds) { + if (overrideAuthority != null) overrideAuthority(overrideAuthority) + if (clientCreds == null) { + usePlaintext() + } } - val grpcServer = GrpcServer(PORT, builder = { - registerServices() - }) + val grpcServer = GrpcServer( + PORT, + credentials = serverCreds, + builder = { + registerServices() + }) grpcServer.start() - test(grpcClient) - grpcServer.shutdown() - grpcServer.awaitTermination() - grpcClient.shutdown() - grpcClient.awaitTermination() + try { + test(grpcClient) + } finally { + grpcServer.shutdown() + grpcServer.awaitTermination() + grpcClient.shutdown() + grpcClient.awaitTermination() + } } } diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbTlsTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbTlsTest.kt new file mode 100644 index 000000000..ce72e3627 --- /dev/null +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbTlsTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.test.proto + +import hello.HelloRequest +import hello.HelloService +import hello.invoke +import kotlinx.coroutines.test.runTest +import kotlinx.rpc.RpcServer +import kotlinx.rpc.grpc.GrpcClient +import kotlinx.rpc.grpc.TlsChannelCredentials +import kotlinx.rpc.grpc.TlsClientAuth +import kotlinx.rpc.grpc.TlsServerCredentials +import kotlinx.rpc.grpc.test.CA_PEM +import kotlinx.rpc.grpc.test.CLIENT_CERT_PEM +import kotlinx.rpc.grpc.test.CLIENT_KEY_PEM +import kotlinx.rpc.grpc.test.EchoRequest +import kotlinx.rpc.grpc.test.EchoService +import kotlinx.rpc.grpc.test.EchoServiceImpl +import kotlinx.rpc.grpc.test.SERVER_CERT_PEM +import kotlinx.rpc.grpc.test.SERVER_KEY_PEM +import kotlinx.rpc.grpc.test.invoke +import kotlinx.rpc.registerService +import kotlinx.rpc.withService +import kotlin.test.Test +import kotlin.test.assertEquals + +class GrpcbTlsTest : GrpcProtoTest() { + + override fun RpcServer.registerServices() { + registerService { EchoServiceImpl() } + } + + @Test + fun testDefaultTlsCall() = runTest { + // uses default client TLS credentials + val grpcClient = GrpcClient("grpcb.in", 9001) + val service = grpcClient.withService() + val request = HelloRequest { + greeting = "world" + } + val result = service.SayHello(request) + + assertEquals("hello world", result.reply) + + // Ensure we don't leak the client channel between tests + grpcClient.shutdown() + grpcClient.awaitTermination() + } + + + @Test + fun testCustomTls() { + val serverTls = TlsServerCredentials(SERVER_CERT_PEM, SERVER_KEY_PEM) + val clientTls = TlsChannelCredentials { trustManager(SERVER_CERT_PEM) } + + runGrpcTest(serverTls, clientTls, overrideAuthority = "foo.test.google.fr") { client -> + val service = client.withService() + val request = EchoRequest { message = "Echo" } + val response = service.UnaryEcho(request) + assertEquals("Echo", response.message) + } + } + + @Test + fun testCustomMTls() = runTest { + val serverTls = TlsServerCredentials(SERVER_CERT_PEM, SERVER_KEY_PEM) { + trustManager(CA_PEM) + clientAuth(TlsClientAuth.REQUIRE) + } + val clientTls = TlsChannelCredentials { + keyManager(CLIENT_CERT_PEM, CLIENT_KEY_PEM) + trustManager(CA_PEM) + } + + runGrpcTest(serverTls, clientTls, overrideAuthority = "foo.test.google.fr") { client -> + val service = client.withService() + val request = EchoRequest { message = "Echo" } + val response = service.UnaryEcho(request) + assertEquals("Echo", response.message) + } + } + +} \ No newline at end of file diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt index 9693ef018..9ebee5923 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt @@ -10,6 +10,10 @@ import cnames.structs.grpc_channel import kotlinx.atomicfu.atomic import kotlinx.cinterop.CPointer import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.alloc +import kotlinx.cinterop.cstr +import kotlinx.cinterop.memScoped +import kotlinx.cinterop.ptr import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob @@ -21,6 +25,9 @@ import kotlinx.rpc.grpc.ManagedChannelPlatform import libkgrpc.GPR_CLOCK_REALTIME import libkgrpc.GRPC_PROPAGATE_DEFAULTS import libkgrpc.gpr_inf_future +import libkgrpc.grpc_arg +import libkgrpc.grpc_arg_type +import libkgrpc.grpc_channel_args import libkgrpc.grpc_channel_create import libkgrpc.grpc_channel_create_call import libkgrpc.grpc_channel_destroy @@ -55,8 +62,22 @@ internal class NativeManagedChannel( // the channel's completion queue, handling all request operations private val cq = CompletionQueue() - internal val raw: CPointer = grpc_channel_create(target, credentials.raw, null) - ?: error("Failed to create channel") + internal val raw: CPointer = memScoped { + val args = authority?.let { + var authorityOverride = alloc { + type = grpc_arg_type.GRPC_ARG_STRING + key = "grpc.ssl_target_name_override".cstr.ptr + value.string = authority.cstr.ptr + } + + alloc { + num_args = 1u + args = authorityOverride.ptr + } + } + grpc_channel_create(target, credentials.raw, args?.ptr) + ?: error("Failed to create channel") + } @Suppress("unused") private val rawCleaner = createCleaner(raw) { @@ -123,7 +144,6 @@ internal class NativeManagedChannel( // to construct a valid HTTP/2 path, we must prepend the name with a slash. // the user does not do this to align it with the java implementation. val methodNameSlice = "/$methodFullName".toGrpcSlice() - val authoritySlice = authority?.toGrpcSlice() val rawCall = grpc_channel_create_call( channel = raw, @@ -131,13 +151,12 @@ internal class NativeManagedChannel( propagation_mask = GRPC_PROPAGATE_DEFAULTS, completion_queue = cq.raw, method = methodNameSlice, - host = authoritySlice, + host = null, deadline = gpr_inf_future(GPR_CLOCK_REALTIME), reserved = null ) ?: error("Failed to create call") grpc_slice_unref(methodNameSlice) - authoritySlice?.let { grpc_slice_unref(it) } return NativeClientCall( cq, rawCall, methodDescriptor, callJob diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/serverCallTags.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/serverCallTags.kt index 58f58d21d..f22bd1f0e 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/serverCallTags.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/serverCallTags.kt @@ -120,9 +120,6 @@ internal class LookupServerCallTag( return } - // TODO: check authority - // val host = rawDetails.host.toByteArray().decodeToString() - var method = rawDetails.method.toByteArray().decodeToString() // gRPC preserves the '/' character in the method name, From 0354de5b274d14b328818ce1b04a547fbd70d744 Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Tue, 9 Sep 2025 12:28:20 +0200 Subject: [PATCH 06/12] grpc-native: Add TLS tests Signed-off-by: Johannes Zottele --- .../kotlin/kotlinx/rpc/grpc/credentials.kt | 2 + .../rpc/grpc/test/proto/GrpcTlsTest.kt | 156 ++++++++++++++++++ .../rpc/grpc/test/proto/GrpcbTlsTest.kt | 86 ---------- .../kotlin/kotlinx/rpc/grpc/test/utils.kt | 21 +++ .../kotlinx/rpc/grpc/credentials.native.kt | 2 +- 5 files changed, 180 insertions(+), 87 deletions(-) create mode 100644 grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcTlsTest.kt delete mode 100644 grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbTlsTest.kt create mode 100644 grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/utils.kt diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt index a0e560372..59f5a0980 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt @@ -44,6 +44,8 @@ public enum class TlsClientAuth { /** * Clients are requested to present their identity, but clients without identities are * permitted. + * Also, if the client certificate is provided but cannot be verified, + * the client is permitted. */ OPTIONAL, diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcTlsTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcTlsTest.kt new file mode 100644 index 000000000..965eddfb1 --- /dev/null +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcTlsTest.kt @@ -0,0 +1,156 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.test.proto + +import hello.HelloRequest +import hello.HelloService +import hello.invoke +import kotlinx.coroutines.test.runTest +import kotlinx.rpc.RpcServer +import kotlinx.rpc.grpc.GrpcClient +import kotlinx.rpc.grpc.StatusCode +import kotlinx.rpc.grpc.TlsChannelCredentials +import kotlinx.rpc.grpc.TlsClientAuth +import kotlinx.rpc.grpc.TlsServerCredentials +import kotlinx.rpc.grpc.test.CA_PEM +import kotlinx.rpc.grpc.test.CLIENT_CERT_PEM +import kotlinx.rpc.grpc.test.CLIENT_KEY_PEM +import kotlinx.rpc.grpc.test.EchoRequest +import kotlinx.rpc.grpc.test.EchoService +import kotlinx.rpc.grpc.test.EchoServiceImpl +import kotlinx.rpc.grpc.test.SERVER_CERT_PEM +import kotlinx.rpc.grpc.test.SERVER_KEY_PEM +import kotlinx.rpc.grpc.test.assertGrpcFailure +import kotlinx.rpc.grpc.test.invoke +import kotlinx.rpc.registerService +import kotlinx.rpc.withService +import kotlin.test.Test +import kotlin.test.assertEquals + +class GrpcTlsTest : GrpcProtoTest() { + + override fun RpcServer.registerServices() { + registerService { EchoServiceImpl() } + } + + @Test + fun `test client side TLS with default credentials - should succeed`() = runTest { + // uses default client TLS credentials + // TODO: Use a test server controlled by us (KRPC-215) + val grpcClient = GrpcClient("grpcb.in", 9001) + val service = grpcClient.withService() + val request = HelloRequest { + greeting = "world" + } + val result = service.SayHello(request) + + assertEquals("hello world", result.reply) + + grpcClient.shutdown() + grpcClient.awaitTermination() + } + + @Test + fun `test TLS with valid certificates - should succeed`() { + val serverTls = TlsServerCredentials(SERVER_CERT_PEM, SERVER_KEY_PEM) + val clientTls = TlsChannelCredentials { trustManager(SERVER_CERT_PEM) } + + runGrpcTest(serverTls, clientTls, overrideAuthority = "foo.test.google.fr", test = ::defaultUnaryTest) + } + + @Test + fun `test mTLS with valid certificates - should succeed`() = runTest { + val serverTls = TlsServerCredentials(SERVER_CERT_PEM, SERVER_KEY_PEM) { + trustManager(CA_PEM) + clientAuth(TlsClientAuth.REQUIRE) + } + val clientTls = TlsChannelCredentials { + keyManager(CLIENT_CERT_PEM, CLIENT_KEY_PEM) + trustManager(CA_PEM) + } + + runGrpcTest(serverTls, clientTls, overrideAuthority = "foo.test.google.fr", test = ::defaultUnaryTest) + } + + @Test + fun `test mTLS with clientAuth optional - should succeed`() = runTest { + // the server uses a trustManager that does not know about the client certificate, + // so the client can authentication cannot be verified. + // but as the clientAuth is optional, the connection will succeed. + val caCertWithoutClient = SERVER_CERT_PEM + val serverTls = TlsServerCredentials(SERVER_CERT_PEM, SERVER_KEY_PEM) { + trustManager(caCertWithoutClient) + // clientAuth is optional, so a client without a certificate can connect + clientAuth(TlsClientAuth.OPTIONAL) + } + val clientTls = TlsChannelCredentials { + keyManager(CLIENT_CERT_PEM, CLIENT_KEY_PEM) + trustManager(CA_PEM) + } + + runGrpcTest(serverTls, clientTls, overrideAuthority = "foo.test.google.fr", test = ::defaultUnaryTest) + } + + @Test + fun `test mTLS with clientAuth required - should fail`() = runTest { + val serverTls = TlsServerCredentials(SERVER_CERT_PEM, SERVER_KEY_PEM) { + trustManager(CA_PEM) + // client must authenticate + clientAuth(TlsClientAuth.REQUIRE) + } + // client does NOT provide keyManager, only trusts CA + val clientTls = TlsChannelCredentials { + trustManager(CA_PEM) + } + + assertGrpcFailure(StatusCode.UNAVAILABLE) { + runGrpcTest(serverTls, clientTls, overrideAuthority = "foo.test.google.fr", test = ::defaultUnaryTest) + } + } + + @Test + fun `test TLS with no client trustManager - should fail`() = runTest { + val serverTls = TlsServerCredentials(SERVER_CERT_PEM, SERVER_KEY_PEM) + // client credential doesn't contain a trustManager, so server authentication will fail + val clientTls = TlsChannelCredentials {} + assertGrpcFailure(StatusCode.UNAVAILABLE) { + runGrpcTest(serverTls, clientTls, overrideAuthority = "foo.test.google.fr", test = ::defaultUnaryTest) + } + } + + @Test + fun `test TLS with invalid authority - should fail`() = runTest { + val serverTls = TlsServerCredentials(SERVER_CERT_PEM, SERVER_KEY_PEM) + val clientTls = TlsChannelCredentials { trustManager(CA_PEM) } + // the authority does not match the certificate + assertGrpcFailure(StatusCode.UNAVAILABLE) { + runGrpcTest(serverTls, clientTls, overrideAuthority = "invalid.host.name", test = ::defaultUnaryTest) + } + } + + @Test + fun `test TLS server with plaintext client - should fail`() = runTest { + val serverTls = TlsServerCredentials(SERVER_CERT_PEM, SERVER_KEY_PEM) + assertGrpcFailure(StatusCode.UNAVAILABLE) { + runGrpcTest(serverCreds = serverTls, overrideAuthority = "foo.test.google.fr", test = ::defaultUnaryTest) + } + } + + @Test + fun `test TLS client with plaintext server - should fail`() = runTest { + val clientTls = TlsChannelCredentials { trustManager(CA_PEM) } + assertGrpcFailure(StatusCode.UNAVAILABLE) { + runGrpcTest(clientCreds = clientTls, overrideAuthority = "foo.test.google.fr", test = ::defaultUnaryTest) + } + } + +} + +private suspend fun defaultUnaryTest(client: GrpcClient) { + val service = client.withService() + val request = EchoRequest { message = "Echo" } + val response = service.UnaryEcho(request) + assertEquals("Echo", response.message) +} \ No newline at end of file diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbTlsTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbTlsTest.kt deleted file mode 100644 index ce72e3627..000000000 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcbTlsTest.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.rpc.grpc.test.proto - -import hello.HelloRequest -import hello.HelloService -import hello.invoke -import kotlinx.coroutines.test.runTest -import kotlinx.rpc.RpcServer -import kotlinx.rpc.grpc.GrpcClient -import kotlinx.rpc.grpc.TlsChannelCredentials -import kotlinx.rpc.grpc.TlsClientAuth -import kotlinx.rpc.grpc.TlsServerCredentials -import kotlinx.rpc.grpc.test.CA_PEM -import kotlinx.rpc.grpc.test.CLIENT_CERT_PEM -import kotlinx.rpc.grpc.test.CLIENT_KEY_PEM -import kotlinx.rpc.grpc.test.EchoRequest -import kotlinx.rpc.grpc.test.EchoService -import kotlinx.rpc.grpc.test.EchoServiceImpl -import kotlinx.rpc.grpc.test.SERVER_CERT_PEM -import kotlinx.rpc.grpc.test.SERVER_KEY_PEM -import kotlinx.rpc.grpc.test.invoke -import kotlinx.rpc.registerService -import kotlinx.rpc.withService -import kotlin.test.Test -import kotlin.test.assertEquals - -class GrpcbTlsTest : GrpcProtoTest() { - - override fun RpcServer.registerServices() { - registerService { EchoServiceImpl() } - } - - @Test - fun testDefaultTlsCall() = runTest { - // uses default client TLS credentials - val grpcClient = GrpcClient("grpcb.in", 9001) - val service = grpcClient.withService() - val request = HelloRequest { - greeting = "world" - } - val result = service.SayHello(request) - - assertEquals("hello world", result.reply) - - // Ensure we don't leak the client channel between tests - grpcClient.shutdown() - grpcClient.awaitTermination() - } - - - @Test - fun testCustomTls() { - val serverTls = TlsServerCredentials(SERVER_CERT_PEM, SERVER_KEY_PEM) - val clientTls = TlsChannelCredentials { trustManager(SERVER_CERT_PEM) } - - runGrpcTest(serverTls, clientTls, overrideAuthority = "foo.test.google.fr") { client -> - val service = client.withService() - val request = EchoRequest { message = "Echo" } - val response = service.UnaryEcho(request) - assertEquals("Echo", response.message) - } - } - - @Test - fun testCustomMTls() = runTest { - val serverTls = TlsServerCredentials(SERVER_CERT_PEM, SERVER_KEY_PEM) { - trustManager(CA_PEM) - clientAuth(TlsClientAuth.REQUIRE) - } - val clientTls = TlsChannelCredentials { - keyManager(CLIENT_CERT_PEM, CLIENT_KEY_PEM) - trustManager(CA_PEM) - } - - runGrpcTest(serverTls, clientTls, overrideAuthority = "foo.test.google.fr") { client -> - val service = client.withService() - val request = EchoRequest { message = "Echo" } - val response = service.UnaryEcho(request) - assertEquals("Echo", response.message) - } - } - -} \ No newline at end of file diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/utils.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/utils.kt new file mode 100644 index 000000000..57282bb57 --- /dev/null +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/utils.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.grpc.test + +import kotlinx.rpc.grpc.StatusCode +import kotlinx.rpc.grpc.StatusException +import kotlinx.rpc.grpc.statusCode +import kotlin.test.assertContains +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + + +fun assertGrpcFailure(statusCode: StatusCode, message: String? = null, block: () -> Unit) { + val exc = assertFailsWith(message) { block() } + assertEquals(statusCode, exc.getStatus().statusCode) + if (message != null) { + assertContains(message, exc.getStatus().getDescription() ?: "") + } +} diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt index 85079f39b..2cc7780cf 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt @@ -187,6 +187,6 @@ private class TlsCredentialsOptionsBuilder { private fun TlsClientAuth.toRaw(): grpc_ssl_client_certificate_request_type = when (this) { TlsClientAuth.NONE -> grpc_ssl_client_certificate_request_type.GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE - TlsClientAuth.OPTIONAL -> grpc_ssl_client_certificate_request_type.GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_AND_VERIFY + TlsClientAuth.OPTIONAL -> grpc_ssl_client_certificate_request_type.GRPC_SSL_REQUEST_CLIENT_CERTIFICATE_BUT_DONT_VERIFY TlsClientAuth.REQUIRE -> grpc_ssl_client_certificate_request_type.GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY } \ No newline at end of file From c28fc9682ec4c9d8c144039234638945a09cd131 Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Tue, 9 Sep 2025 12:30:41 +0200 Subject: [PATCH 07/12] grpc-native: Rename ChannelCredentials to ClientCredentials Signed-off-by: Johannes Zottele --- .../kotlin/kotlinx/rpc/grpc/GrpcClient.kt | 4 +-- .../kotlin/kotlinx/rpc/grpc/ManagedChannel.kt | 4 +-- .../kotlin/kotlinx/rpc/grpc/credentials.kt | 20 +++++++------- .../rpc/grpc/test/proto/GrpcProtoTest.kt | 4 +-- .../rpc/grpc/test/proto/GrpcTlsTest.kt | 16 ++++++------ .../kotlinx/rpc/grpc/ManagedChannel.jvm.kt | 4 +-- .../kotlinx/rpc/grpc/credentials.jvm.kt | 18 ++++++------- .../kotlinx/rpc/grpc/ManagedChannel.native.kt | 10 +++---- .../kotlinx/rpc/grpc/credentials.native.kt | 26 +++++++++---------- .../rpc/grpc/internal/NativeManagedChannel.kt | 4 +-- 10 files changed, 55 insertions(+), 55 deletions(-) diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcClient.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcClient.kt index 7c184e0f8..8204b6f7f 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcClient.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcClient.kt @@ -131,7 +131,7 @@ public class GrpcClient internal constructor( public fun GrpcClient( hostname: String, port: Int, - credentials: ChannelCredentials? = null, + credentials: ClientCredentials? = null, messageCodecResolver: MessageCodecResolver = EmptyMessageCodecResolver, configure: ManagedChannelBuilder<*>.() -> Unit = {}, ): GrpcClient { @@ -144,7 +144,7 @@ public fun GrpcClient( */ public fun GrpcClient( target: String, - credentials: ChannelCredentials? = null, + credentials: ClientCredentials? = null, messageCodecResolver: MessageCodecResolver = EmptyMessageCodecResolver, configure: ManagedChannelBuilder<*>.() -> Unit = {}, ): GrpcClient { diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt index 6414533b7..bcfa015f5 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.kt @@ -76,12 +76,12 @@ public expect abstract class ManagedChannelBuilder> internal expect fun ManagedChannelBuilder( hostname: String, port: Int, - credentials: ChannelCredentials? = null, + credentials: ClientCredentials? = null, ): ManagedChannelBuilder<*> internal expect fun ManagedChannelBuilder( target: String, - credentials: ChannelCredentials? = null, + credentials: ClientCredentials? = null, ): ManagedChannelBuilder<*> internal expect fun ManagedChannelBuilder<*>.buildChannel(): ManagedChannel diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt index 59f5a0980..e31582917 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt @@ -6,17 +6,17 @@ package kotlinx.rpc.grpc/* * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ -public expect abstract class ChannelCredentials +public expect abstract class ClientCredentials public expect abstract class ServerCredentials -public expect class InsecureChannelCredentials : ChannelCredentials +public expect class InsecureClientCredentials : ClientCredentials public expect class InsecureServerCredentials : ServerCredentials -public expect class TlsChannelCredentials : ChannelCredentials +public expect class TlsClientCredentials : ClientCredentials public expect class TlsServerCredentials : ServerCredentials -public fun TlsChannelCredentials(configure: TlsChannelCredentialsBuilder.() -> Unit = {}): ChannelCredentials { - val builder = TlsChannelCredentialsBuilder() +public fun TlsClientCredentials(configure: TlsClientCredentialsBuilder.() -> Unit = {}): ClientCredentials { + val builder = TlsClientCredentialsBuilder() builder.configure() return builder.build() } @@ -31,10 +31,10 @@ public fun TlsServerCredentials( return builder.build() } -public interface TlsChannelCredentialsBuilder { - public fun trustManager(rootCertsPem: String): TlsChannelCredentialsBuilder - public fun keyManager(certChainPem: String, privateKeyPem: String): TlsChannelCredentialsBuilder - public fun build(): ChannelCredentials +public interface TlsClientCredentialsBuilder { + public fun trustManager(rootCertsPem: String): TlsClientCredentialsBuilder + public fun keyManager(certChainPem: String, privateKeyPem: String): TlsClientCredentialsBuilder + public fun build(): ClientCredentials } public enum class TlsClientAuth { @@ -62,7 +62,7 @@ public interface TlsServerCredentialsBuilder { public fun build(): ServerCredentials } -internal expect fun TlsChannelCredentialsBuilder(): TlsChannelCredentialsBuilder +internal expect fun TlsClientCredentialsBuilder(): TlsClientCredentialsBuilder internal expect fun TlsServerCredentialsBuilder( certChain: String, privateKey: String, diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcProtoTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcProtoTest.kt index 07afe3c1f..92050dd19 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcProtoTest.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcProtoTest.kt @@ -8,7 +8,7 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.test.runTest import kotlinx.rpc.RpcServer -import kotlinx.rpc.grpc.ChannelCredentials +import kotlinx.rpc.grpc.ClientCredentials import kotlinx.rpc.grpc.GrpcClient import kotlinx.rpc.grpc.GrpcServer import kotlinx.rpc.grpc.ServerCredentials @@ -20,7 +20,7 @@ abstract class GrpcProtoTest { protected fun runGrpcTest( serverCreds: ServerCredentials? = null, - clientCreds: ChannelCredentials? = null, + clientCreds: ClientCredentials? = null, overrideAuthority: String? = null, test: suspend (GrpcClient) -> Unit, ) = runTest { diff --git a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcTlsTest.kt b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcTlsTest.kt index 965eddfb1..196629f3f 100644 --- a/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcTlsTest.kt +++ b/grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/proto/GrpcTlsTest.kt @@ -11,8 +11,8 @@ import kotlinx.coroutines.test.runTest import kotlinx.rpc.RpcServer import kotlinx.rpc.grpc.GrpcClient import kotlinx.rpc.grpc.StatusCode -import kotlinx.rpc.grpc.TlsChannelCredentials import kotlinx.rpc.grpc.TlsClientAuth +import kotlinx.rpc.grpc.TlsClientCredentials import kotlinx.rpc.grpc.TlsServerCredentials import kotlinx.rpc.grpc.test.CA_PEM import kotlinx.rpc.grpc.test.CLIENT_CERT_PEM @@ -55,7 +55,7 @@ class GrpcTlsTest : GrpcProtoTest() { @Test fun `test TLS with valid certificates - should succeed`() { val serverTls = TlsServerCredentials(SERVER_CERT_PEM, SERVER_KEY_PEM) - val clientTls = TlsChannelCredentials { trustManager(SERVER_CERT_PEM) } + val clientTls = TlsClientCredentials { trustManager(SERVER_CERT_PEM) } runGrpcTest(serverTls, clientTls, overrideAuthority = "foo.test.google.fr", test = ::defaultUnaryTest) } @@ -66,7 +66,7 @@ class GrpcTlsTest : GrpcProtoTest() { trustManager(CA_PEM) clientAuth(TlsClientAuth.REQUIRE) } - val clientTls = TlsChannelCredentials { + val clientTls = TlsClientCredentials { keyManager(CLIENT_CERT_PEM, CLIENT_KEY_PEM) trustManager(CA_PEM) } @@ -85,7 +85,7 @@ class GrpcTlsTest : GrpcProtoTest() { // clientAuth is optional, so a client without a certificate can connect clientAuth(TlsClientAuth.OPTIONAL) } - val clientTls = TlsChannelCredentials { + val clientTls = TlsClientCredentials { keyManager(CLIENT_CERT_PEM, CLIENT_KEY_PEM) trustManager(CA_PEM) } @@ -101,7 +101,7 @@ class GrpcTlsTest : GrpcProtoTest() { clientAuth(TlsClientAuth.REQUIRE) } // client does NOT provide keyManager, only trusts CA - val clientTls = TlsChannelCredentials { + val clientTls = TlsClientCredentials { trustManager(CA_PEM) } @@ -114,7 +114,7 @@ class GrpcTlsTest : GrpcProtoTest() { fun `test TLS with no client trustManager - should fail`() = runTest { val serverTls = TlsServerCredentials(SERVER_CERT_PEM, SERVER_KEY_PEM) // client credential doesn't contain a trustManager, so server authentication will fail - val clientTls = TlsChannelCredentials {} + val clientTls = TlsClientCredentials {} assertGrpcFailure(StatusCode.UNAVAILABLE) { runGrpcTest(serverTls, clientTls, overrideAuthority = "foo.test.google.fr", test = ::defaultUnaryTest) } @@ -123,7 +123,7 @@ class GrpcTlsTest : GrpcProtoTest() { @Test fun `test TLS with invalid authority - should fail`() = runTest { val serverTls = TlsServerCredentials(SERVER_CERT_PEM, SERVER_KEY_PEM) - val clientTls = TlsChannelCredentials { trustManager(CA_PEM) } + val clientTls = TlsClientCredentials { trustManager(CA_PEM) } // the authority does not match the certificate assertGrpcFailure(StatusCode.UNAVAILABLE) { runGrpcTest(serverTls, clientTls, overrideAuthority = "invalid.host.name", test = ::defaultUnaryTest) @@ -140,7 +140,7 @@ class GrpcTlsTest : GrpcProtoTest() { @Test fun `test TLS client with plaintext server - should fail`() = runTest { - val clientTls = TlsChannelCredentials { trustManager(CA_PEM) } + val clientTls = TlsClientCredentials { trustManager(CA_PEM) } assertGrpcFailure(StatusCode.UNAVAILABLE) { runGrpcTest(clientCreds = clientTls, overrideAuthority = "foo.test.google.fr", test = ::defaultUnaryTest) } diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.jvm.kt index 350b5e550..ebd658136 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.jvm.kt @@ -27,7 +27,7 @@ internal actual fun ManagedChannelBuilder<*>.buildChannel(): ManagedChannel { internal actual fun ManagedChannelBuilder( hostname: String, port: Int, - credentials: ChannelCredentials?, + credentials: ClientCredentials?, ): ManagedChannelBuilder<*> { if (credentials != null) return io.grpc.Grpc.newChannelBuilderForAddress(hostname, port, credentials) return io.grpc.ManagedChannelBuilder.forAddress(hostname, port) @@ -35,7 +35,7 @@ internal actual fun ManagedChannelBuilder( internal actual fun ManagedChannelBuilder( target: String, - credentials: ChannelCredentials?, + credentials: ClientCredentials?, ): ManagedChannelBuilder<*> { if (credentials != null) return io.grpc.Grpc.newChannelBuilder(target, credentials) return io.grpc.ManagedChannelBuilder.forTarget(target) diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt index 2e5e247fc..068c61e82 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt @@ -4,24 +4,24 @@ package kotlinx.rpc.grpc -public actual typealias ChannelCredentials = io.grpc.ChannelCredentials +public actual typealias ClientCredentials = io.grpc.ChannelCredentials public actual typealias ServerCredentials = io.grpc.ServerCredentials // we need a wrapper for InsecureChannelCredentials as our constructor would conflict with the private // java constructor. -public actual typealias InsecureChannelCredentials = io.grpc.InsecureChannelCredentials +public actual typealias InsecureClientCredentials = io.grpc.InsecureChannelCredentials public actual typealias InsecureServerCredentials = io.grpc.InsecureServerCredentials -public actual typealias TlsChannelCredentials = io.grpc.TlsChannelCredentials +public actual typealias TlsClientCredentials = io.grpc.TlsChannelCredentials public actual typealias TlsServerCredentials = io.grpc.TlsServerCredentials -internal actual fun TlsChannelCredentialsBuilder(): TlsChannelCredentialsBuilder = - object : TlsChannelCredentialsBuilder { - private var cb = TlsChannelCredentials.newBuilder() +internal actual fun TlsClientCredentialsBuilder(): TlsClientCredentialsBuilder = + object : TlsClientCredentialsBuilder { + private var cb = TlsClientCredentials.newBuilder() - override fun trustManager(rootCertsPem: String): TlsChannelCredentialsBuilder { + override fun trustManager(rootCertsPem: String): TlsClientCredentialsBuilder { cb.trustManager(rootCertsPem.byteInputStream()) return this } @@ -29,12 +29,12 @@ internal actual fun TlsChannelCredentialsBuilder(): TlsChannelCredentialsBuilder override fun keyManager( certChainPem: String, privateKeyPem: String, - ): TlsChannelCredentialsBuilder { + ): TlsClientCredentialsBuilder { cb.keyManager(certChainPem.byteInputStream(), privateKeyPem.byteInputStream()) return this } - override fun build(): ChannelCredentials { + override fun build(): ClientCredentials { return cb.build() } } diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt index 42162f6b4..85a5439e6 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/ManagedChannel.native.kt @@ -28,7 +28,7 @@ public actual abstract class ManagedChannelBuilder> internal class NativeManagedChannelBuilder( private val target: String, - private var credentials: Lazy, + private var credentials: Lazy, ) : ManagedChannelBuilder() { private var authority: String? = null @@ -61,14 +61,14 @@ internal actual fun ManagedChannelBuilder<*>.buildChannel(): ManagedChannel { internal actual fun ManagedChannelBuilder( hostname: String, port: Int, - credentials: ChannelCredentials?, + credentials: ClientCredentials?, ): ManagedChannelBuilder<*> { - val credentials = if (credentials == null) lazy { TlsChannelCredentials() } else lazy { credentials } + val credentials = if (credentials == null) lazy { TlsClientCredentials() } else lazy { credentials } return NativeManagedChannelBuilder(target = "$hostname:$port", credentials) } -internal actual fun ManagedChannelBuilder(target: String, credentials: ChannelCredentials?): ManagedChannelBuilder<*> { - val credentials = if (credentials == null) lazy { TlsChannelCredentials() } else lazy { credentials } +internal actual fun ManagedChannelBuilder(target: String, credentials: ClientCredentials?): ManagedChannelBuilder<*> { + val credentials = if (credentials == null) lazy { TlsClientCredentials() } else lazy { credentials } return NativeManagedChannelBuilder(target, credentials) } diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt index 2cc7780cf..2aa7af18a 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt @@ -31,7 +31,7 @@ import libkgrpc.grpc_tls_server_credentials_create import kotlin.experimental.ExperimentalNativeApi import kotlin.native.ref.createCleaner -public actual abstract class ChannelCredentials internal constructor( +public actual abstract class ClientCredentials internal constructor( internal val raw: CPointer, ) { @Suppress("unused") @@ -49,25 +49,25 @@ public actual abstract class ServerCredentials internal constructor( } } -public actual class InsecureChannelCredentials internal constructor( +public actual class InsecureClientCredentials internal constructor( raw: CPointer, -) : ChannelCredentials(raw) +) : ClientCredentials(raw) public actual class InsecureServerCredentials internal constructor( raw: CPointer, ) : ServerCredentials(raw) -public actual class TlsChannelCredentials internal constructor( +public actual class TlsClientCredentials internal constructor( raw: CPointer, -) : ChannelCredentials(raw) +) : ClientCredentials(raw) public actual class TlsServerCredentials( raw: CPointer, ) : ServerCredentials(raw) -public fun InsecureChannelCredentials(): ChannelCredentials { - return InsecureChannelCredentials( +public fun InsecureChannelCredentials(): ClientCredentials { + return InsecureClientCredentials( grpc_insecure_credentials_create() ?: error("grpc_insecure_credentials_create() returned null") ) } @@ -78,11 +78,11 @@ public fun InsecureServerCredentials(): ServerCredentials { ) } -internal actual fun TlsChannelCredentialsBuilder(): TlsChannelCredentialsBuilder = - object : TlsChannelCredentialsBuilder { +internal actual fun TlsClientCredentialsBuilder(): TlsClientCredentialsBuilder = + object : TlsClientCredentialsBuilder { var optionsBuilder = TlsCredentialsOptionsBuilder() - override fun trustManager(rootCertsPem: String): TlsChannelCredentialsBuilder { + override fun trustManager(rootCertsPem: String): TlsClientCredentialsBuilder { optionsBuilder.trustManager(rootCertsPem) return this } @@ -90,19 +90,19 @@ internal actual fun TlsChannelCredentialsBuilder(): TlsChannelCredentialsBuilder override fun keyManager( certChainPem: String, privateKeyPem: String, - ): TlsChannelCredentialsBuilder { + ): TlsClientCredentialsBuilder { optionsBuilder.keyManager(certChainPem, privateKeyPem) return this } - override fun build(): ChannelCredentials { + override fun build(): ClientCredentials { val opts = optionsBuilder.build() val creds = grpc_tls_credentials_create(opts) ?: run { grpc_tls_credentials_options_destroy(opts); error("TLS channel credential creation failed") } - return TlsChannelCredentials(creds) + return TlsClientCredentials(creds) } } diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt index 9ebee5923..3c06d267d 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt @@ -19,7 +19,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.rpc.grpc.ChannelCredentials +import kotlinx.rpc.grpc.ClientCredentials import kotlinx.rpc.grpc.ManagedChannel import kotlinx.rpc.grpc.ManagedChannelPlatform import libkgrpc.GPR_CLOCK_REALTIME @@ -48,7 +48,7 @@ internal class NativeManagedChannel( target: String, val authority: String?, // we must store them, otherwise the credentials are getting released - credentials: ChannelCredentials, + credentials: ClientCredentials, ) : ManagedChannel, ManagedChannelPlatform() { // a reference to make sure the grpc_init() was called. (it is released after shutdown) From 52cfa5d7de7a1dc8d35103c69b2f2a419cacf2ae Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Tue, 9 Sep 2025 13:36:30 +0200 Subject: [PATCH 08/12] grpc-native: Make TlsCredentials build() method internal Signed-off-by: Johannes Zottele --- .../kotlin/kotlinx/rpc/grpc/credentials.kt | 5 +- .../kotlinx/rpc/grpc/credentials.jvm.kt | 68 +++++++++++-------- .../kotlinx/rpc/grpc/credentials.native.kt | 66 ++++++++++-------- 3 files changed, 81 insertions(+), 58 deletions(-) diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt index e31582917..285f3df75 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt @@ -34,7 +34,6 @@ public fun TlsServerCredentials( public interface TlsClientCredentialsBuilder { public fun trustManager(rootCertsPem: String): TlsClientCredentialsBuilder public fun keyManager(certChainPem: String, privateKeyPem: String): TlsClientCredentialsBuilder - public fun build(): ClientCredentials } public enum class TlsClientAuth { @@ -59,7 +58,6 @@ public enum class TlsClientAuth { public interface TlsServerCredentialsBuilder { public fun trustManager(rootCertsPem: String): TlsServerCredentialsBuilder public fun clientAuth(clientAuth: TlsClientAuth): TlsServerCredentialsBuilder - public fun build(): ServerCredentials } internal expect fun TlsClientCredentialsBuilder(): TlsClientCredentialsBuilder @@ -67,3 +65,6 @@ internal expect fun TlsServerCredentialsBuilder( certChain: String, privateKey: String, ): TlsServerCredentialsBuilder + +internal expect fun TlsClientCredentialsBuilder.build(): ClientCredentials +internal expect fun TlsServerCredentialsBuilder.build(): ServerCredentials diff --git a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt index 068c61e82..3ba8e4ce4 100644 --- a/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt +++ b/grpc/grpc-core/src/jvmMain/kotlin/kotlinx/rpc/grpc/credentials.jvm.kt @@ -16,35 +16,49 @@ public actual typealias InsecureServerCredentials = io.grpc.InsecureServerCreden public actual typealias TlsClientCredentials = io.grpc.TlsChannelCredentials public actual typealias TlsServerCredentials = io.grpc.TlsServerCredentials -internal actual fun TlsClientCredentialsBuilder(): TlsClientCredentialsBuilder = - object : TlsClientCredentialsBuilder { - private var cb = TlsClientCredentials.newBuilder() - - - override fun trustManager(rootCertsPem: String): TlsClientCredentialsBuilder { - cb.trustManager(rootCertsPem.byteInputStream()) - return this - } - - override fun keyManager( - certChainPem: String, - privateKeyPem: String, - ): TlsClientCredentialsBuilder { - cb.keyManager(certChainPem.byteInputStream(), privateKeyPem.byteInputStream()) - return this - } - - override fun build(): ClientCredentials { - return cb.build() - } - } - +internal actual fun TlsClientCredentialsBuilder(): TlsClientCredentialsBuilder = JvmTlsCLientCredentialBuilder() internal actual fun TlsServerCredentialsBuilder( certChain: String, privateKey: String, -): TlsServerCredentialsBuilder = object : TlsServerCredentialsBuilder { +): TlsServerCredentialsBuilder = JvmTlsServerCredentialBuilder(certChain, privateKey) + +internal actual fun TlsClientCredentialsBuilder.build(): ClientCredentials { + return (this as JvmTlsCLientCredentialBuilder).build() +} + +internal actual fun TlsServerCredentialsBuilder.build(): ServerCredentials { + return (this as JvmTlsServerCredentialBuilder).build() +} + +private class JvmTlsCLientCredentialBuilder : TlsClientCredentialsBuilder { + private var cb = TlsClientCredentials.newBuilder() + + + override fun trustManager(rootCertsPem: String): TlsClientCredentialsBuilder { + cb.trustManager(rootCertsPem.byteInputStream()) + return this + } + + override fun keyManager( + certChainPem: String, + privateKeyPem: String, + ): TlsClientCredentialsBuilder { + cb.keyManager(certChainPem.byteInputStream(), privateKeyPem.byteInputStream()) + return this + } + + fun build(): ClientCredentials { + return cb.build() + } +} + +private class JvmTlsServerCredentialBuilder(certChain: String, privateKey: String) : TlsServerCredentialsBuilder { private var sb = TlsServerCredentials.newBuilder() + init { + sb.keyManager(certChain.byteInputStream(), privateKey.byteInputStream()) + } + override fun trustManager(rootCertsPem: String): TlsServerCredentialsBuilder { sb.trustManager(rootCertsPem.byteInputStream()) return this @@ -55,15 +69,13 @@ internal actual fun TlsServerCredentialsBuilder( return this } - init { - sb.keyManager(certChain.byteInputStream(), privateKey.byteInputStream()) - } - override fun build(): ServerCredentials { + fun build(): ServerCredentials { return sb.build() } } + private fun TlsClientAuth.toJava(): io.grpc.TlsServerCredentials.ClientAuth = when (this) { TlsClientAuth.NONE -> io.grpc.TlsServerCredentials.ClientAuth.NONE TlsClientAuth.OPTIONAL -> io.grpc.TlsServerCredentials.ClientAuth.OPTIONAL diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt index 2aa7af18a..b10e7f65b 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/credentials.native.kt @@ -78,38 +78,48 @@ public fun InsecureServerCredentials(): ServerCredentials { ) } -internal actual fun TlsClientCredentialsBuilder(): TlsClientCredentialsBuilder = - object : TlsClientCredentialsBuilder { - var optionsBuilder = TlsCredentialsOptionsBuilder() +internal actual fun TlsClientCredentialsBuilder(): TlsClientCredentialsBuilder = NativeTlsClientCredentialsBuilder() +internal actual fun TlsServerCredentialsBuilder( + certChain: String, + privateKey: String, +): TlsServerCredentialsBuilder = NativeTlsServerCredentialsBuilder(certChain, privateKey) - override fun trustManager(rootCertsPem: String): TlsClientCredentialsBuilder { - optionsBuilder.trustManager(rootCertsPem) - return this - } +internal actual fun TlsClientCredentialsBuilder.build(): ClientCredentials { + return (this as NativeTlsClientCredentialsBuilder).build() +} - override fun keyManager( - certChainPem: String, - privateKeyPem: String, - ): TlsClientCredentialsBuilder { - optionsBuilder.keyManager(certChainPem, privateKeyPem) - return this - } +internal actual fun TlsServerCredentialsBuilder.build(): ServerCredentials { + return (this as NativeTlsServerCredentialsBuilder).build() +} - override fun build(): ClientCredentials { - val opts = optionsBuilder.build() - val creds = grpc_tls_credentials_create(opts) - ?: run { - grpc_tls_credentials_options_destroy(opts); - error("TLS channel credential creation failed") - } - return TlsClientCredentials(creds) - } +private class NativeTlsClientCredentialsBuilder : TlsClientCredentialsBuilder { + var optionsBuilder = TlsCredentialsOptionsBuilder() + + override fun trustManager(rootCertsPem: String): TlsClientCredentialsBuilder { + optionsBuilder.trustManager(rootCertsPem) + return this } -internal actual fun TlsServerCredentialsBuilder( - certChain: String, - privateKey: String, -): TlsServerCredentialsBuilder = object : TlsServerCredentialsBuilder { + override fun keyManager( + certChainPem: String, + privateKeyPem: String, + ): TlsClientCredentialsBuilder { + optionsBuilder.keyManager(certChainPem, privateKeyPem) + return this + } + + fun build(): ClientCredentials { + val opts = optionsBuilder.build() + val creds = grpc_tls_credentials_create(opts) + ?: run { + grpc_tls_credentials_options_destroy(opts); + error("TLS channel credential creation failed") + } + return TlsClientCredentials(creds) + } +} + +private class NativeTlsServerCredentialsBuilder(certChain: String, privateKey: String) : TlsServerCredentialsBuilder { var optionsBuilder = TlsCredentialsOptionsBuilder() init { @@ -126,7 +136,7 @@ internal actual fun TlsServerCredentialsBuilder( return this } - override fun build(): TlsServerCredentials { + fun build(): TlsServerCredentials { val opts = optionsBuilder.build() val creds = grpc_tls_server_credentials_create(opts) ?: run { From 0ea6ce753fc943738b6f556dd19773513d76f815 Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Tue, 9 Sep 2025 13:57:51 +0200 Subject: [PATCH 09/12] grpc-native: Fix wrong comment Signed-off-by: Johannes Zottele --- .../src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt index 285f3df75..dcebf9d5f 100644 --- a/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt +++ b/grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/credentials.kt @@ -2,9 +2,7 @@ * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ -package kotlinx.rpc.grpc/* - * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ +package kotlinx.rpc.grpc public expect abstract class ClientCredentials public expect abstract class ServerCredentials From a04ad58395c338f619d0ff8c3ec7ffc732408573 Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Wed, 10 Sep 2025 14:38:49 +0200 Subject: [PATCH 10/12] grpc: Undo autoformat of generated test files Signed-off-by: Johannes Zottele --- .../kotlinx/rpc/codegen/test/runners/BoxTestGenerated.java | 2 +- .../rpc/codegen/test/runners/DiagnosticTestGenerated.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/BoxTestGenerated.java b/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/BoxTestGenerated.java index d68a29b72..a2cadff3c 100644 --- a/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/BoxTestGenerated.java +++ b/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/BoxTestGenerated.java @@ -7,9 +7,9 @@ package kotlinx.rpc.codegen.test.runners; import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.util.KtTestUtil; import org.jetbrains.kotlin.test.TargetBackend; import org.jetbrains.kotlin.test.TestMetadata; -import org.jetbrains.kotlin.test.util.KtTestUtil; import org.junit.jupiter.api.Test; import java.io.File; diff --git a/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/DiagnosticTestGenerated.java b/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/DiagnosticTestGenerated.java index 724b13026..0c124f313 100644 --- a/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/DiagnosticTestGenerated.java +++ b/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/DiagnosticTestGenerated.java @@ -7,9 +7,9 @@ package kotlinx.rpc.codegen.test.runners; import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.util.KtTestUtil; import org.jetbrains.kotlin.test.TargetBackend; import org.jetbrains.kotlin.test.TestMetadata; -import org.jetbrains.kotlin.test.util.KtTestUtil; import org.junit.jupiter.api.Test; import java.io.File; From 79c5bcafecdf842a20881045f8a8363e0fc878a2 Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Wed, 10 Sep 2025 14:43:09 +0200 Subject: [PATCH 11/12] grpc: Undo autoformat of generated test files Signed-off-by: Johannes Zottele --- .../test/runners/BoxTestGenerated.java | 72 ++++++++-------- .../test/runners/DiagnosticTestGenerated.java | 84 +++++++++---------- 2 files changed, 76 insertions(+), 80 deletions(-) diff --git a/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/BoxTestGenerated.java b/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/BoxTestGenerated.java index a2cadff3c..e29c721ec 100644 --- a/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/BoxTestGenerated.java +++ b/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/BoxTestGenerated.java @@ -15,45 +15,43 @@ import java.io.File; import java.util.regex.Pattern; -/** - * This class is generated by {@link kotlinx.rpc.codegen.test.GenerateTestsKt}. DO NOT MODIFY MANUALLY - */ +/** This class is generated by {@link kotlinx.rpc.codegen.test.GenerateTestsKt}. DO NOT MODIFY MANUALLY */ @SuppressWarnings("all") @TestMetadata("src/testData/box") @TestDataPath("$PROJECT_ROOT") public class BoxTestGenerated extends AbstractBoxTest { - @Test - public void testAllFilesPresentInBox() { - KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("src/testData/box"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); - } - - @Test - @TestMetadata("customParameterTypes.kt") - public void testCustomParameterTypes() { - runTest("src/testData/box/customParameterTypes.kt"); - } - - @Test - @TestMetadata("flowParameter.kt") - public void testFlowParameter() { - runTest("src/testData/box/flowParameter.kt"); - } - - @Test - @TestMetadata("grpc.kt") - public void testGrpc() { - runTest("src/testData/box/grpc.kt"); - } - - @Test - @TestMetadata("multiModule.kt") - public void testMultiModule() { - runTest("src/testData/box/multiModule.kt"); - } - - @Test - @TestMetadata("simple.kt") - public void testSimple() { - runTest("src/testData/box/simple.kt"); - } + @Test + public void testAllFilesPresentInBox() { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("src/testData/box"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @Test + @TestMetadata("customParameterTypes.kt") + public void testCustomParameterTypes() { + runTest("src/testData/box/customParameterTypes.kt"); + } + + @Test + @TestMetadata("flowParameter.kt") + public void testFlowParameter() { + runTest("src/testData/box/flowParameter.kt"); + } + + @Test + @TestMetadata("grpc.kt") + public void testGrpc() { + runTest("src/testData/box/grpc.kt"); + } + + @Test + @TestMetadata("multiModule.kt") + public void testMultiModule() { + runTest("src/testData/box/multiModule.kt"); + } + + @Test + @TestMetadata("simple.kt") + public void testSimple() { + runTest("src/testData/box/simple.kt"); + } } diff --git a/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/DiagnosticTestGenerated.java b/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/DiagnosticTestGenerated.java index 0c124f313..3bc448f56 100644 --- a/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/DiagnosticTestGenerated.java +++ b/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/DiagnosticTestGenerated.java @@ -15,51 +15,49 @@ import java.io.File; import java.util.regex.Pattern; -/** - * This class is generated by {@link kotlinx.rpc.codegen.test.GenerateTestsKt}. DO NOT MODIFY MANUALLY - */ +/** This class is generated by {@link kotlinx.rpc.codegen.test.GenerateTestsKt}. DO NOT MODIFY MANUALLY */ @SuppressWarnings("all") @TestMetadata("src/testData/diagnostics") @TestDataPath("$PROJECT_ROOT") public class DiagnosticTestGenerated extends AbstractDiagnosticTest { - @Test - public void testAllFilesPresentInDiagnostics() { - KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("src/testData/diagnostics"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); - } - - @Test - @TestMetadata("checkedAnnotation.kt") - public void testCheckedAnnotation() { - runTest("src/testData/diagnostics/checkedAnnotation.kt"); - } - - @Test - @TestMetadata("grpc.kt") - public void testGrpc() { - runTest("src/testData/diagnostics/grpc.kt"); - } - - @Test - @TestMetadata("rpcChecked.kt") - public void testRpcChecked() { - runTest("src/testData/diagnostics/rpcChecked.kt"); - } - - @Test - @TestMetadata("rpcService.kt") - public void testRpcService() { - runTest("src/testData/diagnostics/rpcService.kt"); - } - - @Test - @TestMetadata("strictMode.kt") - public void testStrictMode() { - runTest("src/testData/diagnostics/strictMode.kt"); - } - - @Test - @TestMetadata("withCodec.kt") - public void testWithCodec() { - runTest("src/testData/diagnostics/withCodec.kt"); - } + @Test + public void testAllFilesPresentInDiagnostics() { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("src/testData/diagnostics"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @Test + @TestMetadata("checkedAnnotation.kt") + public void testCheckedAnnotation() { + runTest("src/testData/diagnostics/checkedAnnotation.kt"); + } + + @Test + @TestMetadata("grpc.kt") + public void testGrpc() { + runTest("src/testData/diagnostics/grpc.kt"); + } + + @Test + @TestMetadata("rpcChecked.kt") + public void testRpcChecked() { + runTest("src/testData/diagnostics/rpcChecked.kt"); + } + + @Test + @TestMetadata("rpcService.kt") + public void testRpcService() { + runTest("src/testData/diagnostics/rpcService.kt"); + } + + @Test + @TestMetadata("strictMode.kt") + public void testStrictMode() { + runTest("src/testData/diagnostics/strictMode.kt"); + } + + @Test + @TestMetadata("withCodec.kt") + public void testWithCodec() { + runTest("src/testData/diagnostics/withCodec.kt"); + } } From 1d231a9e7178c3f81ecf7120a2c91558582d1cae Mon Sep 17 00:00:00 2001 From: Johannes Zottele Date: Wed, 10 Sep 2025 14:47:20 +0200 Subject: [PATCH 12/12] grpc: Address PR comment Signed-off-by: Johannes Zottele --- .../kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt index 3c06d267d..ba0a7d9f0 100644 --- a/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt +++ b/grpc/grpc-core/src/nativeMain/kotlin/kotlinx/rpc/grpc/internal/NativeManagedChannel.kt @@ -64,7 +64,10 @@ internal class NativeManagedChannel( internal val raw: CPointer = memScoped { val args = authority?.let { - var authorityOverride = alloc { + // the C Core API doesn't have a way to override the authority (used for TLS SNI) as it + // is available in the Java gRPC implementation. + // instead, it can be done by setting the "grpc.ssl_target_name_override" argument. + val authorityOverride = alloc { type = grpc_arg_type.GRPC_ARG_STRING key = "grpc.ssl_target_name_override".cstr.ptr value.string = authority.cstr.ptr