Add API to specify server name for certificate validation for Envoy Mobile
- Dominant language
- C++
- Stars
- 28.9k
- Forks
- 5.6k
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 437
Description
**Description**:
Envoy Mobile currently lacks the ability to validate TLS certificates against a hostname when connecting to an IP address. While setUpstreamTlsSni() sets the SNI during TLS handshake, certificate validation still fails when using IP-based URLs because the validation occurs against the URL host (the IP) rather than the SNI value. Previously the issue was discussed here https://github.com/envoyproxy/envoy/issues/41685.
**Desired Behavior**:
Add an API to EngineBuilder that allows applications to specify the server name used for certificate validation, independent of the connection URL:
This would enable applications to:
1. Connect to backend servers via IP addresses (provided by VPN/network configuration)
2. Validate TLS certificates against the proper domain name (matching certificate CN/SAN)
3. Avoid insecure setTrustChainVerification(ACCEPT_UNTRUSTED) workaround that allows MITM attacks
4. Support private/self-signed certificates without disabling security
**Use Case Scenario**:
A mobile application connects to a backend server. The setup includes:
- Backend's domain name is not in public DNS
- Backend uses self-signed certificate with domain name "some.common.name" as CN instead of ip "ip_address": CN=some.common.name
Currently, there are only two options:
- **Connect using hostname URL** (https://some.common.name) → DNS resolution fails (domain not in public DNS)
- **Connect using IP URL** (https://ip_address) → Certificate validation fails with error: SSL: no alternative certificate subject name matches target ipv4 address
**Current Workaround (Insecure)**:
As @abeyad suggested here https://github.com/envoyproxy/envoy/issues/41685#issuecomment-3469453376, use `EngineBuilder.setTrustChainVerification(ACCEPT_UNTRUSTED)`.
This workaround defeats the purpose of TLS certificate validation and is only marked for "test purposes" in the API documentation.
**Attempted Solutions That Don't Work**:
- setUpstreamTlsSni("backend.drone.id") - Sets SNI header but certificate validation still checks against IP URL
- addDNSPreresolveHostnames() - Only pre-warms DNS cache, doesn't support custom IP mappings
- Setting Host header manually - Blocked by isRestrictedHeader() validation
- Custom network-security-config.xml - Cannot add IP-based certificate pinning for domain certificates
**Error Details**:
When connecting to IP with domain-based certificate:
```
Error: rc: 503|ec: 2|rsp_flags: 5|http: 1|det: upstream_reset_before_response_started{remote_connection_failure|delayed_connect_error:_Connection_refused}
```
Equivalent curl output shows the real issue:
```
SSL: no alternative certificate subject name matches target ipv4 address ''
QUIC connect failed: SSL peer certificate or SSH remote key was not OK
```
**Comparison with Similar Libraries**:
**OkHttp3**:
It solves this with a custom `[Dns](https://square.github.io/okhttp/3.x/okhttp/okhttp3/Dns.html)` interface that allows hostname-to-IP mappings while preserving certificate validation:
```
val client = OkHttpClient.Builder()
.dns(object : Dns {
override fun lookup(hostname: String): List {
if (hostname == "backend.drone.id") {
return listOf(InetAddress.getByName("10.140.252.161"))
}
return Dns.SYSTEM.lookup(hostname)
}
})
.build()
// Connects to IP but validates certificate against hostname - secure ✅
```
**Cloudflare quiche**:
Cloudflare's quiche library solves this with the `server_name` parameter in `[quiche::connect()](https://docs.rs/quiche/latest/quiche/fn.connect.html)`:
> "the optional server_name parameter is used to verify the peer's certificate"
This allows:
- Connecting to an IP address (actual network connection target)
- Validating certificate against a domain name (proper security validation)
- Maintaining security while supporting VPN/private network scenarios
**Proposed API**:
Option 1 - Explicit API (clearest intent):
```
fun setServerNameForCertificateValidation(serverName: String): EngineBuilder
```
Option 2 - Fix setUpstreamTlsSni() to also apply to certificate validation:
```
// Current behavior: only sets SNI, doesn't affect cert validation
// Proposed: set SNI AND use for certificate validation
fun setUpstreamTlsSni(sni: String): EngineBuilder
```
Option 3 - Custom DNS API that automatically handles cert validation:
```
fun addDnsMapping(hostname: String, ipAddress: String): EngineBuilder
// Internally: resolves hostname to IP while validating certs against hostname
```
Relevant Links:
- Previous discussion of this issue: https://github.com/envoyproxy/envoy/issues/41685
- OkHttp DNS interface: https://square.github.io/okhttp/3.x/okhttp/okhttp3/Dns.html
- Cloudflare quiche server_name parameter: https://docs.rs/quiche/latest/quiche/fn.connect.html
- Related PR that modified SNI behavior: https://github.com/envoyproxy/envoy/pull/36903
Contributor guide
Assessment
This issue has not been assessed yet.