Skip to content

[gRPC] iOS native target cannot validate TLS against the system trust store #688

@honzamalousek

Description

@honzamalousek

Is your feature request related to a problem? Please describe.

Version 0.11.0-grpc-187
In GrpcTlsClientCredentialsBuilder there is a comment:

Configures the trust manager with root CA certificates for server authentication.
The trust manager validates the server's certificate chain. The provided root certificates are used to verify that the server's certificate is signed by a trusted CA.
If not specified, the system's default trust store is used.

What works

On JVM/Android, GrpcTlsClientCredentials() with no overrides delegates to the platform default trust manager and validates against the system trust store. This is the expected behavior and works as intended.

What doesn't work on iOS

On the iOS native target (iosArm64 etc.), the same no-override construction fails. The grpc-core C library linked into the .klib does not read the iOS Keychain — at handshake time it tries to load /usr/share/grpc/roots.pem, which doesn't exist on iOS, and the handshake fails with a "Failed to create security handshaker" error.

// iosArm64 — fails with the stack trace below
val client = GrpcClient("your.server.example:443") {
    credentials = GrpcTlsClientCredentials()  // no overrides
}
// The same construction on JVM/Android works against the system trust store.
kotlinx.rpc.grpc.GrpcStatusException: UNAVAILABLE: failed to connect to all addresses;
    last error: UNKNOWN: ipv4:<redacted>:443: Failed to create security handshaker.
    at kfun:kotlinx.rpc.grpc.GrpcStatusException#<init>(...)
    at kfun:kotlinx.rpc.grpc.client.internal.ClientCallScopeImpl$channelResponseListener$3.invoke
    at kfun:kotlinx.rpc.grpc.client.internal.NativeClientCall$startRecvStatus$1.invoke
    at kfun:kotlinx.rpc.grpc.internal#opsCompleteCb
    ... (grpc-core WorkStealingThreadPool native frames)

Describe the solution you'd like

A way to opt into validation against the iOS system trust evaluator (the same one URLSession uses) — without having to bundle and maintain a CA list in user code.

Concretely, I'd like GrpcTlsClientCredentials() with no overrides on the iOS native target to give me the same "use whatever the platform considers trusted" behavior that JVM/Android already does. Whether that means kotlinx-rpc internally bridges to iOS's SecTrust APIs, exposes a hook so consumers can plug in their own validation, or solves it some other way is for the maintainers to decide — I don't know enough about the kotlinx-rpc TLS internals or the grpc-core integration to propose a specific shape.

Describe alternatives you've considered

The only way I've gotten TLS to work on the iOS native target is to pass a PEM bundle to trustManager(...) explicitly — in practice, embedding a snapshot of the Mozilla CA bundle (~228 KB, ~150 root certs) into the app's source/resources and shipping it.

This works, but has three problems:

  1. Maintenance burden. The bundle is a snapshot. Every consumer has to invent
    their own refresh mechanism and remember to run it. An app shipped with a
    12-month-old bundle silently fails when the backend rotates to a cert chain
    rooted in a CA added to the trust store after the snapshot was taken.

  2. No path for user/MDM-installed CAs. Apps deployed in MDM-managed
    environments need to trust corporate root CAs installed via iOS Trust
    Profiles. Those CAs are not in the Mozilla bundle, and there is no
    iOS-public API to enumerate them either (SecTrustCopyAnchorCertificates
    and SecTrustSettingsCopyCertificates are macOS / Mac Catalyst only). So
    bundling cannot solve this case at all — these apps just can't reach their
    backends over kotlinx-rpc gRPC.

  3. Inconsistency across platforms in the same project. A KMP project that
    uses kotlinx-rpc gets system-trust validation on Android for free and a
    hand-rolled bundled-CA pipeline on iOS for the same calls.

Metadata

Metadata

Assignees

Labels

featureNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions